[automerger skipped] AVRCP_Browse: Browse Player-list based on MBS connect (2/2) skipped: f22cf883b5
Change-Id: Ie804fa3b49f01adb78bda4dd4f9592bd14fab042
diff --git a/Android.mk b/Android.mk
index 5ac0ecf..86f3249 100644
--- a/Android.mk
+++ b/Android.mk
@@ -31,10 +31,31 @@
sap-api-java-static \
services.net \
libprotobuf-java-lite \
- bluetooth-protos-lite
+ bluetooth-protos-lite \
LOCAL_STATIC_ANDROID_LIBRARIES := \
- android-support-v4
+ androidx.core_core \
+ androidx.lifecycle_lifecycle-livedata \
+ androidx.room_room-runtime \
+
+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
+
+LOCAL_ANNOTATION_PROCESSOR_CLASSES := \
+ androidx.room.RoomProcessor
+
LOCAL_STATIC_JAVA_LIBRARIES += com.android.emailcommon
LOCAL_PROTOC_OPTIMIZE_TYPE := micro
@@ -43,3 +64,23 @@
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-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 ebafdbc..bc10b4b 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -70,6 +70,16 @@
<uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" />
<uses-permission android:name="com.android.email.permission.ACCESS_PROVIDER"/>
<uses-permission android:name="com.android.email.permission.READ_ATTACHMENT"/>
+ <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
+ <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
+ <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
+ <uses-permission android:name="android.permission.INTERNET" />
+ <uses-permission android:name="android.permission.OVERRIDE_WIFI_CONFIG" />
+
+ <!-- Allows application to write to internal media storage -->
+ <uses-permission android:name="android.permission.WRITE_MEDIA_STORAGE" />
+
+ <uses-sdk android:minSdkVersion="14"/>
<!-- For PBAP Owner Vcard Info -->
<uses-permission android:name="android.permission.READ_PROFILE"/>
@@ -81,7 +91,8 @@
android:supportsRtl="true"
android:usesCleartextTraffic="false"
android:directBootAware="true"
- android:defaultToDeviceProtectedStorage="true">
+ android:defaultToDeviceProtectedStorage="true"
+ android:requestLegacyExternalStorage="true">
<uses-library android:name="javax.obex" />
<provider android:name=".opp.BluetoothOppProvider"
android:authorities="com.android.bluetooth.opp"
@@ -311,7 +322,7 @@
</service>
<service
android:process="@string/process"
- android:name=".a2dpsink.mbs.A2dpMediaBrowserService"
+ android:name=".avrcpcontroller.BluetoothMediaBrowserService"
android:exported="true"
android:enabled="@bool/profile_supported_a2dp_sink"
android:label="@string/a2dp_sink_mbs_label">
@@ -361,14 +372,6 @@
</service>
<service
android:process="@string/process"
- android:name = ".hdp.HealthService"
- android:enabled="@bool/profile_supported_hdp">
- <intent-filter>
- <action android:name="android.bluetooth.IBluetoothHealth" />
- </intent-filter>
- </service>
- <service
- android:process="@string/process"
android:name = ".pan.PanService"
android:enabled="@bool/profile_supported_pan">
<intent-filter>
diff --git a/OWNERS b/OWNERS
new file mode 100644
index 0000000..2c1bdc4
--- /dev/null
+++ b/OWNERS
@@ -0,0 +1 @@
+include platform/system/bt:/OWNERS
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index 9c449e0..d0c92d3 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -9,3 +9,4 @@
-fw src/com/android/bluetooth/
lib/mapapi/com/android/bluetooth/mapapi/
tests/src/com/android/bluetooth/
+aosp_first = ${REPO_ROOT}/frameworks/base/tools/aosp/aosp_sha.sh ${PREUPLOAD_COMMIT} ${PREUPLOAD_FILES}
diff --git a/jni/Android.bp b/jni/Android.bp
index bab3d12..52f653a 100644
--- a/jni/Android.bp
+++ b/jni/Android.bp
@@ -14,7 +14,6 @@
"com_android_bluetooth_hid_host.cpp",
"com_android_bluetooth_hid_device.cpp",
"com_android_bluetooth_hearing_aid.cpp",
- "com_android_bluetooth_hdp.cpp",
"com_android_bluetooth_pan.cpp",
"com_android_bluetooth_gatt.cpp",
"com_android_bluetooth_sdp.cpp",
@@ -34,10 +33,10 @@
"libchrome",
"libnativehelper",
"liblog",
+ "libutils",
],
static_libs: [
"libbluetooth-types",
- "libutils",
"libcutils",
"libbluetoothqti_jni",
],
@@ -47,4 +46,7 @@
"-Wextra",
"-Wno-unused-parameter",
],
+ sanitize: {
+ scs: true,
+ },
}
diff --git a/jni/bluetooth_socket_manager.cc b/jni/bluetooth_socket_manager.cc
index a63beed..ba45aa5 100644
--- a/jni/bluetooth_socket_manager.cc
+++ b/jni/bluetooth_socket_manager.cc
@@ -20,7 +20,6 @@
#include <base/logging.h>
#include <binder/IPCThreadState.h>
-using ::android::OK;
using ::android::String8;
using ::android::binder::Status;
using ::android::os::ParcelFileDescriptor;
@@ -57,8 +56,8 @@
return Status::ok();
}
- _aidl_return->reset(new ParcelFileDescriptor());
- (*_aidl_return)->setFileDescriptor(socket_fd, true);
+ _aidl_return->reset(
+ new ParcelFileDescriptor(android::base::unique_fd(socket_fd)));
return Status::ok();
}
@@ -97,8 +96,8 @@
return Status::ok();
}
- _aidl_return->reset(new ParcelFileDescriptor());
- (*_aidl_return)->setFileDescriptor(socket_fd, true);
+ _aidl_return->reset(
+ new ParcelFileDescriptor(android::base::unique_fd(socket_fd)));
return Status::ok();
}
diff --git a/jni/com_android_bluetooth.h b/jni/com_android_bluetooth.h
index d09d1df..f20f1f3 100644
--- a/jni/com_android_bluetooth.h
+++ b/jni/com_android_bluetooth.h
@@ -54,6 +54,65 @@
return true;
}
+ // stolen from art/runtime/jni/check_jni.cc
+ bool isValidUtf(const char* bytes) const {
+ while (*bytes != '\0') {
+ const uint8_t* utf8 = reinterpret_cast<const uint8_t*>(bytes++);
+ // Switch on the high four bits.
+ switch (*utf8 >> 4) {
+ case 0x00:
+ case 0x01:
+ case 0x02:
+ case 0x03:
+ case 0x04:
+ case 0x05:
+ case 0x06:
+ case 0x07:
+ // Bit pattern 0xxx. No need for any extra bytes.
+ break;
+ case 0x08:
+ case 0x09:
+ case 0x0a:
+ case 0x0b:
+ // Bit patterns 10xx, which are illegal start bytes.
+ return false;
+ case 0x0f:
+ // Bit pattern 1111, which might be the start of a 4 byte sequence.
+ if ((*utf8 & 0x08) == 0) {
+ // Bit pattern 1111 0xxx, which is the start of a 4 byte sequence.
+ // We consume one continuation byte here, and fall through to
+ // consume two more.
+ utf8 = reinterpret_cast<const uint8_t*>(bytes++);
+ if ((*utf8 & 0xc0) != 0x80) {
+ return false;
+ }
+ } else {
+ return false;
+ }
+ // Fall through to the cases below to consume two more continuation
+ // bytes.
+ FALLTHROUGH_INTENDED;
+ case 0x0e:
+ // Bit pattern 1110, so there are two additional bytes.
+ utf8 = reinterpret_cast<const uint8_t*>(bytes++);
+ if ((*utf8 & 0xc0) != 0x80) {
+ return false;
+ }
+ // Fall through to consume one more continuation byte.
+ FALLTHROUGH_INTENDED;
+ case 0x0c:
+ case 0x0d:
+ // Bit pattern 110x, so there is one additional byte.
+ utf8 = reinterpret_cast<const uint8_t*>(bytes++);
+ if ((*utf8 & 0xc0) != 0x80) {
+ return false;
+ }
+ break;
+ }
+ }
+ return true;
+ }
+
JNIEnv *operator-> () const {
return mCallbackEnv;
}
@@ -89,8 +148,6 @@
int register_com_android_bluetooth_hid_device(JNIEnv* env);
-int register_com_android_bluetooth_hdp(JNIEnv* env);
-
int register_com_android_bluetooth_pan(JNIEnv* env);
int register_com_android_bluetooth_gatt (JNIEnv* env);
@@ -106,6 +163,8 @@
int register_com_android_bluetooth_avrcp_ext(JNIEnv* env);
int register_com_android_bluetooth_ba(JNIEnv* env);
+
+int register_com_android_bluetooth_hfp_vendorhfservice(JNIEnv* env);
}
#endif /* COM_ANDROID_BLUETOOTH_H */
diff --git a/jni/com_android_bluetooth_a2dp.cpp b/jni/com_android_bluetooth_a2dp.cpp
index 126405a..709d31d 100644
--- a/jni/com_android_bluetooth_a2dp.cpp
+++ b/jni/com_android_bluetooth_a2dp.cpp
@@ -260,7 +260,8 @@
static void initNative(JNIEnv* env, jobject object,
jint maxConnectedAudioDevices,
- jobjectArray codecConfigArray) {
+ jobjectArray codecConfigArray,
+ jobjectArray codecConfigOffload) {
std::unique_lock<std::shared_timed_mutex> interface_lock(interface_mutex);
std::unique_lock<std::shared_timed_mutex> callbacks_lock(callbacks_mutex);
@@ -306,8 +307,10 @@
std::vector<btav_a2dp_codec_config_t> codec_priorities =
prepareCodecPreferences(env, object, codecConfigArray);
+ std::vector<btav_a2dp_codec_config_t> offload_codec_supported =
+ prepareCodecPreferences(env, object, codecConfigOffload);
bt_status_t status = sBluetoothA2dpInterface->init(
- &sBluetoothA2dpCallbacks, maxConnectedAudioDevices, codec_priorities);
+ &sBluetoothA2dpCallbacks, maxConnectedAudioDevices, codec_priorities, offload_codec_supported);
if (status != BT_STATUS_SUCCESS) {
ALOGE("%s: Failed to initialize Bluetooth A2DP, status: %d", __func__,
status);
@@ -390,6 +393,33 @@
return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
}
+static jboolean setSilenceDeviceNative(JNIEnv* env, jobject object,
+ jbyteArray address, jboolean silence) {
+ ALOGI("%s: sBluetoothA2dpInterface: %p", __func__, sBluetoothA2dpInterface);
+ std::shared_lock<std::shared_timed_mutex> lock(interface_mutex);
+ if (!sBluetoothA2dpInterface) {
+ ALOGE("%s: Failed to get the Bluetooth A2DP Interface", __func__);
+ return JNI_FALSE;
+ }
+
+ jbyte* addr = env->GetByteArrayElements(address, nullptr);
+
+ RawAddress bd_addr = RawAddress::kEmpty;
+ if (addr) {
+ bd_addr.FromOctets(reinterpret_cast<const uint8_t*>(addr));
+ }
+ if (bd_addr == RawAddress::kEmpty) {
+ return JNI_FALSE;
+ }
+ bt_status_t status =
+ sBluetoothA2dpInterface->set_silence_device(bd_addr, silence);
+ if (status != BT_STATUS_SUCCESS) {
+ ALOGE("%s: Failed A2DP set_silence_device, status: %d", __func__, status);
+ }
+ env->ReleaseByteArrayElements(address, addr, 0);
+ return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
+}
+
static jboolean setActiveDeviceNative(JNIEnv* env, jobject object,
jbyteArray address) {
ALOGI("%s: sBluetoothA2dpInterface: %p", __func__, sBluetoothA2dpInterface);
@@ -445,11 +475,12 @@
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},
{"disconnectA2dpNative", "([B)Z", (void*)disconnectA2dpNative},
+ {"setSilenceDeviceNative", "([BZ)Z", (void*)setSilenceDeviceNative},
{"setActiveDeviceNative", "([B)Z", (void*)setActiveDeviceNative},
{"setCodecConfigPreferenceNative",
"([B[Landroid/bluetooth/BluetoothCodecConfig;)Z",
diff --git a/jni/com_android_bluetooth_avrcp_controller.cpp b/jni/com_android_bluetooth_avrcp_controller.cpp
index cc7bb64..d16a04f 100644
--- a/jni/com_android_bluetooth_avrcp_controller.cpp
+++ b/jni/com_android_bluetooth_avrcp_controller.cpp
@@ -25,6 +25,7 @@
#include "utils/Log.h"
#include <string.h>
+#include <shared_mutex>
namespace android {
static jmethodID method_handlePassthroughRsp;
@@ -48,6 +49,8 @@
static jmethodID method_handleChangeFolderRsp;
static jmethodID method_handleSetBrowsedPlayerRsp;
static jmethodID method_handleSetAddressedPlayerRsp;
+static jmethodID method_handleAddressedPlayerChanged;
+static jmethodID method_handleNowPlayingContentChanged;
static jclass class_MediaBrowser_MediaItem;
static jclass class_AvrcpPlayer;
@@ -55,17 +58,23 @@
static const btrc_ctrl_interface_t* sBluetoothAvrcpInterface = NULL;
static const btrc_vendor_ctrl_interface_t* sBluetoothAvrcpVendorInterface = NULL;
static jobject sCallbacksObj = NULL;
+static std::shared_timed_mutex sCallbacks_mutex;
static void btavrcp_passthrough_response_callback(const RawAddress& bd_addr,
int id, int pressed) {
ALOGI("%s: id: %d, pressed: %d", __func__, id, pressed);
+ std::shared_lock<std::shared_timed_mutex> lock(sCallbacks_mutex);
CallbackEnv sCallbackEnv(__func__);
if (!sCallbackEnv.valid()) return;
+ if (!sCallbacksObj) {
+ ALOGE("%s: sCallbacksObj is null", __func__);
+ return;
+ }
ScopedLocalRef<jbyteArray> addr(
sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
if (!addr.get()) {
- ALOGE("Fail to new jbyteArray bd addr for passthrough response");
+ ALOGE("%s: Failed to allocate a new byte array", __func__);
return;
}
@@ -77,8 +86,13 @@
static void btavrcp_groupnavigation_response_callback(int id, int pressed) {
ALOGV("%s", __func__);
+ std::shared_lock<std::shared_timed_mutex> lock(sCallbacks_mutex);
CallbackEnv sCallbackEnv(__func__);
if (!sCallbackEnv.valid()) return;
+ if (!sCallbacksObj) {
+ ALOGE("%s: sCallbacksObj is null", __func__);
+ return;
+ }
sCallbackEnv->CallVoidMethod(sCallbacksObj, method_handleGroupNavigationRsp,
(jint)id, (jint)pressed);
@@ -87,13 +101,18 @@
static void btavrcp_connection_state_callback(bool rc_connect, bool br_connect,
const RawAddress& bd_addr) {
ALOGI("%s: conn state: rc: %d br: %d", __func__, rc_connect, br_connect);
+ std::shared_lock<std::shared_timed_mutex> lock(sCallbacks_mutex);
CallbackEnv sCallbackEnv(__func__);
if (!sCallbackEnv.valid()) return;
+ if (!sCallbacksObj) {
+ ALOGE("%s: sCallbacksObj is null", __func__);
+ return;
+ }
ScopedLocalRef<jbyteArray> addr(
sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
if (!addr.get()) {
- ALOGE("Fail to new jbyteArray bd addr for connection state");
+ ALOGE("%s: Failed to allocate a new byte array", __func__);
return;
}
@@ -107,13 +126,18 @@
static void btavrcp_get_rcfeatures_callback(const RawAddress& bd_addr,
int features) {
ALOGV("%s", __func__);
+ std::shared_lock<std::shared_timed_mutex> lock(sCallbacks_mutex);
CallbackEnv sCallbackEnv(__func__);
if (!sCallbackEnv.valid()) return;
+ if (!sCallbacksObj) {
+ ALOGE("%s: sCallbacksObj is null", __func__);
+ return;
+ }
ScopedLocalRef<jbyteArray> addr(
sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
if (!addr.get()) {
- ALOGE("Fail to new jbyteArray bd addr ");
+ ALOGE("%s: Failed to allocate a new byte array", __func__);
return;
}
@@ -126,13 +150,18 @@
static void btavrcp_setplayerapplicationsetting_rsp_callback(
const RawAddress& bd_addr, uint8_t accepted) {
ALOGV("%s", __func__);
+ std::shared_lock<std::shared_timed_mutex> lock(sCallbacks_mutex);
CallbackEnv sCallbackEnv(__func__);
if (!sCallbackEnv.valid()) return;
+ if (!sCallbacksObj) {
+ ALOGE("%s: sCallbacksObj is null", __func__);
+ return;
+ }
ScopedLocalRef<jbyteArray> addr(
sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
if (!addr.get()) {
- ALOGE("Fail to new jbyteArray bd addr ");
+ ALOGE("%s: Failed to allocate a new byte array", __func__);
return;
}
@@ -227,13 +256,18 @@
btrc_player_app_attr_t* app_attrs, uint8_t num_ext_attr,
btrc_player_app_ext_attr_t* ext_attrs) {
ALOGI("%s", __func__);
+ std::shared_lock<std::shared_timed_mutex> lock(sCallbacks_mutex);
CallbackEnv sCallbackEnv(__func__);
if (!sCallbackEnv.valid()) return;
+ if (!sCallbacksObj) {
+ ALOGE("%s: sCallbacksObj is null", __func__);
+ return;
+ }
ScopedLocalRef<jbyteArray> addr(
sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
if (!addr.get()) {
- ALOGE("Fail to new jbyteArray bd addr ");
+ ALOGE("%s: Failed to allocate a new byte array", __func__);
return;
}
sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
@@ -251,7 +285,7 @@
ScopedLocalRef<jbyteArray> playerattribs(
sCallbackEnv.get(), sCallbackEnv->NewByteArray(arraylen));
if (!playerattribs.get()) {
- ALOGE("Fail to new jbyteArray playerattribs ");
+ ALOGE("%s: Failed to allocate a new byte array", __func__);
return;
}
@@ -274,13 +308,18 @@
static void btavrcp_playerapplicationsetting_changed_callback(
const RawAddress& bd_addr, const btrc_player_settings_t& vals) {
ALOGI("%s", __func__);
+ std::shared_lock<std::shared_timed_mutex> lock(sCallbacks_mutex);
CallbackEnv sCallbackEnv(__func__);
if (!sCallbackEnv.valid()) return;
+ if (!sCallbacksObj) {
+ ALOGE("%s: sCallbacksObj is null", __func__);
+ return;
+ }
ScopedLocalRef<jbyteArray> addr(
sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
if (!addr.get()) {
- ALOGE("Fail to get new array ");
+ ALOGE("%s: Failed to allocate a new byte array", __func__);
return;
}
sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
@@ -312,13 +351,18 @@
static void btavrcp_set_abs_vol_cmd_callback(const RawAddress& bd_addr,
uint8_t abs_vol, uint8_t label) {
ALOGI("%s", __func__);
+ std::shared_lock<std::shared_timed_mutex> lock(sCallbacks_mutex);
CallbackEnv sCallbackEnv(__func__);
if (!sCallbackEnv.valid()) return;
+ if (!sCallbacksObj) {
+ ALOGE("%s: sCallbacksObj is null", __func__);
+ return;
+ }
ScopedLocalRef<jbyteArray> addr(
sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
if (!addr.get()) {
- ALOGE("Fail to get new array ");
+ ALOGE("%s: Failed to allocate a new byte array", __func__);
return;
}
@@ -331,13 +375,18 @@
static void btavrcp_register_notification_absvol_callback(
const RawAddress& bd_addr, uint8_t label) {
ALOGI("%s", __func__);
+ std::shared_lock<std::shared_timed_mutex> lock(sCallbacks_mutex);
CallbackEnv sCallbackEnv(__func__);
if (!sCallbackEnv.valid()) return;
+ if (!sCallbacksObj) {
+ ALOGE("%s: sCallbacksObj is null", __func__);
+ return;
+ }
ScopedLocalRef<jbyteArray> addr(
sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
if (!addr.get()) {
- ALOGE("Fail to get new array ");
+ ALOGE("%s: Failed to allocate a new byte array", __func__);
return;
}
@@ -356,13 +405,18 @@
* Assuming text feild to be null terminated.
*/
ALOGI("%s", __func__);
+ std::shared_lock<std::shared_timed_mutex> lock(sCallbacks_mutex);
CallbackEnv sCallbackEnv(__func__);
if (!sCallbackEnv.valid()) return;
+ if (!sCallbacksObj) {
+ ALOGE("%s: sCallbacksObj is null", __func__);
+ return;
+ }
ScopedLocalRef<jbyteArray> addr(
sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
if (!addr.get()) {
- ALOGE("Fail to get new array ");
+ ALOGE("%s: Failed to allocate a new byte array", __func__);
return;
}
@@ -406,13 +460,18 @@
uint32_t song_len,
uint32_t song_pos) {
ALOGI("%s", __func__);
+ std::shared_lock<std::shared_timed_mutex> lock(sCallbacks_mutex);
CallbackEnv sCallbackEnv(__func__);
if (!sCallbackEnv.valid()) return;
+ if (!sCallbacksObj) {
+ ALOGE("%s: sCallbacksObj is null", __func__);
+ return;
+ }
ScopedLocalRef<jbyteArray> addr(
sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
if (!addr.get()) {
- ALOGE("Fail to get new array ");
+ ALOGE("%s: Failed to allocate a new byte array", __func__);
return;
}
sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
@@ -424,13 +483,18 @@
static void btavrcp_play_status_changed_callback(
const RawAddress& bd_addr, btrc_play_status_t play_status) {
ALOGI("%s", __func__);
+ std::shared_lock<std::shared_timed_mutex> lock(sCallbacks_mutex);
CallbackEnv sCallbackEnv(__func__);
if (!sCallbackEnv.valid()) return;
+ if (!sCallbacksObj) {
+ ALOGE("%s: sCallbacksObj is null", __func__);
+ return;
+ }
ScopedLocalRef<jbyteArray> addr(
sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
if (!addr.get()) {
- ALOGE("Fail to get new array ");
+ ALOGE("%s: Failed to allocate a new byte array", __func__);
return;
}
sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
@@ -447,15 +511,21 @@
* counterparts by calling the java constructor for each of the items.
*/
ALOGV("%s count %d", __func__, count);
+ std::shared_lock<std::shared_timed_mutex> lock(sCallbacks_mutex);
CallbackEnv sCallbackEnv(__func__);
if (!sCallbackEnv.valid()) return;
-
- ScopedLocalRef<jbyteArray> addr(
- sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
- if (!addr.get()) {
- ALOGE("Fail to get new array ");
+ if (!sCallbacksObj) {
+ ALOGE("%s: sCallbacksObj is null", __func__);
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.address);
@@ -491,17 +561,7 @@
return;
}
// Parse UID
- ScopedLocalRef<jbyteArray> uidByteArray(
- sCallbackEnv.get(),
- sCallbackEnv->NewByteArray(sizeof(uint8_t) * BTRC_UID_SIZE));
- if (!uidByteArray.get()) {
- ALOGE("%s can't allocate uid array!", __func__);
- return;
- }
- sCallbackEnv->SetByteArrayRegion(uidByteArray.get(), 0,
- BTRC_UID_SIZE * sizeof(uint8_t),
- (jbyte*)item->media.uid);
-
+ long long uid = *(long long*)item->media.uid;
// Parse Attrs
ScopedLocalRef<jintArray> attrIdArray(
sCallbackEnv.get(),
@@ -527,10 +587,6 @@
ScopedLocalRef<jstring> attrValStr(
sCallbackEnv.get(),
sCallbackEnv->NewStringUTF((char*)(item->media.p_attrs[j].text)));
- if (!uidByteArray.get()) {
- ALOGE("%s can't allocate uid array!", __func__);
- return;
- }
sCallbackEnv->SetObjectArrayElement(attrValArray.get(), j,
attrValStr.get());
}
@@ -538,9 +594,9 @@
ScopedLocalRef<jobject> mediaObj(
sCallbackEnv.get(),
(jobject)sCallbackEnv->CallObjectMethod(
- sCallbacksObj, method_createFromNativeMediaItem,
- uidByteArray.get(), (jint)item->media.type, mediaName.get(),
- attrIdArray.get(), attrValArray.get()));
+ sCallbacksObj, method_createFromNativeMediaItem, uid,
+ (jint)item->media.type, mediaName.get(), attrIdArray.get(),
+ attrValArray.get()));
if (!mediaObj.get()) {
ALOGE("%s failed to creae MediaItem for type ITEM_MEDIA", __func__);
return;
@@ -559,22 +615,12 @@
return;
}
// Parse UID
- ScopedLocalRef<jbyteArray> uidByteArray(
- sCallbackEnv.get(),
- sCallbackEnv->NewByteArray(sizeof(uint8_t) * BTRC_UID_SIZE));
- if (!uidByteArray.get()) {
- ALOGE("%s can't allocate uid array!", __func__);
- return;
- }
- sCallbackEnv->SetByteArrayRegion(uidByteArray.get(), 0,
- BTRC_UID_SIZE * sizeof(uint8_t),
- (jbyte*)item->folder.uid);
-
+ long long uid = *(long long*)item->folder.uid;
ScopedLocalRef<jobject> folderObj(
sCallbackEnv.get(),
(jobject)sCallbackEnv->CallObjectMethod(
- sCallbacksObj, method_createFromNativeFolderItem,
- uidByteArray.get(), (jint)item->folder.type, folderName.get(),
+ sCallbacksObj, method_createFromNativeFolderItem, uid,
+ (jint)item->folder.type, folderName.get(),
(jint)item->folder.playable));
if (!folderObj.get()) {
ALOGE("%s failed to create MediaItem for type ITEM_FOLDER", __func__);
@@ -632,70 +678,129 @@
if (isPlayerListing) {
sCallbackEnv->CallVoidMethod(sCallbacksObj, method_handleGetPlayerItemsRsp,
- itemArray.get(), addr.get());
+ addr.get(), itemArray.get());
} else {
sCallbackEnv->CallVoidMethod(sCallbacksObj, method_handleGetFolderItemsRsp,
- status, itemArray.get(), addr.get());
+ addr.get(), status, itemArray.get());
}
}
static void btavrcp_change_path_callback(const RawAddress& bd_addr,
uint32_t count) {
ALOGI("%s count %d", __func__, count);
+ std::shared_lock<std::shared_timed_mutex> lock(sCallbacks_mutex);
CallbackEnv sCallbackEnv(__func__);
if (!sCallbackEnv.valid()) return;
-
- ScopedLocalRef<jbyteArray> addr(
- sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
- if (!addr.get()) {
- ALOGE("Fail to get new array ");
+ if (!sCallbacksObj) {
+ ALOGE("%s: sCallbacksObj is null", __func__);
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.address);
sCallbackEnv->CallVoidMethod(sCallbacksObj, method_handleChangeFolderRsp,
- (jint)count, addr.get());
+ addr.get(), (jint)count);
}
static void btavrcp_set_browsed_player_callback(const RawAddress& bd_addr,
uint8_t num_items,
uint8_t depth) {
ALOGI("%s items %d depth %d", __func__, num_items, depth);
+ std::shared_lock<std::shared_timed_mutex> lock(sCallbacks_mutex);
CallbackEnv sCallbackEnv(__func__);
if (!sCallbackEnv.valid()) return;
-
- ScopedLocalRef<jbyteArray> addr(
- sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
- if (!addr.get()) {
- ALOGE("Fail to get new array ");
+ if (!sCallbacksObj) {
+ ALOGE("%s: sCallbacksObj is null", __func__);
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.address);
sCallbackEnv->CallVoidMethod(sCallbacksObj, method_handleSetBrowsedPlayerRsp,
- (jint)num_items, (jint)depth, addr.get());
+ addr.get(), (jint)num_items, (jint)depth);
}
static void btavrcp_set_addressed_player_callback(const RawAddress& bd_addr,
uint8_t status) {
ALOGI("%s status %d", __func__, status);
-
+ std::shared_lock<std::shared_timed_mutex> lock(sCallbacks_mutex);
CallbackEnv sCallbackEnv(__func__);
if (!sCallbackEnv.valid()) return;
-
- ScopedLocalRef<jbyteArray> addr(
- sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
- if (!addr.get()) {
- ALOGE("Fail to get new array ");
+ if (!sCallbacksObj) {
+ ALOGE("%s: sCallbacksObj is null", __func__);
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.address);
+
+ sCallbackEnv->CallVoidMethod(sCallbacksObj,
+ method_handleSetAddressedPlayerRsp, addr.get(),
+ (jint)status);
+}
+
+static void btavrcp_addressed_player_changed_callback(const RawAddress& bd_addr,
+ uint16_t id) {
+ ALOGI("%s status %d", __func__, id);
+ std::shared_lock<std::shared_timed_mutex> lock(sCallbacks_mutex);
+ CallbackEnv sCallbackEnv(__func__);
+ if (!sCallbackEnv.valid()) return;
+ if (!sCallbacksObj) {
+ ALOGE("%s: sCallbacksObj is null", __func__);
+ 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.address);
sCallbackEnv->CallVoidMethod(
- sCallbacksObj, method_handleSetAddressedPlayerRsp, (jint)status, addr.get());
+ sCallbacksObj, method_handleAddressedPlayerChanged, addr.get(), (jint)id);
+}
+
+static void btavrcp_now_playing_content_changed_callback(
+ const RawAddress& bd_addr) {
+ ALOGI("%s", __func__);
+
+ CallbackEnv sCallbackEnv(__func__);
+ 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.address);
+
+ sCallbackEnv->CallVoidMethod(
+ sCallbacksObj, method_handleNowPlayingContentChanged, addr.get());
}
static btrc_ctrl_callbacks_t sBluetoothAvrcpCallbacks = {
@@ -715,7 +820,9 @@
btavrcp_get_folder_items_callback,
btavrcp_change_path_callback,
btavrcp_set_browsed_player_callback,
- btavrcp_set_addressed_player_callback};
+ btavrcp_set_addressed_player_callback,
+ btavrcp_addressed_player_changed_callback,
+ btavrcp_now_playing_content_changed_callback};
static btrc_vendor_ctrl_callbacks_t sBluetoothAvrcpVendorCallbacks = {
sizeof(sBluetoothAvrcpVendorCallbacks),
@@ -764,32 +871,39 @@
method_handleGetFolderItemsRsp =
env->GetMethodID(clazz, "handleGetFolderItemsRsp",
- "(I[Landroid/media/browse/MediaBrowser$MediaItem;[B)V");
+ "([BI[Landroid/media/browse/MediaBrowser$MediaItem;)V");
method_handleGetPlayerItemsRsp = env->GetMethodID(
clazz, "handleGetPlayerItemsRsp",
- "([Lcom/android/bluetooth/avrcpcontroller/AvrcpPlayer;[B)V");
+ "([B[Lcom/android/bluetooth/avrcpcontroller/AvrcpPlayer;)V");
method_createFromNativeMediaItem =
env->GetMethodID(clazz, "createFromNativeMediaItem",
- "([BILjava/lang/String;[I[Ljava/lang/String;)Landroid/"
+ "(JILjava/lang/String;[I[Ljava/lang/String;)Landroid/"
"media/browse/MediaBrowser$MediaItem;");
method_createFromNativeFolderItem = env->GetMethodID(
clazz, "createFromNativeFolderItem",
- "([BILjava/lang/String;I)Landroid/media/browse/MediaBrowser$MediaItem;");
+ "(JILjava/lang/String;I)Landroid/media/browse/MediaBrowser$MediaItem;");
method_createFromNativePlayerItem =
env->GetMethodID(clazz, "createFromNativePlayerItem",
"(ILjava/lang/String;[BII)Lcom/android/bluetooth/"
"avrcpcontroller/AvrcpPlayer;");
method_handleChangeFolderRsp =
- env->GetMethodID(clazz, "handleChangeFolderRsp", "(I[B)V");
+ env->GetMethodID(clazz, "handleChangeFolderRsp", "([BI)V");
method_handleSetBrowsedPlayerRsp =
- env->GetMethodID(clazz, "handleSetBrowsedPlayerRsp", "(II[B)V");
+ env->GetMethodID(clazz, "handleSetBrowsedPlayerRsp", "([BII)V");
method_handleSetAddressedPlayerRsp =
- env->GetMethodID(clazz, "handleSetAddressedPlayerRsp", "(I[B)V");
+ env->GetMethodID(clazz, "handleSetAddressedPlayerRsp", "([BI)V");
+ method_handleAddressedPlayerChanged =
+ env->GetMethodID(clazz, "handleAddressedPlayerChanged", "([BI)V");
+ method_handleNowPlayingContentChanged =
+ env->GetMethodID(clazz, "handleNowPlayingContentChanged", "([B)V");
+
ALOGI("%s: succeeds", __func__);
}
static void initNative(JNIEnv* env, jobject object) {
+ std::unique_lock<std::shared_timed_mutex> lock(sCallbacks_mutex);
+
jclass tmpMediaItem =
env->FindClass("android/media/browse/MediaBrowser$MediaItem");
class_MediaBrowser_MediaItem = (jclass)env->NewGlobalRef(tmpMediaItem);
@@ -865,6 +979,8 @@
}
static void cleanupNative(JNIEnv* env, jobject object) {
+ std::unique_lock<std::shared_timed_mutex> lock(sCallbacks_mutex);
+
const bt_interface_t* btInf = getBluetoothInterface();
if (btInf == NULL) {
ALOGE("Bluetooth module is not loaded");
@@ -1122,7 +1238,7 @@
static void changeFolderPathNative(JNIEnv* env, jobject object,
jbyteArray address, jbyte direction,
- jbyteArray uidarr) {
+ jlong uid) {
if (!sBluetoothAvrcpInterface) return;
jbyte* addr = env->GetByteArrayElements(address, NULL);
if (!addr) {
@@ -1130,22 +1246,22 @@
return;
}
- jbyte* uid = env->GetByteArrayElements(uidarr, NULL);
- if (!uid) {
- jniThrowIOException(env, EINVAL);
- return;
- }
+ // jbyte* uid = env->GetByteArrayElements(uidarr, NULL);
+ // if (!uid) {
+ // jniThrowIOException(env, EINVAL);
+ // return;
+ //}
ALOGI("%s: sBluetoothAvrcpInterface: %p", __func__, sBluetoothAvrcpInterface);
RawAddress rawAddress;
rawAddress.FromOctets((uint8_t*)addr);
bt_status_t status = sBluetoothAvrcpInterface->change_folder_path_cmd(
- rawAddress, (uint8_t)direction, (uint8_t*)uid);
+ rawAddress, (uint8_t)direction, (uint8_t*)&uid);
if (status != BT_STATUS_SUCCESS) {
ALOGE("Failed sending changeFolderPathNative command, status: %d", status);
}
- env->ReleaseByteArrayElements(address, addr, 0);
+ // env->ReleaseByteArrayElements(address, addr, 0);
}
static void setBrowsedPlayerNative(JNIEnv* env, jobject object,
@@ -1190,7 +1306,7 @@
}
static void playItemNative(JNIEnv* env, jobject object, jbyteArray address,
- jbyte scope, jbyteArray uidArr, jint uidCounter) {
+ jbyte scope, jlong uid, jint uidCounter) {
if (!sBluetoothAvrcpInterface) return;
jbyte* addr = env->GetByteArrayElements(address, NULL);
if (!addr) {
@@ -1198,17 +1314,17 @@
return;
}
- jbyte* uid = env->GetByteArrayElements(uidArr, NULL);
- if (!uid) {
- jniThrowIOException(env, EINVAL);
- return;
- }
+ // jbyte* uid = env->GetByteArrayElements(uidArr, NULL);
+ // if (!uid) {
+ // jniThrowIOException(env, EINVAL);
+ // return;
+ // }
RawAddress rawAddress;
rawAddress.FromOctets((uint8_t*)addr);
ALOGI("%s: sBluetoothAvrcpInterface: %p", __func__, sBluetoothAvrcpInterface);
bt_status_t status = sBluetoothAvrcpInterface->play_item_cmd(
- rawAddress, (uint8_t)scope, (uint8_t*)uid, (uint16_t)uidCounter);
+ rawAddress, (uint8_t)scope, (uint8_t*)&uid, (uint16_t)uidCounter);
if (status != BT_STATUS_SUCCESS) {
ALOGE("Failed sending playItemNative command, status: %d", status);
}
@@ -1275,8 +1391,8 @@
{"getNowPlayingListNative", "([BII)V", (void*)getNowPlayingListNative},
{"getFolderListNative", "([BII)V", (void*)getFolderListNative},
{"getPlayerListNative", "([BII)V", (void*)getPlayerListNative},
- {"changeFolderPathNative", "([BB[B)V", (void*)changeFolderPathNative},
- {"playItemNative", "([BB[BI)V", (void*)playItemNative},
+ {"changeFolderPathNative", "([BBJ)V", (void*)changeFolderPathNative},
+ {"playItemNative", "([BBJI)V", (void*)playItemNative},
{"setBrowsedPlayerNative", "([BI)V", (void*)setBrowsedPlayerNative},
{"setAddressedPlayerNative", "([BI)V", (void*)setAddressedPlayerNative},
{"getElementAttributesNative", "([BB[B)V",(void *) getElementAttributesNative},
diff --git a/jni/com_android_bluetooth_avrcp_target.cpp b/jni/com_android_bluetooth_avrcp_target.cpp
index 8ac04e4..214feeb 100644
--- a/jni/com_android_bluetooth_avrcp_target.cpp
+++ b/jni/com_android_bluetooth_avrcp_target.cpp
@@ -14,9 +14,10 @@
* limitations under the License.
*/
-#define LOG_TAG "NewAvrcpTargetJni"
+#define LOG_TAG "AvrcpTargetJni"
#include <base/bind.h>
+#include <base/callback.h>
#include <map>
#include <mutex>
#include <shared_mutex>
diff --git a/jni/com_android_bluetooth_btservice_AdapterService.cpp b/jni/com_android_bluetooth_btservice_AdapterService.cpp
index 1008aeb..3fac7e3 100644
--- a/jni/com_android_bluetooth_btservice_AdapterService.cpp
+++ b/jni/com_android_bluetooth_btservice_AdapterService.cpp
@@ -40,8 +40,6 @@
#include <hardware/bluetooth.h>
#include <mutex>
-using base::StringPrintf;
-using bluetooth::Uuid;
using android::bluetooth::BluetoothSocketManagerBinderServer;
namespace android {
@@ -701,7 +699,8 @@
}
}
-static bool initNative(JNIEnv* env, jobject obj) {
+static bool initNative(JNIEnv* env, jobject obj, jboolean isGuest,
+ jboolean isSingleUserMode) {
ALOGV("%s", __func__);
android_bluetooth_UidTraffic.clazz =
@@ -715,7 +714,9 @@
return JNI_FALSE;
}
- int ret = sBluetoothInterface->init(&sBluetoothCallbacks);
+ int ret = sBluetoothInterface->init(&sBluetoothCallbacks,
+ isGuest == JNI_TRUE ? 1 : 0,
+ isSingleUserMode == JNI_TRUE ? 1 : 0);
if (ret != BT_STATUS_SUCCESS && ret != BT_STATUS_DONE) {
ALOGE("Error while setting the callbacks: %d\n", ret);
sBluetoothInterface = NULL;
@@ -772,11 +773,11 @@
return JNI_TRUE;
}
-static jboolean enableNative(JNIEnv* env, jobject obj, jboolean isGuest) {
+static jboolean enableNative(JNIEnv* env, jobject obj) {
ALOGV("%s", __func__);
if (!sBluetoothInterface) return JNI_FALSE;
- int ret = sBluetoothInterface->enable(isGuest == JNI_TRUE ? 1 : 0);
+ int ret = sBluetoothInterface->enable();
return (ret == BT_STATUS_SUCCESS || ret == BT_STATUS_DONE) ? JNI_TRUE
: JNI_FALSE;
}
@@ -1242,12 +1243,31 @@
env->ReleaseByteArrayElements(address, addr, 0);
}
+static jbyteArray obfuscateAddressNative(JNIEnv* env, jobject obj,
+ jbyteArray address) {
+ ALOGV("%s", __func__);
+ if (!sBluetoothInterface) return env->NewByteArray(0);
+ jbyte* addr = env->GetByteArrayElements(address, nullptr);
+ if (addr == nullptr) {
+ jniThrowIOException(env, EINVAL);
+ return env->NewByteArray(0);
+ }
+ RawAddress addr_obj = {};
+ addr_obj.FromOctets((uint8_t*)addr);
+ std::string output = sBluetoothInterface->obfuscate_address(addr_obj);
+ jsize output_size = output.size() * sizeof(char);
+ jbyteArray output_bytes = env->NewByteArray(output_size);
+ env->SetByteArrayRegion(output_bytes, 0, output_size,
+ (const jbyte*)output.data());
+ return output_bytes;
+}
+
static JNINativeMethod sMethods[] = {
/* name, signature, funcPtr */
{"classInitNative", "()V", (void*)classInitNative},
- {"initNative", "()Z", (void*)initNative},
+ {"initNative", "(ZZ)Z", (void*)initNative},
{"cleanupNative", "()V", (void*)cleanupNative},
- {"enableNative", "(Z)Z", (void*)enableNative},
+ {"enableNative", "()Z", (void*)enableNative},
{"disableNative", "()Z", (void*)disableNative},
{"setAdapterPropertyNative", "(I[B)Z", (void*)setAdapterPropertyNative},
{"getAdapterPropertiesNative", "()Z", (void*)getAdapterPropertiesNative},
@@ -1276,7 +1296,8 @@
{"dumpMetricsNative", "()[B", (void*)dumpMetricsNative},
{"factoryResetNative", "()Z", (void*)factoryResetNative},
{"interopDatabaseClearNative", "()V", (void*)interopDatabaseClearNative},
- {"interopDatabaseAddNative", "(I[BI)V", (void*)interopDatabaseAddNative}};
+ {"interopDatabaseAddNative", "(I[BI)V", (void*)interopDatabaseAddNative},
+ {"obfuscateAddressNative", "([B)[B", (void*)obfuscateAddressNative}};
int register_com_android_bluetooth_btservice_AdapterService(JNIEnv* env) {
return jniRegisterNativeMethods(
@@ -1313,6 +1334,12 @@
return JNI_ERR;
}
+ status = android::register_com_android_bluetooth_hfp_vendorhfservice(e);
+ if (status < 0) {
+ ALOGE("jni vendor hfp service registration failure, status: %d", status);
+ return JNI_ERR;
+ }
+
status = android::register_com_android_bluetooth_hfpclient(e);
if (status < 0) {
ALOGE("jni hfp client registration failure, status: %d", status);
@@ -1366,12 +1393,6 @@
return JNI_ERR;
}
- status = android::register_com_android_bluetooth_hdp(e);
- if (status < 0) {
- ALOGE("jni hdp registration failure: %d", status);
- return JNI_ERR;
- }
-
status = android::register_com_android_bluetooth_pan(e);
if (status < 0) {
ALOGE("jni pan registration failure: %d", status);
diff --git a/jni/com_android_bluetooth_gatt.cpp b/jni/com_android_bluetooth_gatt.cpp
index 79726c2..29793bf 100644
--- a/jni/com_android_bluetooth_gatt.cpp
+++ b/jni/com_android_bluetooth_gatt.cpp
@@ -24,6 +24,7 @@
#include "utils/Log.h"
#include <base/bind.h>
+#include <base/callback.h>
#include <string.h>
#include <array>
#include <memory>
@@ -200,6 +201,10 @@
void btgattc_register_app_cb(int status, int clientIf, const Uuid& app_uuid) {
CallbackEnv sCallbackEnv(__func__);
if (!sCallbackEnv.valid()) return;
+ if (!mCallbacksObj) {
+ ALOGE("mCallbacksObj is NULL. Return.");
+ return;
+ }
sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onClientRegistered, status,
clientIf, UUID_PARAMS(app_uuid));
}
@@ -212,6 +217,10 @@
std::vector<uint8_t> adv_data) {
CallbackEnv sCallbackEnv(__func__);
if (!sCallbackEnv.valid()) return;
+ if (!mCallbacksObj) {
+ ALOGE("mCallbacksObj is NULL. Return.");
+ return;
+ }
ScopedLocalRef<jstring> address(sCallbackEnv.get(),
bdaddr2newjstr(sCallbackEnv.get(), bda));
@@ -230,6 +239,10 @@
const RawAddress& bda) {
CallbackEnv sCallbackEnv(__func__);
if (!sCallbackEnv.valid()) return;
+ if (!mCallbacksObj) {
+ ALOGE("mCallbacksObj is NULL. Return.");
+ return;
+ }
ScopedLocalRef<jstring> address(sCallbackEnv.get(),
bdaddr2newjstr(sCallbackEnv.get(), &bda));
@@ -241,6 +254,10 @@
const RawAddress& bda) {
CallbackEnv sCallbackEnv(__func__);
if (!sCallbackEnv.valid()) return;
+ if (!mCallbacksObj) {
+ ALOGE("mCallbacksObj is NULL. Return.");
+ return;
+ }
ScopedLocalRef<jstring> address(sCallbackEnv.get(),
bdaddr2newjstr(sCallbackEnv.get(), &bda));
@@ -251,6 +268,10 @@
void btgattc_search_complete_cb(int conn_id, int status) {
CallbackEnv sCallbackEnv(__func__);
if (!sCallbackEnv.valid()) return;
+ if (!mCallbacksObj) {
+ ALOGE("mCallbacksObj is NULL. Return.");
+ return;
+ }
sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onSearchCompleted, conn_id,
status);
@@ -260,6 +281,10 @@
int status, uint16_t handle) {
CallbackEnv sCallbackEnv(__func__);
if (!sCallbackEnv.valid()) return;
+ if (!mCallbacksObj) {
+ ALOGE("mCallbacksObj is NULL. Return.");
+ return;
+ }
sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onRegisterForNotifications,
conn_id, status, registered, handle);
@@ -268,6 +293,10 @@
void btgattc_notify_cb(int conn_id, const btgatt_notify_params_t& p_data) {
CallbackEnv sCallbackEnv(__func__);
if (!sCallbackEnv.valid()) return;
+ if (!mCallbacksObj) {
+ ALOGE("mCallbacksObj is NULL. Return.");
+ return;
+ }
ScopedLocalRef<jstring> address(
sCallbackEnv.get(), bdaddr2newjstr(sCallbackEnv.get(), &p_data.bda));
@@ -285,6 +314,10 @@
btgatt_read_params_t* p_data) {
CallbackEnv sCallbackEnv(__func__);
if (!sCallbackEnv.valid()) return;
+ if (!mCallbacksObj) {
+ ALOGE("mCallbacksObj is NULL. Return.");
+ return;
+ }
ScopedLocalRef<jbyteArray> jb(sCallbackEnv.get(), NULL);
if (status == 0) { // Success
@@ -304,6 +337,10 @@
void btgattc_write_characteristic_cb(int conn_id, int status, uint16_t handle) {
CallbackEnv sCallbackEnv(__func__);
if (!sCallbackEnv.valid()) return;
+ if (!mCallbacksObj) {
+ ALOGE("mCallbacksObj is NULL. Return.");
+ return;
+ }
sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onWriteCharacteristic,
conn_id, status, handle);
@@ -312,6 +349,10 @@
void btgattc_execute_write_cb(int conn_id, int status) {
CallbackEnv sCallbackEnv(__func__);
if (!sCallbackEnv.valid()) return;
+ if (!mCallbacksObj) {
+ ALOGE("mCallbacksObj is NULL. Return.");
+ return;
+ }
sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onExecuteCompleted,
conn_id, status);
@@ -321,6 +362,10 @@
const btgatt_read_params_t& p_data) {
CallbackEnv sCallbackEnv(__func__);
if (!sCallbackEnv.valid()) return;
+ if (!mCallbacksObj) {
+ ALOGE("mCallbacksObj is NULL. Return.");
+ return;
+ }
ScopedLocalRef<jbyteArray> jb(sCallbackEnv.get(), NULL);
if (p_data.value.len != 0) {
@@ -338,6 +383,10 @@
void btgattc_write_descriptor_cb(int conn_id, int status, uint16_t handle) {
CallbackEnv sCallbackEnv(__func__);
if (!sCallbackEnv.valid()) return;
+ if (!mCallbacksObj) {
+ ALOGE("mCallbacksObj is NULL. Return.");
+ return;
+ }
sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onWriteDescriptor, conn_id,
status, handle);
@@ -347,6 +396,10 @@
int status) {
CallbackEnv sCallbackEnv(__func__);
if (!sCallbackEnv.valid()) return;
+ if (!mCallbacksObj) {
+ ALOGE("mCallbacksObj is NULL. Return.");
+ return;
+ }
ScopedLocalRef<jstring> address(sCallbackEnv.get(),
bdaddr2newjstr(sCallbackEnv.get(), &bda));
@@ -358,6 +411,10 @@
void btgattc_configure_mtu_cb(int conn_id, int status, int mtu) {
CallbackEnv sCallbackEnv(__func__);
if (!sCallbackEnv.valid()) return;
+ if (!mCallbacksObj) {
+ ALOGE("mCallbacksObj is NULL. Return.");
+ return;
+ }
sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onConfigureMTU, conn_id,
status, mtu);
}
@@ -365,6 +422,10 @@
void btgattc_congestion_cb(int conn_id, bool congested) {
CallbackEnv sCallbackEnv(__func__);
if (!sCallbackEnv.valid()) return;
+ if (!mCallbacksObj) {
+ ALOGE("mCallbacksObj is NULL. Return.");
+ return;
+ }
sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onClientCongestion,
conn_id, congested);
}
@@ -373,6 +434,10 @@
int num_records, std::vector<uint8_t> data) {
CallbackEnv sCallbackEnv(__func__);
if (!sCallbackEnv.valid()) return;
+ if (!mCallbacksObj) {
+ ALOGE("mCallbacksObj is NULL. Return.");
+ return;
+ }
ScopedLocalRef<jbyteArray> jb(sCallbackEnv.get(),
sCallbackEnv->NewByteArray(data.size()));
sCallbackEnv->SetByteArrayRegion(jb.get(), 0, data.size(),
@@ -385,6 +450,10 @@
void btgattc_batchscan_threshold_cb(int client_if) {
CallbackEnv sCallbackEnv(__func__);
if (!sCallbackEnv.valid()) return;
+ if (!mCallbacksObj) {
+ ALOGE("mCallbacksObj is NULL. Return.");
+ return;
+ }
sCallbackEnv->CallVoidMethod(mCallbacksObj,
method_onBatchScanThresholdCrossed, client_if);
}
@@ -392,6 +461,10 @@
void btgattc_track_adv_event_cb(btgatt_track_adv_info_t* p_adv_track_info) {
CallbackEnv sCallbackEnv(__func__);
if (!sCallbackEnv.valid()) return;
+ if (!mCallbacksObj) {
+ ALOGE("mCallbacksObj is NULL. Return.");
+ return;
+ }
ScopedLocalRef<jstring> address(
sCallbackEnv.get(),
@@ -495,6 +568,10 @@
int count) {
CallbackEnv sCallbackEnv(__func__);
if (!sCallbackEnv.valid()) return;
+ if (!mCallbacksObj) {
+ ALOGE("mCallbacksObj is NULL. Return.");
+ return;
+ }
jclass arrayListclazz = sCallbackEnv->FindClass("java/util/ArrayList");
ScopedLocalRef<jobject> array(
@@ -514,6 +591,10 @@
uint8_t status) {
CallbackEnv sCallbackEnv(__func__);
if (!sCallbackEnv.valid()) return;
+ if (!mCallbacksObj) {
+ ALOGE("mCallbacksObj is NULL. Return.");
+ return;
+ }
sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onClientPhyUpdate, conn_id,
tx_phy, rx_phy, status);
@@ -523,6 +604,10 @@
uint16_t timeout, uint8_t status) {
CallbackEnv sCallbackEnv(__func__);
if (!sCallbackEnv.valid()) return;
+ if (!mCallbacksObj) {
+ ALOGE("mCallbacksObj is NULL. Return.");
+ return;
+ }
sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onClientConnUpdate,
conn_id, interval, latency, timeout, status);
@@ -563,6 +648,10 @@
void btgatts_register_app_cb(int status, int server_if, const Uuid& uuid) {
CallbackEnv sCallbackEnv(__func__);
if (!sCallbackEnv.valid()) return;
+ if (!mCallbacksObj) {
+ ALOGE("mCallbacksObj is NULL. Return.");
+ return;
+ }
sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onServerRegistered, status,
server_if, UUID_PARAMS(uuid));
}
@@ -571,6 +660,10 @@
const RawAddress& bda) {
CallbackEnv sCallbackEnv(__func__);
if (!sCallbackEnv.valid()) return;
+ if (!mCallbacksObj) {
+ ALOGE("mCallbacksObj is NULL. Return.");
+ return;
+ }
ScopedLocalRef<jstring> address(sCallbackEnv.get(),
bdaddr2newjstr(sCallbackEnv.get(), &bda));
@@ -582,6 +675,10 @@
std::vector<btgatt_db_element_t> service) {
CallbackEnv sCallbackEnv(__func__);
if (!sCallbackEnv.valid()) return;
+ if (!mCallbacksObj) {
+ ALOGE("mCallbacksObj is NULL. Return.");
+ return;
+ }
jclass arrayListclazz = sCallbackEnv->FindClass("java/util/ArrayList");
ScopedLocalRef<jobject> array(
@@ -600,6 +697,10 @@
void btgatts_service_stopped_cb(int status, int server_if, int srvc_handle) {
CallbackEnv sCallbackEnv(__func__);
if (!sCallbackEnv.valid()) return;
+ if (!mCallbacksObj) {
+ ALOGE("mCallbacksObj is NULL. Return.");
+ return;
+ }
sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onServiceStopped, status,
server_if, srvc_handle);
}
@@ -607,6 +708,10 @@
void btgatts_service_deleted_cb(int status, int server_if, int srvc_handle) {
CallbackEnv sCallbackEnv(__func__);
if (!sCallbackEnv.valid()) return;
+ if (!mCallbacksObj) {
+ ALOGE("mCallbacksObj is NULL. Return.");
+ return;
+ }
sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onServiceDeleted, status,
server_if, srvc_handle);
}
@@ -617,6 +722,10 @@
bool is_long) {
CallbackEnv sCallbackEnv(__func__);
if (!sCallbackEnv.valid()) return;
+ if (!mCallbacksObj) {
+ ALOGE("mCallbacksObj is NULL. Return.");
+ return;
+ }
ScopedLocalRef<jstring> address(sCallbackEnv.get(),
bdaddr2newjstr(sCallbackEnv.get(), &bda));
@@ -630,6 +739,10 @@
int offset, bool is_long) {
CallbackEnv sCallbackEnv(__func__);
if (!sCallbackEnv.valid()) return;
+ if (!mCallbacksObj) {
+ ALOGE("mCallbacksObj is NULL. Return.");
+ return;
+ }
ScopedLocalRef<jstring> address(sCallbackEnv.get(),
bdaddr2newjstr(sCallbackEnv.get(), &bda));
@@ -645,6 +758,10 @@
std::vector<uint8_t> value) {
CallbackEnv sCallbackEnv(__func__);
if (!sCallbackEnv.valid()) return;
+ if (!mCallbacksObj) {
+ ALOGE("mCallbacksObj is NULL. Return.");
+ return;
+ }
ScopedLocalRef<jstring> address(sCallbackEnv.get(),
bdaddr2newjstr(sCallbackEnv.get(), &bda));
@@ -666,6 +783,10 @@
std::vector<uint8_t> value) {
CallbackEnv sCallbackEnv(__func__);
if (!sCallbackEnv.valid()) return;
+ if (!mCallbacksObj) {
+ ALOGE("mCallbacksObj is NULL. Return.");
+ return;
+ }
ScopedLocalRef<jstring> address(sCallbackEnv.get(),
bdaddr2newjstr(sCallbackEnv.get(), &bda));
@@ -684,6 +805,10 @@
const RawAddress& bda, int exec_write) {
CallbackEnv sCallbackEnv(__func__);
if (!sCallbackEnv.valid()) return;
+ if (!mCallbacksObj) {
+ ALOGE("mCallbacksObj is NULL. Return.");
+ return;
+ }
ScopedLocalRef<jstring> address(sCallbackEnv.get(),
bdaddr2newjstr(sCallbackEnv.get(), &bda));
@@ -694,6 +819,10 @@
void btgatts_response_confirmation_cb(int status, int handle) {
CallbackEnv sCallbackEnv(__func__);
if (!sCallbackEnv.valid()) return;
+ if (!mCallbacksObj) {
+ ALOGE("mCallbacksObj is NULL. Return.");
+ return;
+ }
sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onResponseSendCompleted,
status, handle);
}
@@ -701,6 +830,10 @@
void btgatts_indication_sent_cb(int conn_id, int status) {
CallbackEnv sCallbackEnv(__func__);
if (!sCallbackEnv.valid()) return;
+ if (!mCallbacksObj) {
+ ALOGE("mCallbacksObj is NULL. Return.");
+ return;
+ }
sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onNotificationSent,
conn_id, status);
}
@@ -708,6 +841,10 @@
void btgatts_congestion_cb(int conn_id, bool congested) {
CallbackEnv sCallbackEnv(__func__);
if (!sCallbackEnv.valid()) return;
+ if (!mCallbacksObj) {
+ ALOGE("mCallbacksObj is NULL. Return.");
+ return;
+ }
sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onServerCongestion,
conn_id, congested);
}
@@ -715,6 +852,10 @@
void btgatts_mtu_changed_cb(int conn_id, int mtu) {
CallbackEnv sCallbackEnv(__func__);
if (!sCallbackEnv.valid()) return;
+ if (!mCallbacksObj) {
+ ALOGE("mCallbacksObj is NULL. Return.");
+ return;
+ }
sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onServerMtuChanged,
conn_id, mtu);
}
@@ -723,6 +864,10 @@
uint8_t status) {
CallbackEnv sCallbackEnv(__func__);
if (!sCallbackEnv.valid()) return;
+ if (!mCallbacksObj) {
+ ALOGE("mCallbacksObj is NULL. Return.");
+ return;
+ }
sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onServerPhyUpdate, conn_id,
tx_phy, rx_phy, status);
@@ -732,6 +877,10 @@
uint16_t timeout, uint8_t status) {
CallbackEnv sCallbackEnv(__func__);
if (!sCallbackEnv.valid()) return;
+ if (!mCallbacksObj) {
+ ALOGE("mCallbacksObj is NULL. Return.");
+ return;
+ }
sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onServerConnUpdate,
conn_id, interval, latency, timeout, status);
@@ -958,6 +1107,10 @@
uint8_t status) {
CallbackEnv sCallbackEnv(__func__);
if (!sCallbackEnv.valid()) return;
+ if (!mCallbacksObj) {
+ ALOGE("mCallbacksObj is NULL. Return.");
+ return;
+ }
sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onScannerRegistered,
status, scannerId, UUID_PARAMS(app_uuid));
}
@@ -1013,6 +1166,10 @@
uint8_t rx_phy, uint8_t status) {
CallbackEnv sCallbackEnv(__func__);
if (!sCallbackEnv.valid()) return;
+ if (!mCallbacksObj) {
+ ALOGE("mCallbacksObj is NULL. Return.");
+ return;
+ }
ScopedLocalRef<jstring> address(sCallbackEnv.get(),
bdaddr2newjstr(sCallbackEnv.get(), &bda));
@@ -1160,6 +1317,10 @@
void set_scan_params_cmpl_cb(int client_if, uint8_t status) {
CallbackEnv sCallbackEnv(__func__);
if (!sCallbackEnv.valid()) return;
+ if (!mCallbacksObj) {
+ ALOGE("mCallbacksObj is NULL. Return.");
+ return;
+ }
sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onScanParamSetupCompleted,
status, client_if);
}
@@ -1190,6 +1351,10 @@
uint8_t status) {
CallbackEnv sCallbackEnv(__func__);
if (!sCallbackEnv.valid()) return;
+ if (!mCallbacksObj) {
+ ALOGE("mCallbacksObj is NULL. Return.");
+ return;
+ }
sCallbackEnv->CallVoidMethod(mCallbacksObj,
method_onScanFilterParamsConfigured, action,
status, client_if, avbl_space);
@@ -1269,6 +1434,10 @@
uint8_t status) {
CallbackEnv sCallbackEnv(__func__);
if (!sCallbackEnv.valid()) return;
+ if (!mCallbacksObj) {
+ ALOGE("mCallbacksObj is NULL. Return.");
+ return;
+ }
sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onScanFilterConfig, action,
status, client_if, filt_type, avbl_space);
}
@@ -1308,6 +1477,9 @@
jfieldID companyMaskFid = env->GetFieldID(entryClazz, "company_mask", "I");
jfieldID dataFid = env->GetFieldID(entryClazz, "data", "[B");
jfieldID dataMaskFid = env->GetFieldID(entryClazz, "data_mask", "[B");
+ jfieldID orgFid = env->GetFieldID(entryClazz, "org_id", "I");
+ jfieldID TDSFlagsFid = env->GetFieldID(entryClazz, "tds_flags", "I");
+ jfieldID TDSFlagsMaskFid = env->GetFieldID(entryClazz, "tds_flags_mask", "I");
for (int i = 0; i < numFilters; ++i) {
ApcfCommand curr;
@@ -1377,6 +1549,10 @@
env->ReleaseByteArrayElements(data_mask.get(), data_array, JNI_ABORT);
}
}
+ curr.org_id = env->GetIntField(current.get(), orgFid);
+ curr.tds_flags = env->GetIntField(current.get(), TDSFlagsFid);
+ curr.tds_flags_mask = env->GetIntField(current.get(), TDSFlagsMaskFid);
+
native_filters.push_back(curr);
}
@@ -1394,6 +1570,10 @@
void scan_enable_cb(uint8_t client_if, uint8_t action, uint8_t status) {
CallbackEnv sCallbackEnv(__func__);
if (!sCallbackEnv.valid()) return;
+ if (!mCallbacksObj) {
+ ALOGE("mCallbacksObj is NULL. Return.");
+ return;
+ }
sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onScanFilterEnableDisabled,
action, status, client_if);
}
@@ -1426,6 +1606,10 @@
void batchscan_cfg_storage_cb(uint8_t client_if, uint8_t status) {
CallbackEnv sCallbackEnv(__func__);
if (!sCallbackEnv.valid()) return;
+ if (!mCallbacksObj) {
+ ALOGE("mCallbacksObj is NULL. Return.");
+ return;
+ }
sCallbackEnv->CallVoidMethod(
mCallbacksObj, method_onBatchScanStorageConfigured, status, client_if);
}
@@ -1443,6 +1627,10 @@
void batchscan_enable_cb(uint8_t client_if, uint8_t status) {
CallbackEnv sCallbackEnv(__func__);
if (!sCallbackEnv.valid()) return;
+ if (!mCallbacksObj) {
+ ALOGE("mCallbacksObj is NULL. Return.");
+ return;
+ }
sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onBatchScanStartStopped,
0 /* unused */, status, client_if);
}
@@ -1517,6 +1705,10 @@
uint8_t rx_phy, uint8_t status) {
CallbackEnv sCallbackEnv(__func__);
if (!sCallbackEnv.valid()) return;
+ if (!mCallbacksObj) {
+ ALOGE("mCallbacksObj is NULL. Return.");
+ return;
+ }
ScopedLocalRef<jstring> address(sCallbackEnv.get(),
bdaddr2newjstr(sCallbackEnv.get(), &bda));
@@ -1794,6 +1986,10 @@
int8_t tx_power, uint8_t status) {
CallbackEnv sCallbackEnv(__func__);
if (!sCallbackEnv.valid()) return;
+ if (!mAdvertiseCallbacksObj) {
+ ALOGE("mAdvertiseCallbacksObj is NULL. Return.");
+ return;
+ }
sCallbackEnv->CallVoidMethod(mAdvertiseCallbacksObj,
method_onAdvertisingSetStarted, reg_id,
advertiser_id, tx_power, status);
@@ -1803,6 +1999,10 @@
uint8_t status) {
CallbackEnv sCallbackEnv(__func__);
if (!sCallbackEnv.valid()) return;
+ if (!mAdvertiseCallbacksObj) {
+ ALOGE("mAdvertiseCallbacksObj is NULL. Return.");
+ return;
+ }
sCallbackEnv->CallVoidMethod(mAdvertiseCallbacksObj,
method_onAdvertisingEnabled, advertiser_id,
false, status);
@@ -1854,6 +2054,10 @@
RawAddress address) {
CallbackEnv sCallbackEnv(__func__);
if (!sCallbackEnv.valid()) return;
+ if (!mAdvertiseCallbacksObj) {
+ ALOGE("mAdvertiseCallbacksObj is NULL. Return.");
+ return;
+ }
ScopedLocalRef<jstring> addr(sCallbackEnv.get(),
bdaddr2newjstr(sCallbackEnv.get(), &address));
@@ -1872,6 +2076,10 @@
uint8_t status) {
CallbackEnv sCallbackEnv(__func__);
if (!sCallbackEnv.valid()) return;
+ if (!mAdvertiseCallbacksObj) {
+ ALOGE("mAdvertiseCallbacksObj is NULL. Return.");
+ return;
+ }
sCallbackEnv->CallVoidMethod(mAdvertiseCallbacksObj, method, advertiser_id,
status);
}
@@ -1879,6 +2087,10 @@
static void enableSetCb(uint8_t advertiser_id, bool enable, uint8_t status) {
CallbackEnv sCallbackEnv(__func__);
if (!sCallbackEnv.valid()) return;
+ if (!mAdvertiseCallbacksObj) {
+ ALOGE("mAdvertiseCallbacksObj is NULL. Return.");
+ return;
+ }
sCallbackEnv->CallVoidMethod(mAdvertiseCallbacksObj,
method_onAdvertisingEnabled, advertiser_id,
enable, status);
@@ -1918,6 +2130,10 @@
uint8_t status, int8_t tx_power) {
CallbackEnv sCallbackEnv(__func__);
if (!sCallbackEnv.valid()) return;
+ if (!mAdvertiseCallbacksObj) {
+ ALOGE("mAdvertiseCallbacksObj is NULL. Return.");
+ return;
+ }
sCallbackEnv->CallVoidMethod(mAdvertiseCallbacksObj,
method_onAdvertisingParametersUpdated,
advertiser_id, tx_power, status);
@@ -1962,6 +2178,10 @@
uint8_t status) {
CallbackEnv sCallbackEnv(__func__);
if (!sCallbackEnv.valid()) return;
+ if (!mAdvertiseCallbacksObj) {
+ ALOGE("mAdvertiseCallbacksObj is NULL. Return.");
+ return;
+ }
sCallbackEnv->CallVoidMethod(mAdvertiseCallbacksObj,
method_onPeriodicAdvertisingEnabled,
advertiser_id, enable, status);
@@ -2006,6 +2226,10 @@
uint8_t phy, uint16_t interval) {
CallbackEnv sCallbackEnv(__func__);
if (!sCallbackEnv.valid()) return;
+ if (!mPeriodicScanCallbacksObj) {
+ ALOGE("mPeriodicScanCallbacksObj is NULL. Return.");
+ return;
+ }
sCallbackEnv->CallVoidMethod(mPeriodicScanCallbacksObj, method_onSyncStarted,
reg_id, sync_handle, sid, address_type, address,
@@ -2016,6 +2240,10 @@
uint8_t data_status, std::vector<uint8_t> data) {
CallbackEnv sCallbackEnv(__func__);
if (!sCallbackEnv.valid()) return;
+ if (!mPeriodicScanCallbacksObj) {
+ ALOGE("mPeriodicScanCallbacksObj is NULL. Return.");
+ return;
+ }
ScopedLocalRef<jbyteArray> jb(sCallbackEnv.get(),
sCallbackEnv->NewByteArray(data.size()));
@@ -2030,6 +2258,10 @@
static void onSyncLost(uint16_t sync_handle) {
CallbackEnv sCallbackEnv(__func__);
if (!sCallbackEnv.valid()) return;
+ if (!mPeriodicScanCallbacksObj) {
+ ALOGE("mPeriodicScanCallbacksObj is NULL. Return.");
+ return;
+ }
sCallbackEnv->CallVoidMethod(mPeriodicScanCallbacksObj, method_onSyncLost,
sync_handle);
diff --git a/jni/com_android_bluetooth_hdp.cpp b/jni/com_android_bluetooth_hdp.cpp
deleted file mode 100644
index 3abc243..0000000
--- a/jni/com_android_bluetooth_hdp.cpp
+++ /dev/null
@@ -1,244 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#define LOG_TAG "BluetoothHealthServiceJni"
-
-#define LOG_NDEBUG 0
-
-#include "android_runtime/AndroidRuntime.h"
-#include "com_android_bluetooth.h"
-#include "hardware/bt_hl.h"
-#include "utils/Log.h"
-
-#include <string.h>
-
-namespace android {
-
-static jmethodID method_onAppRegistrationState;
-static jmethodID method_onChannelStateChanged;
-
-static const bthl_interface_t* sBluetoothHdpInterface = NULL;
-static jobject mCallbacksObj = NULL;
-
-// Define callback functions
-static void app_registration_state_callback(int app_id,
- bthl_app_reg_state_t state) {
- CallbackEnv sCallbackEnv(__func__);
- if (!sCallbackEnv.valid()) return;
- sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onAppRegistrationState,
- app_id, (jint)state);
-}
-
-static void channel_state_callback(int app_id, RawAddress* bd_addr,
- int mdep_cfg_index, int channel_id,
- bthl_channel_state_t state, int fd) {
- CallbackEnv sCallbackEnv(__func__);
- if (!sCallbackEnv.valid()) return;
- ScopedLocalRef<jbyteArray> addr(
- sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
- if (!addr.get()) {
- ALOGE("Fail to new jbyteArray bd addr for channel state");
- return;
- }
-
- // TODO(BT) check if fd is only valid for BTHH_CONN_STATE_CONNECTED state
- jobject fileDescriptor = NULL;
- if (state == BTHL_CONN_STATE_CONNECTED) {
- fileDescriptor = jniCreateFileDescriptor(sCallbackEnv.get(), fd);
- if (!fileDescriptor) {
- ALOGE("Failed to convert file descriptor, fd: %d", fd);
- return;
- }
- }
-
- sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
- (jbyte*)bd_addr);
- sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onChannelStateChanged,
- app_id, addr.get(), mdep_cfg_index, channel_id,
- (jint)state, fileDescriptor);
-}
-
-static bthl_callbacks_t sBluetoothHdpCallbacks = {
- sizeof(sBluetoothHdpCallbacks), app_registration_state_callback,
- channel_state_callback};
-
-// Define native functions
-
-static void classInitNative(JNIEnv* env, jclass clazz) {
- method_onAppRegistrationState =
- env->GetMethodID(clazz, "onAppRegistrationState", "(II)V");
- method_onChannelStateChanged = env->GetMethodID(
- clazz, "onChannelStateChanged", "(I[BIIILjava/io/FileDescriptor;)V");
- ALOGI("%s: succeeds", __func__);
-}
-
-static void initializeNative(JNIEnv* env, jobject object) {
- const bt_interface_t* btInf = getBluetoothInterface();
- if (btInf == NULL) {
- ALOGE("Bluetooth module is not loaded");
- return;
- }
-
- if (sBluetoothHdpInterface != NULL) {
- ALOGW("Cleaning up Bluetooth Health Interface before initializing...");
- sBluetoothHdpInterface->cleanup();
- sBluetoothHdpInterface = NULL;
- }
-
- if (mCallbacksObj != NULL) {
- ALOGW("Cleaning up Bluetooth Health callback object");
- env->DeleteGlobalRef(mCallbacksObj);
- mCallbacksObj = NULL;
- }
-
- sBluetoothHdpInterface =
- (bthl_interface_t*)btInf->get_profile_interface(BT_PROFILE_HEALTH_ID);
- if (sBluetoothHdpInterface == NULL) {
- ALOGE("Failed to get Bluetooth Health Interface");
- return;
- }
-
- bt_status_t status = sBluetoothHdpInterface->init(&sBluetoothHdpCallbacks);
- if (status != BT_STATUS_SUCCESS) {
- ALOGE("Failed to initialize Bluetooth HDP, status: %d", status);
- sBluetoothHdpInterface = NULL;
- return;
- }
-
- mCallbacksObj = env->NewGlobalRef(object);
-}
-
-static void cleanupNative(JNIEnv* env, jobject object) {
- const bt_interface_t* btInf = getBluetoothInterface();
-
- if (btInf == NULL) {
- ALOGE("Bluetooth module is not loaded");
- return;
- }
-
- if (sBluetoothHdpInterface != NULL) {
- ALOGW("Cleaning up Bluetooth Health Interface...");
- sBluetoothHdpInterface->cleanup();
- sBluetoothHdpInterface = NULL;
- }
-
- if (mCallbacksObj != NULL) {
- ALOGW("Cleaning up Bluetooth Health object");
- env->DeleteGlobalRef(mCallbacksObj);
- mCallbacksObj = NULL;
- }
-}
-
-static jint registerHealthAppNative(JNIEnv* env, jobject object, jint data_type,
- jint role, jstring name,
- jint channel_type) {
- if (!sBluetoothHdpInterface) {
- ALOGE(
- "Failed to register health app. No Bluetooth Health Interface "
- "available");
- return -1;
- }
-
- bthl_mdep_cfg_t mdep_cfg;
- mdep_cfg.mdep_role = (bthl_mdep_role_t)role;
- mdep_cfg.data_type = data_type;
- mdep_cfg.channel_type = (bthl_channel_type_t)channel_type;
- // TODO(BT) pass all the followings in from java instead of reuse name
- mdep_cfg.mdep_description = env->GetStringUTFChars(name, NULL);
-
- bthl_reg_param_t reg_param;
- reg_param.application_name = env->GetStringUTFChars(name, NULL);
- reg_param.provider_name = NULL;
- reg_param.srv_name = NULL;
- reg_param.srv_desp = NULL;
- reg_param.number_of_mdeps = 1;
- reg_param.mdep_cfg = &mdep_cfg;
-
- int app_id;
- bt_status_t status =
- sBluetoothHdpInterface->register_application(®_param, &app_id);
- if (status != BT_STATUS_SUCCESS) {
- ALOGE("Failed register health app, status: %d", status);
- return -1;
- }
-
- env->ReleaseStringUTFChars(name, mdep_cfg.mdep_description);
- env->ReleaseStringUTFChars(name, reg_param.application_name);
- return app_id;
-}
-
-static jboolean unregisterHealthAppNative(JNIEnv* env, jobject object,
- int app_id) {
- if (!sBluetoothHdpInterface) return JNI_FALSE;
-
- bt_status_t status = sBluetoothHdpInterface->unregister_application(app_id);
- if (status != BT_STATUS_SUCCESS) {
- ALOGE("Failed to unregister app %d, status: %d", app_id, status);
- }
- return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
-}
-
-static jint connectChannelNative(JNIEnv* env, jobject object,
- jbyteArray address, jint app_id) {
- if (!sBluetoothHdpInterface) return -1;
-
- jbyte* addr = env->GetByteArrayElements(address, NULL);
- if (!addr) {
- ALOGE("Bluetooth device address null");
- return -1;
- }
-
- jint chan_id;
- bt_status_t status = sBluetoothHdpInterface->connect_channel(
- app_id, (RawAddress*)addr, 0, &chan_id);
- if (status != BT_STATUS_SUCCESS) {
- ALOGE("Failed HDP channel connection, status: %d", status);
- chan_id = -1;
- }
- env->ReleaseByteArrayElements(address, addr, 0);
-
- return chan_id;
-}
-
-static jboolean disconnectChannelNative(JNIEnv* env, jobject object,
- jint channel_id) {
- if (!sBluetoothHdpInterface) return JNI_FALSE;
-
- bt_status_t status = sBluetoothHdpInterface->destroy_channel(channel_id);
- if (status != BT_STATUS_SUCCESS) {
- ALOGE("Failed disconnect health channel, status: %d", status);
- return JNI_FALSE;
- }
- return JNI_TRUE;
-}
-
-static JNINativeMethod sMethods[] = {
- {"classInitNative", "()V", (void*)classInitNative},
- {"initializeNative", "()V", (void*)initializeNative},
- {"cleanupNative", "()V", (void*)cleanupNative},
- {"registerHealthAppNative", "(IILjava/lang/String;I)I",
- (void*)registerHealthAppNative},
- {"unregisterHealthAppNative", "(I)Z", (void*)unregisterHealthAppNative},
- {"connectChannelNative", "([BI)I", (void*)connectChannelNative},
- {"disconnectChannelNative", "(I)Z", (void*)disconnectChannelNative},
-};
-
-int register_com_android_bluetooth_hdp(JNIEnv* env) {
- return jniRegisterNativeMethods(env,
- "com/android/bluetooth/hdp/HealthService",
- sMethods, NELEM(sMethods));
-}
-}
diff --git a/jni/com_android_bluetooth_hearing_aid.cpp b/jni/com_android_bluetooth_hearing_aid.cpp
index 1602aac..f93a42a 100644
--- a/jni/com_android_bluetooth_hearing_aid.cpp
+++ b/jni/com_android_bluetooth_hearing_aid.cpp
@@ -198,7 +198,6 @@
jbyteArray address) {
std::shared_lock<std::shared_timed_mutex> lock(interface_mutex);
if (!sHearingAidInterface) return JNI_FALSE;
-
jbyte* addr = env->GetByteArrayElements(address, nullptr);
if (!addr) {
jniThrowIOException(env, EINVAL);
@@ -211,23 +210,6 @@
return JNI_TRUE;
}
-static jboolean removeFromWhiteListNative(JNIEnv* env, jobject object,
- jbyteArray address) {
- std::shared_lock<std::shared_timed_mutex> lock(interface_mutex);
- if (!sHearingAidInterface) return JNI_FALSE;
-
- jbyte* addr = env->GetByteArrayElements(address, nullptr);
- if (!addr) {
- jniThrowIOException(env, EINVAL);
- return JNI_FALSE;
- }
-
- RawAddress* tmpraw = (RawAddress*)addr;
- sHearingAidInterface->RemoveFromWhiteList(*tmpraw);
- env->ReleaseByteArrayElements(address, addr, 0);
- return JNI_TRUE;
-}
-
static void setVolumeNative(JNIEnv* env, jclass clazz, jint volume) {
if (!sHearingAidInterface) {
LOG(ERROR) << __func__
@@ -244,7 +226,6 @@
{"connectHearingAidNative", "([B)Z", (void*)connectHearingAidNative},
{"disconnectHearingAidNative", "([B)Z", (void*)disconnectHearingAidNative},
{"addToWhiteListNative", "([B)Z", (void*)addToWhiteListNative},
- {"removeFromWhiteListNative", "([B)Z", (void*)removeFromWhiteListNative},
{"setVolumeNative", "(I)V", (void*)setVolumeNative},
};
diff --git a/jni/com_android_bluetooth_hfp.cpp b/jni/com_android_bluetooth_hfp.cpp
index 4b01d8d..c3a8ddc 100644
--- a/jni/com_android_bluetooth_hfp.cpp
+++ b/jni/com_android_bluetooth_hfp.cpp
@@ -38,7 +38,7 @@
static jmethodID method_onVolumeChanged;
static jmethodID method_onDialCall;
static jmethodID method_onSendDtmf;
-static jmethodID method_onNoiceReductionEnable;
+static jmethodID method_onNoiseReductionEnable;
static jmethodID method_onWBS;
static jmethodID method_onAtChld;
static jmethodID method_onAtCnum;
@@ -182,6 +182,13 @@
return;
}
+ char null_str[] = "";
+ if (number != nullptr && !sCallbackEnv.isValidUtf(number)) {
+ android_errorWriteLog(0x534e4554, "109838537");
+ ALOGE("%s: number is not a valid UTF string.", __func__);
+ number = null_str;
+ }
+
ScopedLocalRef<jstring> js_number(sCallbackEnv.get(),
sCallbackEnv->NewStringUTF(number));
sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onDialCall,
@@ -215,7 +222,7 @@
ALOGE("Fail to new jbyteArray bd addr for audio state");
return;
}
- sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onNoiceReductionEnable,
+ sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onNoiseReductionEnable,
nrec == bluetooth::headset::BTHF_NREC_START,
addr.get());
}
@@ -319,6 +326,13 @@
return;
}
+ char null_str[] = "";
+ if (!sCallbackEnv.isValidUtf(at_string)) {
+ android_errorWriteLog(0x534e4554, "109838537");
+ ALOGE("%s: at_string is not a valid UTF string.", __func__);
+ at_string = null_str;
+ }
+
ScopedLocalRef<jstring> js_at_string(sCallbackEnv.get(),
sCallbackEnv->NewStringUTF(at_string));
sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onUnknownAt,
@@ -348,6 +362,13 @@
ScopedLocalRef<jbyteArray> addr(sCallbackEnv.get(), marshall_bda(bd_addr));
if (addr.get() == nullptr) return;
+ char null_str[] = "";
+ if (!sCallbackEnv.isValidUtf(at_string)) {
+ android_errorWriteLog(0x534e4554, "109838537");
+ ALOGE("%s: at_string is not a valid UTF string.", __func__);
+ at_string = null_str;
+ }
+
ScopedLocalRef<jstring> js_at_string(sCallbackEnv.get(),
sCallbackEnv->NewStringUTF(at_string));
@@ -396,8 +417,8 @@
method_onDialCall =
env->GetMethodID(clazz, "onDialCall", "(Ljava/lang/String;[B)V");
method_onSendDtmf = env->GetMethodID(clazz, "onSendDtmf", "(I[B)V");
- method_onNoiceReductionEnable =
- env->GetMethodID(clazz, "onNoiceReductionEnable", "(Z[B)V");
+ method_onNoiseReductionEnable =
+ env->GetMethodID(clazz, "onNoiseReductionEnable", "(Z[B)V");
method_onWBS = env->GetMethodID(clazz, "onWBS", "(I[B)V");
method_onAtChld = env->GetMethodID(clazz, "onAtChld", "(I[B)V");
method_onAtCnum = env->GetMethodID(clazz, "onAtCnum", "([B)V");
@@ -805,7 +826,8 @@
static jboolean phoneStateChangeNative(JNIEnv* env, jobject object,
jint num_active, jint num_held,
jint call_state, jstring number_str,
- jint type, jbyteArray address) {
+ jint type, jstring name_str,
+ jbyteArray address) {
std::shared_lock<std::shared_timed_mutex> lock(interface_mutex);
if (!sBluetoothHfpInterface) {
ALOGW("%s: sBluetoothHfpInterface is null", __func__);
@@ -818,14 +840,21 @@
return JNI_FALSE;
}
const char* number = env->GetStringUTFChars(number_str, nullptr);
+ const char* name = nullptr;
+ if (name_str != nullptr) {
+ name = env->GetStringUTFChars(name_str, nullptr);
+ }
bt_status_t status = sBluetoothHfpInterface->PhoneStateChange(
num_active, num_held, (bluetooth::headset::bthf_call_state_t)call_state,
- number, (bluetooth::headset::bthf_call_addrtype_t)type,
+ number, (bluetooth::headset::bthf_call_addrtype_t)type, name,
(RawAddress*)addr);
if (status != BT_STATUS_SUCCESS) {
ALOGE("Failed report phone state change, status: %d", status);
}
env->ReleaseStringUTFChars(number_str, number);
+ if (name != nullptr) {
+ env->ReleaseStringUTFChars(name_str, name);
+ }
env->ReleaseByteArrayElements(address, addr, 0);
return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
}
@@ -909,7 +938,7 @@
{"atResponseCodeNative", "(II[B)Z", (void*)atResponseCodeNative},
{"clccResponseNative", "(IIIIZLjava/lang/String;I[B)Z",
(void*)clccResponseNative},
- {"phoneStateChangeNative", "(IIILjava/lang/String;I[B)Z",
+ {"phoneStateChangeNative", "(IIILjava/lang/String;ILjava/lang/String;[B)Z",
(void*)phoneStateChangeNative},
{"setScoAllowedNative", "(Z)Z", (void*)setScoAllowedNative},
{"sendBsirNative", "(Z[B)Z", (void*)sendBsirNative},
diff --git a/jni/com_android_bluetooth_hfpclient.cpp b/jni/com_android_bluetooth_hfpclient.cpp
index 3dfc86e..f3adba2 100644
--- a/jni/com_android_bluetooth_hfpclient.cpp
+++ b/jni/com_android_bluetooth_hfpclient.cpp
@@ -155,6 +155,13 @@
ScopedLocalRef<jbyteArray> addr(sCallbackEnv.get(), marshall_bda(bd_addr));
if (!addr.get()) return;
+ const char null_str[] = "";
+ if (!sCallbackEnv.isValidUtf(name)) {
+ android_errorWriteLog(0x534e4554, "109838537");
+ ALOGE("%s: name is not a valid UTF string.", __func__);
+ name = null_str;
+ }
+
ScopedLocalRef<jstring> js_name(sCallbackEnv.get(),
sCallbackEnv->NewStringUTF(name));
sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onCurrentOperator,
@@ -219,6 +226,13 @@
ScopedLocalRef<jbyteArray> addr(sCallbackEnv.get(), marshall_bda(bd_addr));
if (!addr.get()) return;
+ const char null_str[] = "";
+ if (!sCallbackEnv.isValidUtf(number)) {
+ android_errorWriteLog(0x534e4554, "109838537");
+ ALOGE("%s: number is not a valid UTF string.", __func__);
+ number = null_str;
+ }
+
ScopedLocalRef<jstring> js_number(sCallbackEnv.get(),
sCallbackEnv->NewStringUTF(number));
sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onClip, js_number.get(),
@@ -231,6 +245,14 @@
ScopedLocalRef<jbyteArray> addr(sCallbackEnv.get(), marshall_bda(bd_addr));
if (!addr.get()) return;
+
+ const char null_str[] = "";
+ if (!sCallbackEnv.isValidUtf(number)) {
+ android_errorWriteLog(0x534e4554, "109838537");
+ ALOGE("%s: number is not a valid UTF string.", __func__);
+ number = null_str;
+ }
+
ScopedLocalRef<jstring> js_number(sCallbackEnv.get(),
sCallbackEnv->NewStringUTF(number));
sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onCallWaiting,
@@ -247,6 +269,14 @@
ScopedLocalRef<jbyteArray> addr(sCallbackEnv.get(), marshall_bda(bd_addr));
if (!addr.get()) return;
+
+ const char null_str[] = "";
+ if (!sCallbackEnv.isValidUtf(number)) {
+ android_errorWriteLog(0x534e4554, "109838537");
+ ALOGE("%s: number is not a valid UTF string.", __func__);
+ number = null_str;
+ }
+
ScopedLocalRef<jstring> js_number(sCallbackEnv.get(),
sCallbackEnv->NewStringUTF(number));
sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onCurrentCalls, index, dir,
@@ -282,6 +312,14 @@
ScopedLocalRef<jbyteArray> addr(sCallbackEnv.get(), marshall_bda(bd_addr));
if (!addr.get()) return;
+
+ const char null_str[] = "";
+ if (!sCallbackEnv.isValidUtf(name)) {
+ android_errorWriteLog(0x534e4554, "109838537");
+ ALOGE("%s: name is not a valid UTF string.", __func__);
+ name = null_str;
+ }
+
ScopedLocalRef<jstring> js_name(sCallbackEnv.get(),
sCallbackEnv->NewStringUTF(name));
sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onSubscriberInfo,
@@ -306,6 +344,14 @@
ScopedLocalRef<jbyteArray> addr(sCallbackEnv.get(), marshall_bda(bd_addr));
if (!addr.get()) return;
+
+ const char null_str[] = "";
+ if (!sCallbackEnv.isValidUtf(number)) {
+ android_errorWriteLog(0x534e4554, "109838537");
+ ALOGE("%s: number is not a valid UTF string.", __func__);
+ number = null_str;
+ }
+
ScopedLocalRef<jstring> js_number(sCallbackEnv.get(),
sCallbackEnv->NewStringUTF(number));
sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onLastVoiceTagNumber,
diff --git a/jni/com_android_bluetooth_sdp.cpp b/jni/com_android_bluetooth_sdp.cpp
index c2eb5ce..827db71 100644
--- a/jni/com_android_bluetooth_sdp.cpp
+++ b/jni/com_android_bluetooth_sdp.cpp
@@ -307,6 +307,37 @@
return handle;
}
+static jint sdpCreatePbapPceRecordNative(JNIEnv* env, jobject obj,
+ jstring name_str, jint version) {
+ ALOGD("%s", __func__);
+ if (!sBluetoothSdpInterface) return -1;
+
+ bluetooth_sdp_record record = {}; // Must be zero initialized
+ record.pce.hdr.type = SDP_TYPE_PBAP_PCE;
+
+ const char* service_name = NULL;
+ if (name_str != NULL) {
+ service_name = env->GetStringUTFChars(name_str, NULL);
+ record.pce.hdr.service_name = (char*)service_name;
+ record.pce.hdr.service_name_length = strlen(service_name);
+ } else {
+ record.pce.hdr.service_name = NULL;
+ record.pce.hdr.service_name_length = 0;
+ }
+ record.pce.hdr.profile_version = version;
+
+ int handle = -1;
+ int ret = sBluetoothSdpInterface->create_sdp_record(&record, &handle);
+ if (ret != BT_STATUS_SUCCESS) {
+ ALOGE("SDP Create record failed: %d", ret);
+ } else {
+ ALOGD("SDP Create record success - handle: %d", handle);
+ }
+
+ if (service_name) env->ReleaseStringUTFChars(name_str, service_name);
+ return handle;
+}
+
static jint sdpCreatePbapPseRecordNative(JNIEnv* env, jobject obj,
jstring name_str, jint scn,
jint l2cap_psm, jint version,
@@ -474,6 +505,8 @@
(void*)sdpCreateMapMasRecordNative},
{"sdpCreateMapMnsRecordNative", "(Ljava/lang/String;IIII)I",
(void*)sdpCreateMapMnsRecordNative},
+ {"sdpCreatePbapPceRecordNative", "(Ljava/lang/String;I)I",
+ (void*)sdpCreatePbapPceRecordNative},
{"sdpCreatePbapPseRecordNative", "(Ljava/lang/String;IIIII)I",
(void*)sdpCreatePbapPseRecordNative},
{"sdpCreateOppOpsRecordNative", "(Ljava/lang/String;III[B)I",
diff --git a/lib/room/annotation-1.0.0-beta01.jar b/lib/room/annotation-1.0.0-beta01.jar
new file mode 100644
index 0000000..124f128
--- /dev/null
+++ b/lib/room/annotation-1.0.0-beta01.jar
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
new file mode 100644
index 0000000..931d4aa
--- /dev/null
+++ b/lib/room/room-common-2.0.0-beta01.jar
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
new file mode 100644
index 0000000..623a07b
--- /dev/null
+++ b/lib/room/room-compiler-2.0.0-beta01.jar
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
new file mode 100644
index 0000000..04bca6f
--- /dev/null
+++ b/lib/room/room-migration-2.0.0-beta01.jar
Binary files differ
diff --git a/res/layout/bt_enabling_progress.xml b/res/layout/bt_enabling_progress.xml
index 7f2fd72..0e4f870 100644
--- a/res/layout/bt_enabling_progress.xml
+++ b/res/layout/bt_enabling_progress.xml
@@ -27,7 +27,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
- <ProgressBar android:id="@+android:id/progress"
+ <ProgressBar android:id="@+id/progress"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
diff --git a/res/mipmap-anydpi/bt_share.xml b/res/mipmap-anydpi/bt_share.xml
new file mode 100644
index 0000000..c99e560
--- /dev/null
+++ b/res/mipmap-anydpi/bt_share.xml
@@ -0,0 +1,28 @@
+<!--
+ 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
+ -->
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+ <foreground>
+ <inset
+ android:drawable="@*android:drawable/ic_bluetooth_share_icon"
+ android:insetTop="25%"
+ android:insetRight="25%"
+ android:insetBottom="25%"
+ android:insetLeft="25%" />
+ </foreground>
+ <background>
+ <color android:color="@android:color/white" />
+ </background>
+</adaptive-icon>
diff --git a/res/mipmap-hdpi/bt_share.png b/res/mipmap-hdpi/bt_share.png
deleted file mode 100644
index 6d16ebf..0000000
--- a/res/mipmap-hdpi/bt_share.png
+++ /dev/null
Binary files differ
diff --git a/res/mipmap-mdpi/bt_share.png b/res/mipmap-mdpi/bt_share.png
deleted file mode 100644
index 1f514d2..0000000
--- a/res/mipmap-mdpi/bt_share.png
+++ /dev/null
Binary files differ
diff --git a/res/mipmap-xhdpi/bt_share.png b/res/mipmap-xhdpi/bt_share.png
deleted file mode 100644
index 7bd0e9c..0000000
--- a/res/mipmap-xhdpi/bt_share.png
+++ /dev/null
Binary files differ
diff --git a/res/mipmap-xxhdpi/bt_share.png b/res/mipmap-xxhdpi/bt_share.png
deleted file mode 100644
index 0fe218e..0000000
--- a/res/mipmap-xxhdpi/bt_share.png
+++ /dev/null
Binary files differ
diff --git a/res/mipmap-xxxhdpi/bt_share.png b/res/mipmap-xxxhdpi/bt_share.png
deleted file mode 100644
index 7bf313f..0000000
--- a/res/mipmap-xxxhdpi/bt_share.png
+++ /dev/null
Binary files differ
diff --git a/res/raw/silent.wav b/res/raw/silent.wav
new file mode 100755
index 0000000..d6d93ef
--- /dev/null
+++ b/res/raw/silent.wav
Binary files differ
diff --git a/res/values-af/strings.xml b/res/values-af/strings.xml
index ce320e1..060fd90 100644
--- a/res/values-af/strings.xml
+++ b/res/values-af/strings.xml
@@ -84,8 +84,8 @@
<string name="bt_toast_4" msgid="4678812947604395649">"Stuur lêer na \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
<string name="bt_toast_5" msgid="2846870992823019494">"Stuur <xliff:g id="NUMBER">%1$s</xliff:g> lêers na \"<xliff:g id="RECIPIENT">%2$s</xliff:g>\""</string>
<string name="bt_toast_6" msgid="1855266596936622458">"Opgehou om lêer na \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\" te stuur"</string>
- <string name="bt_sm_2_1" product="nosdcard" msgid="352165168004521000">"Daar is nie genoeg ruimte op die USB-berging om die lêer van \"<xliff:g id="SENDER">%1$s</xliff:g>\" te stoor nie"</string>
- <string name="bt_sm_2_1" product="default" msgid="1989018443456803630">"Daar is te min spasie op die SD-kaart om die lêer van \"<xliff:g id="SENDER">%1$s</xliff:g>\" te stoor"</string>
+ <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"Daar is te min spasie op die USB-berging om die lêer van \"<xliff:g id="SENDER">%1$s</xliff:g>\" af te stoor"</string>
+ <string name="bt_sm_2_1_default" msgid="9115512207909504071">"Daar is te min spasie op die SD-kaart om die lêer van \"<xliff:g id="SENDER">%1$s</xliff:g>\" af te stoor"</string>
<string name="bt_sm_2_2" msgid="2965243265852680543">"Spasie nodig: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
<string name="ErrorTooManyRequests" msgid="8578277541472944529">"Te veel versoeke word verwerk. Probeer later weer."</string>
<string name="status_pending" msgid="2503691772030877944">"Lêeroordrag nog nie begin nie."</string>
@@ -95,8 +95,8 @@
<string name="status_forbidden" msgid="613956401054050725">"Oordrag deur teikentoestel verbied."</string>
<string name="status_canceled" msgid="6664490318773098285">"Oordrag gekanselleer deur die gebruiker."</string>
<string name="status_file_error" msgid="3671917770630165299">"Bergingsprobleem."</string>
- <string name="status_no_sd_card" product="nosdcard" msgid="1112125377088421469">"Geen USB-berging nie."</string>
- <string name="status_no_sd_card" product="default" msgid="5760944071743325592">"Geen SD-kaart nie. Sit \'n SD-kaart in om oorgedraagde lêers te stoor."</string>
+ <string name="status_no_sd_card_nosdcard" msgid="573631036356922221">"Geen USB-berging nie."</string>
+ <string name="status_no_sd_card_default" msgid="396564893716701954">"Geen SD-kaart nie. Sit \'n SD-kaart in om oorgedraagde lêers te stoor."</string>
<string name="status_connection_error" msgid="947681831523219891">"Verbinding onsuksesvol."</string>
<string name="status_protocol_error" msgid="3245444473429269539">"Versoek kan nie korrek hanteer word nie."</string>
<string name="status_unknown_error" msgid="8156660554237824912">"Onbekende fout"</string>
@@ -122,6 +122,7 @@
<string name="transfer_menu_open" msgid="3368984869083107200">"Open"</string>
<string name="transfer_menu_clear" msgid="5854038118831427492">"Verwyder uit lys"</string>
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"Vee uit"</string>
+ <string name="bluetooth_a2dp_sink_queue_name" msgid="6864149958708669766">"Wat Speel"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"Stoor"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"Kanselleer"</string>
<string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"Kies die rekeninge wat jy deur Bluetooth wil deel. Jy moet steeds enige toegang tot die rekeninge aanvaar wanneer jy koppel."</string>
diff --git a/res/values-am/strings.xml b/res/values-am/strings.xml
index 0f40380..dbf4aad 100644
--- a/res/values-am/strings.xml
+++ b/res/values-am/strings.xml
@@ -84,8 +84,8 @@
<string name="bt_toast_4" msgid="4678812947604395649">"ፋይል ወደ \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\" በመላክ ላይ"</string>
<string name="bt_toast_5" msgid="2846870992823019494">"<xliff:g id="NUMBER">%1$s</xliff:g> ፋይሎችን ወደ \"<xliff:g id="RECIPIENT">%2$s</xliff:g>\" በመላክ ላይ"</string>
<string name="bt_toast_6" msgid="1855266596936622458">"ለ \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\" ፋይል መላክ አቁሟል"</string>
- <string name="bt_sm_2_1" product="nosdcard" msgid="352165168004521000">"በUSB ማከማቻ ላይ ከ<xliff:g id="SENDER">%1$s</xliff:g> ፋይል ለማስቀመጥ ምንም በቂ ባዶ ቦታ የለም"</string>
- <string name="bt_sm_2_1" product="default" msgid="1989018443456803630">"ከ <xliff:g id="SENDER">%1$s</xliff:g> በSD ካርዱ ላይ ፋይሉን ለማስቀመጥ ምንም በቂ ቦታ የለም"</string>
+ <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"በዩኤስቢ ማከማቻ ላይ ከ«<xliff:g id="SENDER">%1$s</xliff:g>» የመጣውን ፋይል ለማስቀመጥ በቂ ቦታ የለም"</string>
+ <string name="bt_sm_2_1_default" msgid="9115512207909504071">"ከ«<xliff:g id="SENDER">%1$s</xliff:g>» የመጣውን ፋይል በኤስዲ ካርዱ ላይ ለማስቀመጥ በቂ ቦታ የለም"</string>
<string name="bt_sm_2_2" msgid="2965243265852680543">"የሚያስፈልግ ቦታ፡ <xliff:g id="SIZE">%1$s</xliff:g>"</string>
<string name="ErrorTooManyRequests" msgid="8578277541472944529">"እጅግ ብዙ ጥየቃዎች ተካሂደዋል። ትንሽ ቆይተው እንደገና ይሞክሩ።"</string>
<string name="status_pending" msgid="2503691772030877944">"የፋይል ዝውውር ገና አልተጀመረም::"</string>
@@ -95,8 +95,8 @@
<string name="status_forbidden" msgid="613956401054050725">"ይህ ዝውውር በታለመው መሣሪያ የተከለከለ ነው።"</string>
<string name="status_canceled" msgid="6664490318773098285">"ዝውውር በተጠቃሚ ተትቷል::"</string>
<string name="status_file_error" msgid="3671917770630165299">"የማከማቻ ጉዳይ"</string>
- <string name="status_no_sd_card" product="nosdcard" msgid="1112125377088421469">"ምንም USB ማከማቻ የለም።"</string>
- <string name="status_no_sd_card" product="default" msgid="5760944071743325592">"ምንምSD ካርድ የለም። የተዘዋወሩ ፋይሎችን ለማስቀመጥ SD ካርድ አስገባ።"</string>
+ <string name="status_no_sd_card_nosdcard" msgid="573631036356922221">"ምንም የዩኤስቢ ማከማቻ የለም።"</string>
+ <string name="status_no_sd_card_default" msgid="396564893716701954">"ምንም ኤስዲ ካርድ የለም። የተዘዋወሩ ፋይሎችን ለማስቀመጥ ኤስዲ ካርድ ያስገቡ።"</string>
<string name="status_connection_error" msgid="947681831523219891">"ተያያዥ አልተሳካም።"</string>
<string name="status_protocol_error" msgid="3245444473429269539">"ጥየቃውን በትክክል መያዝ አይቻልም።"</string>
<string name="status_unknown_error" msgid="8156660554237824912">"ያልታወቀ ስህተት"</string>
@@ -122,6 +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">"አሁን እየተጫወተ ያለ"</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>
diff --git a/res/values-ar/strings.xml b/res/values-ar/strings.xml
index 4bb0f8a..1525a71 100644
--- a/res/values-ar/strings.xml
+++ b/res/values-ar/strings.xml
@@ -84,8 +84,8 @@
<string name="bt_toast_4" msgid="4678812947604395649">"إرسال الملف إلى \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
<string name="bt_toast_5" msgid="2846870992823019494">"إرسال <xliff:g id="NUMBER">%1$s</xliff:g> من الملفات إلى \"<xliff:g id="RECIPIENT">%2$s</xliff:g>\""</string>
<string name="bt_toast_6" msgid="1855266596936622458">"تم إيقاف إرسال الملف إلى \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
- <string name="bt_sm_2_1" product="nosdcard" msgid="352165168004521000">"لا توجد مساحة كافية على وحدة تخزين USB لحفظ الملف من \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
- <string name="bt_sm_2_1" product="default" msgid="1989018443456803630">"لا توجد مساحة كافية على بطاقة SD لحفظ الملف من \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
+ <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"لا تتوفّر مساحة كافية على وحدة تخزين USB لحفظ الملف من \"<xliff:g id="SENDER">%1$s</xliff:g>\"."</string>
+ <string name="bt_sm_2_1_default" msgid="9115512207909504071">"لا تتوفّر مساحة كافية على بطاقة SD لحفظ الملف من \"<xliff:g id="SENDER">%1$s</xliff:g>\"."</string>
<string name="bt_sm_2_2" msgid="2965243265852680543">"المساحة اللازمة: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
<string name="ErrorTooManyRequests" msgid="8578277541472944529">"تتم حاليًا معالجة طلبات كثيرة جدًا. حاول مرة أخرى لاحقًا."</string>
<string name="status_pending" msgid="2503691772030877944">"لم يبدأ نقل الملف بعد."</string>
@@ -95,12 +95,12 @@
<string name="status_forbidden" msgid="613956401054050725">"تم حظر النقل بواسطة الجهاز الهدف."</string>
<string name="status_canceled" msgid="6664490318773098285">"تم إلغاء النقل بواسطة المستخدم."</string>
<string name="status_file_error" msgid="3671917770630165299">"مشكلة في وحدة التخزين."</string>
- <string name="status_no_sd_card" product="nosdcard" msgid="1112125377088421469">"ليس هناك وحدة تخزين USB."</string>
- <string name="status_no_sd_card" product="default" msgid="5760944071743325592">"ليس هناك بطاقة SD. أدرج بطاقة SD لحفظ الملفات المنقولة."</string>
+ <string name="status_no_sd_card_nosdcard" msgid="573631036356922221">"لا تتوفّر وحدة تخزين USB."</string>
+ <string name="status_no_sd_card_default" msgid="396564893716701954">"لا تتوفّر بطاقة SD. يمكنك إدخال بطاقة SD لحفظ الملفات المنقولة."</string>
<string name="status_connection_error" msgid="947681831523219891">"لم يتم الاتصال بنجاح."</string>
<string name="status_protocol_error" msgid="3245444473429269539">"لا يمكن معالجة الطلب بشكل صحيح."</string>
<string name="status_unknown_error" msgid="8156660554237824912">"خطأ غير معروف."</string>
- <string name="btopp_live_folder" msgid="7967791481444474554">"تم الاستلام عبر بلوتوث"</string>
+ <string name="btopp_live_folder" msgid="7967791481444474554">"ملفات بلوتوث المستلَمة"</string>
<string name="opp_notification_group" msgid="3486303082135789982">"مشاركة البلوتوث"</string>
<string name="download_success" msgid="7036160438766730871">"اكتمل استلام <xliff:g id="FILE_SIZE">%1$s</xliff:g>."</string>
<string name="upload_success" msgid="4014469387779648949">"اكتمل إرسال <xliff:g id="FILE_SIZE">%1$s</xliff:g>."</string>
@@ -130,6 +130,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">"التعرّف التلقائي على الموسيقى"</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>
diff --git a/res/values-as/strings.xml b/res/values-as/strings.xml
index bfb3168..fd6ab37 100644
--- a/res/values-as/strings.xml
+++ b/res/values-as/strings.xml
@@ -84,8 +84,8 @@
<string name="bt_toast_4" msgid="4678812947604395649">"\"<xliff:g id="RECIPIENT">%1$s</xliff:g>\"লৈ ফাইল প্ৰেৰণ কৰি থকা হৈছে"</string>
<string name="bt_toast_5" msgid="2846870992823019494">"\"<xliff:g id="RECIPIENT">%2$s</xliff:g>\"লৈ <xliff:g id="NUMBER">%1$s</xliff:g>টা ফাইল পঠিয়াই থকা হৈছে"</string>
<string name="bt_toast_6" msgid="1855266596936622458">"\"<xliff:g id="RECIPIENT">%1$s</xliff:g>\"লৈ ফাইল পঠিওৱা বন্ধ কৰা হ’ল"</string>
- <string name="bt_sm_2_1" product="nosdcard" msgid="352165168004521000">"\"<xliff:g id="SENDER">%1$s</xliff:g>\"ৰ পৰা লাভ কৰা ফাইলটো ছেভ কৰিবলৈ ইউএছবি সঞ্চয়াগাৰত পৰ্যাপ্ত খালী ঠাই নাই"</string>
- <string name="bt_sm_2_1" product="default" msgid="1989018443456803630">"\"<xliff:g id="SENDER">%1$s</xliff:g>\"ৰ পৰা লাভ কৰা ফাইলটো ছেভ কৰিবলৈ এছডি কাৰ্ডত পৰ্যাপ্ত খালী ঠাই নাই"</string>
+ <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"\"<xliff:g id="SENDER">%1$s</xliff:g>\"ৰ পৰা লাভ কৰা ফাইলটো ছেভ কৰিবলৈ USB সঞ্চয়াগাৰত পৰ্যাপ্ত খালী ঠাই নাই"</string>
+ <string name="bt_sm_2_1_default" msgid="9115512207909504071">"\"<xliff:g id="SENDER">%1$s</xliff:g>\"ৰ পৰা লাভ কৰা ফাইলটো ছেভ কৰিবলৈ SD কাৰ্ডত পৰ্যাপ্ত খালী ঠাই নাই"</string>
<string name="bt_sm_2_2" msgid="2965243265852680543">"ইমান খালী ঠাইৰ দৰকাৰ: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
<string name="ErrorTooManyRequests" msgid="8578277541472944529">"বহুত বেছি অনুৰোধৰ ওপৰত প্ৰক্ৰিয়া চলি আছে৷ পিছত আকৌ চেষ্টা কৰক৷"</string>
<string name="status_pending" msgid="2503691772030877944">"ফাইলৰ স্থানান্তৰণ এতিয়ালৈকে আৰম্ভ হোৱা নাই।"</string>
@@ -95,8 +95,8 @@
<string name="status_forbidden" msgid="613956401054050725">"নিৰ্দিষ্ট কৰা ডিভাইচটোৱে স্থানান্তৰণ নিষিদ্ধ কৰিছে।"</string>
<string name="status_canceled" msgid="6664490318773098285">"ব্যৱহাৰকাৰীয়ে স্থানান্তৰণ বাতিল কৰিছে।"</string>
<string name="status_file_error" msgid="3671917770630165299">"সঞ্চয়াগাৰৰ সমস্যা।"</string>
- <string name="status_no_sd_card" product="nosdcard" msgid="1112125377088421469">"কোনো ইউএছবি সঞ্চয়াগাৰ নাই।"</string>
- <string name="status_no_sd_card" product="default" msgid="5760944071743325592">"কোনো এছডি কাৰ্ড নাই। স্থানান্তৰ কৰা ফাইলসমূহ ছেভ কৰিবলৈ এছডি কাৰ্ড ভৰাওক।"</string>
+ <string name="status_no_sd_card_nosdcard" msgid="573631036356922221">"কোনো USB সঞ্চয়াগাৰ নাই।"</string>
+ <string name="status_no_sd_card_default" msgid="396564893716701954">"কোনো SD কাৰ্ড নাই। স্থানান্তৰ কৰা ফাইলসমূহ ছেভ কৰিবলৈ SD কাৰ্ড ভৰাওক।"</string>
<string name="status_connection_error" msgid="947681831523219891">"সংযোগ কৰিব পৰা নগ\'ল।"</string>
<string name="status_protocol_error" msgid="3245444473429269539">"অনুৰোধ সঠিকভাৱে পৰিচালনা কৰিব নোৱাৰি।"</string>
<string name="status_unknown_error" msgid="8156660554237824912">"অজ্ঞাত আসোঁৱাহ।"</string>
@@ -122,6 +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">"সদ্য পৰিৱেশিত গীত"</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>
diff --git a/res/values-az/strings.xml b/res/values-az/strings.xml
index 4511191..4abf91f 100644
--- a/res/values-az/strings.xml
+++ b/res/values-az/strings.xml
@@ -84,8 +84,8 @@
<string name="bt_toast_4" msgid="4678812947604395649">"\"<xliff:g id="RECIPIENT">%1$s</xliff:g>\" adlı istifadəçiyə fayl göndərilir"</string>
<string name="bt_toast_5" msgid="2846870992823019494">"\"<xliff:g id="RECIPIENT">%2$s</xliff:g>\" adlı istifadəçiyə <xliff:g id="NUMBER">%1$s</xliff:g> fayl göndərilir"</string>
<string name="bt_toast_6" msgid="1855266596936622458">"\"<xliff:g id="RECIPIENT">%1$s</xliff:g>\" adlı istifadəçiyə faylın göndərilməsi dayandırıldı"</string>
- <string name="bt_sm_2_1" product="nosdcard" msgid="352165168004521000">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" tərəfindən göndərilən faylı saxlamaq üçün USB yaddaşında kifayət qədər yer yoxdur."</string>
- <string name="bt_sm_2_1" product="default" msgid="1989018443456803630">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" tərəfindən göndərilən faylı saxlamaq üçün SD kartda kifayət qədər yer yoxdur."</string>
+ <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" tərəfindən göndərilən faylı yadda saxlamaq üçün USB yaddaşında kifayət qədər yer yoxdur"</string>
+ <string name="bt_sm_2_1_default" msgid="9115512207909504071">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" tərəfindən göndərilən faylı yadda saxlamaq üçün SD kartda kifayət qədər yer yoxdur"</string>
<string name="bt_sm_2_2" msgid="2965243265852680543">"Gərəkli: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
<string name="ErrorTooManyRequests" msgid="8578277541472944529">"Həddindən çox sorğu işlənilir. Sonra bir daha cəhd edin."</string>
<string name="status_pending" msgid="2503691772030877944">"Fayl transferi hələ başlamayıb."</string>
@@ -95,8 +95,8 @@
<string name="status_forbidden" msgid="613956401054050725">"Transfer hədəf cihaz tərəfindən qadağan edilib."</string>
<string name="status_canceled" msgid="6664490318773098285">"Transfer istifadəçi tərəfindən ləğv edildi."</string>
<string name="status_file_error" msgid="3671917770630165299">"Yaddaş problemi."</string>
- <string name="status_no_sd_card" product="nosdcard" msgid="1112125377088421469">"USB yaddaş yoxdur."</string>
- <string name="status_no_sd_card" product="default" msgid="5760944071743325592">"SD kart yoxdur. Transfer edilən faylları saxlamaq üçün SD kart daxil edin."</string>
+ <string name="status_no_sd_card_nosdcard" msgid="573631036356922221">"USB yaddaş yoxdur."</string>
+ <string name="status_no_sd_card_default" msgid="396564893716701954">"SD kart yoxdur. Köçürülən faylları yadda saxlamaq üçün SD kart daxil edin."</string>
<string name="status_connection_error" msgid="947681831523219891">"Uğursuz bağlantı."</string>
<string name="status_protocol_error" msgid="3245444473429269539">"Sorğu düzgün idarə edilə bilməz."</string>
<string name="status_unknown_error" msgid="8156660554237824912">"Naməlum xəta."</string>
@@ -122,6 +122,7 @@
<string name="transfer_menu_open" msgid="3368984869083107200">"Açın"</string>
<string name="transfer_menu_clear" msgid="5854038118831427492">"Siyahıdan silin"</string>
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"Silin"</string>
+ <string name="bluetooth_a2dp_sink_queue_name" msgid="6864149958708669766">"İndi Efirdə"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"Yadda saxlayın"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"Ləğv edin"</string>
<string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"Bluetooth vasitəsilə paylaşmaq istədiyiniz hesabları seçin. Qoşulma zamanı hesablara olan istənilən girişi qəbul etməlisiniz."</string>
diff --git a/res/values-b+sr+Latn/strings.xml b/res/values-b+sr+Latn/strings.xml
index 9f8735b..4d999f3 100644
--- a/res/values-b+sr+Latn/strings.xml
+++ b/res/values-b+sr+Latn/strings.xml
@@ -84,8 +84,8 @@
<string name="bt_toast_4" msgid="4678812947604395649">"Slanje datoteke primaocu „<xliff:g id="RECIPIENT">%1$s</xliff:g>“"</string>
<string name="bt_toast_5" msgid="2846870992823019494">"Slanje<xliff:g id="NUMBER">%1$s</xliff:g> datoteka primaocu „<xliff:g id="RECIPIENT">%2$s</xliff:g>“"</string>
<string name="bt_toast_6" msgid="1855266596936622458">"Zaustavljeno slanje primaocu „<xliff:g id="RECIPIENT">%1$s</xliff:g>“"</string>
- <string name="bt_sm_2_1" product="nosdcard" msgid="352165168004521000">"Nema dovoljno prostora u USB memoriji da bi se sačuvala datoteka pošiljaoca „<xliff:g id="SENDER">%1$s</xliff:g>“"</string>
- <string name="bt_sm_2_1" product="default" msgid="1989018443456803630">"Nema dovoljno prostora na SD kartici da bi se sačuvala datoteka pošiljaoca „<xliff:g id="SENDER">%1$s</xliff:g>“"</string>
+ <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"Nema dovoljno prostora u USB memoriji da bi se sačuvala datoteka pošiljaoca „<xliff:g id="SENDER">%1$s</xliff:g>“"</string>
+ <string name="bt_sm_2_1_default" msgid="9115512207909504071">"Nema dovoljno prostora na SD kartici da bi se sačuvala datoteka pošiljaoca „<xliff:g id="SENDER">%1$s</xliff:g>“"</string>
<string name="bt_sm_2_2" msgid="2965243265852680543">"Potreban prostor: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
<string name="ErrorTooManyRequests" msgid="8578277541472944529">"Previše zahteva se obrađuje. Probajte ponovo kasnije."</string>
<string name="status_pending" msgid="2503691772030877944">"Prenos datoteke još nije počeo."</string>
@@ -95,8 +95,8 @@
<string name="status_forbidden" msgid="613956401054050725">"Ciljni uređaj je zabranio prenos."</string>
<string name="status_canceled" msgid="6664490318773098285">"Korisnik je otkazao prenos."</string>
<string name="status_file_error" msgid="3671917770630165299">"Problem sa skladištem."</string>
- <string name="status_no_sd_card" product="nosdcard" msgid="1112125377088421469">"Nema USB memorije."</string>
- <string name="status_no_sd_card" product="default" msgid="5760944071743325592">"Nema SD kartice. Umetnite SD karticu da biste sačuvali prenete datoteke."</string>
+ <string name="status_no_sd_card_nosdcard" msgid="573631036356922221">"Nema USB memorije."</string>
+ <string name="status_no_sd_card_default" msgid="396564893716701954">"Nema SD kartice. Umetnite SD karticu da biste sačuvali prenete datoteke."</string>
<string name="status_connection_error" msgid="947681831523219891">"Povezivanje nije uspelo."</string>
<string name="status_protocol_error" msgid="3245444473429269539">"Nije moguće ispravno obraditi zahtev."</string>
<string name="status_unknown_error" msgid="8156660554237824912">"Nepoznata greška."</string>
@@ -124,6 +124,7 @@
<string name="transfer_menu_open" msgid="3368984869083107200">"Otvori"</string>
<string name="transfer_menu_clear" msgid="5854038118831427492">"Obriši sa liste"</string>
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"Brisanje"</string>
+ <string name="bluetooth_a2dp_sink_queue_name" msgid="6864149958708669766">"Trenutno svira"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"Sačuvaj"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"Otkaži"</string>
<string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"Izaberite naloge koje želite da delite preko Bluetooth-a. I dalje morate da prihvatite bilo kakav pristup nalozima pri povezivanju."</string>
diff --git a/res/values-be/strings.xml b/res/values-be/strings.xml
index 98b6477..0026ea6 100644
--- a/res/values-be/strings.xml
+++ b/res/values-be/strings.xml
@@ -84,8 +84,8 @@
<string name="bt_toast_4" msgid="4678812947604395649">"Адпраўка файла атрымальніку \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
<string name="bt_toast_5" msgid="2846870992823019494">"Адпраўка файлаў (<xliff:g id="NUMBER">%1$s</xliff:g>) атрымальніку \"<xliff:g id="RECIPIENT">%2$s</xliff:g>\""</string>
<string name="bt_toast_6" msgid="1855266596936622458">"Адпраўка файла атрымальніку \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\" спыненая"</string>
- <string name="bt_sm_2_1" product="nosdcard" msgid="352165168004521000">"Не хапае месца на USB-назапашвальнiку, каб захаваць файл ад адпраўнiка \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
- <string name="bt_sm_2_1" product="default" msgid="1989018443456803630">"На SD-карце недастаткова месца, каб захаваць файл ад адпраўніка \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
+ <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"У USB-сховішчы не хапае месца, каб захаваць файл ад адпраўшчыка \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
+ <string name="bt_sm_2_1_default" msgid="9115512207909504071">"На SD-карце не хапае месца, каб захаваць файл ад адпраўшчыка \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
<string name="bt_sm_2_2" msgid="2965243265852680543">"Спатрэбіцца месца: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
<string name="ErrorTooManyRequests" msgid="8578277541472944529">"Апрацоўваецца занадта шмат запытаў. Паспрабуйце пазней."</string>
<string name="status_pending" msgid="2503691772030877944">"Перадача файла яшчэ не пачалася"</string>
@@ -95,8 +95,8 @@
<string name="status_forbidden" msgid="613956401054050725">"Перадача забаронена мэтавай прыладай."</string>
<string name="status_canceled" msgid="6664490318773098285">"Перадача адменена карыстальнiкам."</string>
<string name="status_file_error" msgid="3671917770630165299">"Праблема ўнутранага сховiшча"</string>
- <string name="status_no_sd_card" product="nosdcard" msgid="1112125377088421469">"Няма USB-назапашвальнiка."</string>
- <string name="status_no_sd_card" product="default" msgid="5760944071743325592">"Няма SD-карты. Устаўце яе, каб захаваць перададзеныя файлы."</string>
+ <string name="status_no_sd_card_nosdcard" msgid="573631036356922221">"Няма USB-сховішча."</string>
+ <string name="status_no_sd_card_default" msgid="396564893716701954">"Няма SD-карты. Устаўце яе, каб захаваць перададзеныя файлы."</string>
<string name="status_connection_error" msgid="947681831523219891">"Няўдалая спроба падключэння."</string>
<string name="status_protocol_error" msgid="3245444473429269539">"Запыт не можа быць правільна апрацаваны"</string>
<string name="status_unknown_error" msgid="8156660554237824912">"Невядомая памылка."</string>
@@ -126,6 +126,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">"Цяпер іграе"</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>
diff --git a/res/values-bg/strings.xml b/res/values-bg/strings.xml
index db51d2b..1566a37 100644
--- a/res/values-bg/strings.xml
+++ b/res/values-bg/strings.xml
@@ -84,8 +84,8 @@
<string name="bt_toast_4" msgid="4678812947604395649">"Изпраща се файл до „<xliff:g id="RECIPIENT">%1$s</xliff:g>“"</string>
<string name="bt_toast_5" msgid="2846870992823019494">"<xliff:g id="NUMBER">%1$s</xliff:g> файла се изпращат до „<xliff:g id="RECIPIENT">%2$s</xliff:g>“"</string>
<string name="bt_toast_6" msgid="1855266596936622458">"Изпращането на файл до „<xliff:g id="RECIPIENT">%1$s</xliff:g>“ спря"</string>
- <string name="bt_sm_2_1" product="nosdcard" msgid="352165168004521000">"В USB хранилището няма достатъчно място, за да бъде запазен файлът от „<xliff:g id="SENDER">%1$s</xliff:g>“"</string>
- <string name="bt_sm_2_1" product="default" msgid="1989018443456803630">"На SD картата няма достатъчно място, за да бъде запазен файлът от „<xliff:g id="SENDER">%1$s</xliff:g>“"</string>
+ <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"В USB хранилището няма достатъчно място, за да бъде запазен файлът от <xliff:g id="SENDER">%1$s</xliff:g>"</string>
+ <string name="bt_sm_2_1_default" msgid="9115512207909504071">"На SD картата няма достатъчно място, за да бъде запазен файлът от <xliff:g id="SENDER">%1$s</xliff:g>"</string>
<string name="bt_sm_2_2" msgid="2965243265852680543">"Необходимо място: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
<string name="ErrorTooManyRequests" msgid="8578277541472944529">"Обработват се твърде много заявки. Опитайте отново по-късно."</string>
<string name="status_pending" msgid="2503691772030877944">"Прехвърлянето на файла още не е започнало."</string>
@@ -95,8 +95,8 @@
<string name="status_forbidden" msgid="613956401054050725">"Прехвърлянето е забранено от целевото устройство."</string>
<string name="status_canceled" msgid="6664490318773098285">"Прехвърлянето бе анулирано от потребителя."</string>
<string name="status_file_error" msgid="3671917770630165299">"Проблем с хранилището."</string>
- <string name="status_no_sd_card" product="nosdcard" msgid="1112125377088421469">"Няма USB хранилище."</string>
- <string name="status_no_sd_card" product="default" msgid="5760944071743325592">"Няма SD карта. Поставете такава, за да запазите прехвърлените файлове."</string>
+ <string name="status_no_sd_card_nosdcard" msgid="573631036356922221">"Няма USB хранилище."</string>
+ <string name="status_no_sd_card_default" msgid="396564893716701954">"Няма SD карта. Поставете такава, за да запазите прехвърлените файлове."</string>
<string name="status_connection_error" msgid="947681831523219891">"Връзката не е успешна."</string>
<string name="status_protocol_error" msgid="3245444473429269539">"Заявката не може да бъде обработена правилно."</string>
<string name="status_unknown_error" msgid="8156660554237824912">"Неизвестна грешка."</string>
@@ -122,6 +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">"Възпроизвеждано сега съдържание"</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>
diff --git a/res/values-bn/strings.xml b/res/values-bn/strings.xml
index 5e4fbc9..352f359 100644
--- a/res/values-bn/strings.xml
+++ b/res/values-bn/strings.xml
@@ -84,8 +84,8 @@
<string name="bt_toast_4" msgid="4678812947604395649">"\"<xliff:g id="RECIPIENT">%1$s</xliff:g>\" কে ফাইল পাঠানো হচ্ছে"</string>
<string name="bt_toast_5" msgid="2846870992823019494">"\"<xliff:g id="RECIPIENT">%2$s</xliff:g>\" কে <xliff:g id="NUMBER">%1$s</xliff:g>টি ফাইল পাঠানো হচ্ছে"</string>
<string name="bt_toast_6" msgid="1855266596936622458">"\"<xliff:g id="RECIPIENT">%1$s</xliff:g>\" কে ফাইল পাঠানো বন্ধ করা হয়েছে"</string>
- <string name="bt_sm_2_1" product="nosdcard" msgid="352165168004521000">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" এর থেকে ফাইল সংরক্ষণ করার জন্য USB সঞ্চয়স্থানের পর্যাপ্ত জায়গা নেই।"</string>
- <string name="bt_sm_2_1" product="default" msgid="1989018443456803630">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" এর থেকে ফাইল সংরক্ষণ করার জন্য SD কার্ডে পর্যাপ্ত জায়গা নেই।"</string>
+ <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"\"<xliff:g id="SENDER">%1$s</xliff:g>\"-এর পাঠানো ফাইল সেভ করার জন্য ইউএসবি স্টোরেজে পর্যাপ্ত জায়গা নেই"</string>
+ <string name="bt_sm_2_1_default" msgid="9115512207909504071">"\"<xliff:g id="SENDER">%1$s</xliff:g>\"-এর পাঠানো ফাইল সেভ করার জন্য এসডি কার্ডে পর্যাপ্ত জায়গা নেই"</string>
<string name="bt_sm_2_2" msgid="2965243265852680543">"জায়গা প্রয়োজন: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
<string name="ErrorTooManyRequests" msgid="8578277541472944529">"অনেকগুলি অনুরোধ প্রক্রিয়া করা হচ্ছে৷ পরে আবার চেষ্টা করুন৷"</string>
<string name="status_pending" msgid="2503691772030877944">"ফাইল ট্রান্সফার করা এখনও শুরু হয়নি।"</string>
@@ -95,8 +95,8 @@
<string name="status_forbidden" msgid="613956401054050725">"টার্গেট ডিভাইস দ্বারা ট্রান্সফার নিষিদ্ধ করা হয়েছে।"</string>
<string name="status_canceled" msgid="6664490318773098285">"ব্যবহারকারী দ্বারা ট্রান্সফার বাতিল করা হয়েছে।"</string>
<string name="status_file_error" msgid="3671917770630165299">"স্টোরেজ সমস্যা।"</string>
- <string name="status_no_sd_card" product="nosdcard" msgid="1112125377088421469">"কোনও USB স্টোরেজ নেই।"</string>
- <string name="status_no_sd_card" product="default" msgid="5760944071743325592">"কোনো SD কার্ড নেই। স্থানান্তর করা ফাইলগুলি সংরক্ষণ করতে SD কার্ড ঢোকান।"</string>
+ <string name="status_no_sd_card_nosdcard" msgid="573631036356922221">"কোনও ইউএসবি স্টোরেজ নেই।"</string>
+ <string name="status_no_sd_card_default" msgid="396564893716701954">"কোনও এসডি কার্ড নেই। ট্রান্সফার করা ফাইলগুলি সেভ করতে এসডি কার্ড যোগ করুন।"</string>
<string name="status_connection_error" msgid="947681831523219891">"সংযোগ অসফল।"</string>
<string name="status_protocol_error" msgid="3245444473429269539">"অনুরোধ সঠিকভাবে পরিচালনা করা যাবে না।"</string>
<string name="status_unknown_error" msgid="8156660554237824912">"অজানা ত্রুটি৷"</string>
@@ -122,6 +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">"এখন চলছে"</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>
diff --git a/res/values-bs/strings.xml b/res/values-bs/strings.xml
index 3dc12a2..7f6eca1 100644
--- a/res/values-bs/strings.xml
+++ b/res/values-bs/strings.xml
@@ -84,8 +84,8 @@
<string name="bt_toast_4" msgid="4678812947604395649">"Slanje fajla kojeg prima \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
<string name="bt_toast_5" msgid="2846870992823019494">"Slanje <xliff:g id="NUMBER">%1$s</xliff:g> fajl(ov)a, prima \"<xliff:g id="RECIPIENT">%2$s</xliff:g>\""</string>
<string name="bt_toast_6" msgid="1855266596936622458">"Zaustavljeno slanje fajla kojeg prima \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
- <string name="bt_sm_2_1" product="nosdcard" msgid="352165168004521000">"Nema dovoljno prostora na USB pohrani da se sačuva fajl koji šalje \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
- <string name="bt_sm_2_1" product="default" msgid="1989018443456803630">"Nema dovoljno prostora na SD kartici da se sačuva fajl koji šalje \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
+ <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"Nema dovoljno prostora na USB pohrani da se sačuva fajl koji šalje \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
+ <string name="bt_sm_2_1_default" msgid="9115512207909504071">"Nema dovoljno prostora na SD kartici da se sačuva fajl koji šalje \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
<string name="bt_sm_2_2" msgid="2965243265852680543">"Potrebni prostor: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
<string name="ErrorTooManyRequests" msgid="8578277541472944529">"Obrađuje se previše zahtjeva. Pokušajte ponovo kasnije."</string>
<string name="status_pending" msgid="2503691772030877944">"Prenošenje fajla još nije započelo."</string>
@@ -95,8 +95,8 @@
<string name="status_forbidden" msgid="613956401054050725">"Ciljni uređaj je zabranio prenošenje."</string>
<string name="status_canceled" msgid="6664490318773098285">"Korisnik je otkazao prenošenje."</string>
<string name="status_file_error" msgid="3671917770630165299">"Problem s pohranom."</string>
- <string name="status_no_sd_card" product="nosdcard" msgid="1112125377088421469">"Nema USB pohrane."</string>
- <string name="status_no_sd_card" product="default" msgid="5760944071743325592">"Nema SD kartice. Umetnite SD karticu kako biste sačuvali prenesene fajlove."</string>
+ <string name="status_no_sd_card_nosdcard" msgid="573631036356922221">"Nema USB pohrane."</string>
+ <string name="status_no_sd_card_default" msgid="396564893716701954">"Nema SD kartice. Umetnite SD karticu kako biste sačuvali prenesene fajlove."</string>
<string name="status_connection_error" msgid="947681831523219891">"Povezivanje nije uspjelo."</string>
<string name="status_protocol_error" msgid="3245444473429269539">"Nije moguće pravilno obraditi zahtjev."</string>
<string name="status_unknown_error" msgid="8156660554237824912">"Nepoznata greška."</string>
@@ -124,6 +124,7 @@
<string name="transfer_menu_open" msgid="3368984869083107200">"Otvori"</string>
<string name="transfer_menu_clear" msgid="5854038118831427492">"Obriši sa spiska"</string>
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"Obriši"</string>
+ <string name="bluetooth_a2dp_sink_queue_name" msgid="6864149958708669766">"Trenutno se reproducira"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"Sačuvaj"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"Otkaži"</string>
<string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"Odaberite račune koje želite dijeliti preko Bluetootha. I dalje morate prihvatiti bilo koji pristup računima prilikom povezivanja."</string>
diff --git a/res/values-ca/strings.xml b/res/values-ca/strings.xml
index f77534d..2c6ec73 100644
--- a/res/values-ca/strings.xml
+++ b/res/values-ca/strings.xml
@@ -84,8 +84,8 @@
<string name="bt_toast_4" msgid="4678812947604395649">"S\'està enviant el fitxer a \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
<string name="bt_toast_5" msgid="2846870992823019494">"S\'estan enviant <xliff:g id="NUMBER">%1$s</xliff:g> fitxers a \"<xliff:g id="RECIPIENT">%2$s</xliff:g>\""</string>
<string name="bt_toast_6" msgid="1855266596936622458">"S\'ha aturat l\'enviament del fitxer a \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
- <string name="bt_sm_2_1" product="nosdcard" msgid="352165168004521000">"No hi ha prou espai a l\'emmagatzematge USB per desar el fitxer de \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
- <string name="bt_sm_2_1" product="default" msgid="1989018443456803630">"No hi ha prou espai a la targeta SD per desar el fitxer de \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
+ <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"No hi ha prou espai a l\'emmagatzematge USB per desar el fitxer de: <xliff:g id="SENDER">%1$s</xliff:g>"</string>
+ <string name="bt_sm_2_1_default" msgid="9115512207909504071">"No hi ha prou espai a la targeta SD per desar el fitxer de: <xliff:g id="SENDER">%1$s</xliff:g>"</string>
<string name="bt_sm_2_2" msgid="2965243265852680543">"Espai necessari: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
<string name="ErrorTooManyRequests" msgid="8578277541472944529">"S\'estan processant massa sol·licituds. Torneu-ho a provar més tard."</string>
<string name="status_pending" msgid="2503691772030877944">"Encara no s\'ha iniciat la transferència del fitxer."</string>
@@ -95,8 +95,8 @@
<string name="status_forbidden" msgid="613956401054050725">"El dispositiu de destinació no permet la transferència."</string>
<string name="status_canceled" msgid="6664490318773098285">"L\'usuari ha cancel·lat la transferència."</string>
<string name="status_file_error" msgid="3671917770630165299">"Problema d\'emmagatzematge."</string>
- <string name="status_no_sd_card" product="nosdcard" msgid="1112125377088421469">"Sense emmagatzematge USB."</string>
- <string name="status_no_sd_card" product="default" msgid="5760944071743325592">"No hi ha cap targeta SD. Inseriu una targeta SD per desar els fitxers transferits."</string>
+ <string name="status_no_sd_card_nosdcard" msgid="573631036356922221">"Sense emmagatzematge USB."</string>
+ <string name="status_no_sd_card_default" msgid="396564893716701954">"No hi ha cap targeta SD. Insereix-ne una per desar els fitxers transferits."</string>
<string name="status_connection_error" msgid="947681831523219891">"Connexió incorrecta."</string>
<string name="status_protocol_error" msgid="3245444473429269539">"La sol·licitud no es pot processar correctament."</string>
<string name="status_unknown_error" msgid="8156660554237824912">"Error desconegut."</string>
@@ -122,6 +122,7 @@
<string name="transfer_menu_open" msgid="3368984869083107200">"Obre"</string>
<string name="transfer_menu_clear" msgid="5854038118831427492">"Esborra de la llista"</string>
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"Esborra"</string>
+ <string name="bluetooth_a2dp_sink_queue_name" msgid="6864149958708669766">"Reproducció actual"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"Desa"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"Cancel·la"</string>
<string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"Selecciona els comptes que vulguis compartir mitjançant el Bluetooth. Cal que acceptis l\'accés als comptes en connectar-t\'hi."</string>
diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml
index d6020cf..3e76d2b 100644
--- a/res/values-cs/strings.xml
+++ b/res/values-cs/strings.xml
@@ -84,8 +84,8 @@
<string name="bt_toast_4" msgid="4678812947604395649">"Odesílání souboru uživateli <xliff:g id="RECIPIENT">%1$s</xliff:g>"</string>
<string name="bt_toast_5" msgid="2846870992823019494">"Odesílání <xliff:g id="NUMBER">%1$s</xliff:g> souborů uživateli <xliff:g id="RECIPIENT">%2$s</xliff:g>"</string>
<string name="bt_toast_6" msgid="1855266596936622458">"Odesílání souboru uživateli <xliff:g id="RECIPIENT">%1$s</xliff:g> bylo zastaveno"</string>
- <string name="bt_sm_2_1" product="nosdcard" msgid="352165168004521000">"Pro uložení souboru od odesílatele <xliff:g id="SENDER">%1$s</xliff:g> není v úložišti USB dost místa."</string>
- <string name="bt_sm_2_1" product="default" msgid="1989018443456803630">"Na kartě SD není dost místa pro uložení souboru od uživatele <xliff:g id="SENDER">%1$s</xliff:g>"</string>
+ <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"Pro uložení souboru od odesílatele <xliff:g id="SENDER">%1$s</xliff:g> není v úložišti USB dost místa."</string>
+ <string name="bt_sm_2_1_default" msgid="9115512207909504071">"Pro uložení souboru od odesílatele <xliff:g id="SENDER">%1$s</xliff:g> není na SD kartě dost místa."</string>
<string name="bt_sm_2_2" msgid="2965243265852680543">"Požadované místo v paměti: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
<string name="ErrorTooManyRequests" msgid="8578277541472944529">"Je zpracováváno příliš mnoho požadavků. Opakujte akci později."</string>
<string name="status_pending" msgid="2503691772030877944">"Přenos souborů ještě nebyl zahájen."</string>
@@ -95,8 +95,8 @@
<string name="status_forbidden" msgid="613956401054050725">"Přenos byl cílovým zařízením zakázán."</string>
<string name="status_canceled" msgid="6664490318773098285">"Přenos byl zrušen uživatelem."</string>
<string name="status_file_error" msgid="3671917770630165299">"Problém s úložištěm."</string>
- <string name="status_no_sd_card" product="nosdcard" msgid="1112125377088421469">"Žádné úložiště USB."</string>
- <string name="status_no_sd_card" product="default" msgid="5760944071743325592">"Žádná karta SD není dostupná. Chcete-li přenášené soubory uložit, vložte kartu SD."</string>
+ <string name="status_no_sd_card_nosdcard" msgid="573631036356922221">"Žádné úložiště USB."</string>
+ <string name="status_no_sd_card_default" msgid="396564893716701954">"Žádná SD karta není dostupná. Chcete-li přenášené soubory uložit, vložte SD kartu."</string>
<string name="status_connection_error" msgid="947681831523219891">"Připojení se nezdařilo."</string>
<string name="status_protocol_error" msgid="3245444473429269539">"Požadavek není možné správně zpracovat."</string>
<string name="status_unknown_error" msgid="8156660554237824912">"Neznámá chyba."</string>
@@ -126,6 +126,7 @@
<string name="transfer_menu_open" msgid="3368984869083107200">"Otevřít"</string>
<string name="transfer_menu_clear" msgid="5854038118831427492">"Vymazat ze seznamu"</string>
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"Vymazat"</string>
+ <string name="bluetooth_a2dp_sink_queue_name" msgid="6864149958708669766">"Co to hraje"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"Uložit"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"Zrušit"</string>
<string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"Vyberte účty, které chcete sdílet prostřednictvím rozhraní Bluetooth. Při připojování budete přístup k účtům muset i nadále schválit."</string>
diff --git a/res/values-da/strings.xml b/res/values-da/strings.xml
index 7de8ec4..25819a1 100644
--- a/res/values-da/strings.xml
+++ b/res/values-da/strings.xml
@@ -84,8 +84,8 @@
<string name="bt_toast_4" msgid="4678812947604395649">"Sender filen til \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
<string name="bt_toast_5" msgid="2846870992823019494">"Sender <xliff:g id="NUMBER">%1$s</xliff:g> filer til \"<xliff:g id="RECIPIENT">%2$s</xliff:g>\""</string>
<string name="bt_toast_6" msgid="1855266596936622458">"Afsendelse af fil til \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\" stoppede"</string>
- <string name="bt_sm_2_1" product="nosdcard" msgid="352165168004521000">"Der er ikke nok plads på USB-lageret til at gemme filen fra \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
- <string name="bt_sm_2_1" product="default" msgid="1989018443456803630">"Der er ikke nok plads på SD-kortet til at gemme filen fra \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
+ <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"Der er ikke nok plads på USB-lageret til at gemme filen fra \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
+ <string name="bt_sm_2_1_default" msgid="9115512207909504071">"Der er ikke nok plads på SD-kortet til at gemme filen fra \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
<string name="bt_sm_2_2" msgid="2965243265852680543">"Nødvendig plads: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
<string name="ErrorTooManyRequests" msgid="8578277541472944529">"Der behandles for mange anmodninger. Prøv igen senere."</string>
<string name="status_pending" msgid="2503691772030877944">"Filoverførslen er endnu ikke påbegyndt."</string>
@@ -95,8 +95,8 @@
<string name="status_forbidden" msgid="613956401054050725">"Modtagerenheden forbyder overførslen."</string>
<string name="status_canceled" msgid="6664490318773098285">"Overførslen blev annulleret af brugeren."</string>
<string name="status_file_error" msgid="3671917770630165299">"Lagerproblem."</string>
- <string name="status_no_sd_card" product="nosdcard" msgid="1112125377088421469">"Intet USB-lager."</string>
- <string name="status_no_sd_card" product="default" msgid="5760944071743325592">"Intet SD-kort. Indsæt et SD-kort for at gemme overførte filer."</string>
+ <string name="status_no_sd_card_nosdcard" msgid="573631036356922221">"Intet USB-lager."</string>
+ <string name="status_no_sd_card_default" msgid="396564893716701954">"Intet SD-kort. Indsæt et SD-kort for at gemme overførte filer."</string>
<string name="status_connection_error" msgid="947681831523219891">"Forbindelsen mislykkedes."</string>
<string name="status_protocol_error" msgid="3245444473429269539">"Anmodningen kan ikke håndteres korrekt."</string>
<string name="status_unknown_error" msgid="8156660554237824912">"Ukendt fejl."</string>
@@ -122,6 +122,7 @@
<string name="transfer_menu_open" msgid="3368984869083107200">"Åbn"</string>
<string name="transfer_menu_clear" msgid="5854038118831427492">"Fjern fra listen"</string>
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"Ryd"</string>
+ <string name="bluetooth_a2dp_sink_queue_name" msgid="6864149958708669766">"Afspiller nu"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"Gem"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"Annuller"</string>
<string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"Vælg de konti, du vil dele via Bluetooth. Du skal stadig acceptere adgang til kontiene, når du opretter forbindelse."</string>
diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml
index 7f36fce..c9e6554 100644
--- a/res/values-de/strings.xml
+++ b/res/values-de/strings.xml
@@ -34,7 +34,7 @@
<string name="incoming_file_confirm_content" msgid="2752605552743148036">"Eingehende Datei annehmen?"</string>
<string name="incoming_file_confirm_cancel" msgid="2973321832477704805">"Ablehnen"</string>
<string name="incoming_file_confirm_ok" msgid="281462442932231475">"Akzeptieren"</string>
- <string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"OK"</string>
+ <string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"Ok"</string>
<string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"Die Zeit zum Empfang der eingehenden Datei von \"<xliff:g id="SENDER">%1$s</xliff:g>\" ist abgelaufen."</string>
<string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"Eingehende Datei"</string>
<string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> kann jetzt <xliff:g id="FILE">%2$s</xliff:g> senden."</string>
@@ -59,19 +59,19 @@
<string name="download_fail_line1" msgid="3846450148862894552">"Datei nicht empfangen"</string>
<string name="download_fail_line2" msgid="8950394574689971071">"Datei: <xliff:g id="FILE">%1$s</xliff:g>"</string>
<string name="download_fail_line3" msgid="3451040656154861722">"Grund: <xliff:g id="REASON">%1$s</xliff:g>"</string>
- <string name="download_fail_ok" msgid="1521733664438320300">"OK"</string>
+ <string name="download_fail_ok" msgid="1521733664438320300">"Ok"</string>
<string name="download_succ_line5" msgid="4509944688281573595">"Datei empfangen"</string>
<string name="download_succ_ok" msgid="7053688246357050216">"Öffnen"</string>
<string name="upload_line1" msgid="2055952074059709052">"An: \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
<string name="upload_line3" msgid="4920689672457037437">"Dateityp: <xliff:g id="TYPE">%1$s</xliff:g> (<xliff:g id="SIZE">%2$s</xliff:g>)"</string>
<string name="upload_line5" msgid="7759322537674229752">"Datei wird gesendet..."</string>
<string name="upload_succ_line5" msgid="5687317197463383601">"Die Datei wurde gesendet."</string>
- <string name="upload_succ_ok" msgid="7705428476405478828">"OK"</string>
+ <string name="upload_succ_ok" msgid="7705428476405478828">"Ok"</string>
<string name="upload_fail_line1" msgid="7899394672421491701">"Die Datei wurde nicht an \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\" gesendet."</string>
<string name="upload_fail_line1_2" msgid="2108129204050841798">"Datei: <xliff:g id="FILE">%1$s</xliff:g>"</string>
<string name="upload_fail_ok" msgid="5807702461606714296">"Wiederholen"</string>
<string name="upload_fail_cancel" msgid="9118496285835687125">"Schließen"</string>
- <string name="bt_error_btn_ok" msgid="5965151173011534240">"OK"</string>
+ <string name="bt_error_btn_ok" msgid="5965151173011534240">"Ok"</string>
<string name="unknown_file" msgid="6092727753965095366">"Unbekannte Datei"</string>
<string name="unknown_file_desc" msgid="480434281415453287">"Dieser Dateityp kann von keiner App verarbeitet werden. \n"</string>
<string name="not_exist_file" msgid="3489434189599716133">"Keine Datei"</string>
@@ -84,8 +84,8 @@
<string name="bt_toast_4" msgid="4678812947604395649">"Datei wird an \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\" gesendet..."</string>
<string name="bt_toast_5" msgid="2846870992823019494">"<xliff:g id="NUMBER">%1$s</xliff:g> Dateien werden an \"<xliff:g id="RECIPIENT">%2$s</xliff:g>\" gesendet."</string>
<string name="bt_toast_6" msgid="1855266596936622458">"Die Übertragung der Datei an \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\" wurde abgebrochen"</string>
- <string name="bt_sm_2_1" product="nosdcard" msgid="352165168004521000">"Der USB-Speicher verfügt nicht über genügend Speicherplatz, um die Datei von \"<xliff:g id="SENDER">%1$s</xliff:g>\" zu speichern."</string>
- <string name="bt_sm_2_1" product="default" msgid="1989018443456803630">"Die SD-Karte verfügt über zu wenig Speicherplatz für die Datei von \"<xliff:g id="SENDER">%1$s</xliff:g>\"."</string>
+ <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"Der USB-Speicher verfügt nicht über genügend Speicherplatz für die Datei von \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
+ <string name="bt_sm_2_1_default" msgid="9115512207909504071">"Die SD-Karte verfügt über zu wenig Speicherplatz für die Datei von \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
<string name="bt_sm_2_2" msgid="2965243265852680543">"Erforderlicher Speicherplatz: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
<string name="ErrorTooManyRequests" msgid="8578277541472944529">"Es werden zurzeit zu viele Anfragen verarbeitet. Bitte versuche es später noch einmal."</string>
<string name="status_pending" msgid="2503691772030877944">"Die Dateiübertragung wurde noch nicht gestartet."</string>
@@ -95,8 +95,8 @@
<string name="status_forbidden" msgid="613956401054050725">"Übertragung wird durch das Zielgerät verhindert."</string>
<string name="status_canceled" msgid="6664490318773098285">"Übertragung wurde vom Nutzer abgebrochen."</string>
<string name="status_file_error" msgid="3671917770630165299">"Speicherproblem"</string>
- <string name="status_no_sd_card" product="nosdcard" msgid="1112125377088421469">"Kein USB-Speicher"</string>
- <string name="status_no_sd_card" product="default" msgid="5760944071743325592">"Keine SD-Karte. Lege eine SD-Karte ein, um die übertragenen Dateien zu speichern."</string>
+ <string name="status_no_sd_card_nosdcard" msgid="573631036356922221">"Kein USB-Speicher."</string>
+ <string name="status_no_sd_card_default" msgid="396564893716701954">"Keine SD-Karte. Lege eine SD-Karte ein, um die übertragenen Dateien zu speichern."</string>
<string name="status_connection_error" msgid="947681831523219891">"Verbindung fehlgeschlagen"</string>
<string name="status_protocol_error" msgid="3245444473429269539">"Die Anfrage kann nicht richtig verarbeitet werden."</string>
<string name="status_unknown_error" msgid="8156660554237824912">"Unbekannter Fehler"</string>
@@ -122,6 +122,7 @@
<string name="transfer_menu_open" msgid="3368984869083107200">"Öffnen"</string>
<string name="transfer_menu_clear" msgid="5854038118831427492">"Aus Liste löschen"</string>
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"Löschen"</string>
+ <string name="bluetooth_a2dp_sink_queue_name" msgid="6864149958708669766">"Now Playing"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"Speichern"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"Abbrechen"</string>
<string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"Wähle die Konten aus, die du über Bluetooth freigeben möchtest. Du musst jedoch weiterhin jedem Zugriff auf die Konten zustimmen, wenn eine Verbindung hergestellt wird."</string>
diff --git a/res/values-de/test_strings.xml b/res/values-de/test_strings.xml
index 0ce94ff..3e07229 100644
--- a/res/values-de/test_strings.xml
+++ b/res/values-de/test_strings.xml
@@ -6,7 +6,7 @@
<string name="update_record" msgid="2480425402384910635">"Aufnahme bestätigen"</string>
<string name="ack_record" msgid="6716152390978472184">"Aufnahme bestätigen"</string>
<string name="deleteAll_record" msgid="4383349788485210582">"Gesamte Aufnahme löschen"</string>
- <string name="ok_button" msgid="6519033415223065454">"OK"</string>
+ <string name="ok_button" msgid="6519033415223065454">"Ok"</string>
<string name="delete_record" msgid="4645040331967533724">"Aufnahme löschen"</string>
<string name="start_server" msgid="9034821924409165795">"TCP-Server starten"</string>
<string name="notify_server" msgid="4369106744022969655">"TCP-Server benachrichtigen"</string>
diff --git a/res/values-el/strings.xml b/res/values-el/strings.xml
index 71c10d2..1506bd2 100644
--- a/res/values-el/strings.xml
+++ b/res/values-el/strings.xml
@@ -84,8 +84,8 @@
<string name="bt_toast_4" msgid="4678812947604395649">"Γίνεται αποστολή του αρχείου στον παραλήπτη \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
<string name="bt_toast_5" msgid="2846870992823019494">"Αποστολή <xliff:g id="NUMBER">%1$s</xliff:g> αρχείων στον παραλήπτη \"<xliff:g id="RECIPIENT">%2$s</xliff:g>\""</string>
<string name="bt_toast_6" msgid="1855266596936622458">"Διακόπηκε η αποστολή του αρχείου στον παραλήπτη \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
- <string name="bt_sm_2_1" product="nosdcard" msgid="352165168004521000">"Δεν υπάρχει αρκετός χώρος στον χώρο αποθήκευσης USB για την αποθήκευση του αρχείου από τον αποστολέα \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
- <string name="bt_sm_2_1" product="default" msgid="1989018443456803630">"Δεν υπάρχει αρκετός χώρος στην κάρτα SD για την αποθήκευση του αρχείου από τον αποστολέα \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
+ <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"Δεν υπάρχει αρκετός χώρος στον χώρο αποθήκευσης USB για την αποθήκευση του αρχείου από τον αποστολέα \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
+ <string name="bt_sm_2_1_default" msgid="9115512207909504071">"Δεν υπάρχει αρκετός χώρος στην κάρτα SD για την αποθήκευση του αρχείου από τον αποστολέα \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
<string name="bt_sm_2_2" msgid="2965243265852680543">"Απαιτούμενος χώρος: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
<string name="ErrorTooManyRequests" msgid="8578277541472944529">"Πραγματοποιείται επεξεργασία πάρα πολλών αιτημάτων. Προσπαθήστε ξανά αργότερα."</string>
<string name="status_pending" msgid="2503691772030877944">"Η μεταφορά του αρχείου δεν έχει ξεκινήσει ακόμα."</string>
@@ -95,8 +95,8 @@
<string name="status_forbidden" msgid="613956401054050725">"Δεν επιτρέπεται αυτή η μεταφορά από τη συσκευή προορισμού."</string>
<string name="status_canceled" msgid="6664490318773098285">"Η μεταφορά ακυρώθηκε από τον χρήστη."</string>
<string name="status_file_error" msgid="3671917770630165299">"Πρόβλημα αποθηκευτικού χώρου."</string>
- <string name="status_no_sd_card" product="nosdcard" msgid="1112125377088421469">"Δεν υπάρχει αποθηκευτικός χώρος USB."</string>
- <string name="status_no_sd_card" product="default" msgid="5760944071743325592">"Δεν υπάρχει κάρτα SD card. Εισαγάγετε μια κάρτα SD για να αποθηκεύετε τα μεταφερόμενα αρχεία."</string>
+ <string name="status_no_sd_card_nosdcard" msgid="573631036356922221">"Δεν υπάρχει αποθηκευτικός χώρος USB."</string>
+ <string name="status_no_sd_card_default" msgid="396564893716701954">"Δεν υπάρχει κάρτα SD card. Εισαγάγετε μια κάρτα SD για να αποθηκεύετε τα μεταφερόμενα αρχεία."</string>
<string name="status_connection_error" msgid="947681831523219891">"Η σύνδεση δεν ήταν επιτυχής."</string>
<string name="status_protocol_error" msgid="3245444473429269539">"Δεν μπορεί να γίνει σωστός χειρισμός του αιτήματος."</string>
<string name="status_unknown_error" msgid="8156660554237824912">"Άγνωστο σφάλμα."</string>
@@ -122,6 +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">"Ακούγεται τώρα"</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>
diff --git a/res/values-en-rAU/strings.xml b/res/values-en-rAU/strings.xml
index ba19b9c..ddca497 100644
--- a/res/values-en-rAU/strings.xml
+++ b/res/values-en-rAU/strings.xml
@@ -84,8 +84,8 @@
<string name="bt_toast_4" msgid="4678812947604395649">"Sending file to \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
<string name="bt_toast_5" msgid="2846870992823019494">"Sending <xliff:g id="NUMBER">%1$s</xliff:g> files to \"<xliff:g id="RECIPIENT">%2$s</xliff:g>\""</string>
<string name="bt_toast_6" msgid="1855266596936622458">"Stopped sending file to \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
- <string name="bt_sm_2_1" product="nosdcard" msgid="352165168004521000">"There isn\'t enough space in USB storage to save the file from \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
- <string name="bt_sm_2_1" product="default" msgid="1989018443456803630">"There isn\'t enough space on the SD card to save the file from \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
+ <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"There isn\'t enough space in USB storage to save the file from \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
+ <string name="bt_sm_2_1_default" msgid="9115512207909504071">"There isn\'t enough space on the SD card to save the file from \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
<string name="bt_sm_2_2" msgid="2965243265852680543">"Space needed: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
<string name="ErrorTooManyRequests" msgid="8578277541472944529">"Too many requests are being processed. Try again later."</string>
<string name="status_pending" msgid="2503691772030877944">"File transfer not started yet"</string>
@@ -95,8 +95,8 @@
<string name="status_forbidden" msgid="613956401054050725">"Transfer forbidden by target device."</string>
<string name="status_canceled" msgid="6664490318773098285">"Transfer cancelled by user."</string>
<string name="status_file_error" msgid="3671917770630165299">"Storage issue"</string>
- <string name="status_no_sd_card" product="nosdcard" msgid="1112125377088421469">"No USB storage."</string>
- <string name="status_no_sd_card" product="default" msgid="5760944071743325592">"No SD card. Insert an SD card to save transferred files."</string>
+ <string name="status_no_sd_card_nosdcard" msgid="573631036356922221">"No USB storage."</string>
+ <string name="status_no_sd_card_default" msgid="396564893716701954">"No SD card. Insert an SD card to save transferred files."</string>
<string name="status_connection_error" msgid="947681831523219891">"Connection unsuccessful."</string>
<string name="status_protocol_error" msgid="3245444473429269539">"Request can\'t be handled correctly."</string>
<string name="status_unknown_error" msgid="8156660554237824912">"Unknown error."</string>
@@ -122,6 +122,7 @@
<string name="transfer_menu_open" msgid="3368984869083107200">"Open"</string>
<string name="transfer_menu_clear" msgid="5854038118831427492">"Clear from list"</string>
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"Clear"</string>
+ <string name="bluetooth_a2dp_sink_queue_name" msgid="6864149958708669766">"Now Playing"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"Save"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"Cancel"</string>
<string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"Select the accounts that you want to share through Bluetooth. You still have to accept any access to the accounts when connecting."</string>
diff --git a/res/values-en-rCA/strings.xml b/res/values-en-rCA/strings.xml
index efd1453..e549cb6 100644
--- a/res/values-en-rCA/strings.xml
+++ b/res/values-en-rCA/strings.xml
@@ -84,8 +84,8 @@
<string name="bt_toast_4" msgid="4678812947604395649">"Sending file to \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
<string name="bt_toast_5" msgid="2846870992823019494">"Sending <xliff:g id="NUMBER">%1$s</xliff:g> files to \"<xliff:g id="RECIPIENT">%2$s</xliff:g>\""</string>
<string name="bt_toast_6" msgid="1855266596936622458">"Stopped sending file to \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
- <string name="bt_sm_2_1" product="nosdcard" msgid="352165168004521000">"There isn\'t enough space in USB storage to save the file from \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
- <string name="bt_sm_2_1" product="default" msgid="1989018443456803630">"There isn\'t enough space on the SD card to save the file from \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
+ <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"There isn\'t enough space in USB storage to save the file from \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
+ <string name="bt_sm_2_1_default" msgid="9115512207909504071">"There isn\'t enough space on the SD card to save the file from \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
<string name="bt_sm_2_2" msgid="2965243265852680543">"Space needed: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
<string name="ErrorTooManyRequests" msgid="8578277541472944529">"Too many requests are being processed. Try again later."</string>
<string name="status_pending" msgid="2503691772030877944">"File transfer not started yet"</string>
@@ -95,8 +95,8 @@
<string name="status_forbidden" msgid="613956401054050725">"Transfer forbidden by target device."</string>
<string name="status_canceled" msgid="6664490318773098285">"Transfer cancelled by user."</string>
<string name="status_file_error" msgid="3671917770630165299">"Storage issue"</string>
- <string name="status_no_sd_card" product="nosdcard" msgid="1112125377088421469">"No USB storage."</string>
- <string name="status_no_sd_card" product="default" msgid="5760944071743325592">"No SD card. Insert an SD card to save transferred files."</string>
+ <string name="status_no_sd_card_nosdcard" msgid="573631036356922221">"No USB storage."</string>
+ <string name="status_no_sd_card_default" msgid="396564893716701954">"No SD card. Insert an SD card to save transferred files."</string>
<string name="status_connection_error" msgid="947681831523219891">"Connection unsuccessful."</string>
<string name="status_protocol_error" msgid="3245444473429269539">"Request can\'t be handled correctly."</string>
<string name="status_unknown_error" msgid="8156660554237824912">"Unknown error."</string>
@@ -122,6 +122,7 @@
<string name="transfer_menu_open" msgid="3368984869083107200">"Open"</string>
<string name="transfer_menu_clear" msgid="5854038118831427492">"Clear from list"</string>
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"Clear"</string>
+ <string name="bluetooth_a2dp_sink_queue_name" msgid="6864149958708669766">"Now Playing"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"Save"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"Cancel"</string>
<string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"Select the accounts that you want to share through Bluetooth. You still have to accept any access to the accounts when connecting."</string>
diff --git a/res/values-en-rGB/strings.xml b/res/values-en-rGB/strings.xml
index ba19b9c..ddca497 100644
--- a/res/values-en-rGB/strings.xml
+++ b/res/values-en-rGB/strings.xml
@@ -84,8 +84,8 @@
<string name="bt_toast_4" msgid="4678812947604395649">"Sending file to \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
<string name="bt_toast_5" msgid="2846870992823019494">"Sending <xliff:g id="NUMBER">%1$s</xliff:g> files to \"<xliff:g id="RECIPIENT">%2$s</xliff:g>\""</string>
<string name="bt_toast_6" msgid="1855266596936622458">"Stopped sending file to \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
- <string name="bt_sm_2_1" product="nosdcard" msgid="352165168004521000">"There isn\'t enough space in USB storage to save the file from \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
- <string name="bt_sm_2_1" product="default" msgid="1989018443456803630">"There isn\'t enough space on the SD card to save the file from \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
+ <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"There isn\'t enough space in USB storage to save the file from \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
+ <string name="bt_sm_2_1_default" msgid="9115512207909504071">"There isn\'t enough space on the SD card to save the file from \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
<string name="bt_sm_2_2" msgid="2965243265852680543">"Space needed: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
<string name="ErrorTooManyRequests" msgid="8578277541472944529">"Too many requests are being processed. Try again later."</string>
<string name="status_pending" msgid="2503691772030877944">"File transfer not started yet"</string>
@@ -95,8 +95,8 @@
<string name="status_forbidden" msgid="613956401054050725">"Transfer forbidden by target device."</string>
<string name="status_canceled" msgid="6664490318773098285">"Transfer cancelled by user."</string>
<string name="status_file_error" msgid="3671917770630165299">"Storage issue"</string>
- <string name="status_no_sd_card" product="nosdcard" msgid="1112125377088421469">"No USB storage."</string>
- <string name="status_no_sd_card" product="default" msgid="5760944071743325592">"No SD card. Insert an SD card to save transferred files."</string>
+ <string name="status_no_sd_card_nosdcard" msgid="573631036356922221">"No USB storage."</string>
+ <string name="status_no_sd_card_default" msgid="396564893716701954">"No SD card. Insert an SD card to save transferred files."</string>
<string name="status_connection_error" msgid="947681831523219891">"Connection unsuccessful."</string>
<string name="status_protocol_error" msgid="3245444473429269539">"Request can\'t be handled correctly."</string>
<string name="status_unknown_error" msgid="8156660554237824912">"Unknown error."</string>
@@ -122,6 +122,7 @@
<string name="transfer_menu_open" msgid="3368984869083107200">"Open"</string>
<string name="transfer_menu_clear" msgid="5854038118831427492">"Clear from list"</string>
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"Clear"</string>
+ <string name="bluetooth_a2dp_sink_queue_name" msgid="6864149958708669766">"Now Playing"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"Save"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"Cancel"</string>
<string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"Select the accounts that you want to share through Bluetooth. You still have to accept any access to the accounts when connecting."</string>
diff --git a/res/values-en-rIN/strings.xml b/res/values-en-rIN/strings.xml
index ba19b9c..ddca497 100644
--- a/res/values-en-rIN/strings.xml
+++ b/res/values-en-rIN/strings.xml
@@ -84,8 +84,8 @@
<string name="bt_toast_4" msgid="4678812947604395649">"Sending file to \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
<string name="bt_toast_5" msgid="2846870992823019494">"Sending <xliff:g id="NUMBER">%1$s</xliff:g> files to \"<xliff:g id="RECIPIENT">%2$s</xliff:g>\""</string>
<string name="bt_toast_6" msgid="1855266596936622458">"Stopped sending file to \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
- <string name="bt_sm_2_1" product="nosdcard" msgid="352165168004521000">"There isn\'t enough space in USB storage to save the file from \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
- <string name="bt_sm_2_1" product="default" msgid="1989018443456803630">"There isn\'t enough space on the SD card to save the file from \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
+ <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"There isn\'t enough space in USB storage to save the file from \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
+ <string name="bt_sm_2_1_default" msgid="9115512207909504071">"There isn\'t enough space on the SD card to save the file from \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
<string name="bt_sm_2_2" msgid="2965243265852680543">"Space needed: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
<string name="ErrorTooManyRequests" msgid="8578277541472944529">"Too many requests are being processed. Try again later."</string>
<string name="status_pending" msgid="2503691772030877944">"File transfer not started yet"</string>
@@ -95,8 +95,8 @@
<string name="status_forbidden" msgid="613956401054050725">"Transfer forbidden by target device."</string>
<string name="status_canceled" msgid="6664490318773098285">"Transfer cancelled by user."</string>
<string name="status_file_error" msgid="3671917770630165299">"Storage issue"</string>
- <string name="status_no_sd_card" product="nosdcard" msgid="1112125377088421469">"No USB storage."</string>
- <string name="status_no_sd_card" product="default" msgid="5760944071743325592">"No SD card. Insert an SD card to save transferred files."</string>
+ <string name="status_no_sd_card_nosdcard" msgid="573631036356922221">"No USB storage."</string>
+ <string name="status_no_sd_card_default" msgid="396564893716701954">"No SD card. Insert an SD card to save transferred files."</string>
<string name="status_connection_error" msgid="947681831523219891">"Connection unsuccessful."</string>
<string name="status_protocol_error" msgid="3245444473429269539">"Request can\'t be handled correctly."</string>
<string name="status_unknown_error" msgid="8156660554237824912">"Unknown error."</string>
@@ -122,6 +122,7 @@
<string name="transfer_menu_open" msgid="3368984869083107200">"Open"</string>
<string name="transfer_menu_clear" msgid="5854038118831427492">"Clear from list"</string>
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"Clear"</string>
+ <string name="bluetooth_a2dp_sink_queue_name" msgid="6864149958708669766">"Now Playing"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"Save"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"Cancel"</string>
<string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"Select the accounts that you want to share through Bluetooth. You still have to accept any access to the accounts when connecting."</string>
diff --git a/res/values-en-rXC/strings.xml b/res/values-en-rXC/strings.xml
index 47646b1..02178d4 100644
--- a/res/values-en-rXC/strings.xml
+++ b/res/values-en-rXC/strings.xml
@@ -84,8 +84,8 @@
<string name="bt_toast_4" msgid="4678812947604395649">"Sending file to \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
<string name="bt_toast_5" msgid="2846870992823019494">"Sending <xliff:g id="NUMBER">%1$s</xliff:g> files to \"<xliff:g id="RECIPIENT">%2$s</xliff:g>\""</string>
<string name="bt_toast_6" msgid="1855266596936622458">"Stopped sending file to \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
- <string name="bt_sm_2_1" product="nosdcard" msgid="352165168004521000">"There isn\'t enough space in USB storage to save the file from \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
- <string name="bt_sm_2_1" product="default" msgid="1989018443456803630">"There isn\'t enough space on the SD card to save the file from \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
+ <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"There isn\'t enough space in USB storage to save the file from \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
+ <string name="bt_sm_2_1_default" msgid="9115512207909504071">"There isn\'t enough space on the SD card to save the file from \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
<string name="bt_sm_2_2" msgid="2965243265852680543">"Space needed: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
<string name="ErrorTooManyRequests" msgid="8578277541472944529">"Too many requests are being processed. Try again later."</string>
<string name="status_pending" msgid="2503691772030877944">"File transfer not started yet."</string>
@@ -95,8 +95,8 @@
<string name="status_forbidden" msgid="613956401054050725">"Transfer forbidden by target device."</string>
<string name="status_canceled" msgid="6664490318773098285">"Transfer canceled by user."</string>
<string name="status_file_error" msgid="3671917770630165299">"Storage issue."</string>
- <string name="status_no_sd_card" product="nosdcard" msgid="1112125377088421469">"No USB storage."</string>
- <string name="status_no_sd_card" product="default" msgid="5760944071743325592">"No SD card. Insert an SD card to save transferred files."</string>
+ <string name="status_no_sd_card_nosdcard" msgid="573631036356922221">"No USB storage."</string>
+ <string name="status_no_sd_card_default" msgid="396564893716701954">"No SD card. Insert an SD card to save transferred files."</string>
<string name="status_connection_error" msgid="947681831523219891">"Connection unsuccessful."</string>
<string name="status_protocol_error" msgid="3245444473429269539">"Request can\'t be handled correctly."</string>
<string name="status_unknown_error" msgid="8156660554237824912">"Unknown error."</string>
@@ -122,6 +122,7 @@
<string name="transfer_menu_open" msgid="3368984869083107200">"Open"</string>
<string name="transfer_menu_clear" msgid="5854038118831427492">"Clear from list"</string>
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"Clear"</string>
+ <string name="bluetooth_a2dp_sink_queue_name" msgid="6864149958708669766">"Now Playing"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"Save"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"Cancel"</string>
<string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"Select the accounts you want to share through Bluetooth. You still have to accept any access to the accounts when connecting."</string>
diff --git a/res/values-es-rUS/strings.xml b/res/values-es-rUS/strings.xml
index 1b142d4..0fecd58 100644
--- a/res/values-es-rUS/strings.xml
+++ b/res/values-es-rUS/strings.xml
@@ -84,8 +84,8 @@
<string name="bt_toast_4" msgid="4678812947604395649">"Enviando archivo a \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
<string name="bt_toast_5" msgid="2846870992823019494">"Enviando <xliff:g id="NUMBER">%1$s</xliff:g> archivos a \"<xliff:g id="RECIPIENT">%2$s</xliff:g>\""</string>
<string name="bt_toast_6" msgid="1855266596936622458">"Se detuvo el envío del archivo a \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
- <string name="bt_sm_2_1" product="nosdcard" msgid="352165168004521000">"No hay espacio suficiente en el almacenamiento USB para guardar el archivo de \"<xliff:g id="SENDER">%1$s</xliff:g>\"."</string>
- <string name="bt_sm_2_1" product="default" msgid="1989018443456803630">"No hay suficiente espacio en la tarjeta SD para guardar el archivo de \"<xliff:g id="SENDER">%1$s</xliff:g>\"."</string>
+ <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"No hay espacio suficiente en el almacenamiento USB para guardar el archivo de \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
+ <string name="bt_sm_2_1_default" msgid="9115512207909504071">"No hay suficiente espacio en la tarjeta SD para guardar el archivo de \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
<string name="bt_sm_2_2" msgid="2965243265852680543">"Espacio necesario: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
<string name="ErrorTooManyRequests" msgid="8578277541472944529">"Se están procesando demasiadas solicitudes. Vuelve a intentarlo más tarde."</string>
<string name="status_pending" msgid="2503691772030877944">"Aún no comenzó la transferencia de archivos."</string>
@@ -95,8 +95,8 @@
<string name="status_forbidden" msgid="613956401054050725">"El dispositivo de destino prohíbe la transferencia."</string>
<string name="status_canceled" msgid="6664490318773098285">"Transferencia cancelada por el usuario"</string>
<string name="status_file_error" msgid="3671917770630165299">"Problema de almacenamiento"</string>
- <string name="status_no_sd_card" product="nosdcard" msgid="1112125377088421469">"Almacenamiento USB sin espacio"</string>
- <string name="status_no_sd_card" product="default" msgid="5760944071743325592">"No se ha encontrado una tarjeta SD. Inserta una tarjeta SD para guardar los archivos transferidos."</string>
+ <string name="status_no_sd_card_nosdcard" msgid="573631036356922221">"No se encuentra ningún almacenamiento USB."</string>
+ <string name="status_no_sd_card_default" msgid="396564893716701954">"No se ha encontrado una tarjeta SD. Inserta una para guardar los archivos transferidos."</string>
<string name="status_connection_error" msgid="947681831523219891">"Conexión incorrecta"</string>
<string name="status_protocol_error" msgid="3245444473429269539">"No se puede procesar la solicitud correctamente."</string>
<string name="status_unknown_error" msgid="8156660554237824912">"Error desconocido"</string>
@@ -122,6 +122,7 @@
<string name="transfer_menu_open" msgid="3368984869083107200">"Abrir"</string>
<string name="transfer_menu_clear" msgid="5854038118831427492">"Eliminar de la lista"</string>
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"Eliminar"</string>
+ <string name="bluetooth_a2dp_sink_queue_name" msgid="6864149958708669766">"Está Sonando"</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 deseas compartir mediante Bluetooth. Al conectarte, tendrás que aceptar cualquier acceso a las cuentas."</string>
diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml
index 6350a29..a9e448c 100644
--- a/res/values-es/strings.xml
+++ b/res/values-es/strings.xml
@@ -84,8 +84,8 @@
<string name="bt_toast_4" msgid="4678812947604395649">"Enviando archivo a \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
<string name="bt_toast_5" msgid="2846870992823019494">"Enviando <xliff:g id="NUMBER">%1$s</xliff:g> archivos a \"<xliff:g id="RECIPIENT">%2$s</xliff:g>\""</string>
<string name="bt_toast_6" msgid="1855266596936622458">"Se ha detenido el envío del archivo a \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
- <string name="bt_sm_2_1" product="nosdcard" msgid="352165168004521000">"No hay suficiente espacio en el almacenamiento USB para guardar el archivo de \"<xliff:g id="SENDER">%1$s</xliff:g>\"."</string>
- <string name="bt_sm_2_1" product="default" msgid="1989018443456803630">"No hay suficiente espacio en la tarjeta SD para guardar el archivo de \"<xliff:g id="SENDER">%1$s</xliff:g>\"."</string>
+ <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"No hay suficiente espacio en el almacenamiento USB para guardar el archivo de \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
+ <string name="bt_sm_2_1_default" msgid="9115512207909504071">"No hay suficiente espacio en la tarjeta SD para guardar el archivo de \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
<string name="bt_sm_2_2" msgid="2965243265852680543">"Espacio necesario: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
<string name="ErrorTooManyRequests" msgid="8578277541472944529">"Se están procesando demasiadas solicitudes. Vuelve a intentarlo más tarde."</string>
<string name="status_pending" msgid="2503691772030877944">"Aún no se ha iniciado la transferencia de archivos."</string>
@@ -95,8 +95,8 @@
<string name="status_forbidden" msgid="613956401054050725">"El dispositivo de destino no permite la transferencia."</string>
<string name="status_canceled" msgid="6664490318773098285">"Transferencia cancelada por el usuario"</string>
<string name="status_file_error" msgid="3671917770630165299">"Error relacionada con el almacenamiento"</string>
- <string name="status_no_sd_card" product="nosdcard" msgid="1112125377088421469">"Sin almacenamiento USB"</string>
- <string name="status_no_sd_card" product="default" msgid="5760944071743325592">"No se detecta ninguna tarjeta SD. Inserta una tarjeta SD y guarda los archivos transferidos."</string>
+ <string name="status_no_sd_card_nosdcard" msgid="573631036356922221">"Sin almacenamiento USB"</string>
+ <string name="status_no_sd_card_default" msgid="396564893716701954">"No se detecta ninguna tarjeta SD. Inserta una para guardar los archivos transferidos."</string>
<string name="status_connection_error" msgid="947681831523219891">"Conexión incorrecta"</string>
<string name="status_protocol_error" msgid="3245444473429269539">"No se puede procesar la solicitud correctamente."</string>
<string name="status_unknown_error" msgid="8156660554237824912">"Error desconocido"</string>
@@ -122,6 +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_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>
diff --git a/res/values-et/strings.xml b/res/values-et/strings.xml
index 413a4e1..3d98cb3 100644
--- a/res/values-et/strings.xml
+++ b/res/values-et/strings.xml
@@ -84,8 +84,8 @@
<string name="bt_toast_4" msgid="4678812947604395649">"Faili saatmine saajale „<xliff:g id="RECIPIENT">%1$s</xliff:g>”"</string>
<string name="bt_toast_5" msgid="2846870992823019494">"<xliff:g id="NUMBER">%1$s</xliff:g> faili saatmine saajale „<xliff:g id="RECIPIENT">%2$s</xliff:g>”"</string>
<string name="bt_toast_6" msgid="1855266596936622458">"Faili saatmine saajale „<xliff:g id="RECIPIENT">%1$s</xliff:g>” peatatud"</string>
- <string name="bt_sm_2_1" product="nosdcard" msgid="352165168004521000">"USB-mäluseadmes pole saatjalt „<xliff:g id="SENDER">%1$s</xliff:g>” saadud faili salvestamiseks piisavalt ruumi"</string>
- <string name="bt_sm_2_1" product="default" msgid="1989018443456803630">"SD-kaardil pole saatjalt „<xliff:g id="SENDER">%1$s</xliff:g>” saadud faili salvestamiseks piisavalt ruumi"</string>
+ <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"USB-salvestusruumis pole saatjalt „<xliff:g id="SENDER">%1$s</xliff:g>” saadud faili salvestamiseks piisavalt ruumi"</string>
+ <string name="bt_sm_2_1_default" msgid="9115512207909504071">"SD-kaardil pole saatjalt „<xliff:g id="SENDER">%1$s</xliff:g>” saadud faili salvestamiseks piisavalt ruumi"</string>
<string name="bt_sm_2_2" msgid="2965243265852680543">"Vajalik ruum: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
<string name="ErrorTooManyRequests" msgid="8578277541472944529">"Liiga palju taotlusi on töötlemisel. Proovige hiljem uuesti."</string>
<string name="status_pending" msgid="2503691772030877944">"Failiedastust pole veel käivitatud."</string>
@@ -95,8 +95,8 @@
<string name="status_forbidden" msgid="613956401054050725">"Sihtseade on edastuse keelanud."</string>
<string name="status_canceled" msgid="6664490318773098285">"Kasutaja tühistas edastuse."</string>
<string name="status_file_error" msgid="3671917770630165299">"Probleem talletamisega."</string>
- <string name="status_no_sd_card" product="nosdcard" msgid="1112125377088421469">"USB-mäluseadet ei leita."</string>
- <string name="status_no_sd_card" product="default" msgid="5760944071743325592">"SD-kaart puudub. Edastatud failide salvestamiseks sisestage SD-kaart."</string>
+ <string name="status_no_sd_card_nosdcard" msgid="573631036356922221">"USB-salvestusruum puudub."</string>
+ <string name="status_no_sd_card_default" msgid="396564893716701954">"SD-kaart puudub. Edastatud failide salvestamiseks sisestage SD-kaart."</string>
<string name="status_connection_error" msgid="947681831523219891">"Ühendus ebaõnnestus."</string>
<string name="status_protocol_error" msgid="3245444473429269539">"Taotlust ei saa õigesti käsitleda."</string>
<string name="status_unknown_error" msgid="8156660554237824912">"Tundmatu viga."</string>
@@ -122,6 +122,7 @@
<string name="transfer_menu_open" msgid="3368984869083107200">"Ava"</string>
<string name="transfer_menu_clear" msgid="5854038118831427492">"Eemaldage loendist"</string>
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"Kustuta"</string>
+ <string name="bluetooth_a2dp_sink_queue_name" msgid="6864149958708669766">"Hetkel mängimas"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"Salvesta"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"Tühista"</string>
<string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"Valige kontod, mida soovite Bluetoothi kaudu jagada. Ühendamisel peate ikka lubama mis tahes juurdepääsu kontodele."</string>
diff --git a/res/values-eu/strings.xml b/res/values-eu/strings.xml
index 6436c7f..9adbeff 100644
--- a/res/values-eu/strings.xml
+++ b/res/values-eu/strings.xml
@@ -17,17 +17,17 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="permlab_bluetoothShareManager" msgid="311492132450338925">"Atzitu deskargen kudeatzailea."</string>
- <string name="permdesc_bluetoothShareManager" msgid="8930572979123190223">"Bluetooth bidezko partekatzeen kudeatzailea atzitzea eta fitxategiak transferitzeko erabiltzea baimentzen die aplikazioei."</string>
- <string name="permlab_bluetoothWhitelist" msgid="7091552898592306386">"Sartu sarbidedunen zerrendan Bluetooth gailua."</string>
- <string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"Bluetooth gailu bat aldi baterako sarbidedunen zerrendan sartzea baimentzen die aplikazioei, gailu honetara fitxategiak bidaltzeko baimena izan dezan, baina gailu honen erabiltzaileari berrespena eskatu beharrik gabe."</string>
- <string name="bt_share_picker_label" msgid="6268100924487046932">"Bluetooth"</string>
+ <string name="permdesc_bluetoothShareManager" msgid="8930572979123190223">"Bluetooth bidezko partekatzeen kudeatzailea atzitzea eta fitxategiak transferitzeko erabiltzeko baimena ematen die aplikazioei."</string>
+ <string name="permlab_bluetoothWhitelist" msgid="7091552898592306386">"Jarri onartutakoen zerrendan Bluetooth bidezko gailua."</string>
+ <string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"Bluetooth bidezko gailu bat aldi baterako onartutakoen zerrendan jartzeko baimena ematen die aplikazioei, gailu honetara fitxategiak bidaltzeko baimena izan dezan, baina gailu honen erabiltzaileari berrespena eskatu beharrik gabe."</string>
+ <string name="bt_share_picker_label" msgid="6268100924487046932">"Bluetooth-a"</string>
<string name="unknown_device" msgid="9221903979877041009">"Gailu ezezaguna"</string>
<string name="unknownNumber" msgid="4994750948072751566">"Ezezaguna"</string>
<string name="airplane_error_title" msgid="2683839635115739939">"Hegaldi modua"</string>
- <string name="airplane_error_msg" msgid="8698965595254137230">"Ezin duzu Bluetootha Hegaldi moduan erabili."</string>
+ <string name="airplane_error_msg" msgid="8698965595254137230">"Ezin duzu erabili Bluetooth-a Hegaldi moduan."</string>
<string name="bt_enable_title" msgid="8657832550503456572"></string>
- <string name="bt_enable_line1" msgid="7203551583048149">"Bluetooth-zerbitzuak erabiltzeko, Bluetootha aktibatu behar duzu."</string>
- <string name="bt_enable_line2" msgid="4341936569415937994">"Bluetootha aktibatu nahi duzu?"</string>
+ <string name="bt_enable_line1" msgid="7203551583048149">"Bluetooth-zerbitzuak erabiltzeko, Bluetooth-a aktibatu behar duzu."</string>
+ <string name="bt_enable_line2" msgid="4341936569415937994">"Bluetooth-a aktibatu nahi duzu?"</string>
<string name="bt_enable_cancel" msgid="1988832367505151727">"Utzi"</string>
<string name="bt_enable_ok" msgid="3432462749994538265">"Aktibatu"</string>
<string name="incoming_file_confirm_title" msgid="8139874248612182627">"Fitxategi-transferentzia"</string>
@@ -77,15 +77,15 @@
<string name="not_exist_file" msgid="3489434189599716133">"Ez dago fitxategirik"</string>
<string name="not_exist_file_desc" msgid="4059531573790529229">"Ez dago horrelako fitxategirik. \n"</string>
<string name="enabling_progress_title" msgid="436157952334723406">"Itxaron…"</string>
- <string name="enabling_progress_content" msgid="4601542238119927904">"Bluetootha aktibatzen…"</string>
+ <string name="enabling_progress_content" msgid="4601542238119927904">"Bluetooth-a aktibatzen…"</string>
<string name="bt_toast_1" msgid="972182708034353383">"Fitxategia jasoko da. Egoera kontrolatzeko, joan Jakinarazpenen panelera."</string>
<string name="bt_toast_2" msgid="8602553334099066582">"Ezin da fitxategia jaso."</string>
<string name="bt_toast_3" msgid="6707884165086862518">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" igorlearen fitxategia jasotzeari utzi zaio"</string>
<string name="bt_toast_4" msgid="4678812947604395649">"\"<xliff:g id="RECIPIENT">%1$s</xliff:g>\" hartzaileari fitxategia bidaltzen"</string>
<string name="bt_toast_5" msgid="2846870992823019494">"\"<xliff:g id="RECIPIENT">%2$s</xliff:g>\" hartzaileari <xliff:g id="NUMBER">%1$s</xliff:g> fitxategi bidaltzen"</string>
<string name="bt_toast_6" msgid="1855266596936622458">"\"<xliff:g id="RECIPIENT">%1$s</xliff:g>\" hartzaileari fitxategia bidaltzeari utzi zaio."</string>
- <string name="bt_sm_2_1" product="nosdcard" msgid="352165168004521000">"Ez dago \"<xliff:g id="SENDER">%1$s</xliff:g>\" igorlearen fitxategia gordetzeko behar adina leku USB memorian"</string>
- <string name="bt_sm_2_1" product="default" msgid="1989018443456803630">"Ez dago \"<xliff:g id="SENDER">%1$s</xliff:g>\" igorlearen fitxategia gordetzeko behar adina leku SD txartelean"</string>
+ <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"Ez dago \"<xliff:g id="SENDER">%1$s</xliff:g>\" erabiltzailearen fitxategia gordetzeko behar adina toki USB memorian"</string>
+ <string name="bt_sm_2_1_default" msgid="9115512207909504071">"Ez dago \"<xliff:g id="SENDER">%1$s</xliff:g>\" erabiltzailearen fitxategia gordetzeko behar adina toki SD txartelean"</string>
<string name="bt_sm_2_2" msgid="2965243265852680543">"Beharrezko memoria: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
<string name="ErrorTooManyRequests" msgid="8578277541472944529">"Eskaera gehiegi prozesatzen ari dira. Saiatu berriro geroago."</string>
<string name="status_pending" msgid="2503691772030877944">"Ez da fitxategi-transferentzia oraindik hasi."</string>
@@ -95,8 +95,8 @@
<string name="status_forbidden" msgid="613956401054050725">"Xede-gailuak transferentzia debekatu du."</string>
<string name="status_canceled" msgid="6664490318773098285">"Erabiltzaileak bertan behera utzi du transferentzia."</string>
<string name="status_file_error" msgid="3671917770630165299">"Memoria-arazoa."</string>
- <string name="status_no_sd_card" product="nosdcard" msgid="1112125377088421469">"Ez dago USB memoriarik."</string>
- <string name="status_no_sd_card" product="default" msgid="5760944071743325592">"Ez dago SD txartelik. Transferitutako fitxategiak gordetzeko, sartu SD txartel bat."</string>
+ <string name="status_no_sd_card_nosdcard" msgid="573631036356922221">"Ez dago USB memoriarik."</string>
+ <string name="status_no_sd_card_default" msgid="396564893716701954">"Ez dago SD txartelik. Transferitutako fitxategiak gordetzeko, sartu SD txartel bat."</string>
<string name="status_connection_error" msgid="947681831523219891">"Ezin izan da konektatu."</string>
<string name="status_protocol_error" msgid="3245444473429269539">"Ezin da eskaera behar bezala kudeatu."</string>
<string name="status_unknown_error" msgid="8156660554237824912">"Errore ezezaguna."</string>
@@ -122,6 +122,7 @@
<string name="transfer_menu_open" msgid="3368984869083107200">"Ireki"</string>
<string name="transfer_menu_clear" msgid="5854038118831427492">"Garbitu zerrendatik"</string>
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"Garbitu"</string>
+ <string name="bluetooth_a2dp_sink_queue_name" msgid="6864149958708669766">"Orain erreproduzitzen"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"Gorde"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"Utzi"</string>
<string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"Hautatu Bluetooth bidez partekatu nahi dituzun kontuak. Konektatzean, berariaz eman beharko duzu kontuetarako sarbidea."</string>
diff --git a/res/values-eu/test_strings.xml b/res/values-eu/test_strings.xml
index 2497be5..e7236e7 100644
--- a/res/values-eu/test_strings.xml
+++ b/res/values-eu/test_strings.xml
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="app_name" msgid="6006644116867509664">"Bluetooth konexioa"</string>
+ <string name="app_name" msgid="6006644116867509664">"Bluetooth-a"</string>
<string name="insert_record" msgid="1450997173838378132">"Sartu erregistroa"</string>
<string name="update_record" msgid="2480425402384910635">"Berretsi erregistroa"</string>
<string name="ack_record" msgid="6716152390978472184">"ACK erregistroa"</string>
diff --git a/res/values-fa/strings.xml b/res/values-fa/strings.xml
index 29f247a..c95558e 100644
--- a/res/values-fa/strings.xml
+++ b/res/values-fa/strings.xml
@@ -84,8 +84,8 @@
<string name="bt_toast_4" msgid="4678812947604395649">"ارسال فایل به \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
<string name="bt_toast_5" msgid="2846870992823019494">"ارسال <xliff:g id="NUMBER">%1$s</xliff:g> فایل به \"<xliff:g id="RECIPIENT">%2$s</xliff:g>\""</string>
<string name="bt_toast_6" msgid="1855266596936622458">"ارسال فایل به \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\" متوقف شد"</string>
- <string name="bt_sm_2_1" product="nosdcard" msgid="352165168004521000">"برای ذخیره فایل از \"<xliff:g id="SENDER">%1$s</xliff:g>\" فضای کافی در حافظهٔ USB موجود نیست"</string>
- <string name="bt_sm_2_1" product="default" msgid="1989018443456803630">"برای ذخیره فایل \"<xliff:g id="SENDER">%1$s</xliff:g>\" فضای کافی در کارت SD موجود نیست"</string>
+ <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"برای ذخیره فایل از «<xliff:g id="SENDER">%1$s</xliff:g>» فضای کافی در حافظه USB موجود نیست"</string>
+ <string name="bt_sm_2_1_default" msgid="9115512207909504071">"برای ذخیره فایل «<xliff:g id="SENDER">%1$s</xliff:g>» فضای کافی در کارت SD موجود نیست"</string>
<string name="bt_sm_2_2" msgid="2965243265852680543">"فضای مورد نیاز: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
<string name="ErrorTooManyRequests" msgid="8578277541472944529">"درخواستهای بسیاری در حال انجام هستند. بعداً دوباره امتحان کنید."</string>
<string name="status_pending" msgid="2503691772030877944">"انتقال فایل هنوز شروع نشده است."</string>
@@ -95,8 +95,8 @@
<string name="status_forbidden" msgid="613956401054050725">"انتقال توسط دستگاه مقصد ممنوع شده است."</string>
<string name="status_canceled" msgid="6664490318773098285">"انتقال توسط کاربر لغو شد."</string>
<string name="status_file_error" msgid="3671917770630165299">"مشکل ذخیره."</string>
- <string name="status_no_sd_card" product="nosdcard" msgid="1112125377088421469">"حافظهٔ USB وجود ندارد."</string>
- <string name="status_no_sd_card" product="default" msgid="5760944071743325592">"هیچ کارت SD موجود نیست. برای ذخیره فایلهای منتقل شده، یک کارت SD در گوشی قرار دهید."</string>
+ <string name="status_no_sd_card_nosdcard" msgid="573631036356922221">"حافظهٔ USB وجود ندارد."</string>
+ <string name="status_no_sd_card_default" msgid="396564893716701954">"هیچ کارت SD موجود نیست. برای ذخیره فایلهای منتقلشده، کارت SD در گوشی قرار دهید."</string>
<string name="status_connection_error" msgid="947681831523219891">"اتصال ناموفق بود."</string>
<string name="status_protocol_error" msgid="3245444473429269539">"درخواست به درستی انجام نمیشود."</string>
<string name="status_unknown_error" msgid="8156660554237824912">"خطای ناشناس."</string>
@@ -122,6 +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">"اکنون درحال پخش"</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>
diff --git a/res/values-fi/strings.xml b/res/values-fi/strings.xml
index 1e56ca2..0fbc55c 100644
--- a/res/values-fi/strings.xml
+++ b/res/values-fi/strings.xml
@@ -84,8 +84,8 @@
<string name="bt_toast_4" msgid="4678812947604395649">"Lähetetään tiedosto vastaanottajalle <xliff:g id="RECIPIENT">%1$s</xliff:g>"</string>
<string name="bt_toast_5" msgid="2846870992823019494">"Lähetetään <xliff:g id="NUMBER">%1$s</xliff:g> tiedostoa vastaanottajalle <xliff:g id="RECIPIENT">%2$s</xliff:g>"</string>
<string name="bt_toast_6" msgid="1855266596936622458">"Tiedoston lähettäminen vastaanottajalle <xliff:g id="RECIPIENT">%1$s</xliff:g> pysäytetty"</string>
- <string name="bt_sm_2_1" product="nosdcard" msgid="352165168004521000">"USB-tallennustilassa ei ole tarpeeksi tilaa. Tiedostoa lähettäjältä <xliff:g id="SENDER">%1$s</xliff:g> ei voi tallentaa."</string>
- <string name="bt_sm_2_1" product="default" msgid="1989018443456803630">"SD-kortilla ei ole tarpeeksi tilaa. Tiedostoa lähettäjältä <xliff:g id="SENDER">%1$s</xliff:g> ei voi tallentaa."</string>
+ <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"<xliff:g id="SENDER">%1$s</xliff:g> lähettää tiedostoa, jolle ei ole tilaa USB-tallennustilassa"</string>
+ <string name="bt_sm_2_1_default" msgid="9115512207909504071">"<xliff:g id="SENDER">%1$s</xliff:g> lähettää tiedostoa, jolle ei ole tilaa SD-kortilla"</string>
<string name="bt_sm_2_2" msgid="2965243265852680543">"Tilaa tarvitaan: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
<string name="ErrorTooManyRequests" msgid="8578277541472944529">"Liian monta käsiteltävää pyyntöä. Yritä myöhemmin uudelleen."</string>
<string name="status_pending" msgid="2503691772030877944">"Tiedostonsiirto ei ole vielä alkanut."</string>
@@ -95,8 +95,8 @@
<string name="status_forbidden" msgid="613956401054050725">"Kohdelaite kieltää siirron."</string>
<string name="status_canceled" msgid="6664490318773098285">"Käyttäjä peruutti tiedonsiirron."</string>
<string name="status_file_error" msgid="3671917770630165299">"Tallennusongelma."</string>
- <string name="status_no_sd_card" product="nosdcard" msgid="1112125377088421469">"Ei USB-tallennustilaa."</string>
- <string name="status_no_sd_card" product="default" msgid="5760944071743325592">"Ei SD-korttia. Aseta SD-kortti, niin voit tallentaa siirrettyjä tiedostoja."</string>
+ <string name="status_no_sd_card_nosdcard" msgid="573631036356922221">"Ei USB-tallennustilaa"</string>
+ <string name="status_no_sd_card_default" msgid="396564893716701954">"Ei SD-korttia. Aseta SD-kortti, niin voit tallentaa siirrettyjä tiedostoja."</string>
<string name="status_connection_error" msgid="947681831523219891">"Yhteys epäonnistui."</string>
<string name="status_protocol_error" msgid="3245444473429269539">"Pyyntö ei ole käsiteltävissä."</string>
<string name="status_unknown_error" msgid="8156660554237824912">"Tuntematon virhe."</string>
@@ -122,6 +122,7 @@
<string name="transfer_menu_open" msgid="3368984869083107200">"Avaa"</string>
<string name="transfer_menu_clear" msgid="5854038118831427492">"Poista luettelosta"</string>
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"Poista"</string>
+ <string name="bluetooth_a2dp_sink_queue_name" msgid="6864149958708669766">"Musiikintunnistus"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"Tallenna"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"Peruuta"</string>
<string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"Valitse tilit, jotka haluat jakaa Bluetoothin kautta. Tilien käyttöoikeus pitää silti hyväksyä yhdistettäessä."</string>
diff --git a/res/values-fr-rCA/strings.xml b/res/values-fr-rCA/strings.xml
index bcf5718..b881cfc 100644
--- a/res/values-fr-rCA/strings.xml
+++ b/res/values-fr-rCA/strings.xml
@@ -84,8 +84,8 @@
<string name="bt_toast_4" msgid="4678812947604395649">"Envoi du fichier à \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
<string name="bt_toast_5" msgid="2846870992823019494">"Envoi de <xliff:g id="NUMBER">%1$s</xliff:g> fichiers à \"<xliff:g id="RECIPIENT">%2$s</xliff:g>\""</string>
<string name="bt_toast_6" msgid="1855266596936622458">"Envoi du fichier à \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\" interrompu"</string>
- <string name="bt_sm_2_1" product="nosdcard" msgid="352165168004521000">"Espace insuffisant sur la mémoire de stockage USB pour l\'enregistrement du fichier de \"<xliff:g id="SENDER">%1$s</xliff:g>\"."</string>
- <string name="bt_sm_2_1" product="default" msgid="1989018443456803630">"Espace insuffisant sur la carte SD pour l\'enregistrement du fichier de \"<xliff:g id="SENDER">%1$s</xliff:g>\"."</string>
+ <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"Espace insuffisant sur la mémoire de stockage USB pour l\'enregistrement du fichier de « <xliff:g id="SENDER">%1$s</xliff:g> »"</string>
+ <string name="bt_sm_2_1_default" msgid="9115512207909504071">"Espace insuffisant sur la carte SD pour l\'enregistrement du fichier de « <xliff:g id="SENDER">%1$s</xliff:g> »"</string>
<string name="bt_sm_2_2" msgid="2965243265852680543">"Espace requis : <xliff:g id="SIZE">%1$s</xliff:g>"</string>
<string name="ErrorTooManyRequests" msgid="8578277541472944529">"Trop de requêtes sont en cours de traitement. Veuillez réessayer plus tard."</string>
<string name="status_pending" msgid="2503691772030877944">"Le transfert de fichier n\'a pas encore commencé."</string>
@@ -95,8 +95,8 @@
<string name="status_forbidden" msgid="613956401054050725">"L\'appareil cible n\'autorise pas le transfert."</string>
<string name="status_canceled" msgid="6664490318773098285">"Le transfert a été annulé par l\'utilisateur."</string>
<string name="status_file_error" msgid="3671917770630165299">"Problème de mémoire."</string>
- <string name="status_no_sd_card" product="nosdcard" msgid="1112125377088421469">"Aucune mémoire de stockage USB"</string>
- <string name="status_no_sd_card" product="default" msgid="5760944071743325592">"Aucune carte SD trouvée. Insérez une carte SD pour enregistrer les fichiers transférés."</string>
+ <string name="status_no_sd_card_nosdcard" msgid="573631036356922221">"Aucune mémoire de stockage USB"</string>
+ <string name="status_no_sd_card_default" msgid="396564893716701954">"Aucune carte SD trouvée. Insérez une carte SD pour enregistrer les fichiers transférés."</string>
<string name="status_connection_error" msgid="947681831523219891">"Échec de la connexion."</string>
<string name="status_protocol_error" msgid="3245444473429269539">"Impossible de traiter la demande correctement."</string>
<string name="status_unknown_error" msgid="8156660554237824912">"Erreur inconnue."</string>
@@ -122,6 +122,7 @@
<string name="transfer_menu_open" msgid="3368984869083107200">"ouvrir"</string>
<string name="transfer_menu_clear" msgid="5854038118831427492">"Effacer de la liste"</string>
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"Effacer"</string>
+ <string name="bluetooth_a2dp_sink_queue_name" msgid="6864149958708669766">"En cours de lecture"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"Enregistrer"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"Annuler"</string>
<string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"Sélectionnez les comptes que vous souhaitez partager par Bluetooth. Vous devez toujours accepter l\'accès à ces comptes lors de la connexion."</string>
diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml
index 13c3444..06fe209 100644
--- a/res/values-fr/strings.xml
+++ b/res/values-fr/strings.xml
@@ -84,8 +84,8 @@
<string name="bt_toast_4" msgid="4678812947604395649">"Envoi du fichier à \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
<string name="bt_toast_5" msgid="2846870992823019494">"Envoi de <xliff:g id="NUMBER">%1$s</xliff:g> fichiers à \"<xliff:g id="RECIPIENT">%2$s</xliff:g>\""</string>
<string name="bt_toast_6" msgid="1855266596936622458">"Envoi du fichier à \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\" interrompu"</string>
- <string name="bt_sm_2_1" product="nosdcard" msgid="352165168004521000">"Espace insuffisant sur la mémoire de stockage USB pour l\'enregistrement du fichier de \"<xliff:g id="SENDER">%1$s</xliff:g>\"."</string>
- <string name="bt_sm_2_1" product="default" msgid="1989018443456803630">"Espace insuffisant sur la carte SD pour l\'enregistrement du fichier de \"<xliff:g id="SENDER">%1$s</xliff:g>\"."</string>
+ <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"Espace insuffisant sur la mémoire de stockage USB pour l\'enregistrement du fichier de \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
+ <string name="bt_sm_2_1_default" msgid="9115512207909504071">"Espace insuffisant sur la carte SD pour l\'enregistrement du fichier de \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
<string name="bt_sm_2_2" msgid="2965243265852680543">"Espace nécessaire : <xliff:g id="SIZE">%1$s</xliff:g>"</string>
<string name="ErrorTooManyRequests" msgid="8578277541472944529">"Trop de requêtes sont en cours de traitement. Veuillez réessayer ultérieurement."</string>
<string name="status_pending" msgid="2503691772030877944">"Le transfert de fichier n\'a pas encore commencé."</string>
@@ -95,12 +95,12 @@
<string name="status_forbidden" msgid="613956401054050725">"L\'appareil cible n\'autorise pas le transfert."</string>
<string name="status_canceled" msgid="6664490318773098285">"le transfert a été annulé par l\'utilisateur."</string>
<string name="status_file_error" msgid="3671917770630165299">"Problème de mémoire."</string>
- <string name="status_no_sd_card" product="nosdcard" msgid="1112125377088421469">"Aucune mémoire de stockage USB"</string>
- <string name="status_no_sd_card" product="default" msgid="5760944071743325592">"Carte SD absente. Insérez une carte SD pour enregistrer les fichiers transférés."</string>
+ <string name="status_no_sd_card_nosdcard" msgid="573631036356922221">"Aucune mémoire de stockage USB."</string>
+ <string name="status_no_sd_card_default" msgid="396564893716701954">"Carte SD absente. Insérez une carte SD pour enregistrer les fichiers transférés."</string>
<string name="status_connection_error" msgid="947681831523219891">"Échec de la connexion."</string>
<string name="status_protocol_error" msgid="3245444473429269539">"Impossible de traiter la demande correctement."</string>
<string name="status_unknown_error" msgid="8156660554237824912">"Erreur inconnue."</string>
- <string name="btopp_live_folder" msgid="7967791481444474554">"Reçu par Bluetooth"</string>
+ <string name="btopp_live_folder" msgid="7967791481444474554">"Reçus via Bluetooth"</string>
<string name="opp_notification_group" msgid="3486303082135789982">"Partage Bluetooth"</string>
<string name="download_success" msgid="7036160438766730871">"Réception de <xliff:g id="FILE_SIZE">%1$s</xliff:g> terminée"</string>
<string name="upload_success" msgid="4014469387779648949">"Envoi de <xliff:g id="FILE_SIZE">%1$s</xliff:g> terminé"</string>
@@ -122,6 +122,7 @@
<string name="transfer_menu_open" msgid="3368984869083107200">"Ouvrir"</string>
<string name="transfer_menu_clear" msgid="5854038118831427492">"Effacer de la liste"</string>
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"Effacer"</string>
+ <string name="bluetooth_a2dp_sink_queue_name" msgid="6864149958708669766">"En écoute"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"Enregistrer"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"Annuler"</string>
<string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"Sélectionnez les comptes que vous voulez partager via le Bluetooth. Vous devez toujours accepter l\'accès à ces comptes lors de la connexion."</string>
diff --git a/res/values-gl/strings.xml b/res/values-gl/strings.xml
index 6072260..9dedefd 100644
--- a/res/values-gl/strings.xml
+++ b/res/values-gl/strings.xml
@@ -84,8 +84,8 @@
<string name="bt_toast_4" msgid="4678812947604395649">"Enviando ficheiro a \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
<string name="bt_toast_5" msgid="2846870992823019494">"Enviando <xliff:g id="NUMBER">%1$s</xliff:g> ficheiros a \"<xliff:g id="RECIPIENT">%2$s</xliff:g>\""</string>
<string name="bt_toast_6" msgid="1855266596936622458">"Detívose o envío do ficheiro a \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
- <string name="bt_sm_2_1" product="nosdcard" msgid="352165168004521000">"Non hai espazo suficiente no almacenamento USB para gardar o ficheiro de \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
- <string name="bt_sm_2_1" product="default" msgid="1989018443456803630">"Non hai espazo suficiente na tarxeta SD para gardar o ficheiro de \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
+ <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"Non hai espazo suficiente no almacenamento USB para gardar o ficheiro de \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
+ <string name="bt_sm_2_1_default" msgid="9115512207909504071">"Non hai espazo suficiente na tarxeta SD para gardar o ficheiro de \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
<string name="bt_sm_2_2" msgid="2965243265852680543">"Espazo necesario: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
<string name="ErrorTooManyRequests" msgid="8578277541472944529">"Estanse procesando demasiadas solicitudes. Téntao de novo máis tarde."</string>
<string name="status_pending" msgid="2503691772030877944">"Aínda non se iniciou a transferencia de ficheiros."</string>
@@ -95,8 +95,8 @@
<string name="status_forbidden" msgid="613956401054050725">"Transferencia prohibida polo dispositivo de destino."</string>
<string name="status_canceled" msgid="6664490318773098285">"Transferencia cancelada polo usuario."</string>
<string name="status_file_error" msgid="3671917770630165299">"Problema de almacenamento"</string>
- <string name="status_no_sd_card" product="nosdcard" msgid="1112125377088421469">"Non hai almacenamento USB."</string>
- <string name="status_no_sd_card" product="default" msgid="5760944071743325592">"Non hai tarxeta SD. Insire unha tarxeta SD para gardar os ficheiros transferidos."</string>
+ <string name="status_no_sd_card_nosdcard" msgid="573631036356922221">"Non hai almacenamento USB."</string>
+ <string name="status_no_sd_card_default" msgid="396564893716701954">"Non hai tarxeta SD. Insire unha para gardar os ficheiros transferidos."</string>
<string name="status_connection_error" msgid="947681831523219891">"Conexión incorrecta"</string>
<string name="status_protocol_error" msgid="3245444473429269539">"A solicitude non se pode atender correctamente."</string>
<string name="status_unknown_error" msgid="8156660554237824912">"Erro descoñecido"</string>
@@ -122,6 +122,7 @@
<string name="transfer_menu_open" msgid="3368984869083107200">"Abrir"</string>
<string name="transfer_menu_clear" msgid="5854038118831427492">"Borrar da lista"</string>
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"Borrar"</string>
+ <string name="bluetooth_a2dp_sink_queue_name" msgid="6864149958708669766">"Está soando"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"Gardar"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"Cancelar"</string>
<string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"Selecciona as contas que queres compartir a través de Bluetooth. Aínda así, tes que aceptar o acceso ás contas cando te conectes."</string>
diff --git a/res/values-gu/strings.xml b/res/values-gu/strings.xml
index 6860f15..d6e1000 100644
--- a/res/values-gu/strings.xml
+++ b/res/values-gu/strings.xml
@@ -84,8 +84,8 @@
<string name="bt_toast_4" msgid="4678812947604395649">"\"<xliff:g id="RECIPIENT">%1$s</xliff:g>\" પર ફાઇલ મોકલી રહ્યાં છે"</string>
<string name="bt_toast_5" msgid="2846870992823019494">"<xliff:g id="NUMBER">%1$s</xliff:g> ફાઇલો \"<xliff:g id="RECIPIENT">%2$s</xliff:g>\" પર મોકલી રહ્યાં છે"</string>
<string name="bt_toast_6" msgid="1855266596936622458">"\"<xliff:g id="RECIPIENT">%1$s</xliff:g>\" પર ફાઇલ મોકલવું બંધ કર્યું"</string>
- <string name="bt_sm_2_1" product="nosdcard" msgid="352165168004521000">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" થી ફાઇલ USB સંગ્રહમાં સાચવવા માટે પર્યાપ્ત સ્થાન નથી."</string>
- <string name="bt_sm_2_1" product="default" msgid="1989018443456803630">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" થી ફાઇલ SD કાર્ડમાં સાચવવા માટે પર્યાપ્ત સ્થાન નથી."</string>
+ <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"\"<xliff:g id="SENDER">%1$s</xliff:g>\"ની ફાઇલને USB સ્ટોરેજમાં સાચવવા માટે પૂરતી સ્પેસ નથી"</string>
+ <string name="bt_sm_2_1_default" msgid="9115512207909504071">"\"<xliff:g id="SENDER">%1$s</xliff:g>\"ની ફાઇલને SD કાર્ડમાં સાચવવા માટે પૂરતી સ્પેસ નથી"</string>
<string name="bt_sm_2_2" msgid="2965243265852680543">"સ્થાન જરૂરી: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
<string name="ErrorTooManyRequests" msgid="8578277541472944529">"ઘણી બધી વિનંતીઓ પર પ્રક્રિયા કરવામાં આવી રહી છે. પછીથી ફરી પ્રયાસ કરો."</string>
<string name="status_pending" msgid="2503691772030877944">"ફાઇલ સ્થાનાંતરણ હજી પ્રારંભ થયું નથી."</string>
@@ -95,8 +95,8 @@
<string name="status_forbidden" msgid="613956401054050725">"સ્થાનાંતરણ લક્ષિત ઉપકરણ દ્વારા પ્રતિબંધિત."</string>
<string name="status_canceled" msgid="6664490318773098285">"વપરાશકર્તા દ્વારા સ્થાનાંતરણ રદ."</string>
<string name="status_file_error" msgid="3671917770630165299">"સંગ્રહ સમસ્યા."</string>
- <string name="status_no_sd_card" product="nosdcard" msgid="1112125377088421469">"કોઈ USB સ્ટોરેજ નથી."</string>
- <string name="status_no_sd_card" product="default" msgid="5760944071743325592">"SD કાર્ડ નથી. ટ્રાન્સફર ફાઇલો સાચવવા માટે એક SD કાર્ડ શામેલ કરો."</string>
+ <string name="status_no_sd_card_nosdcard" msgid="573631036356922221">"કોઈ USB સ્ટોરેજ નથી."</string>
+ <string name="status_no_sd_card_default" msgid="396564893716701954">"કોઈ SD કાર્ડ નથી. ટ્રાન્સફર કરેલી ફાઇલોને સાચવવા માટે SD કાર્ડ દાખલ કરો."</string>
<string name="status_connection_error" msgid="947681831523219891">"કનેક્શન અસફળ."</string>
<string name="status_protocol_error" msgid="3245444473429269539">"વિનંતી યોગ્ય રીતે હેન્ડલ કરી શકાતી નથી."</string>
<string name="status_unknown_error" msgid="8156660554237824912">"અજાણી ભૂલ."</string>
@@ -122,6 +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">"હમણાં વાગી રહ્યું છે"</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>
diff --git a/res/values-hi/strings.xml b/res/values-hi/strings.xml
index 9e66d3b..30563fb 100644
--- a/res/values-hi/strings.xml
+++ b/res/values-hi/strings.xml
@@ -19,7 +19,7 @@
<string name="permlab_bluetoothShareManager" msgid="311492132450338925">"डाउनलोड प्रबंधक में पहुंच प्राप्त करें."</string>
<string name="permdesc_bluetoothShareManager" msgid="8930572979123190223">"ऐप्लिकेशन को BluetoothShare प्रबंधक के इस्तेमाल की मंज़ूरी देता है और फ़ाइलों को ट्रांसफ़र करने के लिए उसका उपयोग करने देता है."</string>
<string name="permlab_bluetoothWhitelist" msgid="7091552898592306386">"श्वेतसूची bluetooth डिवाइस पहुंच."</string>
- <string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"ऐप को, ब्लूटूथ डिवाइस को उपयोगकर्ता की पुष्टि के बिना इस डिवाइस पर फ़ाइल भेजने की अनुमति देकर, कुछ देर के लिए स्वीकृति देता है"</string>
+ <string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"ऐप को, ब्लूटूथ डिवाइस को उपयोगकर्ता की पुष्टि के बिना इस डिवाइस पर फ़ाइल भेजने की अनुमति देकर, कुछ देर के लिए अनुमति देता है"</string>
<string name="bt_share_picker_label" msgid="6268100924487046932">"ब्लूटूथ"</string>
<string name="unknown_device" msgid="9221903979877041009">"अज्ञात डिवाइस"</string>
<string name="unknownNumber" msgid="4994750948072751566">"अज्ञात"</string>
@@ -43,7 +43,7 @@
<string name="notification_received_fail" msgid="3619350997285714746">"ब्लूटूथ शेयर: फ़ाइल <xliff:g id="FILE">%1$s</xliff:g> प्राप्त नहीं हुई"</string>
<string name="notification_sending" msgid="3035748958534983833">"ब्लूटूथ शेयर: <xliff:g id="FILE">%1$s</xliff:g> भेज रहा है"</string>
<string name="notification_sent" msgid="9218710861333027778">"ब्लूटूथ शेयर: <xliff:g id="FILE">%1$s</xliff:g> भेजा गया"</string>
- <string name="notification_sent_complete" msgid="302943281067557969">"100% पूर्ण"</string>
+ <string name="notification_sent_complete" msgid="302943281067557969">"100% पूरा"</string>
<string name="notification_sent_fail" msgid="6696082233774569445">"ब्लूटूथ शेयर: फ़ाइल <xliff:g id="FILE">%1$s</xliff:g> भेजी नहीं गई"</string>
<string name="download_title" msgid="3353228219772092586">"फ़ाइल ट्रांसफ़र करें"</string>
<string name="download_line1" msgid="4926604799202134144">"प्रेषक: \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
@@ -69,11 +69,11 @@
<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>
- <string name="unknown_file_desc" msgid="480434281415453287">"इस प्रकार की फ़ाइल प्रबंधित करने के लिए कोई ऐप्स नहीं है. \n"</string>
+ <string name="unknown_file_desc" msgid="480434281415453287">"इस प्रकार की फ़ाइल प्रबंधित करने के लिए कोई ऐप्लिकेशन नहीं है. \n"</string>
<string name="not_exist_file" msgid="3489434189599716133">"कोई फ़ाइल नहीं"</string>
<string name="not_exist_file_desc" msgid="4059531573790529229">"फ़ाइल मौजूद नहीं है. \n"</string>
<string name="enabling_progress_title" msgid="436157952334723406">"कृपया प्रतीक्षा करें..."</string>
@@ -84,10 +84,10 @@
<string name="bt_toast_4" msgid="4678812947604395649">"\"<xliff:g id="RECIPIENT">%1$s</xliff:g>\" को फ़ाइल भेज रहा है"</string>
<string name="bt_toast_5" msgid="2846870992823019494">"\"<xliff:g id="RECIPIENT">%2$s</xliff:g>\" को <xliff:g id="NUMBER">%1$s</xliff:g> फ़ाइलें भेज रहा है"</string>
<string name="bt_toast_6" msgid="1855266596936622458">"\"<xliff:g id="RECIPIENT">%1$s</xliff:g>\" को फ़ाइल भेजना रोका गया"</string>
- <string name="bt_sm_2_1" product="nosdcard" msgid="352165168004521000">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" की फ़ाइल सेव करने के लिए USB मेमोरी में ज़रुरत के मुताबिक जगह नहीं है"</string>
- <string name="bt_sm_2_1" product="default" msgid="1989018443456803630">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" की फ़ाइल सेव करने के लिए SD कार्ड पर ज़रुरत के मुताबिक जगह नहीं है"</string>
+ <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" की फ़ाइल सेव करने के लिए USB मेमोरी में ज़रुरत के मुताबिक जगह नहीं है"</string>
+ <string name="bt_sm_2_1_default" msgid="9115512207909504071">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" की फ़ाइल सेव करने के लिए SD कार्ड पर ज़रुरत के मुताबिक जगह नहीं है"</string>
<string name="bt_sm_2_2" msgid="2965243265852680543">"जगह चाहिए: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
- <string name="ErrorTooManyRequests" msgid="8578277541472944529">"बहुत सारे अनुरोधों पर कार्रवाई चल रही है. बाद में पुन: प्रयास करें."</string>
+ <string name="ErrorTooManyRequests" msgid="8578277541472944529">"बहुत सारे अनुरोधों पर कार्रवाई चल रही है. बाद में फिर से प्रयास करें."</string>
<string name="status_pending" msgid="2503691772030877944">"फ़ाइल स्थानांतरण अभी तक प्रारंभ नहीं हुआ."</string>
<string name="status_running" msgid="6562808920311008696">"फ़ाइल स्थानांतरण जारी है."</string>
<string name="status_success" msgid="239573225847565868">"फ़ाइल स्थानांतरण सफलतापूर्वक पूरा हुआ."</string>
@@ -95,15 +95,15 @@
<string name="status_forbidden" msgid="613956401054050725">"लक्ष्य डिवाइस द्वारा स्थानांतरण प्रतिबंधित किया गया."</string>
<string name="status_canceled" msgid="6664490318773098285">"उपयोगकर्ता ने ट्रांसफर रद्द किया."</string>
<string name="status_file_error" msgid="3671917770630165299">"मेमोरी समस्या."</string>
- <string name="status_no_sd_card" product="nosdcard" msgid="1112125377088421469">"कोई USB मेमोरी नहीं."</string>
- <string name="status_no_sd_card" product="default" msgid="5760944071743325592">"कोई SD कार्ड नहीं. ट्रांसफ़र की गई फ़ाइलें सेव करने के लिए SD कार्ड डालें."</string>
+ <string name="status_no_sd_card_nosdcard" msgid="573631036356922221">"कोई USB मेमोरी नहीं."</string>
+ <string name="status_no_sd_card_default" msgid="396564893716701954">"कोई SD कार्ड नहीं. ट्रांसफ़र की गई फ़ाइलें सेव करने के लिए SD कार्ड लगाएं."</string>
<string name="status_connection_error" msgid="947681831523219891">"कनेक्शन विफल."</string>
<string name="status_protocol_error" msgid="3245444473429269539">"अनुरोध को सही तरह से प्रबंधित नहीं किया जा सकता."</string>
<string name="status_unknown_error" msgid="8156660554237824912">"अज्ञात गड़बड़ी."</string>
<string name="btopp_live_folder" msgid="7967791481444474554">"ब्लूटूथ से मिली फ़ाइलें"</string>
<string name="opp_notification_group" msgid="3486303082135789982">"ब्लूटूथ के ज़रिए शेयर"</string>
- <string name="download_success" msgid="7036160438766730871">"<xliff:g id="FILE_SIZE">%1$s</xliff:g> प्राप्ति पूर्ण."</string>
- <string name="upload_success" msgid="4014469387779648949">"<xliff:g id="FILE_SIZE">%1$s</xliff:g> भेजना पूर्ण."</string>
+ <string name="download_success" msgid="7036160438766730871">"<xliff:g id="FILE_SIZE">%1$s</xliff:g> मिलना पूरा हुआ."</string>
+ <string name="upload_success" msgid="4014469387779648949">"<xliff:g id="FILE_SIZE">%1$s</xliff:g> भेजना पूरा हुआ."</string>
<string name="inbound_history_title" msgid="6940914942271327563">"इनबाउंड स्थानांतरण"</string>
<string name="outbound_history_title" msgid="4279418703178140526">"आउटबाउंड स्थानांतरण"</string>
<string name="no_transfers" msgid="3482965619151865672">"कुछ भी ट्रांसफ़र नहीं किया गया."</string>
@@ -122,9 +122,10 @@
<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">"अभी चल रहा है"</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>
+ <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_title" msgid="7420332483392851321">"ब्लूटूथ संदेश साझाकरण सेटिंग"</string>
diff --git a/res/values-hr/strings.xml b/res/values-hr/strings.xml
index 7b50384..d05c7b3 100644
--- a/res/values-hr/strings.xml
+++ b/res/values-hr/strings.xml
@@ -84,8 +84,8 @@
<string name="bt_toast_4" msgid="4678812947604395649">"Slanje datoteke primatelju \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
<string name="bt_toast_5" msgid="2846870992823019494">"Sljedeći broj datoteka: <xliff:g id="NUMBER">%1$s</xliff:g> šalje se primatelju \"<xliff:g id="RECIPIENT">%2$s</xliff:g>\""</string>
<string name="bt_toast_6" msgid="1855266596936622458">"Zaustavljeno slanje datoteke primatelju \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
- <string name="bt_sm_2_1" product="nosdcard" msgid="352165168004521000">"Na USB pohrani nema dovoljno prostora za spremanje datoteke koju šalje pošiljatelj \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
- <string name="bt_sm_2_1" product="default" msgid="1989018443456803630">"Na SD kartici nema dovoljno prostora za spremanje datoteke koju šalje \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
+ <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"Na USB pohrani nema dovoljno prostora za spremanje datoteke koju šalje \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
+ <string name="bt_sm_2_1_default" msgid="9115512207909504071">"Na SD kartici nema dovoljno prostora za spremanje datoteke koju šalje \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
<string name="bt_sm_2_2" msgid="2965243265852680543">"Potrebno prostora: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
<string name="ErrorTooManyRequests" msgid="8578277541472944529">"U obradi je previše zahtjeva. Pokušajte ponovo kasnije."</string>
<string name="status_pending" msgid="2503691772030877944">"Prijenos datoteke još nije započeo."</string>
@@ -95,12 +95,12 @@
<string name="status_forbidden" msgid="613956401054050725">"Ciljni uređaj zabranio je prijenos."</string>
<string name="status_canceled" msgid="6664490318773098285">"Korisnik je otkazao prijenos."</string>
<string name="status_file_error" msgid="3671917770630165299">"Problem s pohranjivanjem."</string>
- <string name="status_no_sd_card" product="nosdcard" msgid="1112125377088421469">"Nema USB memorije."</string>
- <string name="status_no_sd_card" product="default" msgid="5760944071743325592">"Nema SD kartice. Umetnite SD karticu kako biste spremili prenesene datoteke."</string>
+ <string name="status_no_sd_card_nosdcard" msgid="573631036356922221">"Nema USB pohrane."</string>
+ <string name="status_no_sd_card_default" msgid="396564893716701954">"Nema SD kartice. Umetnite SD karticu kako biste spremili prenesene datoteke."</string>
<string name="status_connection_error" msgid="947681831523219891">"Neuspješno povezivanje."</string>
<string name="status_protocol_error" msgid="3245444473429269539">"Zahtjev nije moguće ispravno obraditi."</string>
<string name="status_unknown_error" msgid="8156660554237824912">"Nepoznata pogreška."</string>
- <string name="btopp_live_folder" msgid="7967791481444474554">"Bluetooth primljen"</string>
+ <string name="btopp_live_folder" msgid="7967791481444474554">"Primljeno Bluetoothom"</string>
<string name="opp_notification_group" msgid="3486303082135789982">"Dijeljenje Bluetoothom"</string>
<string name="download_success" msgid="7036160438766730871">"<xliff:g id="FILE_SIZE">%1$s</xliff:g> primljeno u cijelosti."</string>
<string name="upload_success" msgid="4014469387779648949">"<xliff:g id="FILE_SIZE">%1$s</xliff:g> Poslano u potpunosti."</string>
@@ -124,6 +124,7 @@
<string name="transfer_menu_open" msgid="3368984869083107200">"Otvori"</string>
<string name="transfer_menu_clear" msgid="5854038118831427492">"Izbriši s popisa"</string>
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"Brisanje"</string>
+ <string name="bluetooth_a2dp_sink_queue_name" msgid="6864149958708669766">"Upravo svira"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"Spremi"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"Odustani"</string>
<string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"Odaberite račune koje želite dijeliti putem Bluetootha. I dalje morate prihvatiti svako pristupanje računima prilikom povezivanja."</string>
diff --git a/res/values-hu/strings.xml b/res/values-hu/strings.xml
index 36b53d3..4fa442f 100644
--- a/res/values-hu/strings.xml
+++ b/res/values-hu/strings.xml
@@ -84,8 +84,8 @@
<string name="bt_toast_4" msgid="4678812947604395649">"Fájl küldése a következőnek: \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
<string name="bt_toast_5" msgid="2846870992823019494">"<xliff:g id="NUMBER">%1$s</xliff:g> fájl küldése a következőnek: \"<xliff:g id="RECIPIENT">%2$s</xliff:g>\""</string>
<string name="bt_toast_6" msgid="1855266596936622458">"A fájl küldése \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\" címzettnek leállítva"</string>
- <string name="bt_sm_2_1" product="nosdcard" msgid="352165168004521000">"Nincs elég hely az USB-tárolón a(z) \"<xliff:g id="SENDER">%1$s</xliff:g>\" által küldött fájl mentéséhez"</string>
- <string name="bt_sm_2_1" product="default" msgid="1989018443456803630">"Nincs elég hely az SD-kártyán a(z) \"<xliff:g id="SENDER">%1$s</xliff:g>\" által küldött fájl mentéséhez"</string>
+ <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"Nincs elég hely az USB-háttértárban a(z) „<xliff:g id="SENDER">%1$s</xliff:g>” által küldött fájl mentéséhez"</string>
+ <string name="bt_sm_2_1_default" msgid="9115512207909504071">"Nincs elég hely az SD-kártyán a(z) „<xliff:g id="SENDER">%1$s</xliff:g>” által küldött fájl mentéséhez"</string>
<string name="bt_sm_2_2" msgid="2965243265852680543">"Szükséges tárterület: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
<string name="ErrorTooManyRequests" msgid="8578277541472944529">"Túl sok kérés áll feldolgozás alatt. Próbálja újra később."</string>
<string name="status_pending" msgid="2503691772030877944">"A fájlátvitel még nem kezdődött el."</string>
@@ -95,8 +95,8 @@
<string name="status_forbidden" msgid="613956401054050725">"A céleszköz letiltotta az átvitelt."</string>
<string name="status_canceled" msgid="6664490318773098285">"A felhasználó megszakította."</string>
<string name="status_file_error" msgid="3671917770630165299">"Tárolási probléma."</string>
- <string name="status_no_sd_card" product="nosdcard" msgid="1112125377088421469">"Nem található USB-tár."</string>
- <string name="status_no_sd_card" product="default" msgid="5760944071743325592">"Nincs SD-kártya. A küldött fájlok mentéséhez helyezzen be SD-kártyát."</string>
+ <string name="status_no_sd_card_nosdcard" msgid="573631036356922221">"Nem található USB-háttértár."</string>
+ <string name="status_no_sd_card_default" msgid="396564893716701954">"Nincs SD-kártya. A küldött fájlok mentéséhez helyezzen be SD-kártyát."</string>
<string name="status_connection_error" msgid="947681831523219891">"A kapcsolódás sikertelen."</string>
<string name="status_protocol_error" msgid="3245444473429269539">"A kérést nem lehet megfelelően kezelni."</string>
<string name="status_unknown_error" msgid="8156660554237824912">"Ismeretlen hiba."</string>
@@ -122,6 +122,7 @@
<string name="transfer_menu_open" msgid="3368984869083107200">"Megnyitás"</string>
<string name="transfer_menu_clear" msgid="5854038118831427492">"Törlés a listáról"</string>
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"Törlés"</string>
+ <string name="bluetooth_a2dp_sink_queue_name" msgid="6864149958708669766">"Now Playing"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"Mentés"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"Mégse"</string>
<string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"Válassza ki a Bluetooth használatával megosztani kívánt fiókokat. Kapcsolódásnál el kell fogadnia a fiókokhoz való hozzáférést is."</string>
diff --git a/res/values-hy/strings.xml b/res/values-hy/strings.xml
index 6c52e73..bf6fbf3 100644
--- a/res/values-hy/strings.xml
+++ b/res/values-hy/strings.xml
@@ -23,8 +23,8 @@
<string name="bt_share_picker_label" msgid="6268100924487046932">"Bluetooth"</string>
<string name="unknown_device" msgid="9221903979877041009">"Անհայտ սարք"</string>
<string name="unknownNumber" msgid="4994750948072751566">"Անհայտ"</string>
- <string name="airplane_error_title" msgid="2683839635115739939">"Ինքնաթիռի ռեժիմ"</string>
- <string name="airplane_error_msg" msgid="8698965595254137230">"Դուք չեք կարող օգտվել Bluetooth-ից Ինքնաթիռի ռեժիմում:"</string>
+ <string name="airplane_error_title" msgid="2683839635115739939">"Ավիառեժիմ"</string>
+ <string name="airplane_error_msg" msgid="8698965595254137230">"Դուք չեք կարող օգտվել Bluetooth-ից Ավիառեժիմում:"</string>
<string name="bt_enable_title" msgid="8657832550503456572"></string>
<string name="bt_enable_line1" msgid="7203551583048149">"Bluetooth ծառայություններից օգտվելու համար նախ պետք է միացնեք Bluetooth-ը:"</string>
<string name="bt_enable_line2" msgid="4341936569415937994">"Միացնե՞լ Bluetooth-ը հիմա:"</string>
@@ -84,8 +84,8 @@
<string name="bt_toast_4" msgid="4678812947604395649">"Ֆայլն ուղարկվում է «<xliff:g id="RECIPIENT">%1$s</xliff:g>»-ին"</string>
<string name="bt_toast_5" msgid="2846870992823019494">"<xliff:g id="NUMBER">%1$s</xliff:g> ֆայլեր ուղարկվում են «<xliff:g id="RECIPIENT">%2$s</xliff:g>»-ին"</string>
<string name="bt_toast_6" msgid="1855266596936622458">"Դադարեցվեց ֆայլի ուղարկումը «<xliff:g id="RECIPIENT">%1$s</xliff:g>»-ին"</string>
- <string name="bt_sm_2_1" product="nosdcard" msgid="352165168004521000">"USB կրիչի վրա բավարար տեղ չկա «<xliff:g id="SENDER">%1$s</xliff:g>»-ի ֆայլի պահպանման համար"</string>
- <string name="bt_sm_2_1" product="default" msgid="1989018443456803630">"SD քարտի վրա բավարար տեղ չկա «<xliff:g id="SENDER">%1$s</xliff:g>»-ի ֆայլի պահպանման համար"</string>
+ <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"USB կրիչի վրա բավարար տեղ չկա <xliff:g id="SENDER">%1$s</xliff:g>-ի ֆայլը պահելու համար"</string>
+ <string name="bt_sm_2_1_default" msgid="9115512207909504071">"SD քարտում բավարար տեղ չկա <xliff:g id="SENDER">%1$s</xliff:g>-ի ֆայլը պահելու համար"</string>
<string name="bt_sm_2_2" msgid="2965243265852680543">"Անհրաժեշտ տեղը՝ <xliff:g id="SIZE">%1$s</xliff:g>"</string>
<string name="ErrorTooManyRequests" msgid="8578277541472944529">"Չափից շատ հարցումներ են մշակվում: Կրկին փորձեք ավելի ուշ:"</string>
<string name="status_pending" msgid="2503691772030877944">"Ֆայլի փոխանցումը դեռ չի մեկնարկել:"</string>
@@ -95,12 +95,12 @@
<string name="status_forbidden" msgid="613956401054050725">"Փոխանցումն արգելված է նպատակային սարքի կողմից:"</string>
<string name="status_canceled" msgid="6664490318773098285">"Փոխանցումը չեղարկվել է օգտատիրոջ կողմից:"</string>
<string name="status_file_error" msgid="3671917770630165299">"Կրիչի խնդիր:"</string>
- <string name="status_no_sd_card" product="nosdcard" msgid="1112125377088421469">"USB կրիչ չկա:"</string>
- <string name="status_no_sd_card" product="default" msgid="5760944071743325592">"SD քարտ չկա: Տեղադրեք SD քարտ` փոխանցված ֆայլերը պահպանելու համար:"</string>
+ <string name="status_no_sd_card_nosdcard" msgid="573631036356922221">"USB կրիչ չկա:"</string>
+ <string name="status_no_sd_card_default" msgid="396564893716701954">"SD քարտ չկա: Տեղադրեք SD քարտ` փոխանցված ֆայլերը պահելու համար:"</string>
<string name="status_connection_error" msgid="947681831523219891">"Միացումը անհաջող էր:"</string>
<string name="status_protocol_error" msgid="3245444473429269539">"Հարցումը հնարավոր չէ ճշգրտորեն մշակել:"</string>
<string name="status_unknown_error" msgid="8156660554237824912">"Անհայտ սխալ:"</string>
- <string name="btopp_live_folder" msgid="7967791481444474554">"Ստացված է Bluetooth-ով"</string>
+ <string name="btopp_live_folder" msgid="7967791481444474554">"Bluetooth-ով ստացված"</string>
<string name="opp_notification_group" msgid="3486303082135789982">"Bluetooth համօգտագործում"</string>
<string name="download_success" msgid="7036160438766730871">"<xliff:g id="FILE_SIZE">%1$s</xliff:g> ստացումն ավարտված է:"</string>
<string name="upload_success" msgid="4014469387779648949">"<xliff:g id="FILE_SIZE">%1$s</xliff:g> ուղարկումն ավարտված է:"</string>
@@ -122,6 +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">"Այժմ հնչում է"</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>
diff --git a/res/values-hy/test_strings.xml b/res/values-hy/test_strings.xml
index 147a0b4..33693e4 100644
--- a/res/values-hy/test_strings.xml
+++ b/res/values-hy/test_strings.xml
@@ -6,7 +6,7 @@
<string name="update_record" msgid="2480425402384910635">"Հաստատել գրառումը"</string>
<string name="ack_record" msgid="6716152390978472184">"ACK գրառում"</string>
<string name="deleteAll_record" msgid="4383349788485210582">"Ջնջել բոլոր գրառումները"</string>
- <string name="ok_button" msgid="6519033415223065454">"Լավ"</string>
+ <string name="ok_button" msgid="6519033415223065454">"Եղավ"</string>
<string name="delete_record" msgid="4645040331967533724">"Ջնջել գրառումը"</string>
<string name="start_server" msgid="9034821924409165795">"Մեկնարկել TCP սերվերը"</string>
<string name="notify_server" msgid="4369106744022969655">"Ծանուցել TCP սերվերին"</string>
diff --git a/res/values-in/strings.xml b/res/values-in/strings.xml
index adf3b05..15722da 100644
--- a/res/values-in/strings.xml
+++ b/res/values-in/strings.xml
@@ -84,8 +84,8 @@
<string name="bt_toast_4" msgid="4678812947604395649">"Mengirim file ke \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
<string name="bt_toast_5" msgid="2846870992823019494">"Mengirim <xliff:g id="NUMBER">%1$s</xliff:g> file ke \"<xliff:g id="RECIPIENT">%2$s</xliff:g>\""</string>
<string name="bt_toast_6" msgid="1855266596936622458">"Berhenti mengirim file ke \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
- <string name="bt_sm_2_1" product="nosdcard" msgid="352165168004521000">"Ruang pada penyimpanan USB tidak cukup untuk menyimpan file dari \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
- <string name="bt_sm_2_1" product="default" msgid="1989018443456803630">"Ruang pada kartu SD tidak cukup untuk menyimpan file dari \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
+ <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"Ruang pada penyimpanan USB tidak cukup untuk menyimpan file dari \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
+ <string name="bt_sm_2_1_default" msgid="9115512207909504071">"Ruang pada kartu SD tidak cukup untuk menyimpan file dari \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
<string name="bt_sm_2_2" msgid="2965243265852680543">"Ruang yang diperlukan: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
<string name="ErrorTooManyRequests" msgid="8578277541472944529">"Terlalu banyak permintaan yang diproses. Coba lagi nanti."</string>
<string name="status_pending" msgid="2503691772030877944">"Transfer file belum dimulai."</string>
@@ -95,8 +95,8 @@
<string name="status_forbidden" msgid="613956401054050725">"Transfer dilarang oleh perangkat target."</string>
<string name="status_canceled" msgid="6664490318773098285">"Transfer dibatalkan oleh pengguna."</string>
<string name="status_file_error" msgid="3671917770630165299">"Masalah penyimpanan."</string>
- <string name="status_no_sd_card" product="nosdcard" msgid="1112125377088421469">"Tidak ada penyimpanan USB."</string>
- <string name="status_no_sd_card" product="default" msgid="5760944071743325592">"Tidak ada kartu SD. Masukkan kartu SD untuk menyimpan file yang ditransfer."</string>
+ <string name="status_no_sd_card_nosdcard" msgid="573631036356922221">"Tidak ada penyimpanan USB."</string>
+ <string name="status_no_sd_card_default" msgid="396564893716701954">"Tidak ada kartu SD. Masukkan kartu SD untuk menyimpan file yang ditransfer."</string>
<string name="status_connection_error" msgid="947681831523219891">"Sambungan tidak berhasil."</string>
<string name="status_protocol_error" msgid="3245444473429269539">"Permintaan tidak dapat ditangani dengan semestinya."</string>
<string name="status_unknown_error" msgid="8156660554237824912">"Kesalahan tidak dikenal."</string>
@@ -122,6 +122,7 @@
<string name="transfer_menu_open" msgid="3368984869083107200">"Buka"</string>
<string name="transfer_menu_clear" msgid="5854038118831427492">"Hapus dari daftar"</string>
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"Hapus"</string>
+ <string name="bluetooth_a2dp_sink_queue_name" msgid="6864149958708669766">"Now Playing"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"Simpan"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"Batal"</string>
<string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"Pilih akun yang ingin Anda bagikan melalui Bluetooth. Anda masih harus menerima akses apa pun ke akun saat menghubungkan."</string>
diff --git a/res/values-is/strings.xml b/res/values-is/strings.xml
index 44442c1..6246389 100644
--- a/res/values-is/strings.xml
+++ b/res/values-is/strings.xml
@@ -84,8 +84,8 @@
<string name="bt_toast_4" msgid="4678812947604395649">"Sendir skrá til „<xliff:g id="RECIPIENT">%1$s</xliff:g>“"</string>
<string name="bt_toast_5" msgid="2846870992823019494">"Sendir <xliff:g id="NUMBER">%1$s</xliff:g> skrár til „<xliff:g id="RECIPIENT">%2$s</xliff:g>“"</string>
<string name="bt_toast_6" msgid="1855266596936622458">"Sending skráar til „<xliff:g id="RECIPIENT">%1$s</xliff:g>“ stöðvuð"</string>
- <string name="bt_sm_2_1" product="nosdcard" msgid="352165168004521000">"Ekki er nægt pláss í USB-geymslunni til að vista skrána sem „<xliff:g id="SENDER">%1$s</xliff:g>“ sendi"</string>
- <string name="bt_sm_2_1" product="default" msgid="1989018443456803630">"Ekki er nægt pláss á SD-kortinu til að vista skrána sem „<xliff:g id="SENDER">%1$s</xliff:g>“ sendi"</string>
+ <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"Ekki er nægt pláss í USB-geymslunni til að vista skrána sem „<xliff:g id="SENDER">%1$s</xliff:g>“ sendi"</string>
+ <string name="bt_sm_2_1_default" msgid="9115512207909504071">"Ekki er nægt pláss á SD-kortinu til að vista skrána sem „<xliff:g id="SENDER">%1$s</xliff:g>“ sendi"</string>
<string name="bt_sm_2_2" msgid="2965243265852680543">"Pláss sem þarf: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
<string name="ErrorTooManyRequests" msgid="8578277541472944529">"Of margir beiðnir eru í vinnslu. Reyndu aftur síðar."</string>
<string name="status_pending" msgid="2503691772030877944">"Skráaflutningur er enn ekki hafinn."</string>
@@ -95,8 +95,8 @@
<string name="status_forbidden" msgid="613956401054050725">"Viðtökutækið bannaði flutninginn."</string>
<string name="status_canceled" msgid="6664490318773098285">"Notandi hætti við flutninginn."</string>
<string name="status_file_error" msgid="3671917770630165299">"Vandamál með geymslu."</string>
- <string name="status_no_sd_card" product="nosdcard" msgid="1112125377088421469">"Engin USB-geymsla."</string>
- <string name="status_no_sd_card" product="default" msgid="5760944071743325592">"Ekkert SD-kort. Settu SD-kort í til að vista fluttar skrár."</string>
+ <string name="status_no_sd_card_nosdcard" msgid="573631036356922221">"Engin USB-geymsla."</string>
+ <string name="status_no_sd_card_default" msgid="396564893716701954">"Ekkert SD-kort. Settu SD-kort í til að vista fluttar skrár."</string>
<string name="status_connection_error" msgid="947681831523219891">"Tenging mistókst."</string>
<string name="status_protocol_error" msgid="3245444473429269539">"Ekki er hægt að afgreiða beiðnina á réttan hátt."</string>
<string name="status_unknown_error" msgid="8156660554237824912">"Óþekkt villa."</string>
@@ -122,6 +122,7 @@
<string name="transfer_menu_open" msgid="3368984869083107200">"Opna"</string>
<string name="transfer_menu_clear" msgid="5854038118831427492">"Hreinsa af lista"</string>
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"Hreinsa"</string>
+ <string name="bluetooth_a2dp_sink_queue_name" msgid="6864149958708669766">"Í spilun"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"Vista"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"Hætta við"</string>
<string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"Veldu reikningana sem þú vilt deila í gegnum Bluetooth. Þú þarft samt að samþykkja allan aðgang að reikningunum við tengingu."</string>
diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml
index 52f9b3d..e4a486f 100644
--- a/res/values-it/strings.xml
+++ b/res/values-it/strings.xml
@@ -84,8 +84,8 @@
<string name="bt_toast_4" msgid="4678812947604395649">"Invio del file a \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
<string name="bt_toast_5" msgid="2846870992823019494">"Invio di <xliff:g id="NUMBER">%1$s</xliff:g> file a \"<xliff:g id="RECIPIENT">%2$s</xliff:g>\""</string>
<string name="bt_toast_6" msgid="1855266596936622458">"Invio del file a \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\" interrotto"</string>
- <string name="bt_sm_2_1" product="nosdcard" msgid="352165168004521000">"Spazio nell\'archivio USB insufficiente per salvare il file ricevuto da \"<xliff:g id="SENDER">%1$s</xliff:g>\"."</string>
- <string name="bt_sm_2_1" product="default" msgid="1989018443456803630">"Spazio sulla scheda SD insufficiente per salvare il file ricevuto da \"<xliff:g id="SENDER">%1$s</xliff:g>\"."</string>
+ <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"Spazio nell\'archivio USB insufficiente per salvare il file ricevuto da \"<xliff:g id="SENDER">%1$s</xliff:g>\"."</string>
+ <string name="bt_sm_2_1_default" msgid="9115512207909504071">"Spazio sulla scheda SD insufficiente per salvare il file ricevuto da \"<xliff:g id="SENDER">%1$s</xliff:g>\"."</string>
<string name="bt_sm_2_2" msgid="2965243265852680543">"Spazio necessario: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
<string name="ErrorTooManyRequests" msgid="8578277541472944529">"Troppe richieste in fase di elaborazione. Riprova più tardi."</string>
<string name="status_pending" msgid="2503691772030877944">"Trasferimento file non ancora iniziato."</string>
@@ -95,8 +95,8 @@
<string name="status_forbidden" msgid="613956401054050725">"Il dispositivo di destinazione non consente il trasferimento."</string>
<string name="status_canceled" msgid="6664490318773098285">"Trasferimento annullato dall\'utente."</string>
<string name="status_file_error" msgid="3671917770630165299">"Problema di memorizzazione."</string>
- <string name="status_no_sd_card" product="nosdcard" msgid="1112125377088421469">"Nessun archivio USB."</string>
- <string name="status_no_sd_card" product="default" msgid="5760944071743325592">"Nessuna scheda SD. Inserisci una scheda SD per salvare i file trasferiti."</string>
+ <string name="status_no_sd_card_nosdcard" msgid="573631036356922221">"Nessun archivio USB."</string>
+ <string name="status_no_sd_card_default" msgid="396564893716701954">"Nessuna scheda SD. Inserisci una scheda SD per salvare i file trasferiti."</string>
<string name="status_connection_error" msgid="947681831523219891">"Connessione non riuscita."</string>
<string name="status_protocol_error" msgid="3245444473429269539">"Impossibile gestire correttamente la richiesta."</string>
<string name="status_unknown_error" msgid="8156660554237824912">"Errore sconosciuto."</string>
@@ -122,6 +122,7 @@
<string name="transfer_menu_open" msgid="3368984869083107200">"Apri"</string>
<string name="transfer_menu_clear" msgid="5854038118831427492">"Cancella da elenco"</string>
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"Cancella"</string>
+ <string name="bluetooth_a2dp_sink_queue_name" msgid="6864149958708669766">"Now Playing"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"Salva"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"Annulla"</string>
<string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"Seleziona gli account che desideri condividere tramite Bluetooth. Devi comunque accettare tutti gli accessi agli account durante la connessione."</string>
diff --git a/res/values-iw/strings.xml b/res/values-iw/strings.xml
index 602b60e..7fe6a4f 100644
--- a/res/values-iw/strings.xml
+++ b/res/values-iw/strings.xml
@@ -84,8 +84,8 @@
<string name="bt_toast_4" msgid="4678812947604395649">"שולח קובץ אל \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
<string name="bt_toast_5" msgid="2846870992823019494">"שולח <xliff:g id="NUMBER">%1$s</xliff:g> קבצים אל \"<xliff:g id="RECIPIENT">%2$s</xliff:g>\""</string>
<string name="bt_toast_6" msgid="1855266596936622458">"שליחת קובץ אל \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\" הופסקה"</string>
- <string name="bt_sm_2_1" product="nosdcard" msgid="352165168004521000">"אין מספיק שטח פנוי באחסון ה-USB לשמירת הקובץ מאת <xliff:g id="SENDER">%1$s</xliff:g>"</string>
- <string name="bt_sm_2_1" product="default" msgid="1989018443456803630">"אין מספיק שטח אחסון בכרטיס ה-SD לשמירת הקובץ מאת <xliff:g id="SENDER">%1$s</xliff:g>"</string>
+ <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"אין מספיק שטח פנוי באחסון ה-USB לשמירת הקובץ מאת \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
+ <string name="bt_sm_2_1_default" msgid="9115512207909504071">"אין מספיק שטח אחסון בכרטיס ה-SD לשמירת הקובץ מאת \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
<string name="bt_sm_2_2" msgid="2965243265852680543">"שטח דרוש: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
<string name="ErrorTooManyRequests" msgid="8578277541472944529">"בקשות רבות מדי הועברו לעיבוד. נסה שוב מאוחר יותר."</string>
<string name="status_pending" msgid="2503691772030877944">"העברת הקובץ עדיין לא החלה."</string>
@@ -95,8 +95,8 @@
<string name="status_forbidden" msgid="613956401054050725">"מכשיר היעד לא התיר את ההעברה."</string>
<string name="status_canceled" msgid="6664490318773098285">"ההעברה בוטלה על ידי המשתמש."</string>
<string name="status_file_error" msgid="3671917770630165299">"בעיית אחסון."</string>
- <string name="status_no_sd_card" product="nosdcard" msgid="1112125377088421469">"אין אחסון USB."</string>
- <string name="status_no_sd_card" product="default" msgid="5760944071743325592">"אין כרטיס SD. הכנס כרטיס SD כדי לשמור קבצים שהועברו."</string>
+ <string name="status_no_sd_card_nosdcard" msgid="573631036356922221">"אין אחסון USB."</string>
+ <string name="status_no_sd_card_default" msgid="396564893716701954">"אין כרטיס SD. יש להכניס כרטיס SD כדי לשמור קבצים שהועברו."</string>
<string name="status_connection_error" msgid="947681831523219891">"החיבור נכשל."</string>
<string name="status_protocol_error" msgid="3245444473429269539">"לא ניתן לטפל בבקשה כהלכה."</string>
<string name="status_unknown_error" msgid="8156660554237824912">"שגיאה לא ידועה."</string>
@@ -126,6 +126,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">"מה שומעים עכשיו?"</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>
diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml
index b8cfee6..27ccaac 100644
--- a/res/values-ja/strings.xml
+++ b/res/values-ja/strings.xml
@@ -18,10 +18,10 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="permlab_bluetoothShareManager" msgid="311492132450338925">"ダウンロードマネージャーにアクセスします。"</string>
<string name="permdesc_bluetoothShareManager" msgid="8930572979123190223">"BluetoothShareマネージャーへのアクセスとそれを利用したファイル転送をアプリに許可します。"</string>
- <string name="permlab_bluetoothWhitelist" msgid="7091552898592306386">"Bluetooth端末によるアクセスを許可します。"</string>
- <string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"Bluetooth端末によるアクセスを一時的に許可して、ユーザーの確認を受けずにその端末からこの端末にファイルを送信することをアプリに許可します。"</string>
+ <string name="permlab_bluetoothWhitelist" msgid="7091552898592306386">"Bluetoothデバイスによるアクセスを許可します。"</string>
+ <string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"Bluetoothデバイスによるアクセスを一時的に許可して、ユーザーの確認を受けずにそのデバイスからこのデバイスにファイルを送信することをアプリに許可します。"</string>
<string name="bt_share_picker_label" msgid="6268100924487046932">"Bluetooth"</string>
- <string name="unknown_device" msgid="9221903979877041009">"不明なモバイル端末"</string>
+ <string name="unknown_device" msgid="9221903979877041009">"不明なモバイルデバイス"</string>
<string name="unknownNumber" msgid="4994750948072751566">"不明"</string>
<string name="airplane_error_title" msgid="2683839635115739939">"機内モード"</string>
<string name="airplane_error_msg" msgid="8698965595254137230">"機内モードではBluetoothを使用できません。"</string>
@@ -84,19 +84,19 @@
<string name="bt_toast_4" msgid="4678812947604395649">"「<xliff:g id="RECIPIENT">%1$s</xliff:g>」にファイルを送信中"</string>
<string name="bt_toast_5" msgid="2846870992823019494">"<xliff:g id="NUMBER">%1$s</xliff:g>個のファイルを「<xliff:g id="RECIPIENT">%2$s</xliff:g>」に送信中"</string>
<string name="bt_toast_6" msgid="1855266596936622458">"「<xliff:g id="RECIPIENT">%1$s</xliff:g>」へのファイルの送信を停止しました"</string>
- <string name="bt_sm_2_1" product="nosdcard" msgid="352165168004521000">"「<xliff:g id="SENDER">%1$s</xliff:g>」からのファイルを保存するのに十分な空き領域がUSBストレージにありません"</string>
- <string name="bt_sm_2_1" product="default" msgid="1989018443456803630">"「<xliff:g id="SENDER">%1$s</xliff:g>」からのファイルを保存するのに十分な空き領域がSDカードにありません"</string>
+ <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"「<xliff:g id="SENDER">%1$s</xliff:g>」からのファイルを保存するのに十分な空き領域が USB ストレージにありません"</string>
+ <string name="bt_sm_2_1_default" msgid="9115512207909504071">"「<xliff:g id="SENDER">%1$s</xliff:g>」からのファイルを保存するのに十分な空き領域が SD カードにありません"</string>
<string name="bt_sm_2_2" msgid="2965243265852680543">"必要な空き領域: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
<string name="ErrorTooManyRequests" msgid="8578277541472944529">"処理中のリクエストが多すぎるため、しばらくしてからもう一度お試しください。"</string>
<string name="status_pending" msgid="2503691772030877944">"ファイル転送はまだ開始されていません。"</string>
<string name="status_running" msgid="6562808920311008696">"ファイルを転送中です。"</string>
<string name="status_success" msgid="239573225847565868">"ファイル転送が完了しました。"</string>
<string name="status_not_accept" msgid="1695082417193780738">"コンテンツはサポートされていません。"</string>
- <string name="status_forbidden" msgid="613956401054050725">"転送先の端末で転送が禁止されています。"</string>
+ <string name="status_forbidden" msgid="613956401054050725">"転送先のデバイスで転送が禁止されています。"</string>
<string name="status_canceled" msgid="6664490318773098285">"ユーザーが転送をキャンセルしました。"</string>
<string name="status_file_error" msgid="3671917770630165299">"ストレージのエラーです。"</string>
- <string name="status_no_sd_card" product="nosdcard" msgid="1112125377088421469">"USBストレージがありません。"</string>
- <string name="status_no_sd_card" product="default" msgid="5760944071743325592">"SDカードが見つかりません。転送ファイルを保存するSDカードを挿入してください。"</string>
+ <string name="status_no_sd_card_nosdcard" msgid="573631036356922221">"USB ストレージがありません。"</string>
+ <string name="status_no_sd_card_default" msgid="396564893716701954">"SD カードが見つかりません。転送ファイルを保存する SD カードを挿入してください。"</string>
<string name="status_connection_error" msgid="947681831523219891">"接続できませんでした。"</string>
<string name="status_protocol_error" msgid="3245444473429269539">"リクエストを正しく処理できません。"</string>
<string name="status_unknown_error" msgid="8156660554237824912">"不明なエラーです。"</string>
@@ -122,6 +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">"この曲なに?"</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>
diff --git a/res/values-ka/strings.xml b/res/values-ka/strings.xml
index bccc9b9..4983eaa 100644
--- a/res/values-ka/strings.xml
+++ b/res/values-ka/strings.xml
@@ -84,8 +84,8 @@
<string name="bt_toast_4" msgid="4678812947604395649">"„<xliff:g id="RECIPIENT">%1$s</xliff:g>“-თან ფაილის გაგზავნა"</string>
<string name="bt_toast_5" msgid="2846870992823019494">"<xliff:g id="NUMBER">%1$s</xliff:g> ფაილის „<xliff:g id="RECIPIENT">%2$s</xliff:g>“-თან გაგზავნა"</string>
<string name="bt_toast_6" msgid="1855266596936622458">"„<xliff:g id="RECIPIENT">%1$s</xliff:g>“-თან ფაილის გაგზავნა შეჩერდა"</string>
- <string name="bt_sm_2_1" product="nosdcard" msgid="352165168004521000">"ფაილის „<xliff:g id="SENDER">%1$s</xliff:g>“-იდან შესანახად USB მეხსიერებაზე საკმარისი თავისუფალი სივრცე არ არის."</string>
- <string name="bt_sm_2_1" product="default" msgid="1989018443456803630">"„<xliff:g id="SENDER">%1$s</xliff:g>“-იდან ფაილის შესანახად SD ბარათზე საკმარისი ისვრცე არ არის."</string>
+ <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"„<xliff:g id="SENDER">%1$s</xliff:g>“-დან ფაილის შესანახად USB საცავში მეხსიერება არასაკმარისია."</string>
+ <string name="bt_sm_2_1_default" msgid="9115512207909504071">"„<xliff:g id="SENDER">%1$s</xliff:g>“-დან ფაილის შესანახად SD ბარათზე მეხსიერება არასაკმარისია."</string>
<string name="bt_sm_2_2" msgid="2965243265852680543">"საჭირო სივრცე: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
<string name="ErrorTooManyRequests" msgid="8578277541472944529">"მუშავდება ძალიან ბევრი პროცესი. შეეცადეთ მოგვიანებით."</string>
<string name="status_pending" msgid="2503691772030877944">"ფაილის ტრანსფერი ჯერ არ დაწყებულა."</string>
@@ -95,8 +95,8 @@
<string name="status_forbidden" msgid="613956401054050725">"ტრანსფერი აკრძალულია სამიზნე მოწყობილობის მიერ."</string>
<string name="status_canceled" msgid="6664490318773098285">"ტრანსფერი გაუქმდა მომხმარებლის მიერ."</string>
<string name="status_file_error" msgid="3671917770630165299">"მეხსიერების შეცდომა."</string>
- <string name="status_no_sd_card" product="nosdcard" msgid="1112125377088421469">"USB მეხსიერება არ არის."</string>
- <string name="status_no_sd_card" product="default" msgid="5760944071743325592">"SD ბარათი არ არის. ჩადეთ SD ბარათი გადმოგზავნილი ფაილების შესანახად."</string>
+ <string name="status_no_sd_card_nosdcard" msgid="573631036356922221">"USB მეხსიერება არ არის."</string>
+ <string name="status_no_sd_card_default" msgid="396564893716701954">"SD ბარათი არ არის. გადმოგზავნილი ფაილების შესანახად ჩადეთ SD ბარათი."</string>
<string name="status_connection_error" msgid="947681831523219891">"კავშირი ვერ განხორციელდა."</string>
<string name="status_protocol_error" msgid="3245444473429269539">"მოთხოვნის სწორად დამუშავება ვერ ხერხდება."</string>
<string name="status_unknown_error" msgid="8156660554237824912">"უცნობი შეცდომა."</string>
@@ -122,6 +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">"რა უკრავს"</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>
diff --git a/res/values-kk/strings.xml b/res/values-kk/strings.xml
index bb91d3e..82de8a6 100644
--- a/res/values-kk/strings.xml
+++ b/res/values-kk/strings.xml
@@ -84,8 +84,8 @@
<string name="bt_toast_4" msgid="4678812947604395649">"Файлды \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\" байланысына жіберуде"</string>
<string name="bt_toast_5" msgid="2846870992823019494">"<xliff:g id="NUMBER">%1$s</xliff:g> файл \"<xliff:g id="RECIPIENT">%2$s</xliff:g>\" байланысына жіберілуде"</string>
<string name="bt_toast_6" msgid="1855266596936622458">"Файлды \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\" байланысына жіберу доғарылды."</string>
- <string name="bt_sm_2_1" product="nosdcard" msgid="352165168004521000">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" жіберген файлды сақтау үшін USB жадында орын жеткіліксіз."</string>
- <string name="bt_sm_2_1" product="default" msgid="1989018443456803630">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" жіберген файлды сақтау үшін SD картасында орын жеткіліксіз."</string>
+ <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" жіберген файлды сақтау үшін USB жадында орын жеткіліксіз"</string>
+ <string name="bt_sm_2_1_default" msgid="9115512207909504071">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" жіберген файлды сақтау үшін SD картасында орын жеткіліксіз"</string>
<string name="bt_sm_2_2" msgid="2965243265852680543">"Қажет орын мөлшері: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
<string name="ErrorTooManyRequests" msgid="8578277541472944529">"Тым көп өтініштер қаралуда. Кейінірек қайта әрекеттеніп көріңіз."</string>
<string name="status_pending" msgid="2503691772030877944">"Файлды аудару әлі басталған жоқ."</string>
@@ -95,8 +95,8 @@
<string name="status_forbidden" msgid="613956401054050725">"Аударуға қабылдайтын құрылғы тыйым салды."</string>
<string name="status_canceled" msgid="6664490318773098285">"Тасымалды пайдаланушы тоқтатты."</string>
<string name="status_file_error" msgid="3671917770630165299">"Жад ақаулығы."</string>
- <string name="status_no_sd_card" product="nosdcard" msgid="1112125377088421469">"Ешқандай USB жады жоқ."</string>
- <string name="status_no_sd_card" product="default" msgid="5760944071743325592">"SD картасы жоқ. Аударылған файлдарды сақтау үшін SD картасын енгізіңіз."</string>
+ <string name="status_no_sd_card_nosdcard" msgid="573631036356922221">"Ешқандай USB жады жоқ."</string>
+ <string name="status_no_sd_card_default" msgid="396564893716701954">"SD картасы жоқ. Аударылған файлдарды сақтау үшін SD картасын енгізіңіз."</string>
<string name="status_connection_error" msgid="947681831523219891">"Байланыс сәтсіз болды."</string>
<string name="status_protocol_error" msgid="3245444473429269539">"Өтінішті дұрыс орындау мүмкін емес."</string>
<string name="status_unknown_error" msgid="8156660554237824912">"Белгісіз қателік."</string>
@@ -122,6 +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">"Қазір ойнауда"</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>
diff --git a/res/values-km/strings.xml b/res/values-km/strings.xml
index 8be3840..f4b7e7a 100644
--- a/res/values-km/strings.xml
+++ b/res/values-km/strings.xml
@@ -84,8 +84,8 @@
<string name="bt_toast_4" msgid="4678812947604395649">"ផ្ញើឯកសារទៅ \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
<string name="bt_toast_5" msgid="2846870992823019494">"ផ្ញើឯកសារ <xliff:g id="NUMBER">%1$s</xliff:g> ទៅ \"<xliff:g id="RECIPIENT">%2$s</xliff:g>\""</string>
<string name="bt_toast_6" msgid="1855266596936622458">"បានបញ្ឈប់ការផ្ញើឯកសារទៅ \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
- <string name="bt_sm_2_1" product="nosdcard" msgid="352165168004521000">"ឧបករណ៍ផ្ទុកយូអេសប៊ីមិនមានទំហំគ្រប់គ្រាន់ ដើម្បីរក្សាទុកឯកសារពី \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
- <string name="bt_sm_2_1" product="default" msgid="1989018443456803630">"កាតអេសឌីមិនមានទំហំគ្រប់គ្រាន់ ដើម្បីរក្សាទុកឯកសារពី \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
+ <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"មិនមានទំហំផ្ទុកគ្រប់គ្រាន់នៅក្នុងឧបករណ៍ផ្ទុក USB សម្រាប់រក្សាទុកឯកសារពី \"<xliff:g id="SENDER">%1$s</xliff:g>\" ទេ"</string>
+ <string name="bt_sm_2_1_default" msgid="9115512207909504071">"មិនមានទំហំផ្ទុកគ្រប់គ្រាន់នៅលើកាត SD សម្រាប់រក្សាទុកឯកសារពី \"<xliff:g id="SENDER">%1$s</xliff:g>\" ទេ"</string>
<string name="bt_sm_2_2" msgid="2965243265852680543">"ទំហំដែលត្រូវការ៖ <xliff:g id="SIZE">%1$s</xliff:g>"</string>
<string name="ErrorTooManyRequests" msgid="8578277541472944529">"កំពុងដំណើរការសំណើជាច្រើន។ សូមព្យាយាមម្ដងទៀតនៅពេលក្រោយ។"</string>
<string name="status_pending" msgid="2503691772030877944">"មិនទាន់បានចាប់ផ្ដើមផ្ទេរឯកសារនៅឡើយទេ។"</string>
@@ -95,8 +95,8 @@
<string name="status_forbidden" msgid="613956401054050725">"ឧបករណ៍គោលដៅបានហាមឃាត់ការផ្ទេរ។"</string>
<string name="status_canceled" msgid="6664490318773098285">"អ្នកប្រើបានបោះបង់ការផ្ទេរ។"</string>
<string name="status_file_error" msgid="3671917770630165299">"បញ្ហាឧបករណ៍ផ្ទុក។"</string>
- <string name="status_no_sd_card" product="nosdcard" msgid="1112125377088421469">"គ្មានឧបករណ៍ផ្ទុកយូអេសប៊ី។"</string>
- <string name="status_no_sd_card" product="default" msgid="5760944071743325592">"គ្មានកាតអេសឌី។ បញ្ចូលកាតអេសឌី ដើម្បីរក្សាទុកឯកសារដែលបានផ្ទេរ។"</string>
+ <string name="status_no_sd_card_nosdcard" msgid="573631036356922221">"គ្មានឧបករណ៍ផ្ទុក USB ទេ។"</string>
+ <string name="status_no_sd_card_default" msgid="396564893716701954">"គ្មានកាត SD ទេ។ សូមបញ្ចូលកាត SD ដើម្បីរក្សាទុកឯកសារដែលបានផ្ទេរ។"</string>
<string name="status_connection_error" msgid="947681831523219891">"ការតភ្ជាប់មិនជោគជ័យ។"</string>
<string name="status_protocol_error" msgid="3245444473429269539">"មិនអាចដោះស្រាយសំណើដោយត្រឹមត្រូវទេ។"</string>
<string name="status_unknown_error" msgid="8156660554237824912">"មិនស្គាល់កំហុស។"</string>
@@ -122,6 +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_map_settings_save" msgid="7635491847388074606">"រក្សាទុក"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"បោះបង់"</string>
<string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"ជ្រើសគណនីដែលអ្នកចង់ចែករំលែកតាមរយៈប៊្លូធូស។ អ្នកនៅតែត្រូវទទួលយកលទ្ធភាពចូលដំណើរការទាំងឡាយទៅកាន់គណនីនេះដដែល នៅពេលភ្ជាប់។"</string>
diff --git a/res/values-kn/strings.xml b/res/values-kn/strings.xml
index 00b4c1d..0f0e06f 100644
--- a/res/values-kn/strings.xml
+++ b/res/values-kn/strings.xml
@@ -84,8 +84,8 @@
<string name="bt_toast_4" msgid="4678812947604395649">"\"<xliff:g id="RECIPIENT">%1$s</xliff:g>\" ಇವರಿಗೆ ಫೈಲ್ ಕಳುಹಿಸಲಾಗುತ್ತಿದೆ"</string>
<string name="bt_toast_5" msgid="2846870992823019494">"\"<xliff:g id="RECIPIENT">%2$s</xliff:g>\" ಇವರಿಗೆ <xliff:g id="NUMBER">%1$s</xliff:g> ಫೈಲ್ಗಳನ್ನು ಕಳುಹಿಸಲಾಗುತ್ತಿದೆ"</string>
<string name="bt_toast_6" msgid="1855266596936622458">"\"<xliff:g id="RECIPIENT">%1$s</xliff:g>\" ಇವರಿಗೆ ಫೈಲ್ ಕಳುಹಿಸುವುದನ್ನು ನಿಲ್ಲಿಸಲಾಗಿದೆ"</string>
- <string name="bt_sm_2_1" product="nosdcard" msgid="352165168004521000">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" ರಿಂದ ಫೈಲ್ ಉಳಿಸಲು USB ಸಂಗ್ರಹಣೆಯಲ್ಲಿ ಸಾಕಷ್ಟು ಸ್ಥಳಾವಕಾಶವಿಲ್ಲ"</string>
- <string name="bt_sm_2_1" product="default" msgid="1989018443456803630">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" ರಿಂದ ಫೈಲ್ ಉಳಿಸಲು SD ಕಾರ್ಡ್ನಲ್ಲಿ ಸಾಕಷ್ಟು ಸ್ಥಳಾವಕಾಶವಿಲ್ಲ"</string>
+ <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" ರಿಂದ ಫೈಲ್ ಉಳಿಸಲು USB ಸಂಗ್ರಹಣೆಯಲ್ಲಿ ಸಾಕಷ್ಟು ಸ್ಥಳಾವಕಾಶವಿಲ್ಲ"</string>
+ <string name="bt_sm_2_1_default" msgid="9115512207909504071">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" ರಿಂದ ಫೈಲ್ ಉಳಿಸಲು SD ಕಾರ್ಡ್ನಲ್ಲಿ ಸಾಕಷ್ಟು ಸ್ಥಳಾವಕಾಶವಿಲ್ಲ"</string>
<string name="bt_sm_2_2" msgid="2965243265852680543">"ಅಗತ್ಯವಿರುವ ಸ್ಥಳಾವಕಾಶ: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
<string name="ErrorTooManyRequests" msgid="8578277541472944529">"ಹಲವಾರು ವಿನಂತಿಗಳನ್ನು ಪ್ರಕ್ರಿಯೆಗೊಳಿಸಲಾಗುತ್ತಿದೆ. ನಂತರ ಮತ್ತೆ ಪ್ರಯತ್ನಿಸಿ."</string>
<string name="status_pending" msgid="2503691772030877944">"ಫೈಲ್ ವರ್ಗಾವಣೆ ಇನ್ನೂ ಪ್ರಾರಂಭಿಸಿಲ್ಲ."</string>
@@ -95,8 +95,8 @@
<string name="status_forbidden" msgid="613956401054050725">"ಉದ್ದೇಶಿತ ಸಾಧನದಿಂದ ವರ್ಗಾವಣೆಯನ್ನು ನಿಷೇಧಿಸಲಾಗಿದೆ."</string>
<string name="status_canceled" msgid="6664490318773098285">"ಬಳಕೆದಾರರ ಮೂಲಕ ವರ್ಗಾವಣೆಯನ್ನು ರದ್ದುಪಡಿಸಲಾಗಿದೆ."</string>
<string name="status_file_error" msgid="3671917770630165299">"ಸಂಗ್ರಹಣೆಯ ಸಮಸ್ಯೆ."</string>
- <string name="status_no_sd_card" product="nosdcard" msgid="1112125377088421469">"ಯಾವುದೇ USB ಸಂಗ್ರಹಣೆಯಿಲ್ಲ."</string>
- <string name="status_no_sd_card" product="default" msgid="5760944071743325592">"ಯಾವುದೇ SD ಕಾರ್ಡ್ಗಳಿಲ್ಲ. ವರ್ಗಾವಣೆ ಮಾಡಲಾದ ಫೈಲ್ಗಳನ್ನು ಉಳಿಸಲು SD ಕಾರ್ಡ್ವೊಂದನ್ನು ಸೇರಿಸಿ."</string>
+ <string name="status_no_sd_card_nosdcard" msgid="573631036356922221">"ಯಾವುದೇ USB ಸಂಗ್ರಹಣೆಯಿಲ್ಲ."</string>
+ <string name="status_no_sd_card_default" msgid="396564893716701954">"ಯಾವುದೇ SD ಕಾರ್ಡ್ಗಳಿಲ್ಲ. ವರ್ಗಾವಣೆ ಮಾಡಲಾದ ಫೈಲ್ಗಳನ್ನು ಉಳಿಸಲು SD ಕಾರ್ಡ್ವೊಂದನ್ನು ಸೇರಿಸಿ."</string>
<string name="status_connection_error" msgid="947681831523219891">"ಸಂಪರ್ಕವು ವಿಫಲವಾಗಿದೆ."</string>
<string name="status_protocol_error" msgid="3245444473429269539">"ವಿನಂತಿಯನ್ನು ಸರಿಯಾಗಿ ನಿರ್ವಹಿಸಲಾಗುವುದಿಲ್ಲ."</string>
<string name="status_unknown_error" msgid="8156660554237824912">"ಅಪರಿಚಿತ ದೋಷ."</string>
@@ -122,6 +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_map_settings_save" msgid="7635491847388074606">"ಉಳಿಸಿ"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"ರದ್ದುಮಾಡಿ"</string>
<string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"ಬ್ಲೂಟೂತ್ ಮೂಲಕ ಹಂಚಿಕೊಳ್ಳಲು ಬಯಸುವ ಖಾತೆಗಳನ್ನು ಆಯ್ಕೆಮಾಡಿ. ಸಂಪರ್ಕಿಸುವಾಗ ಖಾತೆಗಳಿಗೆ ಯಾವುದೇ ಪ್ರವೇಶವನ್ನು ನೀವು ಈಗಲೂ ಸಮ್ಮತಿಸಬೇಕಾಗುತ್ತದೆ."</string>
diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml
index 83a1a33..f5eeca9 100644
--- a/res/values-ko/strings.xml
+++ b/res/values-ko/strings.xml
@@ -84,8 +84,8 @@
<string name="bt_toast_4" msgid="4678812947604395649">"\'<xliff:g id="RECIPIENT">%1$s</xliff:g>\'님에게 파일을 보내는 중"</string>
<string name="bt_toast_5" msgid="2846870992823019494">"\'<xliff:g id="RECIPIENT">%2$s</xliff:g>\'에 <xliff:g id="NUMBER">%1$s</xliff:g>개 파일을 보내는 중"</string>
<string name="bt_toast_6" msgid="1855266596936622458">"\'<xliff:g id="RECIPIENT">%1$s</xliff:g>\'님에게 파일 보내기가 중지되었습니다."</string>
- <string name="bt_sm_2_1" product="nosdcard" msgid="352165168004521000">"USB 저장소 공간이 부족해 \'<xliff:g id="SENDER">%1$s</xliff:g>\'님이 보낸 파일을 저장할 수 없습니다."</string>
- <string name="bt_sm_2_1" product="default" msgid="1989018443456803630">"SD 카드 공간이 부족해 \'<xliff:g id="SENDER">%1$s</xliff:g>\'님이 보낸 파일을 저장할 수 없습니다."</string>
+ <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"USB 저장소 공간이 부족해 \'<xliff:g id="SENDER">%1$s</xliff:g>\'님이 보낸 파일을 저장할 수 없습니다."</string>
+ <string name="bt_sm_2_1_default" msgid="9115512207909504071">"SD 카드 공간이 부족해 \'<xliff:g id="SENDER">%1$s</xliff:g>\'님이 보낸 파일을 저장할 수 없습니다."</string>
<string name="bt_sm_2_2" msgid="2965243265852680543">"필요한 공간: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
<string name="ErrorTooManyRequests" msgid="8578277541472944529">"처리 중인 요청이 너무 많습니다. 잠시 후에 다시 시도해 주세요."</string>
<string name="status_pending" msgid="2503691772030877944">"파일 전송을 시작하지 않았습니다."</string>
@@ -95,12 +95,12 @@
<string name="status_forbidden" msgid="613956401054050725">"상대 기기에서 전송을 금지했습니다."</string>
<string name="status_canceled" msgid="6664490318773098285">"사용자가 전송을 취소했습니다."</string>
<string name="status_file_error" msgid="3671917770630165299">"저장용량 문제"</string>
- <string name="status_no_sd_card" product="nosdcard" msgid="1112125377088421469">"USB 저장소가 없습니다."</string>
- <string name="status_no_sd_card" product="default" msgid="5760944071743325592">"SD 카드가 없습니다. 전송된 파일을 저장할 SD 카드를 삽입하세요."</string>
+ <string name="status_no_sd_card_nosdcard" msgid="573631036356922221">"USB 저장소가 없습니다."</string>
+ <string name="status_no_sd_card_default" msgid="396564893716701954">"SD 카드가 없습니다. 전송된 파일을 저장할 SD 카드를 삽입하세요."</string>
<string name="status_connection_error" msgid="947681831523219891">"연결하지 못했습니다."</string>
<string name="status_protocol_error" msgid="3245444473429269539">"요청을 제대로 처리할 수 없습니다."</string>
<string name="status_unknown_error" msgid="8156660554237824912">"알 수 없는 오류입니다."</string>
- <string name="btopp_live_folder" msgid="7967791481444474554">"블루투스 수신함"</string>
+ <string name="btopp_live_folder" msgid="7967791481444474554">"블루투스로 받은 파일"</string>
<string name="opp_notification_group" msgid="3486303082135789982">"블루투스 공유"</string>
<string name="download_success" msgid="7036160438766730871">"<xliff:g id="FILE_SIZE">%1$s</xliff:g> 수신을 완료했습니다."</string>
<string name="upload_success" msgid="4014469387779648949">"<xliff:g id="FILE_SIZE">%1$s</xliff:g> 전송을 완료했습니다."</string>
@@ -122,6 +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">"지금 재생 중"</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>
diff --git a/res/values-ky/strings.xml b/res/values-ky/strings.xml
index 64c1ede..0ed4aa9 100644
--- a/res/values-ky/strings.xml
+++ b/res/values-ky/strings.xml
@@ -84,8 +84,8 @@
<string name="bt_toast_4" msgid="4678812947604395649">"Кийинкиге файл жөнөтүлүүдө: \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
<string name="bt_toast_5" msgid="2846870992823019494">"\"<xliff:g id="RECIPIENT">%2$s</xliff:g>\" дарегине <xliff:g id="NUMBER">%1$s</xliff:g> файл жөнөтүлүүдө"</string>
<string name="bt_toast_6" msgid="1855266596936622458">"\"<xliff:g id="RECIPIENT">%1$s</xliff:g>\" дарегине файл жөнөтүү токтотулду"</string>
- <string name="bt_sm_2_1" product="nosdcard" msgid="352165168004521000">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" жөнөткөн файлды сакташ үчүн, USB сактагычта орун жетишсиз."</string>
- <string name="bt_sm_2_1" product="default" msgid="1989018443456803630">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" жөнөткөн файлды сакташ үчүн, SD-картада орун жетишсиз."</string>
+ <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" жөнөткөн файлды сактоо үчүн USB эстутумунда орун жетишсиз"</string>
+ <string name="bt_sm_2_1_default" msgid="9115512207909504071">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" жөнөткөн файлды сактоо үчүн SD-картада орун жетишсиз"</string>
<string name="bt_sm_2_2" msgid="2965243265852680543">"Керектүү орун: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
<string name="ErrorTooManyRequests" msgid="8578277541472944529">"Өтө көп талаптар иштетилүүдө. Кайта аракеттениңиз."</string>
<string name="status_pending" msgid="2503691772030877944">"Файл өткөрүү баштала элек."</string>
@@ -95,9 +95,9 @@
<string name="status_forbidden" msgid="613956401054050725">"Алуучу түзмөк өткөрүүгө тыюу салды."</string>
<string name="status_canceled" msgid="6664490318773098285">"Өткөрүү колдонуучу тарабынан токтотулду."</string>
<string name="status_file_error" msgid="3671917770630165299">"Сактагычта маселе бар."</string>
- <string name="status_no_sd_card" product="nosdcard" msgid="1112125377088421469">"USB сактагыч жок."</string>
- <string name="status_no_sd_card" product="default" msgid="5760944071743325592">"SD-карта жок. SD-карта салып, өткөрүлгөн файлдарды сактаңыз."</string>
- <string name="status_connection_error" msgid="947681831523219891">"Туташуу ийгиликсиз."</string>
+ <string name="status_no_sd_card_nosdcard" msgid="573631036356922221">"USB эстутуму жок."</string>
+ <string name="status_no_sd_card_default" msgid="396564893716701954">"SD-карта жок. Өткөрүлгөн файлдарды сактоо үчүн SD-картаны салыңыз."</string>
+ <string name="status_connection_error" msgid="947681831523219891">"Туташкан жок."</string>
<string name="status_protocol_error" msgid="3245444473429269539">"Сурамды туура иштетүү мүмкүн эмес."</string>
<string name="status_unknown_error" msgid="8156660554237824912">"Белгисиз ката."</string>
<string name="btopp_live_folder" msgid="7967791481444474554">"Bluetooth аркылуу алынгандар"</string>
@@ -122,11 +122,12 @@
<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">"Азыр эмне ойноп жатат?"</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_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">"Bluetooth билдирүү бөлүшүү жөндөөлөрү"</string>
<string name="bluetooth_map_settings_no_account_slots_left" msgid="1796029082612965251">"Аккаунт тандалбай жатат: 0 орун калды"</string>
<string name="bluetooth_connected" msgid="6718623220072656906">"Bluetooth аудио туташты"</string>
diff --git a/res/values-lo/strings.xml b/res/values-lo/strings.xml
index 17ceec0..bebe622 100644
--- a/res/values-lo/strings.xml
+++ b/res/values-lo/strings.xml
@@ -84,8 +84,8 @@
<string name="bt_toast_4" msgid="4678812947604395649">"ກຳລັງສົ່ງໄຟລ໌ໄປຫາ \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
<string name="bt_toast_5" msgid="2846870992823019494">"ກຳລັງສົ່ງ <xliff:g id="NUMBER">%1$s</xliff:g> ໄຟລ໌ໄປຫາ \"<xliff:g id="RECIPIENT">%2$s</xliff:g>\""</string>
<string name="bt_toast_6" msgid="1855266596936622458">"ຢຸດການສົ່ງໄຟລ໌ໄປຫາ \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\" ແລ້ວ"</string>
- <string name="bt_sm_2_1" product="nosdcard" msgid="352165168004521000">"ບ່ອນຈັດເກັບຂໍ້ມູນ USB ບໍ່ພຽງພໍທີ່ຈະບັນທຶກໄຟລ໌ດັ່ງກ່າວຈາກ \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
- <string name="bt_sm_2_1" product="default" msgid="1989018443456803630">"ບ່ອນຈັດເກັບຂໍ້ມູນ SD card ບໍ່ພຽງພໍທີ່ຈະບັນທຶກໄຟລ໌ດັ່ງກ່າວຈາກ \"<xliff:g id="SENDER">%1$s</xliff:g>\" ໄດ້"</string>
+ <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"ບ່ອນຈັດເກັບຂໍ້ມູນ USB ບໍ່ພຽງພໍທີ່ຈະບັນທຶກໄຟລ໌ດັ່ງກ່າວຈາກ \"<xliff:g id="SENDER">%1$s</xliff:g>\" ໄດ້"</string>
+ <string name="bt_sm_2_1_default" msgid="9115512207909504071">"ບ່ອນຈັດເກັບຂໍ້ມູນ SD ກາດບໍ່ພຽງພໍທີ່ຈະບັນທຶກໄຟລ໌ດັ່ງກ່າວຈາກ \"<xliff:g id="SENDER">%1$s</xliff:g>\" ໄດ້"</string>
<string name="bt_sm_2_2" msgid="2965243265852680543">"ພື້ນທີ່ທີ່ຕ້ອງການ: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
<string name="ErrorTooManyRequests" msgid="8578277541472944529">"ມີການຮ້ອງຂໍຫຼາຍເກີນໄປ, ກະລຸນາລອງໃໝ່ພາຍຫຼັງ."</string>
<string name="status_pending" msgid="2503691772030877944">"ການໂອນໄຟລ໌ຍັງບໍ່ທັນໄດ້ເລີ່ມເທື່ອ."</string>
@@ -95,8 +95,8 @@
<string name="status_forbidden" msgid="613956401054050725">"ການໂອນໄຟລ໌ຖືກປິດກັ້ນໂດຍອຸປະກອນປາຍທາງ."</string>
<string name="status_canceled" msgid="6664490318773098285">"ການໂອນຖືກຍົກເລີກໂດຍຜູ່ໃຊ້."</string>
<string name="status_file_error" msgid="3671917770630165299">"ປັນຫາພື້ນທີ່ເກັບຂໍ້ມູນ."</string>
- <string name="status_no_sd_card" product="nosdcard" msgid="1112125377088421469">"ບໍ່ມີບ່ອນຈັດເກັບຂໍ້ມູນ USB."</string>
- <string name="status_no_sd_card" product="default" msgid="5760944071743325592">"ບໍ່ມີ SD card. ໃສ່ SD card ເພື່ອບັນທຶກໄຟລ໌ທີ່ໂອນມາ."</string>
+ <string name="status_no_sd_card_nosdcard" msgid="573631036356922221">"ບໍ່ມີບ່ອນຈັດເກັບຂໍ້ມູນ USB."</string>
+ <string name="status_no_sd_card_default" msgid="396564893716701954">"ບໍ່ມີ SD ກາດ. ໃສ່ SD ກາດເພື່ອບັນທຶກໄຟລ໌ທີ່ໂອນມາ."</string>
<string name="status_connection_error" msgid="947681831523219891">"ການເຊື່ອມຕໍ່ບໍ່ສຳເລັດຜົນ"</string>
<string name="status_protocol_error" msgid="3245444473429269539">"ການຮ້ອງຂໍບໍ່ສາມາດຖືກຈັດການໄດ້ຢ່າງຖືກຕ້ອງ."</string>
<string name="status_unknown_error" msgid="8156660554237824912">"ຄວາມຜິດພາດທີ່ບໍ່ຮູ້ຈັກ."</string>
@@ -122,6 +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_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>
diff --git a/res/values-lt/strings.xml b/res/values-lt/strings.xml
index 0849a5d..5d0f759 100644
--- a/res/values-lt/strings.xml
+++ b/res/values-lt/strings.xml
@@ -84,8 +84,8 @@
<string name="bt_toast_4" msgid="4678812947604395649">"<xliff:g id="RECIPIENT">%1$s</xliff:g> siunčiamas failas"</string>
<string name="bt_toast_5" msgid="2846870992823019494">"<xliff:g id="RECIPIENT">%2$s</xliff:g> siunčiami (-a) <xliff:g id="NUMBER">%1$s</xliff:g> failai (-ų)"</string>
<string name="bt_toast_6" msgid="1855266596936622458">"Sustabdytas failo siuntimas <xliff:g id="RECIPIENT">%1$s</xliff:g>"</string>
- <string name="bt_sm_2_1" product="nosdcard" msgid="352165168004521000">"USB atmintyje nėra pakankamai vietos išsaugoti failą iš „<xliff:g id="SENDER">%1$s</xliff:g>“"</string>
- <string name="bt_sm_2_1" product="default" msgid="1989018443456803630">"SD kortelėje nepakanka vietos išsaugoti failą iš „<xliff:g id="SENDER">%1$s</xliff:g>“"</string>
+ <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"USB atmintyje nėra pakankamai vietos išsaugoti failą iš „<xliff:g id="SENDER">%1$s</xliff:g>“"</string>
+ <string name="bt_sm_2_1_default" msgid="9115512207909504071">"SD kortelėje nepakanka vietos išsaugoti failą iš „<xliff:g id="SENDER">%1$s</xliff:g>“"</string>
<string name="bt_sm_2_2" msgid="2965243265852680543">"Reikalinga vieta: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
<string name="ErrorTooManyRequests" msgid="8578277541472944529">"Apdorojama per daug užklausų. Vėliau bandykite dar kartą."</string>
<string name="status_pending" msgid="2503691772030877944">"Failo perkėlimas dar nepradėtas."</string>
@@ -95,8 +95,8 @@
<string name="status_forbidden" msgid="613956401054050725">"Perkėlimas draudžiamas paskirties įrenginyje."</string>
<string name="status_canceled" msgid="6664490318773098285">"Perkėlimą atšaukė naudotojas."</string>
<string name="status_file_error" msgid="3671917770630165299">"Atmintinės problema."</string>
- <string name="status_no_sd_card" product="nosdcard" msgid="1112125377088421469">"Nėra USB atmintinės."</string>
- <string name="status_no_sd_card" product="default" msgid="5760944071743325592">"Nėra SD kortelės. Įdėkite SD kortelę, kurioje išsaugosite perkeltus failus."</string>
+ <string name="status_no_sd_card_nosdcard" msgid="573631036356922221">"Nėra USB atminties."</string>
+ <string name="status_no_sd_card_default" msgid="396564893716701954">"Nėra SD kortelės. Įdėkite SD kortelę, kurioje išsaugosite perkeltus failus."</string>
<string name="status_connection_error" msgid="947681831523219891">"Nepavyko užmegzti ryšio."</string>
<string name="status_protocol_error" msgid="3245444473429269539">"Nepavyksta tinkamai apdoroti užklausos."</string>
<string name="status_unknown_error" msgid="8156660554237824912">"Nežinoma klaida."</string>
@@ -126,6 +126,7 @@
<string name="transfer_menu_open" msgid="3368984869083107200">"Atidaryti"</string>
<string name="transfer_menu_clear" msgid="5854038118831427492">"Išvalyti iš sąrašo"</string>
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"Išvalyti"</string>
+ <string name="bluetooth_a2dp_sink_queue_name" msgid="6864149958708669766">"Dabar leidžiama"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"Išsaugoti"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"Atšaukti"</string>
<string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"Pasirinkite o paskyras, kurias norite bendrinti per „Bluetooth“. Jungiantis vis tiek reikės suteikti prieigą prie paskyrų."</string>
diff --git a/res/values-lv/strings.xml b/res/values-lv/strings.xml
index 5e2f17c..062a019 100644
--- a/res/values-lv/strings.xml
+++ b/res/values-lv/strings.xml
@@ -84,8 +84,8 @@
<string name="bt_toast_4" msgid="4678812947604395649">"Notiek faila sūtīšana saņēmējam <xliff:g id="RECIPIENT">%1$s</xliff:g>"</string>
<string name="bt_toast_5" msgid="2846870992823019494">"Notiek <xliff:g id="NUMBER">%1$s</xliff:g> failu sūtīšana saņēmējam <xliff:g id="RECIPIENT">%2$s</xliff:g>."</string>
<string name="bt_toast_6" msgid="1855266596936622458">"Faila sūtīšana saņēmējam <xliff:g id="RECIPIENT">%1$s</xliff:g> tika apturēta."</string>
- <string name="bt_sm_2_1" product="nosdcard" msgid="352165168004521000">"USB atmiņā nepietiek vietas, lai saglabātu failu no sūtītāja <xliff:g id="SENDER">%1$s</xliff:g>."</string>
- <string name="bt_sm_2_1" product="default" msgid="1989018443456803630">"SD kartē nepietiek vietas, lai saglabātu failu no sūtītāja <xliff:g id="SENDER">%1$s</xliff:g>"</string>
+ <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"USB atmiņā nepietiek vietas, lai saglabātu failu no sūtītāja <xliff:g id="SENDER">%1$s</xliff:g>."</string>
+ <string name="bt_sm_2_1_default" msgid="9115512207909504071">"SD kartē nepietiek vietas, lai saglabātu failu no sūtītāja <xliff:g id="SENDER">%1$s</xliff:g>."</string>
<string name="bt_sm_2_2" msgid="2965243265852680543">"Nepieciešamā brīvā vieta: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
<string name="ErrorTooManyRequests" msgid="8578277541472944529">"Tiek apstrādāts pārāk liels pieprasījumu skaits. Vēlāk mēģiniet vēlreiz."</string>
<string name="status_pending" msgid="2503691772030877944">"Faila pārsūtīšana vēl nav sākta."</string>
@@ -95,8 +95,8 @@
<string name="status_forbidden" msgid="613956401054050725">"Adresāta ierīcē tika aizliegta pārsūtīšana."</string>
<string name="status_canceled" msgid="6664490318773098285">"Lietotājs atcēla pārsūtīšanu."</string>
<string name="status_file_error" msgid="3671917770630165299">"Atmiņas problēma."</string>
- <string name="status_no_sd_card" product="nosdcard" msgid="1112125377088421469">"Nav USB atmiņas."</string>
- <string name="status_no_sd_card" product="default" msgid="5760944071743325592">"Nav SD kartes. Ievietojiet SD karti, lai saglabātu pārsūtītos failus."</string>
+ <string name="status_no_sd_card_nosdcard" msgid="573631036356922221">"Nav USB atmiņas."</string>
+ <string name="status_no_sd_card_default" msgid="396564893716701954">"Nav SD kartes. Ievietojiet SD karti, lai saglabātu pārsūtītos failus."</string>
<string name="status_connection_error" msgid="947681831523219891">"Neizdevās izveidot savienojumu."</string>
<string name="status_protocol_error" msgid="3245444473429269539">"Pieprasījumu nevar pareizi apstrādāt."</string>
<string name="status_unknown_error" msgid="8156660554237824912">"Nezināma kļūda."</string>
@@ -124,6 +124,7 @@
<string name="transfer_menu_open" msgid="3368984869083107200">"Atvērt"</string>
<string name="transfer_menu_clear" msgid="5854038118831427492">"Notīrīt no saraksta"</string>
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"Notīrīšana"</string>
+ <string name="bluetooth_a2dp_sink_queue_name" msgid="6864149958708669766">"Tagad atskaņo"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"Saglabāt"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"Atcelt"</string>
<string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"Atlasiet kontus, kurus vēlaties koplietot, izmantojot Bluetooth. Kad tiks izveidots savienojums, jums būs arī jāapstiprina piekļuve kontiem."</string>
diff --git a/res/values-mk/strings.xml b/res/values-mk/strings.xml
index c6ec941..f188ae0 100644
--- a/res/values-mk/strings.xml
+++ b/res/values-mk/strings.xml
@@ -84,8 +84,8 @@
<string name="bt_toast_4" msgid="4678812947604395649">"Се испраќа датотека до „<xliff:g id="RECIPIENT">%1$s</xliff:g>“"</string>
<string name="bt_toast_5" msgid="2846870992823019494">"Се испраќаат <xliff:g id="NUMBER">%1$s</xliff:g> датотеки до „<xliff:g id="RECIPIENT">%2$s</xliff:g>“"</string>
<string name="bt_toast_6" msgid="1855266596936622458">"Испраќањето на датотеката до „<xliff:g id="RECIPIENT">%1$s</xliff:g>“ запре"</string>
- <string name="bt_sm_2_1" product="nosdcard" msgid="352165168004521000">"Нема доволно простор на USB меморијата да се зачува датотеката од „<xliff:g id="SENDER">%1$s</xliff:g>’"</string>
- <string name="bt_sm_2_1" product="default" msgid="1989018443456803630">"Нема доволно простор на СД картичката да се зачува датотеката од „<xliff:g id="SENDER">%1$s</xliff:g>“"</string>
+ <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"Нема доволно простор во USB-меморијата за да се зачува датотеката од „<xliff:g id="SENDER">%1$s</xliff:g>’"</string>
+ <string name="bt_sm_2_1_default" msgid="9115512207909504071">"Нема доволно простор на SD-картичката за да се зачува датотеката од „<xliff:g id="SENDER">%1$s</xliff:g>“"</string>
<string name="bt_sm_2_2" msgid="2965243265852680543">"Потребен простор: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
<string name="ErrorTooManyRequests" msgid="8578277541472944529">"Се обработуваат премногу барања. Обидете се повторно подоцна."</string>
<string name="status_pending" msgid="2503691772030877944">"Преносот на датотека сè уште не е започнат."</string>
@@ -93,10 +93,10 @@
<string name="status_success" msgid="239573225847565868">"Преносот на датотека е успешно завршен."</string>
<string name="status_not_accept" msgid="1695082417193780738">"Содржината не е поддржана."</string>
<string name="status_forbidden" msgid="613956401054050725">"Преносот е забранет од целниот уред."</string>
- <string name="status_canceled" msgid="6664490318773098285">"Преносот е откажан од страна на корисникот."</string>
+ <string name="status_canceled" msgid="6664490318773098285">"Корисникот го откажа преносот."</string>
<string name="status_file_error" msgid="3671917770630165299">"Проблем со меморија."</string>
- <string name="status_no_sd_card" product="nosdcard" msgid="1112125377088421469">"Нема USB меморија."</string>
- <string name="status_no_sd_card" product="default" msgid="5760944071743325592">"Нема СД картичка. Вметнете СД картичка да се зачуваат пренесените датотеки."</string>
+ <string name="status_no_sd_card_nosdcard" msgid="573631036356922221">"Нема USB-меморија."</string>
+ <string name="status_no_sd_card_default" msgid="396564893716701954">"Нема SD-картичка. Вметнете SD-картичка за да се зачуваат пренесените датотеки."</string>
<string name="status_connection_error" msgid="947681831523219891">"Врската е неуспешна."</string>
<string name="status_protocol_error" msgid="3245444473429269539">"Барањето не може да се обработи правилно."</string>
<string name="status_unknown_error" msgid="8156660554237824912">"Непозната грешка."</string>
@@ -122,6 +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_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>
diff --git a/res/values-ml/strings.xml b/res/values-ml/strings.xml
index a7ddfeb..08e907f 100644
--- a/res/values-ml/strings.xml
+++ b/res/values-ml/strings.xml
@@ -84,8 +84,8 @@
<string name="bt_toast_4" msgid="4678812947604395649">"\"<xliff:g id="RECIPIENT">%1$s</xliff:g>\" എന്നയാൾക്ക് ഫയൽ അയയ്ക്കുന്നു"</string>
<string name="bt_toast_5" msgid="2846870992823019494">"\"<xliff:g id="RECIPIENT">%2$s</xliff:g>\" എന്നയാൾക്ക് <xliff:g id="NUMBER">%1$s</xliff:g> ഫയലുകൾ അയയ്ക്കുന്നു"</string>
<string name="bt_toast_6" msgid="1855266596936622458">"\"<xliff:g id="RECIPIENT">%1$s</xliff:g>\" എന്നയാൾക്ക് ഫയൽ അയയ്ക്കുന്നത് നിർത്തി"</string>
- <string name="bt_sm_2_1" product="nosdcard" msgid="352165168004521000">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" എന്നയാളിൽ നിന്നുള്ള ഫയൽ സംരക്ഷിക്കാൻ USB സംഭരണത്തിൽ ആവശ്യമായ ഇടമില്ല."</string>
- <string name="bt_sm_2_1" product="default" msgid="1989018443456803630">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" എന്നയാളിൽ നിന്നുള്ള ഫയൽ സംരക്ഷിക്കാൻ SD കാർഡിൽ ആവശ്യമായ ഇടമില്ല."</string>
+ <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" എന്നയാളിൽ നിന്നുള്ള ഫയൽ സംരക്ഷിക്കാൻ USB സ്റ്റോറേജിൽ ആവശ്യമായ ഇടമില്ല."</string>
+ <string name="bt_sm_2_1_default" msgid="9115512207909504071">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" എന്നയാളിൽ നിന്നുള്ള ഫയൽ സംരക്ഷിക്കാൻ SD കാർഡിൽ ആവശ്യമായ ഇടമില്ല."</string>
<string name="bt_sm_2_2" msgid="2965243265852680543">"ആവശ്യമായ ഇടം: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
<string name="ErrorTooManyRequests" msgid="8578277541472944529">"നിരവധി അഭ്യർത്ഥനകൾ പ്രോസസ്സ് ചെയ്യുന്നു. പിന്നീട് വീണ്ടും ശ്രമിക്കുക."</string>
<string name="status_pending" msgid="2503691772030877944">"ഫയൽ കൈമാറ്റം ഇതുവരെ ആരംഭിച്ചിട്ടില്ല."</string>
@@ -95,12 +95,12 @@
<string name="status_forbidden" msgid="613956401054050725">"ലക്ഷ്യസ്ഥാന ഉപകരണം, കൈമാറൽ നിരോധിച്ചിരിക്കുന്നു."</string>
<string name="status_canceled" msgid="6664490318773098285">"കൈമാറൽ ഉപയോക്താവ് റദ്ദാക്കി."</string>
<string name="status_file_error" msgid="3671917770630165299">"സംഭരണ പ്രശ്നം."</string>
- <string name="status_no_sd_card" product="nosdcard" msgid="1112125377088421469">"USB സംഭരണമൊന്നുമില്ല."</string>
- <string name="status_no_sd_card" product="default" msgid="5760944071743325592">"SD കാർഡൊന്നുമില്ല. കൈമാറിയ ഫയലുകൾ സംരക്ഷിക്കാൻ ഒരു SD കാർഡ് ചേർക്കുക."</string>
+ <string name="status_no_sd_card_nosdcard" msgid="573631036356922221">"USB സ്റ്റോറേജൊന്നുമില്ല."</string>
+ <string name="status_no_sd_card_default" msgid="396564893716701954">"SD കാർഡൊന്നുമില്ല. കൈമാറിയ ഫയലുകൾ സംരക്ഷിക്കാൻ ഒരു SD കാർഡ് ചേർക്കുക."</string>
<string name="status_connection_error" msgid="947681831523219891">"കണക്ഷൻ പരാജയപ്പെട്ടു."</string>
<string name="status_protocol_error" msgid="3245444473429269539">"അഭ്യർത്ഥന ശരിയായി കൈകാര്യം ചെയ്യാനാകില്ല."</string>
<string name="status_unknown_error" msgid="8156660554237824912">"അജ്ഞാത പിശക്."</string>
- <string name="btopp_live_folder" msgid="7967791481444474554">"ബ്ലൂടൂത്ത് ലഭിച്ചു"</string>
+ <string name="btopp_live_folder" msgid="7967791481444474554">"ബ്ലൂടൂത്തിലൂടെ ലഭിച്ചവ"</string>
<string name="opp_notification_group" msgid="3486303082135789982">"Bluetooth പങ്കിടൽ"</string>
<string name="download_success" msgid="7036160438766730871">"<xliff:g id="FILE_SIZE">%1$s</xliff:g> നേടൽ പൂർത്തിയായി."</string>
<string name="upload_success" msgid="4014469387779648949">"<xliff:g id="FILE_SIZE">%1$s</xliff:g> അയച്ചത് പൂർത്തിയായി."</string>
@@ -120,6 +120,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">"ഇപ്പോൾ പ്ലേ ചെയ്യുന്നത്"</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>
diff --git a/res/values-mn/strings.xml b/res/values-mn/strings.xml
index 5bc336c..aec448c 100644
--- a/res/values-mn/strings.xml
+++ b/res/values-mn/strings.xml
@@ -84,8 +84,8 @@
<string name="bt_toast_4" msgid="4678812947604395649">"\"<xliff:g id="RECIPIENT">%1$s</xliff:g>\" руу файлыг илгээж байна"</string>
<string name="bt_toast_5" msgid="2846870992823019494">"<xliff:g id="NUMBER">%1$s</xliff:g> файлуудыг \"<xliff:g id="RECIPIENT">%2$s</xliff:g>\" руу илгээж байна"</string>
<string name="bt_toast_6" msgid="1855266596936622458">"\"<xliff:g id="RECIPIENT">%1$s</xliff:g>\" руу файл илгээхийн зогсоосон"</string>
- <string name="bt_sm_2_1" product="nosdcard" msgid="352165168004521000">"USB сан дотор \"<xliff:g id="SENDER">%1$s</xliff:g>\"-с ирүүлсэн файлыг хадгалах хангалттай зай байхгүй байна"</string>
- <string name="bt_sm_2_1" product="default" msgid="1989018443456803630">"SD карт дээр \"<xliff:g id="SENDER">%1$s</xliff:g>\"-с ирүүлсэн файлыг хадгалах хангалттай зай байхгүй байна"</string>
+ <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"USB санд \"<xliff:g id="SENDER">%1$s</xliff:g>\"-с ирсэн файлыг хадгалах хангалттай зай алга"</string>
+ <string name="bt_sm_2_1_default" msgid="9115512207909504071">"SD картад \"<xliff:g id="SENDER">%1$s</xliff:g>\"-с ирсэн файлыг хадгалах хангалттай зай алга"</string>
<string name="bt_sm_2_2" msgid="2965243265852680543">"Шаардлагатай зай: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
<string name="ErrorTooManyRequests" msgid="8578277541472944529">"Хэт олон хүсэлтийг боловсруулж байна. Дараа дахин оролдоно уу."</string>
<string name="status_pending" msgid="2503691772030877944">"Файл дамжуулалт хараахан эхлээгүй байна."</string>
@@ -95,8 +95,8 @@
<string name="status_forbidden" msgid="613956401054050725">"Дамжуулалтыг хүлээн авагч төхөөрөмжөөс хориглосон."</string>
<string name="status_canceled" msgid="6664490318773098285">"Дамжуулалтыг хэрэглэгч цуцалсан."</string>
<string name="status_file_error" msgid="3671917770630165299">"Хадгалах сангийн асуудал."</string>
- <string name="status_no_sd_card" product="nosdcard" msgid="1112125377088421469">"USB сан алга байна."</string>
- <string name="status_no_sd_card" product="default" msgid="5760944071743325592">"SD карт алга байна. Дамжуулсан файлуудыг хадгалахын тулд SD карт хийнэ үү."</string>
+ <string name="status_no_sd_card_nosdcard" msgid="573631036356922221">"USB сан алга."</string>
+ <string name="status_no_sd_card_default" msgid="396564893716701954">"SD карт алга. Шилжүүлсэн файлуудыг хадгалахын тулд SD картыг оруулна уу."</string>
<string name="status_connection_error" msgid="947681831523219891">"Холболт амжилтгүй."</string>
<string name="status_protocol_error" msgid="3245444473429269539">"Хүсэлтийг зөв гүйцэтгэх боломжгүй."</string>
<string name="status_unknown_error" msgid="8156660554237824912">"Тодорхойгүй алдаа."</string>
@@ -122,6 +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_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>
diff --git a/res/values-mr/strings.xml b/res/values-mr/strings.xml
index 345f44f..a7c0f1e 100644
--- a/res/values-mr/strings.xml
+++ b/res/values-mr/strings.xml
@@ -16,9 +16,9 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="permlab_bluetoothShareManager" msgid="311492132450338925">"डाउनलोड व्यवस्थापक अॅक्सेस करा."</string>
+ <string name="permlab_bluetoothShareManager" msgid="311492132450338925">"डाउनलोड व्यवस्थापक अॅक्सेस करा."</string>
<string name="permdesc_bluetoothShareManager" msgid="8930572979123190223">"अॅपला BluetoothShare व्यवस्थापकामध्ये प्रवेश करण्याची आणि फायली स्थानांतरित करण्यासाठी त्याचा वापर करण्याची अनुमती देते."</string>
- <string name="permlab_bluetoothWhitelist" msgid="7091552898592306386">"व्हाइटलिस्ट ब्लूटूथ डिव्हाइस अॅक्सेस."</string>
+ <string name="permlab_bluetoothWhitelist" msgid="7091552898592306386">"व्हाइटलिस्ट ब्लूटूथ डिव्हाइस अॅक्सेस."</string>
<string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"डीव्हाइसला वापरकर्ता पुष्टीशिवाय या डीव्हाइसवर फायली पाठविण्याची अनुमती देऊन ब्लूटूथ डीव्हाइसला तात्पुरते व्हाइटलिस्ट करण्याची अॅपला अनुमती देते."</string>
<string name="bt_share_picker_label" msgid="6268100924487046932">"ब्लूटूथ"</string>
<string name="unknown_device" msgid="9221903979877041009">"अज्ञात डिव्हाइस"</string>
@@ -84,8 +84,8 @@
<string name="bt_toast_4" msgid="4678812947604395649">"\"<xliff:g id="RECIPIENT">%1$s</xliff:g>\" ना फाइल पाठवित आहे"</string>
<string name="bt_toast_5" msgid="2846870992823019494">"\"<xliff:g id="RECIPIENT">%2$s</xliff:g>\" ना <xliff:g id="NUMBER">%1$s</xliff:g> फायली पाठवित आहे"</string>
<string name="bt_toast_6" msgid="1855266596936622458">"\"<xliff:g id="RECIPIENT">%1$s</xliff:g>\" ना फाइल पाठविणे थांबविले"</string>
- <string name="bt_sm_2_1" product="nosdcard" msgid="352165168004521000">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" कडील फाइल सेव्ह करण्यासाठी USB संचयनात पुरेसे स्थान नाही"</string>
- <string name="bt_sm_2_1" product="default" msgid="1989018443456803630">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" कडील फाइल सेव्ह करण्यासाठी SD कार्डवर पुरेसे स्थान नाही"</string>
+ <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" कडील फाइल सेव्ह करण्यासाठी USB स्टोरेजवर पुरेशी जागा नाही"</string>
+ <string name="bt_sm_2_1_default" msgid="9115512207909504071">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" कडील फाइल सेव्ह करण्यासाठी SD कार्डवर पुरेशी जागा नाही"</string>
<string name="bt_sm_2_2" msgid="2965243265852680543">"स्थान आवश्यक: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
<string name="ErrorTooManyRequests" msgid="8578277541472944529">"बर्याच विनंत्यांवर प्रक्रिया होत आहे. नंतर पुन्हा प्रयत्न करा."</string>
<string name="status_pending" msgid="2503691772030877944">"फाइल स्थानांतरणाचा अद्याप प्रारंभ झाला नाही."</string>
@@ -95,8 +95,8 @@
<string name="status_forbidden" msgid="613956401054050725">"लक्ष्य डिव्हाइसद्वारे स्थानांतरण निषिद्ध केले."</string>
<string name="status_canceled" msgid="6664490318773098285">"वापरकर्त्याने स्थानांतरण रद्द केले."</string>
<string name="status_file_error" msgid="3671917770630165299">"संचयन समस्या."</string>
- <string name="status_no_sd_card" product="nosdcard" msgid="1112125377088421469">"USB स्टोरेज नाही."</string>
- <string name="status_no_sd_card" product="default" msgid="5760944071743325592">"SD कार्ड नाही. स्थानांतरित केलेल्या फायली सेव्ह करण्यासाठी SD कार्ड घाला."</string>
+ <string name="status_no_sd_card_nosdcard" msgid="573631036356922221">"USB स्टोरेज नाही."</string>
+ <string name="status_no_sd_card_default" msgid="396564893716701954">"SD कार्ड नाही. ट्रान्सफर केलेल्या फायली सेव्ह करण्यासाठी SD कार्ड घाला."</string>
<string name="status_connection_error" msgid="947681831523219891">"कनेक्शन अयशस्वी."</string>
<string name="status_protocol_error" msgid="3245444473429269539">"विनंती योग्य रीतीने हाताळली जाऊ शकत नाही."</string>
<string name="status_unknown_error" msgid="8156660554237824912">"अज्ञात एरर"</string>
@@ -106,25 +106,26 @@
<string name="upload_success" msgid="4014469387779648949">"<xliff:g id="FILE_SIZE">%1$s</xliff:g> पाठविणे पूर्ण."</string>
<string name="inbound_history_title" msgid="6940914942271327563">"इनबाउंड स्थानांतरणे"</string>
<string name="outbound_history_title" msgid="4279418703178140526">"आउटबाउंड स्थानांतरणे"</string>
- <string name="no_transfers" msgid="3482965619151865672">"ट्रांसफर इतिहास रिक्त आहे."</string>
+ <string name="no_transfers" msgid="3482965619151865672">"ट्रान्सफर इतिहास रिक्त आहे."</string>
<string name="transfer_clear_dlg_msg" msgid="1712376797268438075">"सूचीमधून सर्व आयटम साफ केले जातील."</string>
<string name="outbound_noti_title" msgid="8051906709452260849">"ब्लूटूथ शेअर: पाठवलेल्या फायली"</string>
<string name="inbound_noti_title" msgid="4143352641953027595">"ब्लूटूथ शेअर: मिळवलेल्या फायली"</string>
<plurals name="noti_caption_unsuccessful" formatted="false" msgid="2020750076679526122">
- <item quantity="one"><xliff:g id="UNSUCCESSFUL_NUMBER_1">%1$d</xliff:g> अयशस्वी झाले.</item>
<item quantity="other"><xliff:g id="UNSUCCESSFUL_NUMBER_1">%1$d</xliff:g> अयशस्वी झाले.</item>
+ <item quantity="one"><xliff:g id="UNSUCCESSFUL_NUMBER_0">%1$d</xliff:g> अयशस्वी झाले.</item>
</plurals>
<plurals name="noti_caption_success" formatted="false" msgid="1572472450257645181">
- <item quantity="one"><xliff:g id="SUCCESSFUL_NUMBER_1">%1$d</xliff:g> यशस्वी झाले, %2$s</item>
<item quantity="other"><xliff:g id="SUCCESSFUL_NUMBER_1">%1$d</xliff:g> यशस्वी झाले, %2$s</item>
+ <item quantity="one"><xliff:g id="SUCCESSFUL_NUMBER_0">%1$d</xliff:g> यशस्वी झाले, %2$s</item>
</plurals>
<string name="transfer_menu_clear_all" msgid="790017462957873132">"सूची साफ करा"</string>
<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">"आता प्ले करत आहे"</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>
+ <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_title" msgid="7420332483392851321">"ब्लूटूथ मेसेज सामायिकरण सेटिंग्ज"</string>
@@ -132,5 +133,5 @@
<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_opp_file_limit_exceeded" msgid="8894450394309084519">"4 GB हून मोठ्या फायली ट्रान्सफर करता येणार नाहीत"</string>
</resources>
diff --git a/res/values-mr/strings_sap.xml b/res/values-mr/strings_sap.xml
index 22cbbf8..560d04f 100644
--- a/res/values-mr/strings_sap.xml
+++ b/res/values-mr/strings_sap.xml
@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="bluetooth_sap_notif_title" msgid="6877860822993195074">"ब्लूटूथ सिम अॅक्सेस"</string>
- <string name="bluetooth_sap_notif_ticker" msgid="6807778527893726699">"ब्लूटूथ सिम अॅक्सेस"</string>
+ <string name="bluetooth_sap_notif_title" msgid="6877860822993195074">"ब्लूटूथ सिम अॅक्सेस"</string>
+ <string name="bluetooth_sap_notif_ticker" msgid="6807778527893726699">"ब्लूटूथ सिम अॅक्सेस"</string>
<string name="bluetooth_sap_notif_message" msgid="7138657801087500690">"डिस्कनेक्ट करण्याची क्लायंटला विनंती करायची?"</string>
<string name="bluetooth_sap_notif_disconnecting" msgid="819150843490233288">"क्लायंटने डिस्कनेक्ट करण्याची प्रतीक्षा करत आहे"</string>
<string name="bluetooth_sap_notif_disconnect_button" msgid="3678476872583356919">"डिस्कनेक्ट करा"</string>
diff --git a/res/values-ms/strings.xml b/res/values-ms/strings.xml
index 8afc779..ab9c7a9 100644
--- a/res/values-ms/strings.xml
+++ b/res/values-ms/strings.xml
@@ -84,8 +84,8 @@
<string name="bt_toast_4" msgid="4678812947604395649">"Menghantar fail kepada \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
<string name="bt_toast_5" msgid="2846870992823019494">"Menghantar <xliff:g id="NUMBER">%1$s</xliff:g> fail kepada \"<xliff:g id="RECIPIENT">%2$s</xliff:g>\""</string>
<string name="bt_toast_6" msgid="1855266596936622458">"Penghantaran fail ke \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\" dihentikan"</string>
- <string name="bt_sm_2_1" product="nosdcard" msgid="352165168004521000">"Tiada ruang mencukupi pada storan USB untuk menyimpan fail daripada \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
- <string name="bt_sm_2_1" product="default" msgid="1989018443456803630">"Tiada ruang mencukupi pada kad SD untuk menyimpan fail daripada \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
+ <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"Ruang pada storan USB tidak mencukupi untuk menyimpan fail daripada \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
+ <string name="bt_sm_2_1_default" msgid="9115512207909504071">"Ruang pada kad SD tidak mencukupi untuk menyimpan fail daripada \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
<string name="bt_sm_2_2" msgid="2965243265852680543">"Ruang diperlukan: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
<string name="ErrorTooManyRequests" msgid="8578277541472944529">"Terlalu banyak permintaan sedang diproses. Cuba sebentar lagi."</string>
<string name="status_pending" msgid="2503691772030877944">"Pemindahan fail belum lagi dimulakan."</string>
@@ -95,8 +95,8 @@
<string name="status_forbidden" msgid="613956401054050725">"Pemindahan dilarang oleh peranti sasaran."</string>
<string name="status_canceled" msgid="6664490318773098285">"Pemindahan dibatalkan oleh pengguna."</string>
<string name="status_file_error" msgid="3671917770630165299">"Isu storan."</string>
- <string name="status_no_sd_card" product="nosdcard" msgid="1112125377088421469">"Tiada storan USB."</string>
- <string name="status_no_sd_card" product="default" msgid="5760944071743325592">"Tiada kad SD. Masukkan kad SD untuk menyimpan fail yang dipindahkan."</string>
+ <string name="status_no_sd_card_nosdcard" msgid="573631036356922221">"Tiada storan USB."</string>
+ <string name="status_no_sd_card_default" msgid="396564893716701954">"Tiada kad SD. Masukkan kad SD untuk menyimpan fail yang dipindahkan."</string>
<string name="status_connection_error" msgid="947681831523219891">"Sambungan tidak berjaya."</string>
<string name="status_protocol_error" msgid="3245444473429269539">"Permintaan tidak dapat dikendalikan dengan betul."</string>
<string name="status_unknown_error" msgid="8156660554237824912">"Ralat tidak diketahui."</string>
@@ -122,6 +122,7 @@
<string name="transfer_menu_open" msgid="3368984869083107200">"Buka"</string>
<string name="transfer_menu_clear" msgid="5854038118831427492">"Padam bersih daripada senarai"</string>
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"Padam bersih"</string>
+ <string name="bluetooth_a2dp_sink_queue_name" msgid="6864149958708669766">"Now Playing"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"Simpan"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"Batal"</string>
<string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"Pilih akaun yang anda ingin kongsikan melalui Bluetooth. Anda masih perlu menerima sebarang akses kepada akaun semasa menyambung."</string>
diff --git a/res/values-my/strings.xml b/res/values-my/strings.xml
index cfdea66..7e7763a 100644
--- a/res/values-my/strings.xml
+++ b/res/values-my/strings.xml
@@ -22,7 +22,7 @@
<string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"အက်ပ်အား ဘလူးတုသ်စက်ကို ယာယီခွင့်ပြုစာရင်းထဲ ထည့်ရန်ခွင့်ပြုကာ အသုံးပြုသူ၏ အတည်ပြုချက်မရယူပဲ ဖိုင်များကို စက်ထဲသို့ ပို့ခွင့်ပြုမည်"</string>
<string name="bt_share_picker_label" msgid="6268100924487046932">"ဘလူးတုသ်"</string>
<string name="unknown_device" msgid="9221903979877041009">"မသိသော စက်"</string>
- <string name="unknownNumber" msgid="4994750948072751566">"မသိပါ"</string>
+ <string name="unknownNumber" msgid="4994750948072751566">"မသိ"</string>
<string name="airplane_error_title" msgid="2683839635115739939">"လေယာဉ်ပျံမုဒ်"</string>
<string name="airplane_error_msg" msgid="8698965595254137230">"လေယာဥ်ပျံပေါ်သုံးစနစ်တွင် ဘလူးတုသ်အသုံးပြုမရပါ"</string>
<string name="bt_enable_title" msgid="8657832550503456572"></string>
@@ -84,8 +84,8 @@
<string name="bt_toast_4" msgid="4678812947604395649">"\"<xliff:g id="RECIPIENT">%1$s</xliff:g>\"ထံသို့ ဖိုင်ကိုပို့ပါ"</string>
<string name="bt_toast_5" msgid="2846870992823019494">"<xliff:g id="NUMBER">%1$s</xliff:g> ဖိုင်ကို \"<xliff:g id="RECIPIENT">%2$s</xliff:g>\"ထံသို့ ပို့ပါ"</string>
<string name="bt_toast_6" msgid="1855266596936622458">"\"<xliff:g id="RECIPIENT">%1$s</xliff:g>\"ထံသို့ ဖိုင်ကိုပို့ခြင်းအား ရပ်ဆိုင်းပါ"</string>
- <string name="bt_sm_2_1" product="nosdcard" msgid="352165168004521000">"\"<xliff:g id="SENDER">%1$s</xliff:g>\"မှဖိုင်ကိုသိမ်းဆည်းရန်အတွက် USBသိုလှောင်မှုတွင် လုံလောက်သောနေရာမရှိပါ"</string>
- <string name="bt_sm_2_1" product="default" msgid="1989018443456803630">"\"<xliff:g id="SENDER">%1$s</xliff:g>\"မှဖိုင်ကိုသိမ်းဆည်းရန်အတွက် SDကဒ်ထဲတွင် လုံလောက်သောနေရာမရှိပါ"</string>
+ <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" ထံမှ ဖိုင်ကိုသိမ်းရန် USB သိုလှောင်ခန်းတွင် နေရာမလုံလောက်ပါ"</string>
+ <string name="bt_sm_2_1_default" msgid="9115512207909504071">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" ထံမှ ဖိုင်ကိုသိမ်းရန် SD ကတ်ထဲတွင် နေရာမလုံလောက်ပါ"</string>
<string name="bt_sm_2_2" msgid="2965243265852680543">"လိုအပ်သော နေရာပမာဏ - <xliff:g id="SIZE">%1$s</xliff:g>"</string>
<string name="ErrorTooManyRequests" msgid="8578277541472944529">"တောင်းခံမှုများစွာကို ပြုလုပ်နေသည်၊ ခဏနေ ပြန်ကြိုးစားကြည့်ပါ"</string>
<string name="status_pending" msgid="2503691772030877944">"ဖိုင်လွှဲပြောင်းခြင်း မစရသေးပါ"</string>
@@ -95,8 +95,8 @@
<string name="status_forbidden" msgid="613956401054050725">"ဦးတည်ရာစက်မှ လွှဲပြောင်းခြင်းကို တားမြစ်ခြင်း"</string>
<string name="status_canceled" msgid="6664490318773098285">"အသုံးပြုသူမှ လွှဲပြောင်းခြင်းကို ဖယ်ဖျက်ရန်"</string>
<string name="status_file_error" msgid="3671917770630165299">"သိုလှောင်မှု အချက်"</string>
- <string name="status_no_sd_card" product="nosdcard" msgid="1112125377088421469">"USBသိုလှောင်ရာမရှိပါ"</string>
- <string name="status_no_sd_card" product="default" msgid="5760944071743325592">"SDကဒ်မရှိပါ၊ လွှဲပြောင်းထားသော ဖိုင်များကို သိမ်းရန် SDကဒ်ထည့်ပါ"</string>
+ <string name="status_no_sd_card_nosdcard" msgid="573631036356922221">"USB သိုလှောင်ခန်း မရှိပါ။"</string>
+ <string name="status_no_sd_card_default" msgid="396564893716701954">"SD ကတ်မရှိပါ။ လွှဲပြောင်းထားသော ဖိုင်များကို သိမ်းရန် SD ကတ်ထည့်ပါ။"</string>
<string name="status_connection_error" msgid="947681831523219891">"ချိတ်ဆက်ခြင်း မအောင်မြင်ပါ"</string>
<string name="status_protocol_error" msgid="3245444473429269539">"တောင်းခံခြင်းကို မှန်ကန်စွာကိုင်တွယ်မရပါ"</string>
<string name="status_unknown_error" msgid="8156660554237824912">"အမည်မသိသော မှားယွင်းမှု"</string>
@@ -122,6 +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">"ယခု ဖွင့်နေသည်"</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>
diff --git a/res/values-nb/strings.xml b/res/values-nb/strings.xml
index 12a24c4..e0418b1 100644
--- a/res/values-nb/strings.xml
+++ b/res/values-nb/strings.xml
@@ -84,8 +84,8 @@
<string name="bt_toast_4" msgid="4678812947604395649">"Sender fil til «<xliff:g id="RECIPIENT">%1$s</xliff:g>»"</string>
<string name="bt_toast_5" msgid="2846870992823019494">"Sender <xliff:g id="NUMBER">%1$s</xliff:g> filer til «<xliff:g id="RECIPIENT">%2$s</xliff:g>»"</string>
<string name="bt_toast_6" msgid="1855266596936622458">"Stoppet sendingen av fil til «<xliff:g id="RECIPIENT">%1$s</xliff:g>»"</string>
- <string name="bt_sm_2_1" product="nosdcard" msgid="352165168004521000">"Det er ikke nok plass på USB-lagringen til å lagre filen fra «<xliff:g id="SENDER">%1$s</xliff:g>»"</string>
- <string name="bt_sm_2_1" product="default" msgid="1989018443456803630">"Det er ikke nok plass på SD-kortet til å lagre filen fra «<xliff:g id="SENDER">%1$s</xliff:g>»"</string>
+ <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"Det er ikke nok plass på USB-lagringen til å lagre filen fra «<xliff:g id="SENDER">%1$s</xliff:g>»"</string>
+ <string name="bt_sm_2_1_default" msgid="9115512207909504071">"Det er ikke nok plass på SD-kortet til å lagre filen fra «<xliff:g id="SENDER">%1$s</xliff:g>»"</string>
<string name="bt_sm_2_2" msgid="2965243265852680543">"Nødvendig plass: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
<string name="ErrorTooManyRequests" msgid="8578277541472944529">"For mange forespørsler behandles for øyeblikket. Prøv på nytt senere."</string>
<string name="status_pending" msgid="2503691772030877944">"Filoverføringen har ikke startet ennå."</string>
@@ -95,8 +95,8 @@
<string name="status_forbidden" msgid="613956401054050725">"Overføring nektes av mottakerenheten."</string>
<string name="status_canceled" msgid="6664490318773098285">"Overføring avbrutt av bruker."</string>
<string name="status_file_error" msgid="3671917770630165299">"Problem med lagring."</string>
- <string name="status_no_sd_card" product="nosdcard" msgid="1112125377088421469">"Ingen USB-lagring."</string>
- <string name="status_no_sd_card" product="default" msgid="5760944071743325592">"Mangler minnekort. Sett inn et minnekort for å lagre overførte filer."</string>
+ <string name="status_no_sd_card_nosdcard" msgid="573631036356922221">"Ingen USB-lagring."</string>
+ <string name="status_no_sd_card_default" msgid="396564893716701954">"Mangler SD-kort. Sett inn et SD-kort for å lagre overførte filer."</string>
<string name="status_connection_error" msgid="947681831523219891">"Tilkobling mislyktes."</string>
<string name="status_protocol_error" msgid="3245444473429269539">"Kan ikke behandle forespørsel på riktig måte."</string>
<string name="status_unknown_error" msgid="8156660554237824912">"Ukjent feil."</string>
@@ -122,6 +122,7 @@
<string name="transfer_menu_open" msgid="3368984869083107200">"Åpne"</string>
<string name="transfer_menu_clear" msgid="5854038118831427492">"Fjern fra listen"</string>
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"Tøm"</string>
+ <string name="bluetooth_a2dp_sink_queue_name" msgid="6864149958708669766">"Spilles nå"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"Lagre"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"Avbryt"</string>
<string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"Velg kontoene du vil dele via Bluetooth. Du må fortsatt godta eventuell tilgang til kontoene når du kobler til."</string>
diff --git a/res/values-ne/strings.xml b/res/values-ne/strings.xml
index 24c8a16..639df71 100644
--- a/res/values-ne/strings.xml
+++ b/res/values-ne/strings.xml
@@ -84,8 +84,8 @@
<string name="bt_toast_4" msgid="4678812947604395649">"\"<xliff:g id="RECIPIENT">%1$s</xliff:g>\"लाई फाइल पठाउँदै"</string>
<string name="bt_toast_5" msgid="2846870992823019494">"<xliff:g id="NUMBER">%1$s</xliff:g> फाइल \"<xliff:g id="RECIPIENT">%2$s</xliff:g>\"लाई पठाउँदै"</string>
<string name="bt_toast_6" msgid="1855266596936622458">"\"<xliff:g id="RECIPIENT">%1$s</xliff:g> \"लाई फाइल पठाउन बन्द भयो"</string>
- <string name="bt_sm_2_1" product="nosdcard" msgid="352165168004521000">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" बाट फाइल बचत गर्न USB भण्डारणमा पर्याप्त ठाउँ छैन।"</string>
- <string name="bt_sm_2_1" product="default" msgid="1989018443456803630">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" बाट फाइल बचत गर्न SD कार्डमा पर्याप्त ठाउँ छैन"</string>
+ <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" बाट फाइल सुरक्षित गर्न USB भण्डारणमा पर्याप्त खाली ठाउँ छैन।"</string>
+ <string name="bt_sm_2_1_default" msgid="9115512207909504071">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" बाट फाइल सुरक्षित गर्न SD कार्डमा पर्याप्त खाली ठाउँ छैन"</string>
<string name="bt_sm_2_2" msgid="2965243265852680543">"ठाउँ चाहियो: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
<string name="ErrorTooManyRequests" msgid="8578277541472944529">"निकै धेरै अनुरोधहरू प्रसोधिन भइरहेका छन्। पछि फेरि प्रयास गर्नुहोस्।"</string>
<string name="status_pending" msgid="2503691772030877944">"फाइल स्थानान्तरण अहिलेसम्म सुरु भएको छैन।"</string>
@@ -95,8 +95,8 @@
<string name="status_forbidden" msgid="613956401054050725">"स्थानान्तरण लक्षित उपकरणद्वारा निषेध गरिएको छ।"</string>
<string name="status_canceled" msgid="6664490318773098285">"स्थानान्तरण प्रयोगकर्ताद्वारा रद्द गरियो।"</string>
<string name="status_file_error" msgid="3671917770630165299">"भण्डारण सवाल।"</string>
- <string name="status_no_sd_card" product="nosdcard" msgid="1112125377088421469">"कुनै USB भण्डारण छैन।"</string>
- <string name="status_no_sd_card" product="default" msgid="5760944071743325592">"कुनै SD कार्ड छैन। स्थानान्तरण गरिएका फाइलहरूलाई बचत गर्न एउटा SD कार्ड छिराउनुहोस्।"</string>
+ <string name="status_no_sd_card_nosdcard" msgid="573631036356922221">"कुनै पनि USB भण्डारण छैन।"</string>
+ <string name="status_no_sd_card_default" msgid="396564893716701954">"कुनै पनि SD कार्ड छैन। स्थानान्तरण गरिएका फाइलहरू सुरक्षित गर्न SD कार्ड छिराउनुहोस्।"</string>
<string name="status_connection_error" msgid="947681831523219891">"जडान असफल।"</string>
<string name="status_protocol_error" msgid="3245444473429269539">"अनुरोधलाई सही रूपमा सम्हाल्न सकिँदैन।"</string>
<string name="status_unknown_error" msgid="8156660554237824912">"अज्ञात त्रुटि।"</string>
@@ -122,6 +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_map_settings_save" msgid="7635491847388074606">"सुरक्षित गर्नुहोस्"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"रद्द गर्नुहोस्"</string>
<string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"तपाईंले ब्लुटुथ मार्फत साझेदारी गर्न चाहेका खाताहरू चयन गर्नुहोस्। तपाईंले अझै पनि खाताहरूमा जडान गर्दा कुनै पनि पहुँच स्वीकार गर्नुपर्छ।"</string>
diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml
index 39e300d..9207378 100644
--- a/res/values-nl/strings.xml
+++ b/res/values-nl/strings.xml
@@ -84,8 +84,8 @@
<string name="bt_toast_4" msgid="4678812947604395649">"Bestand verzenden naar \'<xliff:g id="RECIPIENT">%1$s</xliff:g>\'"</string>
<string name="bt_toast_5" msgid="2846870992823019494">"<xliff:g id="NUMBER">%1$s</xliff:g> bestanden verzenden naar \'<xliff:g id="RECIPIENT">%2$s</xliff:g>\'"</string>
<string name="bt_toast_6" msgid="1855266596936622458">"Verzenden van bestand naar \'<xliff:g id="RECIPIENT">%1$s</xliff:g>\' is beëindigd"</string>
- <string name="bt_sm_2_1" product="nosdcard" msgid="352165168004521000">"Er is onvoldoende ruimte in de USB-opslag beschikbaar om het bestand van \'<xliff:g id="SENDER">%1$s</xliff:g>\' op te slaan"</string>
- <string name="bt_sm_2_1" product="default" msgid="1989018443456803630">"Er is onvoldoende ruimte op de SD-kaart beschikbaar om het bestand van \'<xliff:g id="SENDER">%1$s</xliff:g>\' op te slaan"</string>
+ <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"Er is onvoldoende ruimte in de USB-opslag beschikbaar om het bestand van \'<xliff:g id="SENDER">%1$s</xliff:g>\' op te slaan"</string>
+ <string name="bt_sm_2_1_default" msgid="9115512207909504071">"Er is onvoldoende ruimte op de SD-kaart beschikbaar om het bestand van \'<xliff:g id="SENDER">%1$s</xliff:g>\' op te slaan"</string>
<string name="bt_sm_2_2" msgid="2965243265852680543">"Benodigde ruimte: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
<string name="ErrorTooManyRequests" msgid="8578277541472944529">"Er worden te veel aanvragen verwerkt. Probeer het later opnieuw."</string>
<string name="status_pending" msgid="2503691772030877944">"Bestandsoverdracht nog niet gestart."</string>
@@ -95,8 +95,8 @@
<string name="status_forbidden" msgid="613956401054050725">"Overdracht wordt niet toegestaan door het doelapparaat."</string>
<string name="status_canceled" msgid="6664490318773098285">"Overdracht geannuleerd door gebruiker."</string>
<string name="status_file_error" msgid="3671917770630165299">"Probleem met opslagruimte."</string>
- <string name="status_no_sd_card" product="nosdcard" msgid="1112125377088421469">"Geen USB-opslag."</string>
- <string name="status_no_sd_card" product="default" msgid="5760944071743325592">"Geen SD-kaart. Plaats een SD-kaart om overgedragen bestanden op te slaan."</string>
+ <string name="status_no_sd_card_nosdcard" msgid="573631036356922221">"Geen USB-opslag."</string>
+ <string name="status_no_sd_card_default" msgid="396564893716701954">"Geen SD-kaart. Plaats een SD-kaart om overgedragen bestanden op te slaan."</string>
<string name="status_connection_error" msgid="947681831523219891">"Verbinding mislukt."</string>
<string name="status_protocol_error" msgid="3245444473429269539">"Het verzoek kan niet correct worden verwerkt."</string>
<string name="status_unknown_error" msgid="8156660554237824912">"Onbekende fout."</string>
@@ -122,6 +122,7 @@
<string name="transfer_menu_open" msgid="3368984869083107200">"Openen"</string>
<string name="transfer_menu_clear" msgid="5854038118831427492">"Wissen uit lijst"</string>
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"Wissen"</string>
+ <string name="bluetooth_a2dp_sink_queue_name" msgid="6864149958708669766">"Now Playing"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"Opslaan"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"Annuleren"</string>
<string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"Selecteer de accounts die je wilt delen via Bluetooth. Je moet nog steeds elke toegang tot de accounts accepteren wanneer er verbinding wordt gemaakt."</string>
diff --git a/res/values-or/strings.xml b/res/values-or/strings.xml
index b93a78d..8819773 100644
--- a/res/values-or/strings.xml
+++ b/res/values-or/strings.xml
@@ -18,15 +18,15 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="permlab_bluetoothShareManager" msgid="311492132450338925">"ଡାଉନଲୋଡ୍ ମ୍ୟାନେଜର୍କୁ ଆକ୍ସେସ୍ କରନ୍ତୁ।"</string>
<string name="permdesc_bluetoothShareManager" msgid="8930572979123190223">"BluetoothShare ମ୍ୟାନେଜର୍ ଆକ୍ସେସ୍ କରି ଫାଇଲ୍ଗୁଡ଼ିକ ଟ୍ରାନ୍ସଫର୍ କରିବାକୁ ବ୍ୟବହାର କରିବା ପାଇଁ ଆପ୍କୁ ଅନୁମତି ଦିଅନ୍ତୁ।"</string>
- <string name="permlab_bluetoothWhitelist" msgid="7091552898592306386">"ବ୍ଲୁ-ଟୂଥ୍ ଡିଭାଇସ୍ ଆକ୍ସେସ୍କୁ ସ୍ୱୀକୃତି ଦିଅନ୍ତୁ।"</string>
- <string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"ୟୁଜର୍ଙ୍କ ସୁନିଶ୍ଚିତତା ବିନା ଏହି ଡିଭାଇସ୍କୁ ଫାଇଲ୍ ପଠାଇବା ନିମନ୍ତେ ଗୋଟିଏ ବ୍ଲୁ-ଟୂଥ୍ ଡିଭାଇସ୍କୁ ଅନୁମତି ଦେଇ କିଛି ସମୟ ପାଇଁ ଆପ୍କୁ ସ୍ୱୀକୃତି ଦେଇଥାଏ।"</string>
- <string name="bt_share_picker_label" msgid="6268100924487046932">"ବ୍ଲୁ-ଟୂଥ୍"</string>
+ <string name="permlab_bluetoothWhitelist" msgid="7091552898592306386">"ବ୍ଲୁଟୂଥ୍ ଡିଭାଇସ୍ ଆକ୍ସେସ୍କୁ ସ୍ୱୀକୃତି ଦିଅନ୍ତୁ।"</string>
+ <string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"ୟୁଜର୍ଙ୍କ ସୁନିଶ୍ଚିତତା ବିନା ଏହି ଡିଭାଇସ୍କୁ ଫାଇଲ୍ ପଠାଇବା ନିମନ୍ତେ ଗୋଟିଏ ବ୍ଲୁଟୂଥ୍ ଡିଭାଇସ୍କୁ ଅନୁମତି ଦେଇ କିଛି ସମୟ ପାଇଁ ଆପ୍କୁ ସ୍ୱୀକୃତି ଦେଇଥାଏ।"</string>
+ <string name="bt_share_picker_label" msgid="6268100924487046932">"ବ୍ଲୁଟୂଥ୍"</string>
<string name="unknown_device" msgid="9221903979877041009">"ଅଜଣା ଡିଭାଇସ୍"</string>
<string name="unknownNumber" msgid="4994750948072751566">"ଅଜଣା"</string>
<string name="airplane_error_title" msgid="2683839635115739939">"ଏରୋପ୍ଲେନ୍ ମୋଡ୍"</string>
- <string name="airplane_error_msg" msgid="8698965595254137230">"ଆପଣ, ଏୟାରପ୍ଲେନ୍ ମୋଡ୍ରେ ବ୍ଲୁ-ଟୂଥ୍ ବ୍ୟବହାର କରିପାରିବେ ନାହିଁ।"</string>
+ <string name="airplane_error_msg" msgid="8698965595254137230">"ଆପଣ, ଏୟାରପ୍ଲେନ୍ ମୋଡ୍ରେ ବ୍ଲୁଟୂଥ୍ ବ୍ୟବହାର କରିପାରିବେ ନାହିଁ।"</string>
<string name="bt_enable_title" msgid="8657832550503456572"></string>
- <string name="bt_enable_line1" msgid="7203551583048149">"ବ୍ଲୁ-ଟୂଥ୍ ସେବା ବ୍ୟବହାର କରିବା ପାଇଁ, ଆପଣଙ୍କୁ ପ୍ରଥମେ ବ୍ଲୁ-ଟୂଥ୍ ଅନ୍ କରିବାକୁ ପଡ଼ିବ।"</string>
+ <string name="bt_enable_line1" msgid="7203551583048149">"ବ୍ଲୁଟୂଥ୍ ସେବା ବ୍ୟବହାର କରିବା ପାଇଁ, ଆପଣଙ୍କୁ ପ୍ରଥମେ ବ୍ଲୁଟୂଥ୍ ଅନ୍ କରିବାକୁ ପଡ଼ିବ।"</string>
<string name="bt_enable_line2" msgid="4341936569415937994">"ବ୍ଲୁ-ଟୁଥ୍କୁ ଏବେ ଅନ୍ କରିବେ?"</string>
<string name="bt_enable_cancel" msgid="1988832367505151727">"କ୍ୟାନ୍ସଲ୍"</string>
<string name="bt_enable_ok" msgid="3432462749994538265">"ଅନ୍ କରନ୍ତୁ"</string>
@@ -38,13 +38,13 @@
<string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"\"<xliff:g id="SENDER">%1$s</xliff:g>\"ଙ୍କଠାରୁ ଆସୁଥିବା ଫାଇଲ୍ ସ୍ୱୀକାର କରୁଥିବାବେଳେ ସମୟ ସମାପ୍ତ ହୋଇଗଲା"</string>
<string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"ଆସୁଥିବା ଫାଇଲ୍"</string>
<string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="FILE">%2$s</xliff:g> ପଠାଇବା ପାଇଁ <xliff:g id="SENDER">%1$s</xliff:g> ପ୍ରସ୍ତୁତ"</string>
- <string name="notification_receiving" msgid="4674648179652543984">"ବ୍ଲୁ-ଟୂଥ୍ ଶେୟାର୍: <xliff:g id="FILE">%1$s</xliff:g> ପ୍ରାପ୍ତ କରୁଛି"</string>
- <string name="notification_received" msgid="3324588019186687985">"ବ୍ଲୁ-ଟୂଥ୍ ଶେୟାର୍: <xliff:g id="FILE">%1$s</xliff:g> ପ୍ରାପ୍ତ କରାଯାଇଛି"</string>
- <string name="notification_received_fail" msgid="3619350997285714746">"ବ୍ଲୁ-ଟୂଥ୍ ଶେୟାର୍: <xliff:g id="FILE">%1$s</xliff:g> ଫାଇଲ୍ ପ୍ରାପ୍ତ କରାଯାଇନାହିଁ"</string>
- <string name="notification_sending" msgid="3035748958534983833">"ବ୍ଲୁ-ଟୂଥ୍ ଶେୟାର୍: <xliff:g id="FILE">%1$s</xliff:g> ପଠାଉଛି"</string>
- <string name="notification_sent" msgid="9218710861333027778">"ବ୍ଲୁ-ଟୂଥ୍ ଶେୟାର୍: <xliff:g id="FILE">%1$s</xliff:g> ପଠାଗଲା"</string>
+ <string name="notification_receiving" msgid="4674648179652543984">"ବ୍ଲୁଟୂଥ୍ ସେୟାର୍: <xliff:g id="FILE">%1$s</xliff:g> ପ୍ରାପ୍ତ କରୁଛି"</string>
+ <string name="notification_received" msgid="3324588019186687985">"ବ୍ଲୁଟୂଥ୍ ସେୟାର୍: <xliff:g id="FILE">%1$s</xliff:g> ପ୍ରାପ୍ତ କରାଯାଇଛି"</string>
+ <string name="notification_received_fail" msgid="3619350997285714746">"ବ୍ଲୁଟୂଥ୍ ସେୟାର୍: <xliff:g id="FILE">%1$s</xliff:g> ଫାଇଲ୍ ପ୍ରାପ୍ତ କରାଯାଇନାହିଁ"</string>
+ <string name="notification_sending" msgid="3035748958534983833">"ବ୍ଲୁଟୂଥ୍ ସେୟାର୍: <xliff:g id="FILE">%1$s</xliff:g> ପଠାଉଛି"</string>
+ <string name="notification_sent" msgid="9218710861333027778">"ବ୍ଲୁଟୂଥ୍ ସେୟାର୍: <xliff:g id="FILE">%1$s</xliff:g> ପଠାଗଲା"</string>
<string name="notification_sent_complete" msgid="302943281067557969">"100% ସମ୍ପୂର୍ଣ୍ଣ"</string>
- <string name="notification_sent_fail" msgid="6696082233774569445">"ବ୍ଲୁ-ଟୂଥ୍ ଶେୟାର୍: <xliff:g id="FILE">%1$s</xliff:g> ଫାଇଲ୍ ପଠାଯାଇନାହିଁ"</string>
+ <string name="notification_sent_fail" msgid="6696082233774569445">"ବ୍ଲୁଟୂଥ୍ ସେୟାର୍: <xliff:g id="FILE">%1$s</xliff:g> ଫାଇଲ୍ ପଠାଯାଇନାହିଁ"</string>
<string name="download_title" msgid="3353228219772092586">"ଫାଇଲ୍ ଟ୍ରାନ୍ସଫର୍ କରନ୍ତୁ"</string>
<string name="download_line1" msgid="4926604799202134144">"ପ୍ରେରକ: \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
<string name="download_line2" msgid="5876973543019417712">"ଫାଇଲ୍: <xliff:g id="FILE">%1$s</xliff:g>"</string>
@@ -77,15 +77,15 @@
<string name="not_exist_file" msgid="3489434189599716133">"କୌଣସି ଫାଇଲ୍ ନାହିଁ"</string>
<string name="not_exist_file_desc" msgid="4059531573790529229">"ଏଭଳି କୌଣସି ଫାଇଲ୍ ନାହିଁ। \n"</string>
<string name="enabling_progress_title" msgid="436157952334723406">"ଦୟାକରି ଅପେକ୍ଷା କରନ୍ତୁ…"</string>
- <string name="enabling_progress_content" msgid="4601542238119927904">"ବ୍ଲୁ-ଟୂଥ୍ ଅନ୍ କରୁଛି…"</string>
+ <string name="enabling_progress_content" msgid="4601542238119927904">"ବ୍ଲୁଟୂଥ୍ ଅନ୍ କରୁଛି…"</string>
<string name="bt_toast_1" msgid="972182708034353383">"ଫାଇଲ୍କୁ ଗ୍ରହଣ କରାଯିବ। ବିଜ୍ଞପ୍ତି ପ୍ୟାନେଲ୍ରେ ପ୍ରଗତିକୁ ଦେଖନ୍ତୁ।"</string>
<string name="bt_toast_2" msgid="8602553334099066582">"ଫାଇଲ୍କୁ ଗ୍ରହଣ କରାଯାଇପାରିବ ନାହିଁ।"</string>
<string name="bt_toast_3" msgid="6707884165086862518">"\"<xliff:g id="SENDER">%1$s</xliff:g>\"ଙ୍କଠାରୁ ଗ୍ରହଣ କରିବା ବନ୍ଦ ହୋଇଗଲା"</string>
<string name="bt_toast_4" msgid="4678812947604395649">"\"<xliff:g id="RECIPIENT">%1$s</xliff:g>\"ଙ୍କୁ ଫାଇଲ୍ ପଠାଯାଉଛି"</string>
<string name="bt_toast_5" msgid="2846870992823019494">"\"<xliff:g id="RECIPIENT">%2$s</xliff:g>\"ଙ୍କୁ <xliff:g id="NUMBER">%1$s</xliff:g>ଟି ଫାଇଲ୍ ପଠାଯାଉଛି"</string>
<string name="bt_toast_6" msgid="1855266596936622458">"\"<xliff:g id="RECIPIENT">%1$s</xliff:g>\"ଙ୍କୁ ଫାଇଲ୍ ପଠାଇବା ବନ୍ଦ ହୋଇଗଲା"</string>
- <string name="bt_sm_2_1" product="nosdcard" msgid="352165168004521000">"\"<xliff:g id="SENDER">%1$s</xliff:g>\"ଙ୍କଠାରୁ ଫାଇଲ୍ ସେଭ୍ କରିବା ପାଇଁ USB ଷ୍ଟୋରେଜ୍ରେ ଯଥେଷ୍ଟ ସ୍ପେସ୍ ନାହିଁ"</string>
- <string name="bt_sm_2_1" product="default" msgid="1989018443456803630">"\"<xliff:g id="SENDER">%1$s</xliff:g>\"ଙ୍କଠାରୁ ଫାଇଲ୍ ସେଭ୍ କରିବା ପାଇଁ SD କାର୍ଡରେ ଯଥେଷ୍ଟ ସ୍ପେସ୍ ନାହିଁ"</string>
+ <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"\"<xliff:g id="SENDER">%1$s</xliff:g>\"ଙ୍କ ଠାରୁ ଫାଇଲ୍ ସେଭ୍ କରିବା ପାଇଁ USB ଷ୍ଟୋରେଜ୍ରେ ଯଥେଷ୍ଟ ସ୍ପେସ୍ ନାହିଁ"</string>
+ <string name="bt_sm_2_1_default" msgid="9115512207909504071">"\"<xliff:g id="SENDER">%1$s</xliff:g>\"ଙ୍କ ଠାରୁ ଫାଇଲ୍ ସେଭ୍ କରିବା ପାଇଁ SD କାର୍ଡରେ ଯଥେଷ୍ଟ ସ୍ପେସ୍ ନାହିଁ"</string>
<string name="bt_sm_2_2" msgid="2965243265852680543">"ସ୍ପେସ୍ ଆବଶ୍ୟକ: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
<string name="ErrorTooManyRequests" msgid="8578277541472944529">"ଅନେକ ଅନୁରୋଧ ଉପରେ କାମ ଚାଲୁଛି। ପରେ ପୁଣି ଚେଷ୍ଟା କରନ୍ତୁ।"</string>
<string name="status_pending" msgid="2503691772030877944">"ଏପର୍ଯ୍ୟନ୍ତ ଫାଇଲ୍ ଟ୍ରାନ୍ସଫର୍ ଆରମ୍ଭ ହୋଇନାହିଁ।"</string>
@@ -95,21 +95,21 @@
<string name="status_forbidden" msgid="613956401054050725">"ଟାର୍ଗେଟ୍ ଡିଭାଇସ୍ ଦ୍ୱାରା ଟ୍ରାନ୍ସଫର୍ ପ୍ରତିବନ୍ଧିତ କରାଯାଇଛି।"</string>
<string name="status_canceled" msgid="6664490318773098285">"ୟୁଜର୍ଙ୍କ ଦ୍ୱାରା ଟ୍ରାନ୍ସଫର୍ କ୍ୟାନ୍ସଲ୍ କରାଗଲା।"</string>
<string name="status_file_error" msgid="3671917770630165299">"ଷ୍ଟୋରେଜ୍ ସମସ୍ୟା।"</string>
- <string name="status_no_sd_card" product="nosdcard" msgid="1112125377088421469">"କୌଣସି USB ଷ୍ଟୋରେଜ୍ ନାହିଁ।"</string>
- <string name="status_no_sd_card" product="default" msgid="5760944071743325592">"କୌଣସି SD କାର୍ଡ ନାହିଁ। ଟ୍ରାନ୍ସଫର୍ କରାଯାଇଥିବା ଫାଇଲ୍ଗୁଡ଼ିକୁ ସେଭ୍ କରିବା ପାଇଁ ଗୋଟିଏ SD କାର୍ଡ ଭର୍ତ୍ତି କରନ୍ତୁ।"</string>
+ <string name="status_no_sd_card_nosdcard" msgid="573631036356922221">"କୌଣସି USB ଷ୍ଟୋରେଜ୍ ନାହିଁ।"</string>
+ <string name="status_no_sd_card_default" msgid="396564893716701954">"କୌଣସି SD କାର୍ଡ ନାହିଁ। ଟ୍ରାନ୍ସଫର୍ କରାଯାଇଥିବା ଫାଇଲ୍ଗୁଡ଼ିକୁ ସେଭ୍ କରିବା ପାଇଁ ଗୋଟିଏ SD କାର୍ଡ ଭର୍ତ୍ତି କରନ୍ତୁ।"</string>
<string name="status_connection_error" msgid="947681831523219891">"ସଂଯୋଗ ବିଫଳ ହେଲା।"</string>
<string name="status_protocol_error" msgid="3245444473429269539">"ଅନୁରୋଧକୁ ଠିକ୍ ଭାବେ ସମ୍ଭାଳି ହେବନାହିଁ।"</string>
<string name="status_unknown_error" msgid="8156660554237824912">"ଅଜଣା ତୃଟି।"</string>
- <string name="btopp_live_folder" msgid="7967791481444474554">"ବ୍ଲୁ-ଟୂଥ୍ ପ୍ରାପ୍ତ ହେଲା"</string>
- <string name="opp_notification_group" msgid="3486303082135789982">"ବ୍ଲୁ-ଟୂଥ୍ ଶେୟାର୍"</string>
+ <string name="btopp_live_folder" msgid="7967791481444474554">"ବ୍ଲୁଟୂଥ୍ ପ୍ରାପ୍ତ ହେଲା"</string>
+ <string name="opp_notification_group" msgid="3486303082135789982">"ବ୍ଲୁଟୂଥ୍ ସେୟାର୍"</string>
<string name="download_success" msgid="7036160438766730871">"<xliff:g id="FILE_SIZE">%1$s</xliff:g> ପ୍ରାପ୍ତ କରିବା ସମ୍ପୂର୍ଣ୍ଣ ହେଲା।"</string>
<string name="upload_success" msgid="4014469387779648949">"<xliff:g id="FILE_SIZE">%1$s</xliff:g> ପଠାଇବା ସମ୍ପୂର୍ଣ୍ଣ ହେଲା।"</string>
<string name="inbound_history_title" msgid="6940914942271327563">"ଇନ୍ବାଉଣ୍ଡ ଟ୍ରାନ୍ସଫର୍"</string>
<string name="outbound_history_title" msgid="4279418703178140526">"ଆଉଟ୍ବାଉଣ୍ଡ ଟ୍ରାନ୍ସଫର୍"</string>
- <string name="no_transfers" msgid="3482965619151865672">"ଟ୍ରାନ୍ସଫର୍ ହିଷ୍ଟୋରୀ ଖାଲି ଅଛି।"</string>
+ <string name="no_transfers" msgid="3482965619151865672">"ସ୍ଥାନାନ୍ତର ଇତିହାସ ଖାଲି ଅଛି।"</string>
<string name="transfer_clear_dlg_msg" msgid="1712376797268438075">"ତାଲିକାରୁ ସମସ୍ତ ଆଇଟମ୍କୁ ଖାଲି କରିଦିଆଯିବ।"</string>
- <string name="outbound_noti_title" msgid="8051906709452260849">"ବ୍ଲୁ-ଟୂଥ୍ ଶେୟାର୍: ପଠାଯାଇଥିବା ଫାଇଲ୍"</string>
- <string name="inbound_noti_title" msgid="4143352641953027595">"ବ୍ଲୁ-ଟୂଥ୍ ଶେୟାର୍: ପ୍ରାପ୍ତ କରାଯାଇଥିବା ଫାଇଲ୍"</string>
+ <string name="outbound_noti_title" msgid="8051906709452260849">"ବ୍ଲୁଟୂଥ୍ ସେୟାର୍: ପଠାଯାଇଥିବା ଫାଇଲ୍"</string>
+ <string name="inbound_noti_title" msgid="4143352641953027595">"ବ୍ଲୁଟୂଥ୍ ସେୟାର୍: ପ୍ରାପ୍ତ କରାଯାଇଥିବା ଫାଇଲ୍"</string>
<plurals name="noti_caption_unsuccessful" formatted="false" msgid="2020750076679526122">
<item quantity="other"><xliff:g id="UNSUCCESSFUL_NUMBER_1">%1$d</xliff:g> ବିଫଳ.</item>
<item quantity="one"><xliff:g id="UNSUCCESSFUL_NUMBER_0">%1$d</xliff:g> ବିଫଳ.</item>
@@ -122,15 +122,16 @@
<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">"ବର୍ତ୍ତମାନ ଚାଲୁଛି"</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>
+ <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_title" msgid="7420332483392851321">"ବ୍ଲୁ-ଟୂଥ୍ ମେସେଜ୍ ଶେୟାରିଙ୍ଗ ସେଟିଙ୍ଗ"</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_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">"4GBରୁ ବଡ଼ ଫାଇଲ୍ଗୁଡ଼ିକୁ ଟ୍ରାନ୍ସଫର୍ କରାଯାଇପାରିବ ନାହିଁ"</string>
</resources>
diff --git a/res/values-or/strings_pbap.xml b/res/values-or/strings_pbap.xml
index 804f4a2..844a464 100644
--- a/res/values-or/strings_pbap.xml
+++ b/res/values-or/strings_pbap.xml
@@ -2,7 +2,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="pbap_session_key_dialog_title" msgid="3580996574333882561">"%1$s ପାଇଁ ସେସନ୍ କୀ’ ଟାଇପ୍ କରନ୍ତୁ"</string>
- <string name="pbap_session_key_dialog_header" msgid="2772472422782758981">"ବ୍ଲୁ-ଟୂଥ୍ ସେସନ୍ କୀ ଆବଶ୍ୟକ"</string>
+ <string name="pbap_session_key_dialog_header" msgid="2772472422782758981">"ବ୍ଲୁଟୂଥ୍ ସେସନ୍ କୀ ଆବଶ୍ୟକ"</string>
<string name="pbap_acceptance_timeout_message" msgid="1107401415099814293">"%1$s ସହ ଯୋଡ଼ି ହେବାର ସମୟ ସମାପ୍ତ ହୋଇଯାଇଛି"</string>
<string name="pbap_authentication_timeout_message" msgid="4166979525521902687">"%1$s ସହ ସେସନ୍ କୀ’ ଲେଖିବାର ସମୟ ସମାପ୍ତ ହୋଇଯାଇଛି"</string>
<string name="auth_notif_ticker" msgid="1575825798053163744">"Obex ପ୍ରମାଣୀକରଣ ଅନୁରୋଧ"</string>
@@ -12,5 +12,5 @@
<string name="unknownName" msgid="2841414754740600042">"ଅଜଣା ନାମ"</string>
<string name="localPhoneName" msgid="2349001318925409159">"ମୋର ନାମ"</string>
<string name="defaultnumber" msgid="8520116145890867338">"000000"</string>
- <string name="pbap_notification_group" msgid="8487669554703627168">"ବ୍ଲୁ-ଟୂଥ୍ ଯୋଗାଯୋଗ ଶେୟାର୍ କରନ୍ତୁ"</string>
+ <string name="pbap_notification_group" msgid="8487669554703627168">"ବ୍ଲୁଟୂଥ୍ ଯୋଗାଯୋଗ ସେୟାର୍ କରନ୍ତୁ"</string>
</resources>
diff --git a/res/values-or/strings_sap.xml b/res/values-or/strings_sap.xml
index f52d36f..aa190db 100644
--- a/res/values-or/strings_sap.xml
+++ b/res/values-or/strings_sap.xml
@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="bluetooth_sap_notif_title" msgid="6877860822993195074">"ବ୍ଲୁ-ଟୂଥ୍ SIM ଆକ୍ସେସ୍"</string>
- <string name="bluetooth_sap_notif_ticker" msgid="6807778527893726699">"ବ୍ଲୁ-ଟୂଥ୍ SIM ଆକ୍ସେସ୍"</string>
+ <string name="bluetooth_sap_notif_title" msgid="6877860822993195074">"ବ୍ଲୁଟୂଥ୍ SIM ଆକ୍ସେସ୍"</string>
+ <string name="bluetooth_sap_notif_ticker" msgid="6807778527893726699">"ବ୍ଲୁଟୂଥ୍ SIM ଆକ୍ସେସ୍"</string>
<string name="bluetooth_sap_notif_message" msgid="7138657801087500690">"ବିଚ୍ଛିନ୍ନ କରିବା ପାଇଁ କ୍ଲାଏଣ୍ଟଙ୍କୁ ଅନୁରୋଧ କରିବେ?"</string>
<string name="bluetooth_sap_notif_disconnecting" msgid="819150843490233288">"ବିଚ୍ଛିନ୍ନ କରିବା ପାଇଁ କ୍ଲାଏଣ୍ଟଙ୍କ ଅପେକ୍ଷା କରାଯାଉଛି"</string>
<string name="bluetooth_sap_notif_disconnect_button" msgid="3678476872583356919">"ବିଚ୍ଛିନ୍ନ କରନ୍ତୁ"</string>
diff --git a/res/values-or/test_strings.xml b/res/values-or/test_strings.xml
index 693cd65..4649a0a 100644
--- a/res/values-or/test_strings.xml
+++ b/res/values-or/test_strings.xml
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="app_name" msgid="6006644116867509664">"ବ୍ଲୁ-ଟୂଥ୍"</string>
+ <string name="app_name" msgid="6006644116867509664">"ବ୍ଲୁଟୂଥ୍"</string>
<string name="insert_record" msgid="1450997173838378132">"ରେକର୍ଡ ଭର୍ତ୍ତି କରନ୍ତୁ"</string>
<string name="update_record" msgid="2480425402384910635">"ରେକର୍ଡ ସୁନିଶ୍ଚିତ କରନ୍ତୁ"</string>
<string name="ack_record" msgid="6716152390978472184">"ରେକର୍ଡ ସ୍ୱୀକୃତ କରନ୍ତୁ"</string>
diff --git a/res/values-pa/strings.xml b/res/values-pa/strings.xml
index 0e6a0f1..07cab6a 100644
--- a/res/values-pa/strings.xml
+++ b/res/values-pa/strings.xml
@@ -17,7 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="permlab_bluetoothShareManager" msgid="311492132450338925">"ਡਾਊਨਲੋਡ ਪ੍ਰਬੰਧਕ ਤੱਕ ਪਹੁੰਚ।"</string>
- <string name="permdesc_bluetoothShareManager" msgid="8930572979123190223">"ਐਪ ਨੂੰ ਬਲੂਟੁੱਥShare ਪ੍ਰਬੰਧਕ ਤੱਕ ਪਹੁੰਚ ਅਤੇ ਫ਼ਾਈਲਾਂ ਟ੍ਰਾਂਸਫਰ ਕਰਨ ਲਈ ਇਸਦੀ ਵਰਤੋਂ ਕਰਨ ਦੀ ਆਗਿਆ ਦਿੰਦਾ ਹੈ।"</string>
+ <string name="permdesc_bluetoothShareManager" msgid="8930572979123190223">"ਐਪ ਨੂੰ BluetoothShare ਪ੍ਰਬੰਧਕ ਤੱਕ ਪਹੁੰਚ ਅਤੇ ਫ਼ਾਈਲਾਂ ਟ੍ਰਾਂਸਫਰ ਕਰਨ ਲਈ ਇਸਦੀ ਵਰਤੋਂ ਕਰਨ ਦਿੰਦਾ ਹੈ।"</string>
<string name="permlab_bluetoothWhitelist" msgid="7091552898592306386">"ਵਾਈਟਲਿਸਟ ਬਲੂਟੱਥ ਡੀਵਾਈਸ ਪਹੁੰਚ।"</string>
<string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"ਐਪ ਨੂੰ ਇਹ ਆਗਿਆ ਦਿੰਦੇ ਹੋਏ ਕਿ ਡੀਵਾਈਸ ਵਰਤੋਂਕਾਰ ਦੀ ਪੁਸ਼ਟੀ ਤੋਂ ਬਿਨਾਂ ਇਸ ਡੀਵਾਈਸ ਨੂੰ ਫ਼ਾਈਲਾਂ ਭੇਜੇ, ਇੱਕ ਬਲੂਟੁੱਥ ਡੀਵਾਈਸ ਨੂੰ ਅਸਥਾਈ ਤੌਰ ਤੇ ਵਾਈਟਲਿਸਟ ਕਰਨ ਦੀ ਆਗਿਆ ਦਿੰਦਾ ਹੈ।"</string>
<string name="bt_share_picker_label" msgid="6268100924487046932">"ਬਲੂਟੁੱਥ"</string>
@@ -84,8 +84,8 @@
<string name="bt_toast_4" msgid="4678812947604395649">"\"<xliff:g id="RECIPIENT">%1$s</xliff:g>\" ਨੂੰ ਫਾਈਲ ਭੇਜ ਰਿਹਾ ਹੈ"</string>
<string name="bt_toast_5" msgid="2846870992823019494">"\"<xliff:g id="RECIPIENT">%2$s</xliff:g>\" ਨੂੰ <xliff:g id="NUMBER">%1$s</xliff:g> ਫਾਈਲਾਂ ਭੇਜ ਰਿਹਾ ਹੈ"</string>
<string name="bt_toast_6" msgid="1855266596936622458">"\"<xliff:g id="RECIPIENT">%1$s</xliff:g>\" ਨੂੰ ਫਾਈਲ ਭੇਜਣਾ ਰੋਕਿਆ ਗਿਆ"</string>
- <string name="bt_sm_2_1" product="nosdcard" msgid="352165168004521000">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" ਦੀ ਫ਼ਾਈਲ ਰੱਖਿਅਤ ਕਰਨ ਲਈ USB ਸਟੋਰੇਜ ਵਿੱਚ ਪੂਰੀ ਜਗ੍ਹਾ ਨਹੀਂ ਹੈ।"</string>
- <string name="bt_sm_2_1" product="default" msgid="1989018443456803630">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" ਦੀ ਫ਼ਾਈਲ ਰੱਖਿਅਤ ਕਰਨ ਲਈ SD ਕਾਰਡ ਵਿੱਚ ਪੂਰੀ ਜਗ੍ਹਾ ਨਹੀਂ ਹੈ।"</string>
+ <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" ਦੀ ਫ਼ਾਈਲ ਰੱਖਿਅਤ ਕਰਨ ਲਈ USB ਸਟੋਰੇਜ ਵਿੱਚ ਪੂਰੀ ਜਗ੍ਹਾ ਨਹੀਂ ਹੈ।"</string>
+ <string name="bt_sm_2_1_default" msgid="9115512207909504071">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" ਦੀ ਫ਼ਾਈਲ ਰੱਖਿਅਤ ਕਰਨ ਲਈ SD ਕਾਰਡ ਵਿੱਚ ਪੂਰੀ ਜਗ੍ਹਾ ਨਹੀਂ ਹੈ।"</string>
<string name="bt_sm_2_2" msgid="2965243265852680543">"ਲੁੜੀਂਦਾ ਸਪੇਸ: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
<string name="ErrorTooManyRequests" msgid="8578277541472944529">"ਬਹੁਤ ਜ਼ਿਆਦਾ ਬੇਨਤੀਆਂ ਦੀ ਪ੍ਰਕਿਰਿਆ ਕੀਤੀ ਜਾ ਰਹੀ ਹੈ। ਬਾਅਦ ਵਿੱਚ ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ।"</string>
<string name="status_pending" msgid="2503691772030877944">"ਫਾਈਲ ਟ੍ਰਾਂਸਫਰ ਅਜੇ ਚਾਲੂ ਨਹੀਂ ਹੋਈ।"</string>
@@ -95,8 +95,8 @@
<string name="status_forbidden" msgid="613956401054050725">"ਟੀਚਾ ਡੀਵਾਈਸ ਵੱਲੋਂ ਟ੍ਰਾਂਸਫਰ ਵਰਜਿਤ।"</string>
<string name="status_canceled" msgid="6664490318773098285">"ਉਪਭੋਗਤਾ ਵੱਲੋਂ ਟ੍ਰਾਂਸਫਰ ਰੱਦ ਕੀਤਾ ਗਿਆ।"</string>
<string name="status_file_error" msgid="3671917770630165299">"ਸਟੋਰੇਜ ਸਮੱਸਿਆ।"</string>
- <string name="status_no_sd_card" product="nosdcard" msgid="1112125377088421469">"ਕੋਈ USB ਸਟੋਰੇਜ ਨਹੀਂ ਹੈ।"</string>
- <string name="status_no_sd_card" product="default" msgid="5760944071743325592">"ਕੋਈ SD ਕਾਰਡ ਨਹੀਂ। ਟ੍ਰਾਂਸਫ਼ਰ ਕੀਤੀਆਂ ਫਾਈਲਾਂ ਨੂੰ ਰੱਖਿਅਤ ਕਰਨ ਲਈ ਇੱਕ SD ਕਾਰਡ ਪਾਓ।"</string>
+ <string name="status_no_sd_card_nosdcard" msgid="573631036356922221">"ਕੋਈ USB ਸਟੋਰੇਜ ਨਹੀਂ ਹੈ।"</string>
+ <string name="status_no_sd_card_default" msgid="396564893716701954">"ਕੋਈ SD ਕਾਰਡ ਨਹੀਂ। ਟ੍ਰਾਂਸਫ਼ਰ ਕੀਤੀਆਂ ਫ਼ਾਈਲਾਂ ਨੂੰ ਰੱਖਿਅਤ ਕਰਨ ਲਈ ਇੱਕ SD ਕਾਰਡ ਪਾਓ।"</string>
<string name="status_connection_error" msgid="947681831523219891">"ਕਨੈਕਸ਼ਨ ਅਸਫਲ।"</string>
<string name="status_protocol_error" msgid="3245444473429269539">"ਬੇਨਤੀ ਨੂੰ ਸਹੀ ਢੰਗ ਨਾਲ ਸੰਭਾਲਿਆ ਨਹੀਂ ਜਾ ਸਕਦਾ।"</string>
<string name="status_unknown_error" msgid="8156660554237824912">"ਅਗਿਆਤ ਅਸ਼ੁੱਧੀ।"</string>
@@ -122,6 +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">"ਹੁਣੇ ਚੱਲ ਰਿਹਾ ਹੈ"</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>
diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml
index ee09650..8c4869f 100644
--- a/res/values-pl/strings.xml
+++ b/res/values-pl/strings.xml
@@ -84,8 +84,8 @@
<string name="bt_toast_4" msgid="4678812947604395649">"Wysyłanie pliku do urządzenia „<xliff:g id="RECIPIENT">%1$s</xliff:g>”"</string>
<string name="bt_toast_5" msgid="2846870992823019494">"Wysyłanie <xliff:g id="NUMBER">%1$s</xliff:g> plików do urządzenia „<xliff:g id="RECIPIENT">%2$s</xliff:g>”"</string>
<string name="bt_toast_6" msgid="1855266596936622458">"Zatrzymano wysyłanie pliku do urządzenia „<xliff:g id="RECIPIENT">%1$s</xliff:g>”"</string>
- <string name="bt_sm_2_1" product="nosdcard" msgid="352165168004521000">"Za mało miejsca na nośniku USB do zapisania pliku z urządzenia „<xliff:g id="SENDER">%1$s</xliff:g>”."</string>
- <string name="bt_sm_2_1" product="default" msgid="1989018443456803630">"Za mało miejsca na karcie SD do zapisania pliku z urządzenia „<xliff:g id="SENDER">%1$s</xliff:g>”."</string>
+ <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"Za mało miejsca na nośniku USB do zapisania pliku z urządzenia „<xliff:g id="SENDER">%1$s</xliff:g>”."</string>
+ <string name="bt_sm_2_1_default" msgid="9115512207909504071">"Za mało miejsca na karcie SD do zapisania pliku z urządzenia „<xliff:g id="SENDER">%1$s</xliff:g>”."</string>
<string name="bt_sm_2_2" msgid="2965243265852680543">"Wymagane miejsce: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
<string name="ErrorTooManyRequests" msgid="8578277541472944529">"Przetwarzanych jest zbyt wiele żądań. Spróbuj ponownie później."</string>
<string name="status_pending" msgid="2503691772030877944">"Przesyłanie pliku jeszcze się nie rozpoczęło."</string>
@@ -95,8 +95,8 @@
<string name="status_forbidden" msgid="613956401054050725">"Przesyłanie zabronione przez urządzenie docelowe."</string>
<string name="status_canceled" msgid="6664490318773098285">"Przesyłanie anulowane przez użytkownika."</string>
<string name="status_file_error" msgid="3671917770630165299">"Problem z pamięcią masową"</string>
- <string name="status_no_sd_card" product="nosdcard" msgid="1112125377088421469">"Brak nośnika USB."</string>
- <string name="status_no_sd_card" product="default" msgid="5760944071743325592">"Brak karty SD. Włóż kartę SD, aby zapisać przesłane pliki."</string>
+ <string name="status_no_sd_card_nosdcard" msgid="573631036356922221">"Brak nośnika USB."</string>
+ <string name="status_no_sd_card_default" msgid="396564893716701954">"Brak karty SD. Włóż kartę SD, by zapisać przesłane pliki."</string>
<string name="status_connection_error" msgid="947681831523219891">"Nie można połączyć."</string>
<string name="status_protocol_error" msgid="3245444473429269539">"Nie można poprawnie obsłużyć żądania."</string>
<string name="status_unknown_error" msgid="8156660554237824912">"Nieznany błąd."</string>
@@ -126,6 +126,7 @@
<string name="transfer_menu_open" msgid="3368984869083107200">"Otwórz"</string>
<string name="transfer_menu_clear" msgid="5854038118831427492">"Usuń z listy"</string>
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"Wyczyść"</string>
+ <string name="bluetooth_a2dp_sink_queue_name" msgid="6864149958708669766">"Co jest grane"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"Zapisz"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"Anuluj"</string>
<string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"Wybierz konta, które chcesz udostępnić przez Bluetooth. Wymagana jest zgoda na dostęp do kont."</string>
diff --git a/res/values-pt-rPT/strings.xml b/res/values-pt-rPT/strings.xml
index ffc70a4..09e6781 100644
--- a/res/values-pt-rPT/strings.xml
+++ b/res/values-pt-rPT/strings.xml
@@ -84,8 +84,8 @@
<string name="bt_toast_4" msgid="4678812947604395649">"A enviar ficheiro para \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
<string name="bt_toast_5" msgid="2846870992823019494">"A enviar <xliff:g id="NUMBER">%1$s</xliff:g> ficheiros para \"<xliff:g id="RECIPIENT">%2$s</xliff:g>\""</string>
<string name="bt_toast_6" msgid="1855266596936622458">"O envio do ficheiro para \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\" foi interrompido"</string>
- <string name="bt_sm_2_1" product="nosdcard" msgid="352165168004521000">"Não existe espaço suficiente na memória de armazenamento USB para guardar o ficheiro de “<xliff:g id="SENDER">%1$s</xliff:g>”"</string>
- <string name="bt_sm_2_1" product="default" msgid="1989018443456803630">"Não existe espaço suficiente no cartão SD para guardar o ficheiro de “<xliff:g id="SENDER">%1$s</xliff:g>”"</string>
+ <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"Não existe espaço suficiente na memória USB para guardar o ficheiro de \"<xliff:g id="SENDER">%1$s</xliff:g>\"."</string>
+ <string name="bt_sm_2_1_default" msgid="9115512207909504071">"Não existe espaço suficiente no cartão SD para guardar o ficheiro de \"<xliff:g id="SENDER">%1$s</xliff:g>\"."</string>
<string name="bt_sm_2_2" msgid="2965243265852680543">"Espaço necessário: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
<string name="ErrorTooManyRequests" msgid="8578277541472944529">"Existem demasiados pedidos em processamento. Tente novamente mais tarde."</string>
<string name="status_pending" msgid="2503691772030877944">"Ainda não foi iniciada a transferência do ficheiro."</string>
@@ -95,8 +95,8 @@
<string name="status_forbidden" msgid="613956401054050725">"Transferência proibida pelo aparelho de destino."</string>
<string name="status_canceled" msgid="6664490318773098285">"Transferência cancelada pelo utilizador."</string>
<string name="status_file_error" msgid="3671917770630165299">"Problema de armazenamento."</string>
- <string name="status_no_sd_card" product="nosdcard" msgid="1112125377088421469">"Sem armazenamento USB."</string>
- <string name="status_no_sd_card" product="default" msgid="5760944071743325592">"Sem cartão SD. Insira um cartão SD para guardar os ficheiros transferidos."</string>
+ <string name="status_no_sd_card_nosdcard" msgid="573631036356922221">"Sem memória USB."</string>
+ <string name="status_no_sd_card_default" msgid="396564893716701954">"Sem cartão SD. Insira um cartão SD para guardar os ficheiros transferidos."</string>
<string name="status_connection_error" msgid="947681831523219891">"A ligação falhou."</string>
<string name="status_protocol_error" msgid="3245444473429269539">"Não é possível processar o pedido corretamente."</string>
<string name="status_unknown_error" msgid="8156660554237824912">"Erro desconhecido."</string>
@@ -122,6 +122,7 @@
<string name="transfer_menu_open" msgid="3368984869083107200">"Abrir"</string>
<string name="transfer_menu_clear" msgid="5854038118831427492">"Limpar da lista"</string>
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"Limpar"</string>
+ <string name="bluetooth_a2dp_sink_queue_name" msgid="6864149958708669766">"A tocar"</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">"Selecione as contas que pretende partilhar através de Bluetooth. Ao ligar, ainda tem de aceitar eventuais acessos às contas."</string>
diff --git a/res/values-pt-rPT/strings_pbap.xml b/res/values-pt-rPT/strings_pbap.xml
index e0d59cd..49f9855 100644
--- a/res/values-pt-rPT/strings_pbap.xml
+++ b/res/values-pt-rPT/strings_pbap.xml
@@ -13,4 +13,9 @@
<string name="localPhoneName" msgid="2349001318925409159">"O meu nome"</string>
<string name="defaultnumber" msgid="8520116145890867338">"000000"</string>
<string name="pbap_notification_group" msgid="8487669554703627168">"Partilha de contactos por Bluetooth"</string>
+ <string name="remote_pbap_version_change">Alteração remota da versão do perfil da lista telefônica</string>
+ <string name="phonebook_advance_feature_support">Recurso Avançado da Lista Telefônica Suportado</string>
+ <string name="remote_phonebook_feature_downgrade">Downgrade do Recurso da Lista Telefônica Remota</string>
+ <string name="repair_for_adv_phonebook_feature">Re-par para Recurso Avançado Agenda</string>
+ <string name="repair_for_phonebook_access_version_comp">Re-par para compatibilidade de versão de acesso à lista telefônica</string>
</resources>
diff --git a/res/values-pt/strings.xml b/res/values-pt/strings.xml
index d024a42..8472432 100644
--- a/res/values-pt/strings.xml
+++ b/res/values-pt/strings.xml
@@ -84,8 +84,8 @@
<string name="bt_toast_4" msgid="4678812947604395649">"Enviando arquivo para \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
<string name="bt_toast_5" msgid="2846870992823019494">"Enviando <xliff:g id="NUMBER">%1$s</xliff:g> arquivos para \"<xliff:g id="RECIPIENT">%2$s</xliff:g>\""</string>
<string name="bt_toast_6" msgid="1855266596936622458">"Envio de arquivo para \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\" interrompido"</string>
- <string name="bt_sm_2_1" product="nosdcard" msgid="352165168004521000">"Não há espaço suficiente no armazenamento USB para salvar o arquivo de \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
- <string name="bt_sm_2_1" product="default" msgid="1989018443456803630">"Não há espaço suficiente no cartão SD para salvar o arquivo de \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
+ <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"Não há espaço suficiente no armazenamento USB para salvar o arquivo de \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
+ <string name="bt_sm_2_1_default" msgid="9115512207909504071">"Não há espaço suficiente no cartão SD para salvar o arquivo de \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
<string name="bt_sm_2_2" msgid="2965243265852680543">"Espaço necessário: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
<string name="ErrorTooManyRequests" msgid="8578277541472944529">"Há muitas solicitações sendo processadas. Tente novamente mais tarde."</string>
<string name="status_pending" msgid="2503691772030877944">"A transferência de arquivo ainda não foi iniciada."</string>
@@ -95,12 +95,12 @@
<string name="status_forbidden" msgid="613956401054050725">"Transferência proibida pelo aparelho de destino."</string>
<string name="status_canceled" msgid="6664490318773098285">"Transferência cancelada pelo usuário."</string>
<string name="status_file_error" msgid="3671917770630165299">"Problema de armazenamento."</string>
- <string name="status_no_sd_card" product="nosdcard" msgid="1112125377088421469">"Nenhum armazenamento USB."</string>
- <string name="status_no_sd_card" product="default" msgid="5760944071743325592">"Não há cartão SD. Insira um cartão SD para salvar os arquivos transferidos."</string>
+ <string name="status_no_sd_card_nosdcard" msgid="573631036356922221">"Nenhum armazenamento USB."</string>
+ <string name="status_no_sd_card_default" msgid="396564893716701954">"Não há cartão SD. Insira um cartão SD para salvar os arquivos transferidos."</string>
<string name="status_connection_error" msgid="947681831523219891">"Falha na conexão."</string>
<string name="status_protocol_error" msgid="3245444473429269539">"Não é possível tratar a solicitação corretamente."</string>
<string name="status_unknown_error" msgid="8156660554237824912">"Erro desconhecido."</string>
- <string name="btopp_live_folder" msgid="7967791481444474554">"Bluetooth recebido"</string>
+ <string name="btopp_live_folder" msgid="7967791481444474554">"Recebido por Bluetooth"</string>
<string name="opp_notification_group" msgid="3486303082135789982">"Compartilhamento Bluetooth"</string>
<string name="download_success" msgid="7036160438766730871">"<xliff:g id="FILE_SIZE">%1$s</xliff:g> Recebimento concluído."</string>
<string name="upload_success" msgid="4014469387779648949">"<xliff:g id="FILE_SIZE">%1$s</xliff:g> Envio concluído."</string>
@@ -122,6 +122,7 @@
<string name="transfer_menu_open" msgid="3368984869083107200">"Abrir"</string>
<string name="transfer_menu_clear" msgid="5854038118831427492">"Limpar da lista"</string>
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"Limpar"</string>
+ <string name="bluetooth_a2dp_sink_queue_name" msgid="6864149958708669766">"Tocando agora"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"Salvar"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"Cancelar"</string>
<string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"Selecione as contas que você quer compartilhar via Bluetooth. Você ainda precisa aceitar qualquer acesso às contas ao se conectar."</string>
diff --git a/res/values-pt/strings_pbap.xml b/res/values-pt/strings_pbap.xml
index 46ed715..b9a78ad 100644
--- a/res/values-pt/strings_pbap.xml
+++ b/res/values-pt/strings_pbap.xml
@@ -13,4 +13,9 @@
<string name="localPhoneName" msgid="2349001318925409159">"Meu nome"</string>
<string name="defaultnumber" msgid="8520116145890867338">"000000"</string>
<string name="pbap_notification_group" msgid="8487669554703627168">"Compartilhamento de contato via Bluetooth"</string>
+ <string name="remote_pbap_version_change">Alteração remota da versão do perfil da lista telefônica</string>
+ <string name="phonebook_advance_feature_support">Recurso Avançado da Lista Telefônica Suportado</string>
+ <string name="remote_phonebook_feature_downgrade">Downgrade do Recurso da Lista Telefônica Remota</string>
+ <string name="repair_for_adv_phonebook_feature">Re-par para Recurso Avançado Agenda</string>
+ <string name="repair_for_phonebook_access_version_comp">Re-par para compatibilidade de versão de acesso à lista telefônica</string>
</resources>
diff --git a/res/values-ro/strings.xml b/res/values-ro/strings.xml
index b35a73a..67f117d 100644
--- a/res/values-ro/strings.xml
+++ b/res/values-ro/strings.xml
@@ -84,8 +84,8 @@
<string name="bt_toast_4" msgid="4678812947604395649">"Se trimite fișierul către „<xliff:g id="RECIPIENT">%1$s</xliff:g>”"</string>
<string name="bt_toast_5" msgid="2846870992823019494">"Se trimit <xliff:g id="NUMBER">%1$s</xliff:g> fișiere către „<xliff:g id="RECIPIENT">%2$s</xliff:g>”"</string>
<string name="bt_toast_6" msgid="1855266596936622458">"Trimiterea fișierului către „<xliff:g id="RECIPIENT">%1$s</xliff:g>” a fost anulată"</string>
- <string name="bt_sm_2_1" product="nosdcard" msgid="352165168004521000">"Nu există spațiu suficient pe stocarea USB pentru a salva fișierul de la „<xliff:g id="SENDER">%1$s</xliff:g>”"</string>
- <string name="bt_sm_2_1" product="default" msgid="1989018443456803630">"Nu există suficient spațiu pe cardul SD pentru a salva fișierul de la „<xliff:g id="SENDER">%1$s</xliff:g>”"</string>
+ <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"Nu există spațiu suficient pe spațiul de stocare USB pentru a salva fișierul de la „<xliff:g id="SENDER">%1$s</xliff:g>”"</string>
+ <string name="bt_sm_2_1_default" msgid="9115512207909504071">"Nu există suficient spațiu pe cardul SD pentru a salva fișierul de la „<xliff:g id="SENDER">%1$s</xliff:g>”"</string>
<string name="bt_sm_2_2" msgid="2965243265852680543">"Spațiu necesar: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
<string name="ErrorTooManyRequests" msgid="8578277541472944529">"Există prea multe solicitări în curs de procesare. Încercați din nou mai târziu."</string>
<string name="status_pending" msgid="2503691772030877944">"Transferul fișierului nu a început încă."</string>
@@ -95,8 +95,8 @@
<string name="status_forbidden" msgid="613956401054050725">"Transfer interzis de dispozitivul de destinație."</string>
<string name="status_canceled" msgid="6664490318773098285">"Transfer anulat de către utilizator."</string>
<string name="status_file_error" msgid="3671917770630165299">"Problemă de stocare."</string>
- <string name="status_no_sd_card" product="nosdcard" msgid="1112125377088421469">"Nu există spațiu de stocare USB."</string>
- <string name="status_no_sd_card" product="default" msgid="5760944071743325592">"Nu există card SD. Introduceți un card SD pentru a salva fișierele transferate."</string>
+ <string name="status_no_sd_card_nosdcard" msgid="573631036356922221">"Nu există spațiu de stocare USB."</string>
+ <string name="status_no_sd_card_default" msgid="396564893716701954">"Nu există card SD. Introduceți un card SD pentru a salva fișierele transferate."</string>
<string name="status_connection_error" msgid="947681831523219891">"Conectare eșuată."</string>
<string name="status_protocol_error" msgid="3245444473429269539">"Solicitarea nu poate fi gestionată corect."</string>
<string name="status_unknown_error" msgid="8156660554237824912">"Eroare necunoscută."</string>
@@ -124,6 +124,7 @@
<string name="transfer_menu_open" msgid="3368984869083107200">"Deschideți"</string>
<string name="transfer_menu_clear" msgid="5854038118831427492">"Ștergeți din listă"</string>
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"Ștergeți"</string>
+ <string name="bluetooth_a2dp_sink_queue_name" msgid="6864149958708669766">"Now Playing"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"Salvați"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"Anulați"</string>
<string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"Selectați conturile la care doriți să permiteți accesul prin Bluetooth. Va trebui să acceptați accesul la conturi la conectare."</string>
diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml
index c3a2e08..87c1492 100644
--- a/res/values-ru/strings.xml
+++ b/res/values-ru/strings.xml
@@ -84,8 +84,8 @@
<string name="bt_toast_4" msgid="4678812947604395649">"Отправка файла на \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
<string name="bt_toast_5" msgid="2846870992823019494">"Отправка файлов (<xliff:g id="NUMBER">%1$s</xliff:g>) на \"<xliff:g id="RECIPIENT">%2$s</xliff:g>\""</string>
<string name="bt_toast_6" msgid="1855266596936622458">"Отправка файла на \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\" остановлена"</string>
- <string name="bt_sm_2_1" product="nosdcard" msgid="352165168004521000">"На USB-накопителе нет места, чтобы сохранить файл с устройства \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
- <string name="bt_sm_2_1" product="default" msgid="1989018443456803630">"На SD-карте нет места, чтобы сохранить файл с устройства \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
+ <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"На USB-накопителе нет места, чтобы сохранить файл с устройства \"<xliff:g id="SENDER">%1$s</xliff:g>\"."</string>
+ <string name="bt_sm_2_1_default" msgid="9115512207909504071">"На SD-карте нет места, чтобы сохранить файл с устройства \"<xliff:g id="SENDER">%1$s</xliff:g>\"."</string>
<string name="bt_sm_2_2" msgid="2965243265852680543">"Необходимое свободное место: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
<string name="ErrorTooManyRequests" msgid="8578277541472944529">"Обрабатывается слишком много запросов. Повторите попытку позднее."</string>
<string name="status_pending" msgid="2503691772030877944">"Передача файла еще не началась."</string>
@@ -95,8 +95,8 @@
<string name="status_forbidden" msgid="613956401054050725">"Передача запрещена на принимающем устройстве."</string>
<string name="status_canceled" msgid="6664490318773098285">"Передача отменена."</string>
<string name="status_file_error" msgid="3671917770630165299">"Ошибка хранилища."</string>
- <string name="status_no_sd_card" product="nosdcard" msgid="1112125377088421469">"USB-накопитель не найден."</string>
- <string name="status_no_sd_card" product="default" msgid="5760944071743325592">"Отсутствует SD-карта. Вставьте SD-карту для сохранения переданных файлов."</string>
+ <string name="status_no_sd_card_nosdcard" msgid="573631036356922221">"USB-накопитель не найден."</string>
+ <string name="status_no_sd_card_default" msgid="396564893716701954">"Чтобы сохранить переданные файлы, вставьте SD-карту."</string>
<string name="status_connection_error" msgid="947681831523219891">"Не удалось установить соединение."</string>
<string name="status_protocol_error" msgid="3245444473429269539">"Невозможно правильно обработать запрос."</string>
<string name="status_unknown_error" msgid="8156660554237824912">"Неизвестная ошибка"</string>
@@ -126,6 +126,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">"Что сейчас играет?"</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>
diff --git a/res/values-si/strings.xml b/res/values-si/strings.xml
index 47a53f1..ed6e355 100644
--- a/res/values-si/strings.xml
+++ b/res/values-si/strings.xml
@@ -84,8 +84,8 @@
<string name="bt_toast_4" msgid="4678812947604395649">"\"<xliff:g id="RECIPIENT">%1$s</xliff:g>\" වෙත ගොනුව යැවේ"</string>
<string name="bt_toast_5" msgid="2846870992823019494">"\"<xliff:g id="RECIPIENT">%2$s</xliff:g>\" වෙත <xliff:g id="NUMBER">%1$s</xliff:g> ගොනු යැවේ"</string>
<string name="bt_toast_6" msgid="1855266596936622458">"\"<xliff:g id="RECIPIENT">%1$s</xliff:g>\" වෙත ගොනුව යැවීම නතර විය"</string>
- <string name="bt_sm_2_1" product="nosdcard" msgid="352165168004521000">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" වෙතින් ගොනුව සුරැකීමට USB ආචයනය තුළ ප්රමාණවත් ඉඩක් නැත"</string>
- <string name="bt_sm_2_1" product="default" msgid="1989018443456803630">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" වෙතින් ගොනුව සුරැකීමට SD කාඩ්පත මත ප්රමාණවත් ඉඩක් නැත"</string>
+ <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" වෙතින් ගොනුව සුරැකීමට USB ආචයනය තුළ ප්රමාණවත් ඉඩක් නැත"</string>
+ <string name="bt_sm_2_1_default" msgid="9115512207909504071">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" වෙතින් ගොනුව සුරැකීමට SD කාඩ්පතේ ප්රමාණවත් ඉඩක් නැත"</string>
<string name="bt_sm_2_2" msgid="2965243265852680543">"අවශ්ය ඉඩ: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
<string name="ErrorTooManyRequests" msgid="8578277541472944529">"ඉල්ලීම් බොහෝ ගණනක් ක්රියාත්මක වෙමින් පවතී. පසුව නැවත උත්සාහ කරන්න."</string>
<string name="status_pending" msgid="2503691772030877944">"ගොනු මාරුව තවම ආරම්භ වී නැත."</string>
@@ -95,8 +95,8 @@
<string name="status_forbidden" msgid="613956401054050725">"ඉලක්ක උපාංගය විසින් තහනම් කළ මාරුව"</string>
<string name="status_canceled" msgid="6664490318773098285">"මාරුව පරිශීලක විසින් අවලංගු කරන ලදී."</string>
<string name="status_file_error" msgid="3671917770630165299">"ආචයනය ගැටලුව."</string>
- <string name="status_no_sd_card" product="nosdcard" msgid="1112125377088421469">"USB ආචයනයක් නැත."</string>
- <string name="status_no_sd_card" product="default" msgid="5760944071743325592">"SD කාඩ්පතක් නොමැත. මාරු කළ ගොනු සුරැකීමට SD කාඩ්පතක් ඇතුළු කරන්න."</string>
+ <string name="status_no_sd_card_nosdcard" msgid="573631036356922221">"USB ආචයනයක් නැත."</string>
+ <string name="status_no_sd_card_default" msgid="396564893716701954">"SD කාඩ්පතක් නොමැත. මාරු කළ ගොනු සුරැකීමට SD කාඩ්පතක් ඇතුළු කරන්න."</string>
<string name="status_connection_error" msgid="947681831523219891">"සම්බන්ධය අසාර්ථකයි."</string>
<string name="status_protocol_error" msgid="3245444473429269539">"ඉල්ලීම නිවැරදිව හැසිරවීමට නොහැකිය."</string>
<string name="status_unknown_error" msgid="8156660554237824912">"නොදන්නා දෝෂයකි."</string>
@@ -122,6 +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">"දැන් වාදනය වේ"</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>
diff --git a/res/values-sk/strings.xml b/res/values-sk/strings.xml
index de2d261..d189d0d 100644
--- a/res/values-sk/strings.xml
+++ b/res/values-sk/strings.xml
@@ -84,8 +84,8 @@
<string name="bt_toast_4" msgid="4678812947604395649">"Odosielanie súboru používateľovi <xliff:g id="RECIPIENT">%1$s</xliff:g>"</string>
<string name="bt_toast_5" msgid="2846870992823019494">"Odosielanie súborov (počet: <xliff:g id="NUMBER">%1$s</xliff:g>) používateľovi <xliff:g id="RECIPIENT">%2$s</xliff:g>"</string>
<string name="bt_toast_6" msgid="1855266596936622458">"Odosielanie súboru používateľovi <xliff:g id="RECIPIENT">%1$s</xliff:g> bolo zastavené"</string>
- <string name="bt_sm_2_1" product="nosdcard" msgid="352165168004521000">"V úložisku USB nie je dostatok miesta na uloženie súboru od používateľa „<xliff:g id="SENDER">%1$s</xliff:g>“"</string>
- <string name="bt_sm_2_1" product="default" msgid="1989018443456803630">"Na SD karte nie je dostatok miesta na uloženie súboru od používateľa „<xliff:g id="SENDER">%1$s</xliff:g>“"</string>
+ <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"V úložisku USB nie je dostatok miesta na uloženie súboru od odosielateľa <xliff:g id="SENDER">%1$s</xliff:g>"</string>
+ <string name="bt_sm_2_1_default" msgid="9115512207909504071">"Na SD karte nie je dostatok miesta na uloženie súboru od odosielateľa <xliff:g id="SENDER">%1$s</xliff:g>"</string>
<string name="bt_sm_2_2" msgid="2965243265852680543">"Požadované miesto v pamäti: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
<string name="ErrorTooManyRequests" msgid="8578277541472944529">"Spracúva sa príliš veľa žiadostí. Opakujte akciu neskôr."</string>
<string name="status_pending" msgid="2503691772030877944">"Prenos súborov ešte nebol spustený."</string>
@@ -95,8 +95,8 @@
<string name="status_forbidden" msgid="613956401054050725">"Prenos bol zakázaný cieľovým zariadením."</string>
<string name="status_canceled" msgid="6664490318773098285">"Prenos bol zrušený používateľom."</string>
<string name="status_file_error" msgid="3671917770630165299">"Problém s ukladacím priestorom."</string>
- <string name="status_no_sd_card" product="nosdcard" msgid="1112125377088421469">"Žiadny ukladací priestor USB."</string>
- <string name="status_no_sd_card" product="default" msgid="5760944071743325592">"Žiadna SD karta nie je dostupná. Ak chcete prenášané súbory uložiť, vložte SD kartu."</string>
+ <string name="status_no_sd_card_nosdcard" msgid="573631036356922221">"Žiadne úložisko USB."</string>
+ <string name="status_no_sd_card_default" msgid="396564893716701954">"Nie je k dispozícii žiadna SD karta. Ak chcete prenášané súbory uložiť, vložte SD kartu."</string>
<string name="status_connection_error" msgid="947681831523219891">"Neúspešný pokus o pripojenie."</string>
<string name="status_protocol_error" msgid="3245444473429269539">"Žiadosť nie je možné správne spracovať."</string>
<string name="status_unknown_error" msgid="8156660554237824912">"Neznáma chyba."</string>
@@ -126,6 +126,7 @@
<string name="transfer_menu_open" msgid="3368984869083107200">"Otvoriť"</string>
<string name="transfer_menu_clear" msgid="5854038118831427492">"Vymazať zo zoznamu"</string>
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"Vymazať"</string>
+ <string name="bluetooth_a2dp_sink_queue_name" msgid="6864149958708669766">"Čo to hrá"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"Uložiť"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"Zrušiť"</string>
<string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"Vyberte účty, ktoré chcete zdieľať prostredníctvom rozhrania Bluetooth. Počas pripájania budete musieť aj tak prijať akékoľvek prístupy k účtom."</string>
diff --git a/res/values-sl/strings.xml b/res/values-sl/strings.xml
index a62bf24..912ba37 100644
--- a/res/values-sl/strings.xml
+++ b/res/values-sl/strings.xml
@@ -84,8 +84,8 @@
<string name="bt_toast_4" msgid="4678812947604395649">"Pošiljanje datoteke prejemniku »<xliff:g id="RECIPIENT">%1$s</xliff:g>«"</string>
<string name="bt_toast_5" msgid="2846870992823019494">"Pošiljanje <xliff:g id="NUMBER">%1$s</xliff:g> datotek prejemniku »<xliff:g id="RECIPIENT">%2$s</xliff:g>«"</string>
<string name="bt_toast_6" msgid="1855266596936622458">"Pošiljanje datoteke prejemniku »<xliff:g id="RECIPIENT">%1$s</xliff:g>« je ustavljeno"</string>
- <string name="bt_sm_2_1" product="nosdcard" msgid="352165168004521000">"V pomnilniku USB ni dovolj prostora za shranjevanje datoteke pošiljatelja »<xliff:g id="SENDER">%1$s</xliff:g>«"</string>
- <string name="bt_sm_2_1" product="default" msgid="1989018443456803630">"Na kartici SD ni dovolj prostora za shranjevanje datoteke pošiljatelja »<xliff:g id="SENDER">%1$s</xliff:g>«"</string>
+ <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"V shrambi USB ni dovolj prostora za shranjevanje datoteke pošiljatelja »<xliff:g id="SENDER">%1$s</xliff:g>«"</string>
+ <string name="bt_sm_2_1_default" msgid="9115512207909504071">"Na kartici SD ni dovolj prostora za shranjevanje datoteke pošiljatelja »<xliff:g id="SENDER">%1$s</xliff:g>«"</string>
<string name="bt_sm_2_2" msgid="2965243265852680543">"Zahtevani prostor: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
<string name="ErrorTooManyRequests" msgid="8578277541472944529">"V obdelavi je preveč zahtev. Poskusite znova pozneje."</string>
<string name="status_pending" msgid="2503691772030877944">"Prenos datoteke se še ni začel."</string>
@@ -95,8 +95,8 @@
<string name="status_forbidden" msgid="613956401054050725">"Prenos je prepovedala ciljna naprava."</string>
<string name="status_canceled" msgid="6664490318773098285">"Prenos je prekinil uporabnik."</string>
<string name="status_file_error" msgid="3671917770630165299">"Težava s pomnilnikom."</string>
- <string name="status_no_sd_card" product="nosdcard" msgid="1112125377088421469">"Ni pomnilnika USB."</string>
- <string name="status_no_sd_card" product="default" msgid="5760944071743325592">"Ni kartice SD. Če želite shraniti prenesene datoteke, vstavite kartico SD."</string>
+ <string name="status_no_sd_card_nosdcard" msgid="573631036356922221">"Ni shrambe USB."</string>
+ <string name="status_no_sd_card_default" msgid="396564893716701954">"Ni kartice SD. Če želite shraniti prenesene datoteke, vstavite kartico SD."</string>
<string name="status_connection_error" msgid="947681831523219891">"Povezava neuspešna."</string>
<string name="status_protocol_error" msgid="3245444473429269539">"Zahteve ni mogoče pravilno obravnavati."</string>
<string name="status_unknown_error" msgid="8156660554237824912">"Neznana napaka."</string>
@@ -126,6 +126,7 @@
<string name="transfer_menu_open" msgid="3368984869083107200">"Odpri"</string>
<string name="transfer_menu_clear" msgid="5854038118831427492">"Počisti s seznama"</string>
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"Počisti"</string>
+ <string name="bluetooth_a2dp_sink_queue_name" msgid="6864149958708669766">"Zdaj se predvaja"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"Shrani"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"Prekliči"</string>
<string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"Izberite račune, ki jih želite dati v skupno rabo prek Bluetootha. Pri vzpostavljanju povezave morate še vedno sprejeti morebiten dostop do računov."</string>
diff --git a/res/values-sq/strings.xml b/res/values-sq/strings.xml
index efb4f0f..1a95dcd 100644
--- a/res/values-sq/strings.xml
+++ b/res/values-sq/strings.xml
@@ -84,8 +84,8 @@
<string name="bt_toast_4" msgid="4678812947604395649">"Po e dërgon skedarin te \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
<string name="bt_toast_5" msgid="2846870992823019494">"Po dërgon <xliff:g id="NUMBER">%1$s</xliff:g> skedarë te \"<xliff:g id="RECIPIENT">%2$s</xliff:g>\""</string>
<string name="bt_toast_6" msgid="1855266596936622458">"Ndaloi dërgimin e skedarit te \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
- <string name="bt_sm_2_1" product="nosdcard" msgid="352165168004521000">"Nuk ka hapësirë të mjaftueshme në USB për të ruajtur skedarin nga \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
- <string name="bt_sm_2_1" product="default" msgid="1989018443456803630">"Nuk ka hapësirë të mjaftueshme në kartën SD për të ruajtur skedarin nga \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
+ <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"Nuk ka hapësirë të mjaftueshme në USB për të ruajtur skedarin nga \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
+ <string name="bt_sm_2_1_default" msgid="9115512207909504071">"Nuk ka hapësirë të mjaftueshme në kartën SD për të ruajtur skedarin nga \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
<string name="bt_sm_2_2" msgid="2965243265852680543">"Hapësira e nevojshme: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
<string name="ErrorTooManyRequests" msgid="8578277541472944529">"Po përpunohen shumë kërkesa. Provo sërish më vonë."</string>
<string name="status_pending" msgid="2503691772030877944">"Transferimi i skedarit nuk ka filluar ende."</string>
@@ -95,8 +95,8 @@
<string name="status_forbidden" msgid="613956401054050725">"Transferimi ndalohet nga pajisja pritëse."</string>
<string name="status_canceled" msgid="6664490318773098285">"Transferimi u anulua nga përdoruesi."</string>
<string name="status_file_error" msgid="3671917770630165299">"Problem me hapësirën ruajtëse."</string>
- <string name="status_no_sd_card" product="nosdcard" msgid="1112125377088421469">"Nuk ka hapësirë ruajtëse USB."</string>
- <string name="status_no_sd_card" product="default" msgid="5760944071743325592">"Nuk ka kartë SD. Vendos një kartë SD për të ruajtur skedarët e transferuar."</string>
+ <string name="status_no_sd_card_nosdcard" msgid="573631036356922221">"Nuk ka hapësirë ruajtëse USB."</string>
+ <string name="status_no_sd_card_default" msgid="396564893716701954">"Nuk ka kartë SD. Vendos një kartë SD për të ruajtur skedarët e transferuar."</string>
<string name="status_connection_error" msgid="947681831523219891">"Lidhja ishte e pasuksesshme."</string>
<string name="status_protocol_error" msgid="3245444473429269539">"Kërkesa nuk mund të trajtohet si duhet."</string>
<string name="status_unknown_error" msgid="8156660554237824912">"Gabim i panjohur."</string>
@@ -122,6 +122,7 @@
<string name="transfer_menu_open" msgid="3368984869083107200">"Hap"</string>
<string name="transfer_menu_clear" msgid="5854038118831427492">"Pastro nga lista"</string>
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"Pastro"</string>
+ <string name="bluetooth_a2dp_sink_queue_name" msgid="6864149958708669766">"Po luhet tani"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"Ruaj"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"Anulo"</string>
<string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"Zgjidh llogaritë që dëshiron të ndash me Bluetooth. Duhet të pranosh përsëri çdo qasje te llogaritë kur të lidhesh."</string>
diff --git a/res/values-sr/strings.xml b/res/values-sr/strings.xml
index 789c242..6bdf67a 100644
--- a/res/values-sr/strings.xml
+++ b/res/values-sr/strings.xml
@@ -84,8 +84,8 @@
<string name="bt_toast_4" msgid="4678812947604395649">"Слање датотеке примаоцу „<xliff:g id="RECIPIENT">%1$s</xliff:g>“"</string>
<string name="bt_toast_5" msgid="2846870992823019494">"Слање<xliff:g id="NUMBER">%1$s</xliff:g> датотека примаоцу „<xliff:g id="RECIPIENT">%2$s</xliff:g>“"</string>
<string name="bt_toast_6" msgid="1855266596936622458">"Заустављено слање примаоцу „<xliff:g id="RECIPIENT">%1$s</xliff:g>“"</string>
- <string name="bt_sm_2_1" product="nosdcard" msgid="352165168004521000">"Нема довољно простора у USB меморији да би се сачувала датотека пошиљаоца „<xliff:g id="SENDER">%1$s</xliff:g>“"</string>
- <string name="bt_sm_2_1" product="default" msgid="1989018443456803630">"Нема довољно простора на SD картици да би се сачувала датотека пошиљаоца „<xliff:g id="SENDER">%1$s</xliff:g>“"</string>
+ <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"Нема довољно простора у USB меморији да би се сачувала датотека пошиљаоца „<xliff:g id="SENDER">%1$s</xliff:g>“"</string>
+ <string name="bt_sm_2_1_default" msgid="9115512207909504071">"Нема довољно простора на SD картици да би се сачувала датотека пошиљаоца „<xliff:g id="SENDER">%1$s</xliff:g>“"</string>
<string name="bt_sm_2_2" msgid="2965243265852680543">"Потребан простор: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
<string name="ErrorTooManyRequests" msgid="8578277541472944529">"Превише захтева се обрађује. Пробајте поново касније."</string>
<string name="status_pending" msgid="2503691772030877944">"Пренос датотеке још није почео."</string>
@@ -95,8 +95,8 @@
<string name="status_forbidden" msgid="613956401054050725">"Циљни уређај је забранио пренос."</string>
<string name="status_canceled" msgid="6664490318773098285">"Корисник је отказао пренос."</string>
<string name="status_file_error" msgid="3671917770630165299">"Проблем са складиштем."</string>
- <string name="status_no_sd_card" product="nosdcard" msgid="1112125377088421469">"Нема USB меморије."</string>
- <string name="status_no_sd_card" product="default" msgid="5760944071743325592">"Нема SD картице. Уметните SD картицу да бисте сачували пренете датотеке."</string>
+ <string name="status_no_sd_card_nosdcard" msgid="573631036356922221">"Нема USB меморије."</string>
+ <string name="status_no_sd_card_default" msgid="396564893716701954">"Нема SD картице. Уметните SD картицу да бисте сачували пренете датотеке."</string>
<string name="status_connection_error" msgid="947681831523219891">"Повезивање није успело."</string>
<string name="status_protocol_error" msgid="3245444473429269539">"Није могуће исправно обрадити захтев."</string>
<string name="status_unknown_error" msgid="8156660554237824912">"Непозната грешка."</string>
@@ -124,6 +124,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">"Тренутно свира"</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>
diff --git a/res/values-sv/strings.xml b/res/values-sv/strings.xml
index de55790..7c1609f 100644
--- a/res/values-sv/strings.xml
+++ b/res/values-sv/strings.xml
@@ -84,8 +84,8 @@
<string name="bt_toast_4" msgid="4678812947604395649">"Skickar fil till <xliff:g id="RECIPIENT">%1$s</xliff:g>"</string>
<string name="bt_toast_5" msgid="2846870992823019494">"Skickar <xliff:g id="NUMBER">%1$s</xliff:g> filer till <xliff:g id="RECIPIENT">%2$s</xliff:g>"</string>
<string name="bt_toast_6" msgid="1855266596936622458">"Filöverföringen till <xliff:g id="RECIPIENT">%1$s</xliff:g> har avbrutits"</string>
- <string name="bt_sm_2_1" product="nosdcard" msgid="352165168004521000">"Det finns inte tillräckligt mycket utrymme på USB-lagringsenheten för att spara filen från <xliff:g id="SENDER">%1$s</xliff:g>"</string>
- <string name="bt_sm_2_1" product="default" msgid="1989018443456803630">"Det finns för lite utrymme på SD-kortet för att spara filen från <xliff:g id="SENDER">%1$s</xliff:g>"</string>
+ <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"Det finns inte tillräckligt mycket utrymme på USB-lagringsenheten för att spara filen från <xliff:g id="SENDER">%1$s</xliff:g>"</string>
+ <string name="bt_sm_2_1_default" msgid="9115512207909504071">"Det finns för lite utrymme på SD-kortet för att spara filen från <xliff:g id="SENDER">%1$s</xliff:g>"</string>
<string name="bt_sm_2_2" msgid="2965243265852680543">"Utrymmesbehov: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
<string name="ErrorTooManyRequests" msgid="8578277541472944529">"För många begäranden bearbetas. Försök igen senare."</string>
<string name="status_pending" msgid="2503691772030877944">"Filöverföringen har inte börjat."</string>
@@ -95,8 +95,8 @@
<string name="status_forbidden" msgid="613956401054050725">"Målenheten tillåter inte överföringen."</string>
<string name="status_canceled" msgid="6664490318773098285">"Överföringen avbröts av användaren."</string>
<string name="status_file_error" msgid="3671917770630165299">"Lagringsproblem."</string>
- <string name="status_no_sd_card" product="nosdcard" msgid="1112125377088421469">"Ingen USB-lagringsenhet."</string>
- <string name="status_no_sd_card" product="default" msgid="5760944071743325592">"Det finns inget SD-kort. Sätt i ett SD-kort om du vill spara överförda filer."</string>
+ <string name="status_no_sd_card_nosdcard" msgid="573631036356922221">"Ingen USB-lagringsenhet."</string>
+ <string name="status_no_sd_card_default" msgid="396564893716701954">"Det finns inget SD-kort. Sätt i ett SD-kort om du vill spara överförda filer."</string>
<string name="status_connection_error" msgid="947681831523219891">"Anslutningen misslyckades."</string>
<string name="status_protocol_error" msgid="3245444473429269539">"Begäran kan inte hanteras korrekt."</string>
<string name="status_unknown_error" msgid="8156660554237824912">"Okänt fel."</string>
@@ -122,6 +122,7 @@
<string name="transfer_menu_open" msgid="3368984869083107200">"Öppna"</string>
<string name="transfer_menu_clear" msgid="5854038118831427492">"Ta bort från listan"</string>
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"Rensa"</string>
+ <string name="bluetooth_a2dp_sink_queue_name" msgid="6864149958708669766">"Nu spelas"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"Spara"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"Avbryt"</string>
<string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"Välj de konton du vill dela via Bluetooth. Du måste fortfarande godkänna åtkomsten till kontona vid anslutning."</string>
diff --git a/res/values-sw/strings.xml b/res/values-sw/strings.xml
index 705984a..39f803e 100644
--- a/res/values-sw/strings.xml
+++ b/res/values-sw/strings.xml
@@ -84,8 +84,8 @@
<string name="bt_toast_4" msgid="4678812947604395649">"Inatuma faili kwa \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
<string name="bt_toast_5" msgid="2846870992823019494">"Inatuma faili <xliff:g id="NUMBER">%1$s</xliff:g> kwa \"<xliff:g id="RECIPIENT">%2$s</xliff:g>\""</string>
<string name="bt_toast_6" msgid="1855266596936622458">"Ilikomesha utumaji faili kwa \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
- <string name="bt_sm_2_1" product="nosdcard" msgid="352165168004521000">"Hakuna nafasi ya kutosha kwenye hifadhi ya USB ili kuhifadhi faili kutoka kwa \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
- <string name="bt_sm_2_1" product="default" msgid="1989018443456803630">"Hakuna nafasi ya kutosha katika kadi ya SD ya kuhifadhi faili kutoka \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
+ <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"Hakuna nafasi ya kutosha katika hifadhi ya USB ya kuhifadhi faili kutoka kwa \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
+ <string name="bt_sm_2_1_default" msgid="9115512207909504071">"Hakuna nafasi ya kutosha katika kadi ya SD ya kuhifadhi faili kutoka kwa \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
<string name="bt_sm_2_2" msgid="2965243265852680543">"Nafasi inayohitajika: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
<string name="ErrorTooManyRequests" msgid="8578277541472944529">"Maombi mengi sana yanashughulikiwa. Jaribu tena baadaye."</string>
<string name="status_pending" msgid="2503691772030877944">"Uhamishaji wa faili bado haijaanzishwa."</string>
@@ -95,8 +95,8 @@
<string name="status_forbidden" msgid="613956401054050725">"Uhamishaji umekatazwa na kifaa kinacholengwa."</string>
<string name="status_canceled" msgid="6664490318773098285">"Uhamishaji umeghairiwa na mtumiaji."</string>
<string name="status_file_error" msgid="3671917770630165299">"Suala la hifadhi."</string>
- <string name="status_no_sd_card" product="nosdcard" msgid="1112125377088421469">"Hakuna hifadhi ya USB."</string>
- <string name="status_no_sd_card" product="default" msgid="5760944071743325592">"Hakuna kadi ya SD. Ingiza kadi ya SD ili kuhifadhi faili zilizohamishwa."</string>
+ <string name="status_no_sd_card_nosdcard" msgid="573631036356922221">"Hakuna hifadhi ya USB."</string>
+ <string name="status_no_sd_card_default" msgid="396564893716701954">"Hakuna kadi ya SD. Weka kadi ya SD ili uhifadhi faili zinazohamishwa."</string>
<string name="status_connection_error" msgid="947681831523219891">"Muunganisho haujafanikiwa."</string>
<string name="status_protocol_error" msgid="3245444473429269539">"Ombi haliwezi kushughulikiwa kwa usahihi."</string>
<string name="status_unknown_error" msgid="8156660554237824912">"Hitilafu isiyojulikana."</string>
@@ -122,6 +122,7 @@
<string name="transfer_menu_open" msgid="3368984869083107200">"Fungua"</string>
<string name="transfer_menu_clear" msgid="5854038118831427492">"Futa kutoka orodha"</string>
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"Futa"</string>
+ <string name="bluetooth_a2dp_sink_queue_name" msgid="6864149958708669766">"Inayocheza Sasa"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"Hifadhi"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"Ghairi"</string>
<string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"Chagua akaunti unazotaka kushiriki kupitia Bluetooth. Bado unatakiwa kutoa idhini ya ufikiaji wowote kwenye akaunti yako wakati unaunganisha."</string>
diff --git a/res/values-ta/strings.xml b/res/values-ta/strings.xml
index 47c5d33..54677ac 100644
--- a/res/values-ta/strings.xml
+++ b/res/values-ta/strings.xml
@@ -17,9 +17,9 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="permlab_bluetoothShareManager" msgid="311492132450338925">"பதிவிறக்க நிர்வாகியை அணுகவும்."</string>
- <string name="permdesc_bluetoothShareManager" msgid="8930572979123190223">"BluetoothShare நிர்வாகியை அணுகுவதற்குப் பயன்பாட்டை அனுமதித்து, கோப்புகளைப் பரிமாற்ற அதைப் பயன்படுத்துகிறது."</string>
+ <string name="permdesc_bluetoothShareManager" msgid="8930572979123190223">"BluetoothShare நிர்வாகியை அணுகுவதற்குப் ஆப்ஸை அனுமதித்து, கோப்புகளைப் பரிமாற்ற அதைப் பயன்படுத்துகிறது."</string>
<string name="permlab_bluetoothWhitelist" msgid="7091552898592306386">"ஏற்புபட்டியல் புளூடூத் சாதன அணுகல்."</string>
- <string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"புளூடூத் சாதனத்தைத் தற்காலிகமாக ஏற்புபட்டியலில் சேர்க்க பயன்பாட்டை அனுமதிக்கிறது, பயனரின் உறுதிபடுத்தலின்றி கோப்புகளை இந்தச் சாதனத்திற்கு அனுப்ப அதை அனுமதிக்கிறது."</string>
+ <string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"புளூடூத் சாதனத்தைத் தற்காலிகமாக ஏற்புபட்டியலில் சேர்க்க ஆப்ஸை அனுமதிக்கிறது, பயனரின் உறுதிபடுத்தலின்றி கோப்புகளை இந்தச் சாதனத்திற்கு அனுப்ப அதை அனுமதிக்கிறது."</string>
<string name="bt_share_picker_label" msgid="6268100924487046932">"புளூடூத்"</string>
<string name="unknown_device" msgid="9221903979877041009">"அறியப்படாத சாதனம்"</string>
<string name="unknownNumber" msgid="4994750948072751566">"அறியப்படாதது"</string>
@@ -84,8 +84,8 @@
<string name="bt_toast_4" msgid="4678812947604395649">"\"<xliff:g id="RECIPIENT">%1$s</xliff:g>\" க்கு கோப்பு அனுப்பப்படுகிறது"</string>
<string name="bt_toast_5" msgid="2846870992823019494">"<xliff:g id="NUMBER">%1$s</xliff:g> கோப்புகள் \"<xliff:g id="RECIPIENT">%2$s</xliff:g>\" க்கு அனுப்பப்படுகின்றன"</string>
<string name="bt_toast_6" msgid="1855266596936622458">"\"<xliff:g id="RECIPIENT">%1$s</xliff:g>\" க்கு கோப்பு அனுப்புவது நிறுத்தப்பட்டது"</string>
- <string name="bt_sm_2_1" product="nosdcard" msgid="352165168004521000">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" இடமிருந்து வரும் கோப்பைச் சேமிக்க USB சேமிப்பிடத்தில் போதுமான இடம் இல்லை"</string>
- <string name="bt_sm_2_1" product="default" msgid="1989018443456803630">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" இடமிருந்து வரும் கோப்பைச் சேமிக்க SD கார்டில் போதுமான இடம் இல்லை"</string>
+ <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" அனுப்பும் ஃபைலைச் சேமிக்க USB சேமிப்பிடத்தில் போதுமான இடம் இல்லை"</string>
+ <string name="bt_sm_2_1_default" msgid="9115512207909504071">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" அனுப்பும் ஃபைலைச் சேமிக்க SD கார்டில் போதுமான இடம் இல்லை"</string>
<string name="bt_sm_2_2" msgid="2965243265852680543">"தேவைப்படும் இடம்: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
<string name="ErrorTooManyRequests" msgid="8578277541472944529">"மிக அதிகமான கோரிக்கைகள் செயல்படுத்தப்படுகின்றன. பிறகு முயற்சிக்கவும்."</string>
<string name="status_pending" msgid="2503691772030877944">"கோப்பு பரிமாற்றம் இன்னும் தொடங்கவில்லை."</string>
@@ -95,8 +95,8 @@
<string name="status_forbidden" msgid="613956401054050725">"இலக்குச் சாதனம் பரிமாற்றத்தைத் தடைசெய்தது."</string>
<string name="status_canceled" msgid="6664490318773098285">"பயனர் இடமாற்றத்தை ரத்துசெய்தார்."</string>
<string name="status_file_error" msgid="3671917770630165299">"சேமிப்பிடத்தில் சிக்கல்."</string>
- <string name="status_no_sd_card" product="nosdcard" msgid="1112125377088421469">"USB சேமிப்பிடம் இல்லை."</string>
- <string name="status_no_sd_card" product="default" msgid="5760944071743325592">"SD கார்டு இல்லை. பரிமாற்றப்பட்ட கோப்புகளைச் சேமிக்க SD கார்டைச் செருகவும்."</string>
+ <string name="status_no_sd_card_nosdcard" msgid="573631036356922221">"USB சேமிப்பிடம் இல்லை."</string>
+ <string name="status_no_sd_card_default" msgid="396564893716701954">"SD கார்டு இல்லை. அனுப்பப்பட்ட ஃபைல்களைச் சேமிக்க SD கார்டைச் செருகவும்."</string>
<string name="status_connection_error" msgid="947681831523219891">"இணைப்பு தோல்வி."</string>
<string name="status_protocol_error" msgid="3245444473429269539">"கோரிக்கை சரியாக கையாளப்படவில்லை."</string>
<string name="status_unknown_error" msgid="8156660554237824912">"தெரியாத பிழை."</string>
@@ -122,6 +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">"பாடல் விவரம்"</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>
diff --git a/res/values-te/strings.xml b/res/values-te/strings.xml
index 79ea712..cb26069 100644
--- a/res/values-te/strings.xml
+++ b/res/values-te/strings.xml
@@ -84,8 +84,8 @@
<string name="bt_toast_4" msgid="4678812947604395649">"ఫైల్ను \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\"కి పంపుతోంది"</string>
<string name="bt_toast_5" msgid="2846870992823019494">"<xliff:g id="NUMBER">%1$s</xliff:g> ఫైల్లను \"<xliff:g id="RECIPIENT">%2$s</xliff:g>\"కి పంపుతోంది"</string>
<string name="bt_toast_6" msgid="1855266596936622458">"ఫైల్ను \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\"కి పంపడం ఆపివేయబడింది"</string>
- <string name="bt_sm_2_1" product="nosdcard" msgid="352165168004521000">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" పంపిన పైల్ను సేవ్ చేయడానికి USB నిల్వలో తగినంత స్థలం లేదు"</string>
- <string name="bt_sm_2_1" product="default" msgid="1989018443456803630">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" పంపిన పైల్ను సేవ్ చేయడానికి SD కార్డులో తగినంత స్థలం లేదు"</string>
+ <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" పంపిన పైల్ను సేవ్ చేయడానికి USB నిల్వలో తగినంత స్థలం లేదు"</string>
+ <string name="bt_sm_2_1_default" msgid="9115512207909504071">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" పంపిన పైల్ను సేవ్ చేయడానికి SD కార్డులో తగినంత స్థలం లేదు"</string>
<string name="bt_sm_2_2" msgid="2965243265852680543">"కావలసిన స్థలం: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
<string name="ErrorTooManyRequests" msgid="8578277541472944529">"చాలా ఎక్కువ అభ్యర్థనలు ప్రాసెస్ చేయబడుతున్నాయి. తర్వాత మళ్లీ ప్రయత్నించండి."</string>
<string name="status_pending" msgid="2503691772030877944">"ఫైల్ బదిలీ ఇంకా ప్రారంభించబడలేదు."</string>
@@ -95,8 +95,8 @@
<string name="status_forbidden" msgid="613956401054050725">"లక్ష్య పరికరం బదిలీని నిషేధించింది."</string>
<string name="status_canceled" msgid="6664490318773098285">"వినియోగదారు బదిలీని రద్దు చేసారు."</string>
<string name="status_file_error" msgid="3671917770630165299">"నిల్వ సమస్య."</string>
- <string name="status_no_sd_card" product="nosdcard" msgid="1112125377088421469">"USB నిల్వ లేదు."</string>
- <string name="status_no_sd_card" product="default" msgid="5760944071743325592">"SD కార్డు లేదు. బదిలీ చేయబడిన ఫైల్లను సేవ్ చేయడానికి SD కార్డుని చొప్పించండి."</string>
+ <string name="status_no_sd_card_nosdcard" msgid="573631036356922221">"USB నిల్వ లేదు."</string>
+ <string name="status_no_sd_card_default" msgid="396564893716701954">"SD కార్డు లేదు. బదిలీ చేయబడిన ఫైల్లను సేవ్ చేయడానికి SD కార్డుని చొప్పించండి."</string>
<string name="status_connection_error" msgid="947681831523219891">"కనెక్షన్ విఫలమైంది."</string>
<string name="status_protocol_error" msgid="3245444473429269539">"అభ్యర్థన సరిగ్గా నిర్వహించబడదు."</string>
<string name="status_unknown_error" msgid="8156660554237824912">"తెలియని ఎర్రర్."</string>
@@ -122,6 +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">"ప్రస్తుతం ప్లే అవుతున్నవి"</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>
diff --git a/res/values-th/strings.xml b/res/values-th/strings.xml
index 8163820..8cfec4f 100644
--- a/res/values-th/strings.xml
+++ b/res/values-th/strings.xml
@@ -23,7 +23,7 @@
<string name="bt_share_picker_label" msgid="6268100924487046932">"บลูทูธ"</string>
<string name="unknown_device" msgid="9221903979877041009">"อุปกรณ์ที่ไม่รู้จัก"</string>
<string name="unknownNumber" msgid="4994750948072751566">"ไม่รู้จัก"</string>
- <string name="airplane_error_title" msgid="2683839635115739939">"โหมดใช้งานบนเครื่องบิน"</string>
+ <string name="airplane_error_title" msgid="2683839635115739939">"โหมดบนเครื่องบิน"</string>
<string name="airplane_error_msg" msgid="8698965595254137230">"คุณไม่สามารถใช้บลูทูธในโหมดใช้งานบนเครื่องบินได้"</string>
<string name="bt_enable_title" msgid="8657832550503456572"></string>
<string name="bt_enable_line1" msgid="7203551583048149">" เมื่อต้องการใช้บริการบลูทูธ คุณต้องเปิดบลูทูธก่อน"</string>
@@ -84,8 +84,8 @@
<string name="bt_toast_4" msgid="4678812947604395649">"กำลังส่งไฟล์ถึง \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
<string name="bt_toast_5" msgid="2846870992823019494">"กำลังส่ง <xliff:g id="NUMBER">%1$s</xliff:g> ไฟล์ถึง \"<xliff:g id="RECIPIENT">%2$s</xliff:g>\""</string>
<string name="bt_toast_6" msgid="1855266596936622458">"หยุดส่งไฟล์ถึง \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
- <string name="bt_sm_2_1" product="nosdcard" msgid="352165168004521000">"ที่จัดเก็บข้อมูล USB มีพื้นที่ว่างไม่เพียงพอที่จะบันทึกไฟล์จาก \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
- <string name="bt_sm_2_1" product="default" msgid="1989018443456803630">"การ์ด SD มีพื้นที่ว่างไม่เพียงพอที่จะบันทึกไฟล์จาก \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
+ <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"ที่จัดเก็บข้อมูล USB มีพื้นที่ว่างไม่เพียงพอที่จะบันทึกไฟล์จาก \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
+ <string name="bt_sm_2_1_default" msgid="9115512207909504071">"การ์ด SD มีพื้นที่ว่างไม่เพียงพอที่จะบันทึกไฟล์จาก \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
<string name="bt_sm_2_2" msgid="2965243265852680543">"พื้นที่ที่ต้องใช้: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
<string name="ErrorTooManyRequests" msgid="8578277541472944529">"กำลังประมวลผลคำขอมากเกินไป โปรดลองใหม่อีกครั้ง"</string>
<string name="status_pending" msgid="2503691772030877944">"ยังไม่ได้เริ่มการถ่ายโอนไฟล์"</string>
@@ -95,18 +95,18 @@
<string name="status_forbidden" msgid="613956401054050725">"อุปกรณ์เป้าหมายไม่รับการถ่ายโอน"</string>
<string name="status_canceled" msgid="6664490318773098285">"ผู้ใช้ยกเลิกการถ่ายโอน"</string>
<string name="status_file_error" msgid="3671917770630165299">"ปัญหาของที่จัดเก็บข้อมูล"</string>
- <string name="status_no_sd_card" product="nosdcard" msgid="1112125377088421469">"ไม่มีที่เก็บข้อมูล USB"</string>
- <string name="status_no_sd_card" product="default" msgid="5760944071743325592">"ไม่มีการ์ด SD ใส่การ์ด SD เพื่อบันทึกไฟล์ที่ถ่ายโอน"</string>
+ <string name="status_no_sd_card_nosdcard" msgid="573631036356922221">"ไม่มีที่เก็บข้อมูล USB"</string>
+ <string name="status_no_sd_card_default" msgid="396564893716701954">"ไม่มีการ์ด SD ใส่การ์ด SD เพื่อบันทึกไฟล์ที่ถ่ายโอน"</string>
<string name="status_connection_error" msgid="947681831523219891">"การเชื่อมต่อไม่สำเร็จ"</string>
<string name="status_protocol_error" msgid="3245444473429269539">"ไม่สามารถจัดการคำขอได้อย่างถูกต้อง"</string>
<string name="status_unknown_error" msgid="8156660554237824912">"ข้อผิดพลาดที่ไม่ทราบสาเหตุ"</string>
- <string name="btopp_live_folder" msgid="7967791481444474554">"ได้รับบลูทูธแล้ว"</string>
+ <string name="btopp_live_folder" msgid="7967791481444474554">"ไฟล์ที่ได้รับแล้วผ่านบลูทูธ"</string>
<string name="opp_notification_group" msgid="3486303082135789982">"การแชร์ทางบลูทูธ"</string>
<string name="download_success" msgid="7036160438766730871">"ได้รับแล้ว <xliff:g id="FILE_SIZE">%1$s</xliff:g>"</string>
<string name="upload_success" msgid="4014469387779648949">"ส่ง <xliff:g id="FILE_SIZE">%1$s</xliff:g> เรียบร้อยแล้ว"</string>
<string name="inbound_history_title" msgid="6940914942271327563">"การถ่ายโอนขาเข้า"</string>
<string name="outbound_history_title" msgid="4279418703178140526">"การถ่ายโอนข้อมูล"</string>
- <string name="no_transfers" msgid="3482965619151865672">"ประวัติการถ่ายโอนว่างเปล่า"</string>
+ <string name="no_transfers" msgid="3482965619151865672">"ไม่มีประวัติการโอนข้อมูล"</string>
<string name="transfer_clear_dlg_msg" msgid="1712376797268438075">"รายการทั้งหมดจะถูกล้างจากรายการ"</string>
<string name="outbound_noti_title" msgid="8051906709452260849">"การแชร์ทางบลูทูธ: ส่งไฟล์แล้ว"</string>
<string name="inbound_noti_title" msgid="4143352641953027595">"การแชร์ทางบลูทูธ: รับไฟล์แล้ว"</string>
@@ -122,6 +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">"กำลังเล่น"</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>
diff --git a/res/values-tl/strings.xml b/res/values-tl/strings.xml
index 4c7059e..1f50ceb 100644
--- a/res/values-tl/strings.xml
+++ b/res/values-tl/strings.xml
@@ -84,8 +84,8 @@
<string name="bt_toast_4" msgid="4678812947604395649">"Nagpapadala ng file kay \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
<string name="bt_toast_5" msgid="2846870992823019494">"Ipinapadala ang <xliff:g id="NUMBER">%1$s</xliff:g> (na) file kay \"<xliff:g id="RECIPIENT">%2$s</xliff:g>\""</string>
<string name="bt_toast_6" msgid="1855266596936622458">"Ihininto ang pagpapadala ng file kay \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
- <string name="bt_sm_2_1" product="nosdcard" msgid="352165168004521000">"Walang sapat na espasyo sa USB storage upang i-save ang file mula kay \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
- <string name="bt_sm_2_1" product="default" msgid="1989018443456803630">"Walang sapat na espasyo sa SD card upang i-save ang file mula kay \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
+ <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"Walang sapat na espasyo sa USB storage para ma-save ang file mula kay \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
+ <string name="bt_sm_2_1_default" msgid="9115512207909504071">"Walang sapat na espasyo sa SD card para ma-save ang file mula kay \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
<string name="bt_sm_2_2" msgid="2965243265852680543">"Kailangang puwang: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
<string name="ErrorTooManyRequests" msgid="8578277541472944529">"Masyadong maraming kahilingan ang pinoproseso. Subukang muli sa ibang pagkakataon."</string>
<string name="status_pending" msgid="2503691772030877944">"Hindi pa nasimulan ang paglilipat ng file."</string>
@@ -95,8 +95,8 @@
<string name="status_forbidden" msgid="613956401054050725">"Ipinagbabawal ng target na device ang paglilipat."</string>
<string name="status_canceled" msgid="6664490318773098285">"Kinansela ng user ang paglilipat."</string>
<string name="status_file_error" msgid="3671917770630165299">"Isyu sa pag-iimbak."</string>
- <string name="status_no_sd_card" product="nosdcard" msgid="1112125377088421469">"Walang storage na USB."</string>
- <string name="status_no_sd_card" product="default" msgid="5760944071743325592">"Walang SD card. Magpasok ng isang SD card upang i-save ang mga inilipat na file."</string>
+ <string name="status_no_sd_card_nosdcard" msgid="573631036356922221">"Walang USB storage."</string>
+ <string name="status_no_sd_card_default" msgid="396564893716701954">"Walang SD card. Maglagay ng SD card para ma-save ang mga inilipat na file."</string>
<string name="status_connection_error" msgid="947681831523219891">"Hindi matagumpay ang koneksyon."</string>
<string name="status_protocol_error" msgid="3245444473429269539">"Hindi mapangasiwaan nang tama ang kahilingan."</string>
<string name="status_unknown_error" msgid="8156660554237824912">"Hindi kilalang error."</string>
@@ -122,6 +122,7 @@
<string name="transfer_menu_open" msgid="3368984869083107200">"Buksan"</string>
<string name="transfer_menu_clear" msgid="5854038118831427492">"I-clear mula sa listahan"</string>
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"I-clear"</string>
+ <string name="bluetooth_a2dp_sink_queue_name" msgid="6864149958708669766">"Nagpi-play Ngayon"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"I-save"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"Kanselahin"</string>
<string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"Piliin ang mga account na gusto mong ibahagi sa pamamagitan ng Bluetooth. Kailangan mo pa ring tanggapin ang anumang pag-access sa mga account kapag kumokonekta."</string>
diff --git a/res/values-tr/strings.xml b/res/values-tr/strings.xml
index 2d2fd00..71ce9a4 100644
--- a/res/values-tr/strings.xml
+++ b/res/values-tr/strings.xml
@@ -84,8 +84,8 @@
<string name="bt_toast_4" msgid="4678812947604395649">"Dosya \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\" hedefine gönderiliyor"</string>
<string name="bt_toast_5" msgid="2846870992823019494">"<xliff:g id="NUMBER">%1$s</xliff:g> dosya \"<xliff:g id="RECIPIENT">%2$s</xliff:g>\" hedefine gönderiliyor"</string>
<string name="bt_toast_6" msgid="1855266596936622458">"\"<xliff:g id="RECIPIENT">%1$s</xliff:g>\" hedefine dosya gönderme işlemi durduruldu"</string>
- <string name="bt_sm_2_1" product="nosdcard" msgid="352165168004521000">"USB bellekte \"<xliff:g id="SENDER">%1$s</xliff:g>\" kaynağından gelen dosyayı kaydedecek kadar alan yok"</string>
- <string name="bt_sm_2_1" product="default" msgid="1989018443456803630">"SD kartta \"<xliff:g id="SENDER">%1$s</xliff:g>\" kaynağından gelen dosyayı kaydedecek kadar alan yok"</string>
+ <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"USB depolama biriminde \"<xliff:g id="SENDER">%1$s</xliff:g>\" kaynağından gelen dosyayı kaydedecek kadar alan yok"</string>
+ <string name="bt_sm_2_1_default" msgid="9115512207909504071">"SD kartta \"<xliff:g id="SENDER">%1$s</xliff:g>\" kaynağından gelen dosyayı kaydedecek kadar alan yok"</string>
<string name="bt_sm_2_2" msgid="2965243265852680543">"Gereken alan: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
<string name="ErrorTooManyRequests" msgid="8578277541472944529">"Çok fazla sayıda istek işleniyor. Daha sonra yeniden deneyin."</string>
<string name="status_pending" msgid="2503691772030877944">"Dosya aktarımı henüz başlatılmadı."</string>
@@ -95,8 +95,8 @@
<string name="status_forbidden" msgid="613956401054050725">"Aktarım, hedef cihaz tarafından yasaklandı."</string>
<string name="status_canceled" msgid="6664490318773098285">"Aktarım, kullanıcı tarafından iptal edildi."</string>
<string name="status_file_error" msgid="3671917770630165299">"Depolama sorunu."</string>
- <string name="status_no_sd_card" product="nosdcard" msgid="1112125377088421469">"USB bellek yok."</string>
- <string name="status_no_sd_card" product="default" msgid="5760944071743325592">"SD kart yok. Aktarılan dosyaları kaydetmek için lütfen bir SD kart takın."</string>
+ <string name="status_no_sd_card_nosdcard" msgid="573631036356922221">"USB bellek yok."</string>
+ <string name="status_no_sd_card_default" msgid="396564893716701954">"SD kart yok. Aktarılan dosyaları kaydetmek için lütfen bir SD kart takın."</string>
<string name="status_connection_error" msgid="947681831523219891">"Bağlantı başarısız."</string>
<string name="status_protocol_error" msgid="3245444473429269539">"İstek düzgün bir şekilde işlenemiyor."</string>
<string name="status_unknown_error" msgid="8156660554237824912">"Bilinmeyen hata."</string>
@@ -122,6 +122,7 @@
<string name="transfer_menu_open" msgid="3368984869083107200">"Aç"</string>
<string name="transfer_menu_clear" msgid="5854038118831427492">"Listeden temizle"</string>
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"Temizle"</string>
+ <string name="bluetooth_a2dp_sink_queue_name" msgid="6864149958708669766">"Ne Çalıyor?"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"Kaydet"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"İptal"</string>
<string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"Bluetooth üzerinden paylaşmak istediğiniz hesapları seçin. Bağlanırken yine de hesaplara erişimi kabul etmeniz gerekmektedir."</string>
diff --git a/res/values-uk/strings.xml b/res/values-uk/strings.xml
index cbb9f66..e357d13 100644
--- a/res/values-uk/strings.xml
+++ b/res/values-uk/strings.xml
@@ -84,8 +84,8 @@
<string name="bt_toast_4" msgid="4678812947604395649">"Надсил-ня файлу до \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
<string name="bt_toast_5" msgid="2846870992823019494">"Надсил-ня файлів (<xliff:g id="NUMBER">%1$s</xliff:g>) до \"<xliff:g id="RECIPIENT">%2$s</xliff:g>\""</string>
<string name="bt_toast_6" msgid="1855266596936622458">"Зупинено надсил. файлу до \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
- <string name="bt_sm_2_1" product="nosdcard" msgid="352165168004521000">"На носії USB недостатньо місця, щоб зберегти файл від відправника \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
- <string name="bt_sm_2_1" product="default" msgid="1989018443456803630">"На карті SD недостатньо місця, щоб зберегти файл від користувача \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
+ <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"На носії USB замало місця, щоб зберегти файл, який надсилає <xliff:g id="SENDER">%1$s</xliff:g>"</string>
+ <string name="bt_sm_2_1_default" msgid="9115512207909504071">"На карті SD замало місця, щоб зберегти файл, який надсилає <xliff:g id="SENDER">%1$s</xliff:g>"</string>
<string name="bt_sm_2_2" msgid="2965243265852680543">"Потрібно місця: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
<string name="ErrorTooManyRequests" msgid="8578277541472944529">"Обробляється забагато запитів. Спробуйте пізніше."</string>
<string name="status_pending" msgid="2503691772030877944">"Передавання файлів ще не почалося."</string>
@@ -95,8 +95,8 @@
<string name="status_forbidden" msgid="613956401054050725">"Передавання заборонено цільовим пристроєм."</string>
<string name="status_canceled" msgid="6664490318773098285">"Передавання скасовано користувачем."</string>
<string name="status_file_error" msgid="3671917770630165299">"Проблема зі зберіганням."</string>
- <string name="status_no_sd_card" product="nosdcard" msgid="1112125377088421469">"Відсутній носій USB."</string>
- <string name="status_no_sd_card" product="default" msgid="5760944071743325592">"Немає карти SD. Вставте карту SD, щоб збер. пересл. файли."</string>
+ <string name="status_no_sd_card_nosdcard" msgid="573631036356922221">"Немає носія USB."</string>
+ <string name="status_no_sd_card_default" msgid="396564893716701954">"Немає карти SD. Щоб зберегти перенесені файли, вставте карту SD."</string>
<string name="status_connection_error" msgid="947681831523219891">"Помилка з’єднання."</string>
<string name="status_protocol_error" msgid="3245444473429269539">"Запит неможливо обробити належним чином."</string>
<string name="status_unknown_error" msgid="8156660554237824912">"Невідома помилка."</string>
@@ -126,6 +126,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">"Зараз грає"</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>
diff --git a/res/values-ur/strings.xml b/res/values-ur/strings.xml
index 3e46987..cc13746 100644
--- a/res/values-ur/strings.xml
+++ b/res/values-ur/strings.xml
@@ -84,8 +84,8 @@
<string name="bt_toast_4" msgid="4678812947604395649">"\"<xliff:g id="RECIPIENT">%1$s</xliff:g>\" کو فائل بھیجی جا رہی ہے"</string>
<string name="bt_toast_5" msgid="2846870992823019494">"<xliff:g id="NUMBER">%1$s</xliff:g> فائلیں \"<xliff:g id="RECIPIENT">%2$s</xliff:g>\" کو بھیجی جا رہی ہیں"</string>
<string name="bt_toast_6" msgid="1855266596936622458">"\"<xliff:g id="RECIPIENT">%1$s</xliff:g>\" کو فائل بھیجنا بند ہو گیا"</string>
- <string name="bt_sm_2_1" product="nosdcard" msgid="352165168004521000">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" کی جانب سے فائل محفوظ کرنے کیلئے USB اسٹوریج میں کافی جگہ نہیں ہے"</string>
- <string name="bt_sm_2_1" product="default" msgid="1989018443456803630">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" کی جانب سے فائل محفوظ کرنے کیلئے SD کارڈ میں کافی جگہ نہیں ہے"</string>
+ <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" کی جانب سے موصول ہونے والی فائل کو محفوظ کرنے کے لیے USB اسٹوریج میں کافی جگہ نہیں ہے"</string>
+ <string name="bt_sm_2_1_default" msgid="9115512207909504071">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" کی جانب سے موصول ہونے والی فائل کو محفوظ کرنے کے لیے SD کارڈ میں کافی جگہ نہیں ہے"</string>
<string name="bt_sm_2_2" msgid="2965243265852680543">"جگہ درکار ہے: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
<string name="ErrorTooManyRequests" msgid="8578277541472944529">"کافی زیادہ درخواستوں پر کارروائی کی جا رہی ہے۔ بعد میں دوبارہ کوشش کریں۔"</string>
<string name="status_pending" msgid="2503691772030877944">"فائل کی منتقلی ابھی شروع نہیں ہوئی۔"</string>
@@ -95,8 +95,8 @@
<string name="status_forbidden" msgid="613956401054050725">"ٹارگٹ آلہ کے ذریعہ منتقلی ممنوع ہے۔"</string>
<string name="status_canceled" msgid="6664490318773098285">"صارف نے منتقلی منسوخ کر دی۔"</string>
<string name="status_file_error" msgid="3671917770630165299">"اسٹوریج کا مسئلہ۔"</string>
- <string name="status_no_sd_card" product="nosdcard" msgid="1112125377088421469">"کوئی USB اسٹوریج نہیں ہے۔"</string>
- <string name="status_no_sd_card" product="default" msgid="5760944071743325592">"کوئی SD کارڈ نہیں ہے۔ منتقل کردہ فائلیں محفوظ کرنے کیلئے ایک SD کارڈ داخل کریں۔"</string>
+ <string name="status_no_sd_card_nosdcard" msgid="573631036356922221">"کوئی USB اسٹوریج نہیں ہے۔"</string>
+ <string name="status_no_sd_card_default" msgid="396564893716701954">"کوئی SD کارڈ نہیں ہے۔ منتقل کردہ فائلز کو محفوظ کرنے کے لیے ایک SD کارڈ داخل کریں۔"</string>
<string name="status_connection_error" msgid="947681831523219891">"کنکشن ناکام ہو گیا۔"</string>
<string name="status_protocol_error" msgid="3245444473429269539">"درخواست کو ٹھیک سے ہینڈل نہیں کیا جا سکتا۔"</string>
<string name="status_unknown_error" msgid="8156660554237824912">"نامعلوم خرابی۔"</string>
@@ -122,6 +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_map_settings_save" msgid="7635491847388074606">"محفوظ کریں"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"منسوخ کریں"</string>
<string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"ان اکاؤنٹس کو منتخب کریں جن کا آپ بلوٹوتھ کے ذریعے اشتراک کرنا چاہتے ہیں۔ آپ کو ابھی بھی منسلک ہوتے وقت اکاؤنٹس تک کسی بھی رسائی کو قبول کرنا ہوگا۔"</string>
diff --git a/res/values-uz/strings.xml b/res/values-uz/strings.xml
index 88535c5..dac670b 100644
--- a/res/values-uz/strings.xml
+++ b/res/values-uz/strings.xml
@@ -84,8 +84,8 @@
<string name="bt_toast_4" msgid="4678812947604395649">"\"<xliff:g id="RECIPIENT">%1$s</xliff:g>\"ga fayl jo‘natilmoqda"</string>
<string name="bt_toast_5" msgid="2846870992823019494">"<xliff:g id="NUMBER">%1$s</xliff:g> ta fayl \"<xliff:g id="RECIPIENT">%2$s</xliff:g>\"ga jo‘natimoqda"</string>
<string name="bt_toast_6" msgid="1855266596936622458">"\"<xliff:g id="RECIPIENT">%1$s</xliff:g>\"ga fayl jo‘natish to‘xtatildi"</string>
- <string name="bt_sm_2_1" product="nosdcard" msgid="352165168004521000">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" yuborgan faylni saqlash uchun USB xotirada yetarlicha bo‘sh joy yo‘q."</string>
- <string name="bt_sm_2_1" product="default" msgid="1989018443456803630">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" yuborgan faylni saqlash uchun SD kartada yetarlicha bo‘sh joy yo‘q"</string>
+ <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"“<xliff:g id="SENDER">%1$s</xliff:g>” yuborgan faylni saqlash uchun USB xotirada joy yetarli emas"</string>
+ <string name="bt_sm_2_1_default" msgid="9115512207909504071">"“<xliff:g id="SENDER">%1$s</xliff:g>” yuborgan faylni saqlash uchun SD kartada joy yetarli emas"</string>
<string name="bt_sm_2_2" msgid="2965243265852680543">"Kerakli bo‘sh joy: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
<string name="ErrorTooManyRequests" msgid="8578277541472944529">"Juda ko‘p so‘rovlarga ishlov berilmoqda. Keyinroq urinib ko‘ring."</string>
<string name="status_pending" msgid="2503691772030877944">"Fayl o‘tkazmasi hali boshlanmadi."</string>
@@ -95,8 +95,8 @@
<string name="status_forbidden" msgid="613956401054050725">"Qabul qiluvchi qurilmada fayl o‘tkazish taqiqlangan."</string>
<string name="status_canceled" msgid="6664490318773098285">"O‘tkazma bekor qilindi."</string>
<string name="status_file_error" msgid="3671917770630165299">"Saqlash muammolari."</string>
- <string name="status_no_sd_card" product="nosdcard" msgid="1112125377088421469">"USB xotira yo‘q."</string>
- <string name="status_no_sd_card" product="default" msgid="5760944071743325592">"SD karta yo‘q. O‘tkazilgan fayllarni saqlash uchun SD karta kiriting."</string>
+ <string name="status_no_sd_card_nosdcard" msgid="573631036356922221">"USB xotira topilmadi."</string>
+ <string name="status_no_sd_card_default" msgid="396564893716701954">"SD karta topilmadi. Qabul qilingan fayllarni saqlash uchun SD kartani soling."</string>
<string name="status_connection_error" msgid="947681831523219891">"Ulanish muvaffaqiyatsiz yakunlandi."</string>
<string name="status_protocol_error" msgid="3245444473429269539">"So‘rovni to‘g‘ri bajarib bo‘lmaydi."</string>
<string name="status_unknown_error" msgid="8156660554237824912">"Noma’lum xato."</string>
@@ -122,6 +122,7 @@
<string name="transfer_menu_open" msgid="3368984869083107200">"Ochish"</string>
<string name="transfer_menu_clear" msgid="5854038118831427492">"Ro‘yxatdan o‘chirish"</string>
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"Tozalash"</string>
+ <string name="bluetooth_a2dp_sink_queue_name" msgid="6864149958708669766">"Ijro qilinmoqda"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"Saqlash"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"Bekor qilish"</string>
<string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"Bluetooth orqali narsa o‘tkazmoqchi bo‘lgan hisoblarni tanlang. Har safar ulanishda so‘rovni tasdiqlash talab qilinadi."</string>
diff --git a/res/values-vi/strings.xml b/res/values-vi/strings.xml
index 0be8dea..4a40dfe 100644
--- a/res/values-vi/strings.xml
+++ b/res/values-vi/strings.xml
@@ -84,8 +84,8 @@
<string name="bt_toast_4" msgid="4678812947604395649">"Đang gửi tệp tới \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
<string name="bt_toast_5" msgid="2846870992823019494">"Đang gửi <xliff:g id="NUMBER">%1$s</xliff:g> tệp tới \"<xliff:g id="RECIPIENT">%2$s</xliff:g>\""</string>
<string name="bt_toast_6" msgid="1855266596936622458">"Đã dừng gửi tệp tới \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
- <string name="bt_sm_2_1" product="nosdcard" msgid="352165168004521000">"Không đủ dung lượng trong bộ nhớ USB để lưu tệp từ \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
- <string name="bt_sm_2_1" product="default" msgid="1989018443456803630">"Không đủ dung lượng trên thẻ SD để lưu tệp từ \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
+ <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"Không đủ dung lượng trong bộ nhớ USB để lưu tệp từ \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
+ <string name="bt_sm_2_1_default" msgid="9115512207909504071">"Không đủ dung lượng trên thẻ SD để lưu tệp từ \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
<string name="bt_sm_2_2" msgid="2965243265852680543">"Dung lượng cần: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
<string name="ErrorTooManyRequests" msgid="8578277541472944529">"Quá nhiều yêu cầu đang được xử lý. Hãy thử lại sau."</string>
<string name="status_pending" msgid="2503691772030877944">"Chuyển tệp chưa bắt đầu."</string>
@@ -95,8 +95,8 @@
<string name="status_forbidden" msgid="613956401054050725">"Quá trình chuyển tệp bị thiết bị đích chặn."</string>
<string name="status_canceled" msgid="6664490318773098285">"Chuyển tệp đã bị người dùng hủy."</string>
<string name="status_file_error" msgid="3671917770630165299">"Sự cố về lưu trữ."</string>
- <string name="status_no_sd_card" product="nosdcard" msgid="1112125377088421469">"Không có bộ nhớ USB."</string>
- <string name="status_no_sd_card" product="default" msgid="5760944071743325592">"Không có thẻ SD nào. Hãy lắp thẻ SD để lưu tệp được chuyển."</string>
+ <string name="status_no_sd_card_nosdcard" msgid="573631036356922221">"Không có bộ nhớ USB."</string>
+ <string name="status_no_sd_card_default" msgid="396564893716701954">"Không có thẻ SD nào. Hãy lắp thẻ SD để lưu tệp được chuyển."</string>
<string name="status_connection_error" msgid="947681831523219891">"Kết nối không thành công."</string>
<string name="status_protocol_error" msgid="3245444473429269539">"Không thể xử lý yêu cầu đúng cách."</string>
<string name="status_unknown_error" msgid="8156660554237824912">"Lỗi không xác định."</string>
@@ -122,6 +122,7 @@
<string name="transfer_menu_open" msgid="3368984869083107200">"Mở"</string>
<string name="transfer_menu_clear" msgid="5854038118831427492">"Xóa khỏi danh sách"</string>
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"Xóa"</string>
+ <string name="bluetooth_a2dp_sink_queue_name" msgid="6864149958708669766">"Phát hiện nhạc"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"Lưu"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"Hủy"</string>
<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>
diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml
index 5a8fb6e..af01d6a 100644
--- a/res/values-zh-rCN/strings.xml
+++ b/res/values-zh-rCN/strings.xml
@@ -84,8 +84,8 @@
<string name="bt_toast_4" msgid="4678812947604395649">"正在向“<xliff:g id="RECIPIENT">%1$s</xliff:g>”发送文件"</string>
<string name="bt_toast_5" msgid="2846870992823019494">"正在向“<xliff:g id="RECIPIENT">%2$s</xliff:g>”发送<xliff:g id="NUMBER">%1$s</xliff:g>个文件"</string>
<string name="bt_toast_6" msgid="1855266596936622458">"已停止向“<xliff:g id="RECIPIENT">%1$s</xliff:g>”发送文件"</string>
- <string name="bt_sm_2_1" product="nosdcard" msgid="352165168004521000">"USB存储设备空间不足,无法保存来自“<xliff:g id="SENDER">%1$s</xliff:g>”的文件"</string>
- <string name="bt_sm_2_1" product="default" msgid="1989018443456803630">"SD卡存储空间不足,无法保存来自“<xliff:g id="SENDER">%1$s</xliff:g>”的文件"</string>
+ <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"USB 存储设备空间不足,无法保存来自“<xliff:g id="SENDER">%1$s</xliff:g>”的文件"</string>
+ <string name="bt_sm_2_1_default" msgid="9115512207909504071">"SD 卡存储空间不足,无法保存来自“<xliff:g id="SENDER">%1$s</xliff:g>”的文件"</string>
<string name="bt_sm_2_2" msgid="2965243265852680543">"所需空间:<xliff:g id="SIZE">%1$s</xliff:g>"</string>
<string name="ErrorTooManyRequests" msgid="8578277541472944529">"正在处理的请求太多。请稍后重试。"</string>
<string name="status_pending" msgid="2503691772030877944">"尚未开始传输文件。"</string>
@@ -95,8 +95,8 @@
<string name="status_forbidden" msgid="613956401054050725">"目标设备禁止进行传输。"</string>
<string name="status_canceled" msgid="6664490318773098285">"用户取消了传输。"</string>
<string name="status_file_error" msgid="3671917770630165299">"存储问题。"</string>
- <string name="status_no_sd_card" product="nosdcard" msgid="1112125377088421469">"没有USB存储设备。"</string>
- <string name="status_no_sd_card" product="default" msgid="5760944071743325592">"无SD卡。请插入SD卡保存传输的文件。"</string>
+ <string name="status_no_sd_card_nosdcard" msgid="573631036356922221">"没有 USB 存储设备。"</string>
+ <string name="status_no_sd_card_default" msgid="396564893716701954">"无 SD 卡。请插入 SD 卡以保存传输的文件。"</string>
<string name="status_connection_error" msgid="947681831523219891">"连接失败。"</string>
<string name="status_protocol_error" msgid="3245444473429269539">"无法正确处理请求。"</string>
<string name="status_unknown_error" msgid="8156660554237824912">"未知错误。"</string>
@@ -122,6 +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">"闻曲知音"</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>
diff --git a/res/values-zh-rHK/strings.xml b/res/values-zh-rHK/strings.xml
index e2bba89..dfb352a 100644
--- a/res/values-zh-rHK/strings.xml
+++ b/res/values-zh-rHK/strings.xml
@@ -84,8 +84,8 @@
<string name="bt_toast_4" msgid="4678812947604395649">"正在將檔案傳送給「<xliff:g id="RECIPIENT">%1$s</xliff:g>」"</string>
<string name="bt_toast_5" msgid="2846870992823019494">"正在將 <xliff:g id="NUMBER">%1$s</xliff:g> 個檔案傳送給「<xliff:g id="RECIPIENT">%2$s</xliff:g>」"</string>
<string name="bt_toast_6" msgid="1855266596936622458">"已停止將檔案傳送給「<xliff:g id="RECIPIENT">%1$s</xliff:g>」"</string>
- <string name="bt_sm_2_1" product="nosdcard" msgid="352165168004521000">"USB 儲存裝置上空間不足,無法儲存「<xliff:g id="SENDER">%1$s</xliff:g>」傳來的檔案"</string>
- <string name="bt_sm_2_1" product="default" msgid="1989018443456803630">"SD 記憶卡上空間不足,無法儲存「<xliff:g id="SENDER">%1$s</xliff:g>」傳來的檔案"</string>
+ <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"USB 儲存空間上的儲存空間不足,無法儲存「<xliff:g id="SENDER">%1$s</xliff:g>」傳來的檔案"</string>
+ <string name="bt_sm_2_1_default" msgid="9115512207909504071">"SD 卡上的儲存空間不足,無法儲存「<xliff:g id="SENDER">%1$s</xliff:g>」傳來的檔案"</string>
<string name="bt_sm_2_2" msgid="2965243265852680543">"所需儲存空間:<xliff:g id="SIZE">%1$s</xliff:g>"</string>
<string name="ErrorTooManyRequests" msgid="8578277541472944529">"同時處理過多要求,請稍後再試。"</string>
<string name="status_pending" msgid="2503691772030877944">"尚未開始傳輸檔案。"</string>
@@ -95,8 +95,8 @@
<string name="status_forbidden" msgid="613956401054050725">"目標裝置禁止傳輸。"</string>
<string name="status_canceled" msgid="6664490318773098285">"使用者已取消傳輸。"</string>
<string name="status_file_error" msgid="3671917770630165299">"儲存空間問題。"</string>
- <string name="status_no_sd_card" product="nosdcard" msgid="1112125377088421469">"沒有 USB 儲存裝置。"</string>
- <string name="status_no_sd_card" product="default" msgid="5760944071743325592">"沒有 SD 卡,請插入 SD 卡來儲存傳輸的檔案。"</string>
+ <string name="status_no_sd_card_nosdcard" msgid="573631036356922221">"沒有 USB 儲存空間。"</string>
+ <string name="status_no_sd_card_default" msgid="396564893716701954">"沒有 SD 卡,請插入 SD 卡來儲存傳輸的檔案。"</string>
<string name="status_connection_error" msgid="947681831523219891">"連線失敗。"</string>
<string name="status_protocol_error" msgid="3245444473429269539">"無法正確處理要求。"</string>
<string name="status_unknown_error" msgid="8156660554237824912">"未知錯誤。"</string>
@@ -106,7 +106,7 @@
<string name="upload_success" msgid="4014469387779648949">"已完成 <xliff:g id="FILE_SIZE">%1$s</xliff:g> 的傳送作業。"</string>
<string name="inbound_history_title" msgid="6940914942271327563">"外來傳輸"</string>
<string name="outbound_history_title" msgid="4279418703178140526">"向外傳輸"</string>
- <string name="no_transfers" msgid="3482965619151865672">"傳輸記錄是空的。"</string>
+ <string name="no_transfers" msgid="3482965619151865672">"傳輸記錄為空白。"</string>
<string name="transfer_clear_dlg_msg" msgid="1712376797268438075">"將會從清單清除所有項目。"</string>
<string name="outbound_noti_title" msgid="8051906709452260849">"藍牙分享:傳送的檔案"</string>
<string name="inbound_noti_title" msgid="4143352641953027595">"藍牙分享:接收的檔案"</string>
@@ -122,6 +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">"歌曲識別"</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>
diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml
index 66e7d2d..d52c996 100644
--- a/res/values-zh-rTW/strings.xml
+++ b/res/values-zh-rTW/strings.xml
@@ -84,8 +84,8 @@
<string name="bt_toast_4" msgid="4678812947604395649">"正在將檔案傳送給「<xliff:g id="RECIPIENT">%1$s</xliff:g>」"</string>
<string name="bt_toast_5" msgid="2846870992823019494">"正在將 <xliff:g id="NUMBER">%1$s</xliff:g> 個檔案傳送給「<xliff:g id="RECIPIENT">%2$s</xliff:g>」"</string>
<string name="bt_toast_6" msgid="1855266596936622458">"已停止將檔案傳送給「<xliff:g id="RECIPIENT">%1$s</xliff:g>」"</string>
- <string name="bt_sm_2_1" product="nosdcard" msgid="352165168004521000">"USB 儲存裝置上沒有足夠的空間可以儲存「<xliff:g id="SENDER">%1$s</xliff:g>」傳來的檔案"</string>
- <string name="bt_sm_2_1" product="default" msgid="1989018443456803630">"SD 卡上沒有足夠的空間可以儲存「<xliff:g id="SENDER">%1$s</xliff:g>」傳來的檔案"</string>
+ <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"USB 儲存空間上沒有足夠的空間可以儲存「<xliff:g id="SENDER">%1$s</xliff:g>」傳來的檔案"</string>
+ <string name="bt_sm_2_1_default" msgid="9115512207909504071">"SD 卡上沒有足夠的空間可以儲存「<xliff:g id="SENDER">%1$s</xliff:g>」傳來的檔案"</string>
<string name="bt_sm_2_2" msgid="2965243265852680543">"所需儲存空間:<xliff:g id="SIZE">%1$s</xliff:g>"</string>
<string name="ErrorTooManyRequests" msgid="8578277541472944529">"系統正在處理多個要求,請稍後再試。"</string>
<string name="status_pending" msgid="2503691772030877944">"尚未開始傳輸檔案。"</string>
@@ -95,8 +95,8 @@
<string name="status_forbidden" msgid="613956401054050725">"目標裝置禁止進行傳輸。"</string>
<string name="status_canceled" msgid="6664490318773098285">"使用者已取消傳輸。"</string>
<string name="status_file_error" msgid="3671917770630165299">"儲存空間問題。"</string>
- <string name="status_no_sd_card" product="nosdcard" msgid="1112125377088421469">"沒有 USB 儲存裝置。"</string>
- <string name="status_no_sd_card" product="default" msgid="5760944071743325592">"沒有 SD 卡,請插入 SD 卡來儲存傳輸的檔案。"</string>
+ <string name="status_no_sd_card_nosdcard" msgid="573631036356922221">"沒有 USB 儲存空間。"</string>
+ <string name="status_no_sd_card_default" msgid="396564893716701954">"沒有 SD 卡,請插入 SD 卡來儲存傳輸的檔案。"</string>
<string name="status_connection_error" msgid="947681831523219891">"連線失敗。"</string>
<string name="status_protocol_error" msgid="3245444473429269539">"無法正確處理要求。"</string>
<string name="status_unknown_error" msgid="8156660554237824912">"未知的錯誤。"</string>
@@ -122,6 +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">"聽聲辨曲"</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>
diff --git a/res/values-zu/strings.xml b/res/values-zu/strings.xml
index 79c3ca2..1bc0fe6 100644
--- a/res/values-zu/strings.xml
+++ b/res/values-zu/strings.xml
@@ -84,8 +84,8 @@
<string name="bt_toast_4" msgid="4678812947604395649">"Ithumela ifayela ku- \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
<string name="bt_toast_5" msgid="2846870992823019494">"Ithumela <xliff:g id="NUMBER">%1$s</xliff:g> amafayela ku- \"<xliff:g id="RECIPIENT">%2$s</xliff:g>\""</string>
<string name="bt_toast_6" msgid="1855266596936622458">"Imise ukuthumela ifayela ku- \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
- <string name="bt_sm_2_1" product="nosdcard" msgid="352165168004521000">"Asikho isikhala esanele kwindawo yokugcina ye-USB ukulondoloza ifayela kusuka ku- \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
- <string name="bt_sm_2_1" product="default" msgid="1989018443456803630">"Asikho isikhala esanele ekhadini le-SD ukulongcina ifayela esuka ku- \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
+ <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"Asikho isikhala esanele kwindawo yokugcina ye-USB ukulondoloza ifayela kusuka ku- \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
+ <string name="bt_sm_2_1_default" msgid="9115512207909504071">"Asikho isikhala esanele ekhadini le-SD ukulongcina ifayela esuka ku- \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
<string name="bt_sm_2_2" msgid="2965243265852680543">"Isikhala esidingekayo: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
<string name="ErrorTooManyRequests" msgid="8578277541472944529">"Kunezicelo eziningi ezenziwayo. Zama futhi emva kwesikhathi."</string>
<string name="status_pending" msgid="2503691772030877944">"Ukudlulisa ifayela akuqalisiwe okwamanje"</string>
@@ -95,8 +95,8 @@
<string name="status_forbidden" msgid="613956401054050725">"Lokhu kudlulisa kunqatshelwe idivayisi eqondiwe"</string>
<string name="status_canceled" msgid="6664490318773098285">"Ukudlulisa kukhanselwe umsebenzisi."</string>
<string name="status_file_error" msgid="3671917770630165299">"Inkinga yokugcina"</string>
- <string name="status_no_sd_card" product="nosdcard" msgid="1112125377088421469">"Akukho sitoreji se-USB."</string>
- <string name="status_no_sd_card" product="default" msgid="5760944071743325592">"Alikho ikhadi le-SD. Faka ikhadi le-SD ukulondoloza amafayela adlulisiwe."</string>
+ <string name="status_no_sd_card_nosdcard" msgid="573631036356922221">"Akukho sitoreji se-USB."</string>
+ <string name="status_no_sd_card_default" msgid="396564893716701954">"Alikho ikhadi le-SD. Faka ikhadi le-SD ukulondoloza amafayela adlulisiwe."</string>
<string name="status_connection_error" msgid="947681831523219891">"Ukuxhumeka akuphumelelanga."</string>
<string name="status_protocol_error" msgid="3245444473429269539">"Isicelo asikwazi ukuphathwa ngokulungile"</string>
<string name="status_unknown_error" msgid="8156660554237824912">"Iphutha elingaziwa"</string>
@@ -122,6 +122,7 @@
<string name="transfer_menu_open" msgid="3368984869083107200">"Vula"</string>
<string name="transfer_menu_clear" msgid="5854038118831427492">"Sula ohlwini"</string>
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"Sula"</string>
+ <string name="bluetooth_a2dp_sink_queue_name" msgid="6864149958708669766">"Okudlala manje"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"Londoloza"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"Khansela"</string>
<string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"Khetha ama-akhawunti ofuna ukwabelana nawo nge-Bluetooth. Kusazomele wamukele noma yikuphi ukufinyelelwa kuma-akhawunti uma kuxhunywa."</string>
diff --git a/res/values/config.xml b/res/values/config.xml
index cf2ceef..7236b2f 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -15,7 +15,6 @@
<resources>
<bool name="profile_supported_a2dp">true</bool>
<bool name="profile_supported_a2dp_sink">false</bool>
- <bool name="profile_supported_hdp">true</bool>
<bool name="profile_supported_hs_hfp">true</bool>
<bool name="profile_supported_hfpclient">false</bool>
<bool name="profile_supported_hid_host">true</bool>
@@ -27,12 +26,11 @@
<bool name="pbap_use_profile_for_owner_vcard">true</bool>
<bool name="profile_supported_map">true</bool>
<bool name="profile_supported_avrcp_target">true</bool>
- <bool name="profile_supported_avrcp_controller">false</bool>
- <bool name="profile_supported_sap">false</bool>
+ <bool name="profile_supported_avrcp_controller">true</bool>
+ <bool name="profile_supported_sap">true</bool>
<bool name="profile_supported_pbapclient">false</bool>
<bool name="profile_supported_mapmce">false</bool>
<bool name="profile_supported_hid_device">true</bool>
- <bool name="profile_supported_hearing_aid">false</bool>
<bool name="profile_supported_ba">false</bool>
<!-- If true, we will require location to be enabled on the device to
@@ -72,10 +70,15 @@
<!-- For A2DP sink ducking volume feature. -->
<integer name="a2dp_sink_duck_percent">25</integer>
+ <!-- If true, device requests audio focus and start avrcp updates on source start or play -->
+ <bool name="a2dp_sink_automatically_request_audio_focus">false</bool>
<!-- For enabling the hfp client connection service -->
<bool name="hfp_client_connection_service_enabled">false</bool>
+ <!-- For supporting emergency call through the hfp client connection service -->
+ <bool name="hfp_client_connection_service_support_emergency_call">false</bool>
+
<!-- Enabling autoconnect over pan -->
<bool name="config_bluetooth_pan_enable_autoconnect">true</bool>
@@ -94,6 +97,12 @@
<integer name="a2dp_source_codec_priority_aptx_hd">5001</integer>
<integer name="a2dp_source_codec_priority_aptx_adaptive">6001</integer>
<integer name="a2dp_source_codec_priority_aptx_tws">7001</integer>
+ <!-- max priority is used to dynamically increase the priority
+ of codecs(mainly Aptx Adaptive) to highest value based
+ on config
+ Value of a2dp_source_codec_priority_max should be set to
+ 1000 + priority of codec with highest priority-->
+ <integer name="a2dp_source_codec_priority_max">8001</integer>
<!-- Package that is responsible for user interaction on pairing request,
success or cancel.
diff --git a/res/values/strings.xml b/res/values/strings.xml
index dcb992b..03d700c 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -178,9 +178,9 @@
<string name="bt_toast_6">Stopped sending file to \u0022<xliff:g id="recipient">%1$s</xliff:g>\u0022</string>
<!-- Bluetooth System Messages [CHAR LIMIT=NONE] -->
- <string name="bt_sm_2_1" product="nosdcard">There isn\'t enough space in USB storage to save the file from \u0022<xliff:g id="sender">%1$s</xliff:g>\u0022</string>
+ <string name="bt_sm_2_1_nosdcard">There isn\'t enough space in USB storage to save the file from \u0022<xliff:g id="sender">%1$s</xliff:g>\u0022</string>
<!-- Bluetooth System Messages -->
- <string name="bt_sm_2_1" product="default">There isn\'t enough space on the SD card to save the file from \u0022<xliff:g id="sender">%1$s</xliff:g>\u0022</string>
+ <string name="bt_sm_2_1_default">There isn\'t enough space on the SD card to save the file from \u0022<xliff:g id="sender">%1$s</xliff:g>\u0022</string>
<string name="bt_sm_2_2">Space needed: <xliff:g id="size">%1$s</xliff:g></string>
<string name="ErrorTooManyRequests">Too many requests are being processed. Try again later.</string>
@@ -194,8 +194,8 @@
<string name="status_canceled">Transfer canceled by user.</string>
<string name="status_file_error">Storage issue.</string>
<!-- Shown when USB storage cannot be found. [CHAR LIMIT=NONE] -->
- <string name="status_no_sd_card" product="nosdcard">No USB storage.</string>
- <string name="status_no_sd_card" product="default">No SD card. Insert an SD card to save transferred files.</string>
+ <string name="status_no_sd_card_nosdcard">No USB storage.</string>
+ <string name="status_no_sd_card_default">No SD card. Insert an SD card to save transferred files.</string>
<string name="status_connection_error">Connection unsuccessful.</string>
<string name="status_protocol_error">Request can\'t be handled correctly.</string>
<string name="status_unknown_error">Unknown error.</string>
@@ -237,6 +237,7 @@
<string name="process" translate="false"><xliff:g id="x" /></string>
+ <string name="bluetooth_a2dp_sink_queue_name">Now Playing</string>
<string name="bluetooth_map_settings_save">Save</string>
<string name="bluetooth_map_settings_cancel">Cancel</string>
<string name="bluetooth_map_settings_intro">Select the accounts you want to share through Bluetooth. You still have to accept any access to the accounts when connecting.</string>
@@ -244,6 +245,11 @@
<string name="bluetooth_map_settings_app_icon">Application Icon</string>
<string name="bluetooth_map_settings_title">Bluetooth Message Sharing Settings</string>
<string name="bluetooth_map_settings_no_account_slots_left">Cannot select account. 0 slots left</string>
+ <!-- MAP version upgrade / down grade -->
+ <string name="bluetooth_map_remote_advance_feature_support">Message access Advance Feature Supported</string>
+ <string name="bluetooth_map_remote_message_access_feature_downgrade">Remote Message access Feature Downgrade</string>
+ <string name="bluetooth_map_repair_for_adv_message_access_feature">Re-pair for Advance Message access Feature</string>
+ <string name="bluetooth_map_repair_for_message_access_version_comp">Re-pair for Message access Version Compatibility</string>
<string name="bluetooth_connected">Bluetooth audio connected</string>
<string name="bluetooth_disconnected">Bluetooth audio disconnected"</string>
<string name="a2dp_sink_mbs_label">Bluetooth Audio</string>
diff --git a/res/values/strings_pbap.xml b/res/values/strings_pbap.xml
index bb2586c..abe0686 100644
--- a/res/values/strings_pbap.xml
+++ b/res/values/strings_pbap.xml
@@ -14,4 +14,9 @@
<string name="localPhoneName">My name</string>
<string name="defaultnumber">000000</string>
<string name="pbap_notification_group">Bluetooth Contact share</string>
+ <string name="remote_pbap_version_change">Remote Phonebook Profile Version Change</string>
+ <string name="phonebook_advance_feature_support">Phonebook Advance Feature Supported</string>
+ <string name="remote_phonebook_feature_downgrade">Remote Phonebook Feature Downgrade</string>
+ <string name="repair_for_adv_phonebook_feature">Re-pair for Advance Phonebook Feature</string>
+ <string name="repair_for_phonebook_access_version_comp">Re-pair for Phonebook Access Version Compatibility</string>
</resources>
diff --git a/src/com/android/bluetooth/Utils.java b/src/com/android/bluetooth/Utils.java
index bb6bf6c..2052483 100644
--- a/src/com/android/bluetooth/Utils.java
+++ b/src/com/android/bluetooth/Utils.java
@@ -24,6 +24,7 @@
import android.content.ContextWrapper;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
+import android.location.LocationManager;
import android.os.Binder;
import android.os.Build;
import android.os.ParcelUuid;
@@ -38,6 +39,8 @@
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
+import java.nio.charset.Charset;
+import java.nio.charset.CharsetDecoder;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
@@ -110,6 +113,24 @@
return sb.toString();
}
+ /**
+ * A parser to transfer a byte array to a UTF8 string
+ *
+ * @param valueBuf the byte array to transfer
+ * @return the transferred UTF8 string
+ */
+ public static String byteArrayToUtf8String(byte[] valueBuf) {
+ CharsetDecoder decoder = Charset.forName("UTF8").newDecoder();
+ ByteBuffer byteBuffer = ByteBuffer.wrap(valueBuf);
+ String valueStr = "";
+ try {
+ valueStr = decoder.decode(byteBuffer).toString();
+ } catch (Exception ex) {
+ Log.e(TAG, "Error when parsing byte array to UTF8 String. " + ex);
+ }
+ return valueStr;
+ }
+
public static byte[] intToByteArray(int value) {
ByteBuffer converter = ByteBuffer.allocate(4);
converter.order(ByteOrder.nativeOrder());
@@ -278,52 +299,107 @@
}
/**
- * Checks that calling process has android.Manifest.permission.ACCESS_COARSE_LOCATION or
- * android.Manifest.permission.ACCESS_FINE_LOCATION and a corresponding app op is allowed
+ * Checks whether location is off and must be on for us to perform some operation
*/
- public static boolean checkCallerHasLocationPermission(Context context, AppOpsManager appOps,
- String callingPackage) {
- if (context.checkCallingOrSelfPermission(android.Manifest.permission.ACCESS_FINE_LOCATION)
- == PackageManager.PERMISSION_GRANTED && isAppOppAllowed(
- appOps, AppOpsManager.OP_FINE_LOCATION, callingPackage)) {
+ public static boolean blockedByLocationOff(Context context, UserHandle userHandle) {
+ return !context.getSystemService(LocationManager.class)
+ .isLocationEnabledForUser(userHandle);
+ }
+
+ /**
+ * Checks that calling process has android.Manifest.permission.ACCESS_COARSE_LOCATION and
+ * OP_COARSE_LOCATION is allowed
+ */
+ public static boolean checkCallerHasCoarseLocation(Context context, AppOpsManager appOps,
+ String callingPackage, UserHandle userHandle) {
+ if (blockedByLocationOff(context, userHandle)) {
+ Log.e(TAG, "Permission denial: Location is off.");
+ return false;
+ }
+
+ // Check coarse, but note fine
+ if (context.checkCallingOrSelfPermission(
+ android.Manifest.permission.ACCESS_COARSE_LOCATION)
+ == PackageManager.PERMISSION_GRANTED
+ && isAppOppAllowed(appOps, AppOpsManager.OP_FINE_LOCATION, callingPackage)) {
return true;
}
- if (context.checkCallingOrSelfPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION)
- == PackageManager.PERMISSION_GRANTED && isAppOppAllowed(
- appOps, AppOpsManager.OP_COARSE_LOCATION, callingPackage)) {
- return true;
- }
- // Enforce location permission for apps targeting M and later versions
- if (isMApp(context, callingPackage)) {
- // PEERS_MAC_ADDRESS is another way to get scan results without
- // requiring location permissions, so only throw an exception here
- // if PEERS_MAC_ADDRESS permission is missing as well
- if (!checkCallerHasPeersMacAddressPermission(context)) {
- throw new SecurityException("Need ACCESS_COARSE_LOCATION or "
- + "ACCESS_FINE_LOCATION permission to get scan results");
- }
- } else {
- // Pre-M apps running in the foreground should continue getting scan results
- if (isForegroundApp(context, callingPackage)) {
- return true;
- }
- Log.e(TAG, "Permission denial: Need ACCESS_COARSE_LOCATION or ACCESS_FINE_LOCATION "
- + "permission to get scan results");
- }
+ Log.e(TAG, "Permission denial: Need ACCESS_COARSE_LOCATION "
+ + "permission to get scan results");
return false;
}
/**
- * Returns true if the caller holds PEERS_MAC_ADDRESS.
+ * Checks that calling process has android.Manifest.permission.ACCESS_COARSE_LOCATION and
+ * OP_COARSE_LOCATION is allowed or android.Manifest.permission.ACCESS_FINE_LOCATION and
+ * OP_FINE_LOCATION is allowed
*/
- public static boolean checkCallerHasPeersMacAddressPermission(Context context) {
- return context.checkCallingOrSelfPermission(android.Manifest.permission.PEERS_MAC_ADDRESS)
+ public static boolean checkCallerHasCoarseOrFineLocation(Context context, AppOpsManager appOps,
+ String callingPackage, UserHandle userHandle) {
+ if (blockedByLocationOff(context, userHandle)) {
+ Log.e(TAG, "Permission denial: Location is off.");
+ return false;
+ }
+
+ if (context.checkCallingOrSelfPermission(
+ android.Manifest.permission.ACCESS_FINE_LOCATION)
+ == PackageManager.PERMISSION_GRANTED
+ && isAppOppAllowed(appOps, AppOpsManager.OP_FINE_LOCATION, callingPackage)) {
+ return true;
+ }
+
+ // Check coarse, but note fine
+ if (context.checkCallingOrSelfPermission(
+ android.Manifest.permission.ACCESS_COARSE_LOCATION)
+ == PackageManager.PERMISSION_GRANTED
+ && isAppOppAllowed(appOps, AppOpsManager.OP_FINE_LOCATION, callingPackage)) {
+ return true;
+ }
+
+ Log.e(TAG, "Permission denial: Need ACCESS_COARSE_LOCATION or ACCESS_FINE_LOCATION"
+ + "permission to get scan results");
+ return false;
+ }
+
+ /**
+ * Checks that calling process has android.Manifest.permission.ACCESS_FINE_LOCATION and
+ * OP_FINE_LOCATION is allowed
+ */
+ public static boolean checkCallerHasFineLocation(Context context, AppOpsManager appOps,
+ String callingPackage, UserHandle userHandle) {
+ if (blockedByLocationOff(context, userHandle)) {
+ Log.e(TAG, "Permission denial: Location is off.");
+ return false;
+ }
+
+ if (context.checkCallingOrSelfPermission(
+ android.Manifest.permission.ACCESS_FINE_LOCATION)
+ == PackageManager.PERMISSION_GRANTED
+ && isAppOppAllowed(appOps, AppOpsManager.OP_FINE_LOCATION, callingPackage)) {
+ return true;
+ }
+
+ Log.e(TAG, "Permission denial: Need ACCESS_FINE_LOCATION "
+ + "permission to get scan results");
+ return false;
+ }
+
+ /**
+ * Returns true if the caller holds NETWORK_SETTINGS
+ */
+ public static boolean checkCallerHasNetworkSettingsPermission(Context context) {
+ return context.checkCallingOrSelfPermission(android.Manifest.permission.NETWORK_SETTINGS)
== PackageManager.PERMISSION_GRANTED;
}
- public static boolean isLegacyForegroundApp(Context context, String pkgName) {
- return !isMApp(context, pkgName) && isForegroundApp(context, pkgName);
+ /**
+ * Returns true if the caller holds NETWORK_SETUP_WIZARD
+ */
+ public static boolean checkCallerHasNetworkSetupWizardPermission(Context context) {
+ return context.checkCallingOrSelfPermission(
+ android.Manifest.permission.NETWORK_SETUP_WIZARD)
+ == PackageManager.PERMISSION_GRANTED;
}
private static boolean isMApp(Context context, String pkgName) {
@@ -336,15 +412,14 @@
return true;
}
- /**
- * Return true if the specified package name is a foreground app.
- *
- * @param pkgName application package name.
- */
- private static boolean isForegroundApp(Context context, String pkgName) {
- ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
- List<ActivityManager.RunningTaskInfo> tasks = am.getRunningTasks(1);
- return !tasks.isEmpty() && pkgName.equals(tasks.get(0).topActivity.getPackageName());
+ public static boolean isQApp(Context context, String pkgName) {
+ try {
+ return context.getPackageManager().getApplicationInfo(pkgName, 0).targetSdkVersion
+ >= Build.VERSION_CODES.Q;
+ } catch (PackageManager.NameNotFoundException e) {
+ // In case of exception, assume Q app
+ }
+ return true;
}
private static boolean isAppOppAllowed(AppOpsManager appOps, int op, String callingPackage) {
diff --git a/src/com/android/bluetooth/a2dp/A2dpCodecConfig.java b/src/com/android/bluetooth/a2dp/A2dpCodecConfig.java
index b8c897b..f6d42ea 100644
--- a/src/com/android/bluetooth/a2dp/A2dpCodecConfig.java
+++ b/src/com/android/bluetooth/a2dp/A2dpCodecConfig.java
@@ -17,13 +17,18 @@
package com.android.bluetooth.a2dp;
import android.bluetooth.BluetoothCodecConfig;
+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.os.SystemProperties;
+import android.util.Log;
import com.android.bluetooth.R;
import com.android.bluetooth.btservice.AdapterService;
+
+import java.util.Arrays;
+import java.util.Objects;
/*
* A2DP Codec Configuration setup.
*/
@@ -34,7 +39,6 @@
private Context mContext;
private A2dpNativeInterface mA2dpNativeInterface;
- private static String BT_SOC;
private BluetoothCodecConfig[] mCodecConfigPriorities;
private int mA2dpSourceCodecPrioritySbc = BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT;
private int mA2dpSourceCodecPriorityAac = BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT;
@@ -43,7 +47,7 @@
private int mA2dpSourceCodecPriorityAptxAdaptive = BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT;
private int mA2dpSourceCodecPriorityLdac = BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT;
private int mA2dpSourceCodecPriorityAptxTwsp = BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT;
- //private int SOURCE_CODEC_TYPE_APTX_TWS = BluetoothCodecConfig.SOURCE_CODEC_TYPE_MAX + 1;
+ private int assigned_codec_length = 0;
A2dpCodecConfig(Context context, A2dpNativeInterface a2dpNativeInterface) {
mContext = context;
mA2dpNativeInterface = a2dpNativeInterface;
@@ -55,20 +59,56 @@
}
void setCodecConfigPreference(BluetoothDevice device,
- BluetoothCodecConfig codecConfig) {
+ BluetoothCodecStatus codecStatus,
+ BluetoothCodecConfig newCodecConfig) {
+ Objects.requireNonNull(codecStatus);
+
+ // Check whether the codecConfig is selectable for this Bluetooth device.
+ BluetoothCodecConfig[] selectableCodecs = codecStatus.getCodecsSelectableCapabilities();
+ if (!Arrays.asList(selectableCodecs).stream().anyMatch(codec ->
+ codec.isMandatoryCodec())) {
+ // Do not set codec preference to native if the selectableCodecs not contain mandatory
+ // codec. The reason could be remote codec negotiation is not completed yet.
+ Log.w(TAG, "setCodecConfigPreference: must have mandatory codec before changing.");
+ return;
+ }
+ if (!codecStatus.isCodecConfigSelectable(newCodecConfig)) {
+ Log.w(TAG, "setCodecConfigPreference: invalid codec "
+ + Objects.toString(newCodecConfig));
+ return;
+ }
+
+ // Check whether the codecConfig would change current codec config.
+ int prioritizedCodecType = getPrioitizedCodecType(newCodecConfig, selectableCodecs);
+ BluetoothCodecConfig currentCodecConfig = codecStatus.getCodecConfig();
+ if (prioritizedCodecType == currentCodecConfig.getCodecType()
+ && (prioritizedCodecType != newCodecConfig.getCodecType()
+ || (currentCodecConfig.similarCodecFeedingParameters(newCodecConfig)
+ && currentCodecConfig.sameCodecSpecificParameters(newCodecConfig)))) {
+ // Same codec with same parameters, no need to send this request to native.
+ Log.w(TAG, "setCodecConfigPreference: codec not changed.");
+ return;
+ }
+
BluetoothCodecConfig[] codecConfigArray = new BluetoothCodecConfig[1];
- codecConfigArray[0] = codecConfig;
+ codecConfigArray[0] = newCodecConfig;
mA2dpNativeInterface.setCodecConfigPreference(device, codecConfigArray);
}
- void enableOptionalCodecs(BluetoothDevice device) {
+ void enableOptionalCodecs(BluetoothDevice device, BluetoothCodecConfig currentCodecConfig) {
+ if (currentCodecConfig != null && !currentCodecConfig.isMandatoryCodec()) {
+ Log.i(TAG, "enableOptionalCodecs: already using optional codec "
+ + currentCodecConfig.getCodecName());
+ return;
+ }
+
BluetoothCodecConfig[] codecConfigArray = assignCodecConfigPriorities();
if (codecConfigArray == null) {
return;
}
// Set the mandatory codec's priority to default, and remove the rest
- for (int i = 0; i < codecConfigArray.length; i++) {
+ for (int i = 0; i < assigned_codec_length; i++) {
BluetoothCodecConfig codecConfig = codecConfigArray[i];
if (!codecConfig.isMandatoryCodec()) {
codecConfigArray[i] = null;
@@ -78,13 +118,18 @@
mA2dpNativeInterface.setCodecConfigPreference(device, codecConfigArray);
}
- void disableOptionalCodecs(BluetoothDevice device) {
+ void disableOptionalCodecs(BluetoothDevice device, BluetoothCodecConfig currentCodecConfig) {
+ if (currentCodecConfig != null && currentCodecConfig.isMandatoryCodec()) {
+ Log.i(TAG, "disableOptionalCodecs: already using mandatory codec.");
+ return;
+ }
+
BluetoothCodecConfig[] codecConfigArray = assignCodecConfigPriorities();
if (codecConfigArray == null) {
return;
}
// Set the mandatory codec's priority to highest, and remove the rest
- for (int i = 0; i < codecConfigArray.length; i++) {
+ for (int i = 0; i < assigned_codec_length; i++) {
BluetoothCodecConfig codecConfig = codecConfigArray[i];
if (codecConfig.isMandatoryCodec()) {
codecConfig.setCodecPriority(BluetoothCodecConfig.CODEC_PRIORITY_HIGHEST);
@@ -95,6 +140,21 @@
mA2dpNativeInterface.setCodecConfigPreference(device, codecConfigArray);
}
+ // Get the codec type of the highest priority of selectableCodecs and codecConfig.
+ private int getPrioitizedCodecType(BluetoothCodecConfig codecConfig,
+ BluetoothCodecConfig[] selectableCodecs) {
+ BluetoothCodecConfig prioritizedCodecConfig = codecConfig;
+ for (BluetoothCodecConfig config : selectableCodecs) {
+ if (prioritizedCodecConfig == null) {
+ prioritizedCodecConfig = config;
+ }
+ if (config.getCodecPriority() > prioritizedCodecConfig.getCodecPriority()) {
+ prioritizedCodecConfig = config;
+ }
+ }
+ return prioritizedCodecConfig.getCodecType();
+ }
+
// Assign the A2DP Source codec config priorities
private BluetoothCodecConfig[] assignCodecConfigPriorities() {
Resources resources = mContext.getResources();
@@ -102,8 +162,9 @@
return null;
}
- BT_SOC = SystemProperties.get("vendor.bluetooth.soc");
int value;
+ AdapterService mAdapterService = AdapterService.getAdapterService();
+ String a2dp_offload_cap = mAdapterService.getA2apOffloadCapability();
try {
value = resources.getInteger(R.integer.a2dp_source_codec_priority_sbc);
} catch (NotFoundException e) {
@@ -112,6 +173,10 @@
if ((value >= BluetoothCodecConfig.CODEC_PRIORITY_DISABLED) && (value
< BluetoothCodecConfig.CODEC_PRIORITY_HIGHEST)) {
mA2dpSourceCodecPrioritySbc = value;
+ if (a2dp_offload_cap != null && !a2dp_offload_cap.isEmpty() &&
+ !a2dp_offload_cap.contains("sbc")) {
+ mA2dpSourceCodecPrioritySbc = BluetoothCodecConfig.CODEC_PRIORITY_DISABLED;
+ }
}
try {
@@ -122,6 +187,10 @@
if ((value >= BluetoothCodecConfig.CODEC_PRIORITY_DISABLED) && (value
< BluetoothCodecConfig.CODEC_PRIORITY_HIGHEST)) {
mA2dpSourceCodecPriorityAac = value;
+ if (a2dp_offload_cap != null && !a2dp_offload_cap.isEmpty() &&
+ !a2dp_offload_cap.contains("aac")) {
+ mA2dpSourceCodecPriorityAac = BluetoothCodecConfig.CODEC_PRIORITY_DISABLED;
+ }
}
try {
@@ -132,18 +201,41 @@
if ((value >= BluetoothCodecConfig.CODEC_PRIORITY_DISABLED) && (value
< BluetoothCodecConfig.CODEC_PRIORITY_HIGHEST)) {
mA2dpSourceCodecPriorityAptx = value;
+ if (a2dp_offload_cap != null && !a2dp_offload_cap.isEmpty() &&
+ !a2dp_offload_cap.contains("aptx")) {
+ mA2dpSourceCodecPriorityAptx = BluetoothCodecConfig.CODEC_PRIORITY_DISABLED;
+ }
}
- try {
- value = resources.getInteger(R.integer.a2dp_source_codec_priority_aptx_adaptive);
- } catch (NotFoundException e) {
- value = BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT;
- }
- if ((value >= BluetoothCodecConfig.CODEC_PRIORITY_DISABLED) && (value
- < BluetoothCodecConfig.CODEC_PRIORITY_HIGHEST)) {
- mA2dpSourceCodecPriorityAptxAdaptive = value;
+ if(mAdapterService.isSplitA2DPSourceAPTXADAPTIVE()) {
+ try {
+ value = resources.getInteger(R.integer.a2dp_source_codec_priority_aptx_adaptive);
+ } catch (NotFoundException e) {
+ value = BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT;
+ }
+ if ((value >= BluetoothCodecConfig.CODEC_PRIORITY_DISABLED) && (value
+ < BluetoothCodecConfig.CODEC_PRIORITY_HIGHEST)) {
+ if (a2dp_offload_cap != null && !a2dp_offload_cap.isEmpty()) {
+ if(a2dp_offload_cap.contains("aptxadaptiver2")) {
+ int aptxaa_r2_priority;
+ try {
+ aptxaa_r2_priority = resources.getInteger(R.integer.a2dp_source_codec_priority_max);
+ } catch (NotFoundException e) {
+ aptxaa_r2_priority = value;
+ }
+ value = aptxaa_r2_priority;
+ } else if(!a2dp_offload_cap.contains("aptxadaptive")) {
+ value = BluetoothCodecConfig.CODEC_PRIORITY_DISABLED;
+ Log.w(TAG, "Disable Aptx Adaptive. Entry not present in offload property");
+ }
+ }
+ mA2dpSourceCodecPriorityAptxAdaptive = value;
+ Log.i(TAG, "Aptx Adaptive priority: " + mA2dpSourceCodecPriorityAptxAdaptive);
+ }
+ } else {
+ mA2dpSourceCodecPriorityAptxAdaptive = BluetoothCodecConfig.CODEC_PRIORITY_DISABLED;
}
- if(BT_SOC.equals("cherokee")) {
+ if(mAdapterService.isSplitA2DPSourceAPTXHD()) {
try {
value = resources.getInteger(R.integer.a2dp_source_codec_priority_aptx_hd);
} catch (NotFoundException e) {
@@ -152,13 +244,17 @@
if ((value >= BluetoothCodecConfig.CODEC_PRIORITY_DISABLED) && (value
< BluetoothCodecConfig.CODEC_PRIORITY_HIGHEST)) {
mA2dpSourceCodecPriorityAptxHd = value;
+ if (a2dp_offload_cap != null && !a2dp_offload_cap.isEmpty() &&
+ !a2dp_offload_cap.contains("aptxhd")) {
+ mA2dpSourceCodecPriorityAptxHd = BluetoothCodecConfig.CODEC_PRIORITY_DISABLED;
+ }
}
} else {
mA2dpSourceCodecPriorityAptxHd = BluetoothCodecConfig.CODEC_PRIORITY_DISABLED;
}
- if(BT_SOC.equals("cherokee")) {
+ if(mAdapterService.isSplitA2DPSourceLDAC()) {
try {
value = resources.getInteger(R.integer.a2dp_source_codec_priority_ldac);
} catch (NotFoundException e) {
@@ -167,11 +263,14 @@
if ((value >= BluetoothCodecConfig.CODEC_PRIORITY_DISABLED) && (value
< BluetoothCodecConfig.CODEC_PRIORITY_HIGHEST)) {
mA2dpSourceCodecPriorityLdac = value;
+ if (a2dp_offload_cap != null && !a2dp_offload_cap.isEmpty() &&
+ !a2dp_offload_cap.contains("ldac")) {
+ mA2dpSourceCodecPriorityLdac = BluetoothCodecConfig.CODEC_PRIORITY_DISABLED;
+ }
}
} else {
mA2dpSourceCodecPriorityLdac = BluetoothCodecConfig.CODEC_PRIORITY_DISABLED;
}
- AdapterService mAdapterService = AdapterService.getAdapterService();
if (mAdapterService.isVendorIntfEnabled()) {
try {
value = resources.getInteger(R.integer.a2dp_source_codec_priority_aptx_tws);
@@ -181,6 +280,10 @@
if ((value >= BluetoothCodecConfig.CODEC_PRIORITY_DISABLED) && (value
< BluetoothCodecConfig.CODEC_PRIORITY_HIGHEST)) {
mA2dpSourceCodecPriorityAptxTwsp = value;
+ if (a2dp_offload_cap != null && !a2dp_offload_cap.isEmpty() &&
+ !a2dp_offload_cap.contains("aptxtws")) {
+ mA2dpSourceCodecPriorityAptxTwsp = BluetoothCodecConfig.CODEC_PRIORITY_DISABLED;
+ }
}
} else {
mA2dpSourceCodecPriorityAptxTwsp = BluetoothCodecConfig.CODEC_PRIORITY_DISABLED;
@@ -237,6 +340,7 @@
.CHANNEL_MODE_NONE, 0 /* codecSpecific1 */,
0 /* codecSpecific2 */, 0 /* codecSpecific3 */, 0 /* codecSpecific4 */);
codecConfigArray[codecCount++] = codecConfig;
+ assigned_codec_length = codecCount;
return codecConfigArray;
}
}
diff --git a/src/com/android/bluetooth/a2dp/A2dpNativeInterface.java b/src/com/android/bluetooth/a2dp/A2dpNativeInterface.java
index e01b325..2c5c713 100644
--- a/src/com/android/bluetooth/a2dp/A2dpNativeInterface.java
+++ b/src/com/android/bluetooth/a2dp/A2dpNativeInterface.java
@@ -25,11 +25,12 @@
import android.bluetooth.BluetoothCodecConfig;
import android.bluetooth.BluetoothCodecStatus;
import android.bluetooth.BluetoothDevice;
-import android.support.annotation.VisibleForTesting;
import android.util.Log;
+import java.util.List;
import com.android.bluetooth.Utils;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
/**
* A2DP Native Interface to/from JNI.
@@ -75,8 +76,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[] codecConfigOffload) {
+ initNative(maxConnectedAudioDevices, codecConfigPriorities, codecConfigOffload);
}
/**
@@ -107,6 +109,16 @@
}
/**
+ * Sets a connected A2DP remote device to silence mode.
+ *
+ * @param device the remote device
+ * @return true on success, otherwise false.
+ */
+ public boolean setSilenceDevice(BluetoothDevice device, boolean silence) {
+ return setSilenceDeviceNative(getByteAddress(device), silence);
+ }
+
+ /**
* Sets a connected A2DP remote device as active.
*
* @param device the remote device
@@ -195,10 +207,12 @@
// Native methods that call into the JNI interface
private static native void classInitNative();
private native void initNative(int maxConnectedAudioDevices,
- BluetoothCodecConfig[] codecConfigPriorities);
+ BluetoothCodecConfig[] codecConfigPriorities,
+ BluetoothCodecConfig[] codecConfigOffload);
private native void cleanupNative();
private native boolean connectA2dpNative(byte[] address);
private native boolean disconnectA2dpNative(byte[] address);
+ private native boolean setSilenceDeviceNative(byte[] address, boolean silence);
private native boolean setActiveDeviceNative(byte[] address);
private native boolean setCodecConfigPreferenceNative(byte[] address,
BluetoothCodecConfig[] codecConfigArray);
diff --git a/src/com/android/bluetooth/a2dp/A2dpService.java b/src/com/android/bluetooth/a2dp/A2dpService.java
index 75b88c2..9866312 100644
--- a/src/com/android/bluetooth/a2dp/A2dpService.java
+++ b/src/com/android/bluetooth/a2dp/A2dpService.java
@@ -17,7 +17,6 @@
package com.android.bluetooth.a2dp;
import android.bluetooth.BluetoothA2dp;
-import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothCodecConfig;
import android.bluetooth.BluetoothCodecStatus;
import android.bluetooth.BluetoothDevice;
@@ -30,29 +29,31 @@
import android.content.IntentFilter;
import android.media.AudioManager;
import android.os.HandlerThread;
-import android.provider.Settings;
-import android.support.annotation.GuardedBy;
-import android.support.annotation.VisibleForTesting;
import android.util.Log;
-import android.os.SystemProperties;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
+import android.util.StatsLog;
+import android.os.Handler;
+import android.os.Message;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+
+import com.android.bluetooth.BluetoothMetricsProto;
import com.android.bluetooth.Utils;
import com.android.bluetooth.avrcp.Avrcp;
import com.android.bluetooth.avrcp.Avrcp_ext;
import com.android.bluetooth.avrcp.AvrcpTargetService;
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.bluetooth.ba.BATService;
-import android.os.SystemClock;
import com.android.bluetooth.gatt.GattService;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
-import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
@@ -66,17 +67,19 @@
private static A2dpService sA2dpService;
- private BluetoothAdapter mAdapter;
private AdapterService mAdapterService;
private HandlerThread mStateMachinesThread;
private Avrcp mAvrcp;
private Avrcp_ext mAvrcp_ext;
private final Object mBtA2dpLock = new Object();
+ private final Object mBtTwsLock = new Object();
private final Object mBtAvrcpLock = new Object();
private final Object mActiveDeviceLock = new Object();
@VisibleForTesting
A2dpNativeInterface mA2dpNativeInterface;
+ @VisibleForTesting
+ ServiceFactory mFactory = new ServiceFactory();
private AudioManager mAudioManager;
private A2dpCodecConfig mA2dpCodecConfig;
@@ -87,10 +90,6 @@
private static final int[] CONNECTING_CONNECTED_STATES = {
BluetoothProfile.STATE_CONNECTING, BluetoothProfile.STATE_CONNECTED
};
- // A2DP disconnet will be delayed at audioservice,
- // follwoing flags capture delay and delay time.
- private int mDisconnectDelay = 0;
- private long mDisconnectTime = 0;
// Upper limit of all A2DP devices: Bonded or Connected
private static final int MAX_A2DP_STATE_MACHINES = 50;
@@ -108,26 +107,40 @@
private boolean mIsTwsPlusMonoSupported = false;
private String mTwsPlusChannelMode = "dual-mono";
private BluetoothDevice mDummyDevice = null;
+ private static final int max_tws_connection = 2;
+ private static final int min_tws_connection = 1;
- private static final long AptxBLEScanMask = 0x3000;
- private static final long Aptx_BLEScanEnable = 0x1000;
- private static final long Aptx_BLEScanDisable = 0x2000;
+ private static final int APTX_HQ = 0x1000;
+ private static final int APTX_LL = 0x2000;
+ private static final int APTX_ULL = 0x6000;
+ private static final long APTX_MODE_MASK = 0x7000;
+ private static final long APTX_SCAN_FILTER_MASK = 0x8000;
+
private static final int SET_EBMONO_CFG = 1;
- private static final int MonoCfg_Timeout = 5000;
+ private static final int SET_EBDUALMONO_CFG = 2;
+ private static final int MonoCfg_Timeout = 3000;
+ private static final int DualMonoCfg_Timeout = 3000;
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg)
{
+ synchronized(mBtTwsLock) {
switch (msg.what) {
case SET_EBMONO_CFG:
Log.d(TAG, "setparameters to Mono");
mAudioManager.setParameters("TwsChannelConfig=mono");
mTwsPlusChannelMode = "mono";
break;
+ case SET_EBDUALMONO_CFG:
+ Log.d(TAG, "setparameters to Dual-Mono");
+ mAudioManager.setParameters("TwsChannelConfig=dual-mono");
+ mTwsPlusChannelMode = "dual-mono";
+ break;
default:
break;
}
+ }
}
};
@@ -149,10 +162,8 @@
return true;
}
- // Step 1: Get BluetoothAdapter, AdapterService, A2dpNativeInterface, AudioManager.
+ // Step 1: Get AdapterService, A2dpNativeInterface, AudioManager.
// None of them can be null.
- mAdapter = Objects.requireNonNull(BluetoothAdapter.getDefaultAdapter(),
- "BluetoothAdapter cannot be null when A2dpService starts");
mAdapterService = Objects.requireNonNull(AdapterService.getAdapterService(),
"AdapterService cannot be null when A2dpService starts");
mA2dpNativeInterface = Objects.requireNonNull(A2dpNativeInterface.getInstance(),
@@ -164,18 +175,23 @@
// Step 2: Get maximum number of connected audio devices
mMaxConnectedAudioDevices = mAdapterService.getMaxConnectedAudioDevices();
mSetMaxConnectedAudioDevices = mMaxConnectedAudioDevices;
+ Log.i(TAG, "Max connected audio devices set to " + mMaxConnectedAudioDevices);
if (mAdapterService.isVendorIntfEnabled()) {
String twsPlusEnabled = SystemProperties.get("persist.vendor.btstack.enable.twsplus");
if (!twsPlusEnabled.isEmpty() && "true".equals(twsPlusEnabled)) {
mIsTwsPlusEnabled = true;
}
Log.i(TAG, "mMaxConnectedAudioDevices: " + mMaxConnectedAudioDevices);
- if (mIsTwsPlusEnabled) {
+ String twsShoEnabled = SystemProperties.get("persist.vendor.btstack.enable.twsplussho");
+ if (!twsShoEnabled.isEmpty() && "true".equals(twsShoEnabled) &&
+ (mIsTwsPlusEnabled == true) && mMaxConnectedAudioDevices <= 2) {
+ mMaxConnectedAudioDevices = 3;
+ Log.i(TAG, "TWS+ SHO enabled mMaxConnectedAudioDevices changed to: " + mMaxConnectedAudioDevices);
+ } else if (mIsTwsPlusEnabled && mMaxConnectedAudioDevices < 2) {
mMaxConnectedAudioDevices = 2;
- } else if (mMaxConnectedAudioDevices > 2) {
- mMaxConnectedAudioDevices = 2;
- mSetMaxConnectedAudioDevices = mMaxConnectedAudioDevices;
+ Log.i(TAG, "TWS+ enabled mMaxConnectedAudioDevices changed to: " + mMaxConnectedAudioDevices);
}
+ mSetMaxConnectedAudioDevices = mMaxConnectedAudioDevices;
String twsPlusMonoEnabled = SystemProperties.get("persist.vendor.btstack.twsplus.monosupport");
if (!twsPlusMonoEnabled.isEmpty() && "true".equals(twsPlusMonoEnabled)) {
mIsTwsPlusMonoSupported = true;
@@ -186,7 +202,6 @@
}
Log.d(TAG, "Default TwsPlus ChannelMode: " + mTwsPlusChannelMode);
}
- Log.i(TAG, "Max connected audio devices set to " + mMaxConnectedAudioDevices);
// Step 3: Setup AVRCP
if(mAdapterService.isVendorIntfEnabled())
@@ -203,8 +218,12 @@
mA2dpCodecConfig = new A2dpCodecConfig(this, mA2dpNativeInterface);
// Step 6: Initialize native interface
+ List<BluetoothCodecConfig> mCodecConfigOffload;
+ mCodecConfigOffload = mAudioManager.getHwOffloadEncodingFormatsSupportedForA2DP();
+ BluetoothCodecConfig[] OffloadCodecConfig = new BluetoothCodecConfig[mCodecConfigOffload.size()];
+ OffloadCodecConfig = mCodecConfigOffload.toArray(OffloadCodecConfig);
mA2dpNativeInterface.init(mMaxConnectedAudioDevices,
- mA2dpCodecConfig.codecConfigPriorities());
+ mA2dpCodecConfig.codecConfigPriorities(),OffloadCodecConfig);
// Step 7: Check if A2DP is in offload mode
mA2dpOffloadEnabled = mAdapterService.isA2dpOffloadEnabled();
@@ -239,13 +258,6 @@
return true;
}
- // Step 10: Store volume if there is an active device
- if (mActiveDevice != null && AvrcpTargetService.get() != null) {
- AvrcpTargetService.get().storeVolumeForDevice(mActiveDevice);
- }
- if (mActiveDevice != null && mAvrcp_ext != null)
- mAvrcp_ext.storeVolumeForDevice(mActiveDevice);
-
// Step 9: Clear active device and stop playing audio
removeActiveDevice(true);
@@ -301,11 +313,11 @@
mMaxConnectedAudioDevices = 1;
}
mSetMaxConnectedAudioDevices = 1;
- // Step 1: Clear BluetoothAdapter, AdapterService, A2dpNativeInterface, AudioManager
+
+ // Step 1: Clear AdapterService, A2dpNativeInterface, AudioManager
mAudioManager = null;
mA2dpNativeInterface = null;
mAdapterService = null;
- mAdapter = null;
return true;
}
@@ -351,10 +363,24 @@
}
synchronized (mBtA2dpLock) {
- disconnectExisting = false;
- if (!connectionAllowedCheckMaxDevices(device) && !disconnectExisting) {
- Log.e(TAG, "Cannot connect to " + device + " : too many connected devices");
- return false;
+ if (!connectionAllowedCheckMaxDevices(device)) {
+ // when mMaxConnectedAudioDevices is one, disconnect current device first.
+ if (mMaxConnectedAudioDevices == 1) {
+ List<BluetoothDevice> sinks = getDevicesMatchingConnectionStates(
+ new int[] {BluetoothProfile.STATE_CONNECTED,
+ BluetoothProfile.STATE_CONNECTING,
+ BluetoothProfile.STATE_DISCONNECTING});
+ for (BluetoothDevice sink : sinks) {
+ if (sink.equals(device)) {
+ Log.w(TAG, "Connecting to device " + device + " : disconnect skipped");
+ continue;
+ }
+ disconnect(sink);
+ }
+ } else {
+ Log.e(TAG, "Cannot connect to " + device + " : too many connected devices");
+ return false;
+ }
}
if (disconnectExisting) {
disconnectExisting = false;
@@ -406,54 +432,62 @@
return devices;
}
}
- private boolean isConnectionAllowed(BluetoothDevice device, boolean tws_connected,
+ private boolean isConnectionAllowed(BluetoothDevice device, int tws_connected,
int num_connected) {
if (!mIsTwsPlusEnabled && mAdapterService.isTwsPlusDevice(device)) {
Log.d(TAG, "No TWSPLUS connections as It is not Enabled");
return false;
}
if (num_connected == 0) return true;
-
+ Log.d(TAG,"isConnectionAllowed");
List <BluetoothDevice> connectingConnectedDevices =
getDevicesMatchingConnectionStates(CONNECTING_CONNECTED_STATES);
- BluetoothDevice mConnDev = connectingConnectedDevices.get(0);
+ BluetoothDevice mConnDev = null;
+ if (mMaxConnectedAudioDevices > 2 && tws_connected > 0) {
+ for (BluetoothDevice connectingConnectedDevice : connectingConnectedDevices) {
+ if (mAdapterService.isTwsPlusDevice(connectingConnectedDevice)) {
+ mConnDev = connectingConnectedDevice;
+ break;
+ }
+ }
+ } else if (!connectingConnectedDevices.isEmpty()) {
+ mConnDev = connectingConnectedDevices.get(0);
+ }
if (mA2dpStackEvent == A2dpStackEvent.CONNECTION_STATE_CONNECTING ||
mA2dpStackEvent == A2dpStackEvent.CONNECTION_STATE_CONNECTED) {
- if ((!mAdapterService.isTwsPlusDevice(device) && tws_connected) ||
- (mAdapterService.isTwsPlusDevice(device) && !tws_connected)) {
+ //Handle incoming connection
+ if (!mAdapterService.isTwsPlusDevice(device) &&
+ ((mMaxConnectedAudioDevices - max_tws_connection) - (num_connected - tws_connected)) < 1) {
Log.d(TAG,"isConnectionAllowed: incoming connection not allowed");
mA2dpStackEvent = EVENT_TYPE_NONE;
return false;
}
}
- if (num_connected > 1 &&
- ((!tws_connected && mAdapterService.isTwsPlusDevice(device)) ||
- (tws_connected && !mAdapterService.isTwsPlusDevice(device)))) {
- Log.d(TAG,"isConnectionAllowed: Max connections reached");
- return false;
- }
- if (!tws_connected && mAdapterService.isTwsPlusDevice(device)) {
- Log.d(TAG,"isConnectionAllowed: Disconnect legacy device for outgoing TWSP connection");
- disconnectExisting = true;
- return false;
- }
- if (tws_connected && mAdapterService.isTwsPlusDevice(device)) {
- //if (num_connected == mMaxConnectedAudioDevices) {
- if (num_connected > 1) {
- Log.d(TAG,"isConnectionAllowed: Max TWS connected, disconnect first");
- return false;
- } else if(mAdapterService.getTwsPlusPeerAddress(mConnDev).equals(device.getAddress())) {
- Log.d(TAG,"isConnectionAllowed: Peer earbud pair allow connection");
- return true;
- } else {
- Log.d(TAG,"isConnectionAllowed: Unpaired earbud, disconnect previous TWS+ device");
- disconnectExisting = true;
+ if (mAdapterService.isTwsPlusDevice(device)) {
+ if (tws_connected == max_tws_connection) {
+ Log.d(TAG,"isConnectionAllowed:TWS+ pair connected, disallow other TWS+ connection");
return false;
}
- } else if (tws_connected && !mAdapterService.isTwsPlusDevice(device)) {
- Log.d(TAG,"isConnectionAllowed: Disconnect tws device to connect to legacy headset");
- disconnectExisting = true;
- return false;
+ if ((tws_connected > 0 && (mMaxConnectedAudioDevices - num_connected) >= min_tws_connection) ||
+ (tws_connected == 0 && (mMaxConnectedAudioDevices - num_connected) >= max_tws_connection)){
+ if ((tws_connected == 0) || (tws_connected == min_tws_connection && mConnDev != null &&
+ mAdapterService.getTwsPlusPeerAddress(mConnDev).equals(device.getAddress()))) {
+ Log.d(TAG,"isConnectionAllowed: Allow TWS+ connection");
+ return true;
+ }
+ } else {
+ Log.d(TAG,"isConnectionAllowed: Too many connections, TWS+ connection not allowed");
+ return false;
+ }
+ } else {
+ if ((tws_connected == max_tws_connection && (mMaxConnectedAudioDevices - num_connected) >= 1) ||
+ (tws_connected == min_tws_connection && (mMaxConnectedAudioDevices - num_connected) >= 2)) {
+ Log.d(TAG,"isConnectionAllowed: Allow legacy connection");
+ return true;
+ } else {
+ Log.d(TAG,"isConnectionAllowed: Too many connections, legacy connection not allowed");
+ return false;
+ }
}
return false;
}
@@ -466,7 +500,7 @@
*/
private boolean connectionAllowedCheckMaxDevices(BluetoothDevice device) {
int connected = 0;
- boolean tws_device = false;
+ int tws_device = 0;
// Count devices that are in the process of connecting or already connected
synchronized (mBtA2dpLock) {
for (A2dpStateMachine sm : mStateMachines.values()) {
@@ -476,9 +510,8 @@
if (Objects.equals(device, sm.getDevice())) {
return true; // Already connected or accounted for
}
- if (tws_device == false) {
- tws_device = mAdapterService.isTwsPlusDevice(sm.getDevice());
- }
+ if (mAdapterService.isTwsPlusDevice(sm.getDevice()))
+ tws_device++;
connected++;
break;
default:
@@ -486,10 +519,11 @@
}
}
}
- Log.d(TAG,"connectionAllowedCheckMaxDevices connected = " + connected);
+ Log.d(TAG,"connectionAllowedCheckMaxDevices connected = " + connected +
+ "tws connected = " + tws_device);
if (mAdapterService.isVendorIntfEnabled() &&
- (tws_device || mAdapterService.isTwsPlusDevice(device) ||
- (tws_device && connected == mMaxConnectedAudioDevices &&
+ ((tws_device > 0) || mAdapterService.isTwsPlusDevice(device) ||
+ ((tws_device > 0) && connected == mMaxConnectedAudioDevices &&
!mAdapterService.isTwsPlusDevice(device)))) {
return isConnectionAllowed(device, tws_device, connected);
}
@@ -498,7 +532,7 @@
disconnectExisting = true;
return true;
}
- return (connected < mMaxConnectedAudioDevices);
+ return (connected < mSetMaxConnectedAudioDevices);
}
/**
@@ -510,7 +544,7 @@
* request, otherwise is for incoming connection request
* @return true if connection is allowed, otherwise false
*/
- @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
public boolean okToConnect(BluetoothDevice device, boolean isOutgoingRequest) {
Log.i(TAG, "okToConnect: device " + device + " isOutgoingRequest: " + isOutgoingRequest);
// Check if this is an incoming connection in Quiet mode.
@@ -526,18 +560,24 @@
}
// Check priority and accept or reject the connection.
+ // Note: Logic can be simplified, but keeping it this way for readability
int priority = getPriority(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);
+ // If priority is undefined, it is likely that service discovery has not completed and peer
+ // initiated the connection. Allow this connection only if the device is bonded or bonding
+ boolean serviceDiscoveryPending = (priority == BluetoothProfile.PRIORITY_UNDEFINED)
+ && (bondState == BluetoothDevice.BOND_BONDING
+ || bondState == BluetoothDevice.BOND_BONDED);
+ // Also allow connection when device is bonded/bonding and priority is ON/AUTO_CONNECT.
+ boolean isEnabled = (priority == BluetoothProfile.PRIORITY_ON
+ || priority == BluetoothProfile.PRIORITY_AUTO_CONNECT)
+ && (bondState == BluetoothDevice.BOND_BONDED
+ || bondState == BluetoothDevice.BOND_BONDING);
+ if (!serviceDiscoveryPending && !isEnabled) {
+ // Otherwise, reject the connection if no service discovery is pending and priority is
+ // neither PRIORITY_ON nor PRIORITY_AUTO_CONNECT
+ Log.w(TAG, "okToConnect: return false, priority=" + priority + ", bondState="
+ + bondState);
return false;
}
return true;
@@ -546,8 +586,14 @@
List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
List<BluetoothDevice> devices = new ArrayList<>();
- Set<BluetoothDevice> bondedDevices = mAdapter.getBondedDevices();
- synchronized (mBtA2dpLock) {
+ if (states == null) {
+ return devices;
+ }
+ final BluetoothDevice[] bondedDevices = mAdapterService.getBondedDevices();
+ if (bondedDevices == null) {
+ return devices;
+ }
+ synchronized (mStateMachines) {
for (BluetoothDevice device : bondedDevices) {
if (!BluetoothUuid.isUuidPresent(mAdapterService.getRemoteUuids(device),
BluetoothUuid.AudioSink)) {
@@ -558,9 +604,10 @@
if (sm != null) {
connectionState = sm.getConnectionState();
}
- for (int i = 0; i < states.length; i++) {
- if (connectionState == states[i]) {
+ for (int state : states) {
+ if (connectionState == state) {
devices.add(device);
+ break;
}
}
}
@@ -595,14 +642,25 @@
}
}
+ private void storeActiveDeviceVolume() {
+ // Make sure volume has been stored before been removed from active.
+ if (mFactory.getAvrcpTargetService() != null && mActiveDevice != null) {
+ mFactory.getAvrcpTargetService().storeVolumeForDevice(mActiveDevice);
+ }
+ if (mActiveDevice != null && mAvrcp_ext != null) {
+ mAvrcp_ext.storeVolumeForDevice(mActiveDevice);
+ }
+ }
+
private void removeActiveDevice(boolean forceStopPlayingAudio) {
BluetoothDevice previousActiveDevice = mActiveDevice;
synchronized (mBtA2dpLock) {
- // Clear the active device
- mActiveDevice = null;
+ // Make sure volume has been store before device been remove from active.
+ storeActiveDeviceVolume();
+
// This needs to happen before we inform the audio manager that the device
- // disconnected. Please see comment in broadcastActiveDevice() for why.
- broadcastActiveDevice(null);
+ // disconnected. Please see comment in updateAndBroadcastActiveDevice() for why.
+ updateAndBroadcastActiveDevice(null);
if (previousActiveDevice == null) {
return;
@@ -623,27 +681,57 @@
isBAActive = (mBatService != null) && (mBatService.isBATActive());
Log.d(TAG," removeActiveDevice: BA active " + isBAActive);
// If BA streaming is ongoing, we don't want to pause music player
- if(isBAActive) {
- suppressNoisyIntent = true;
- Log.d(TAG," BA Active, suppress noisy intent");
+ if(!isBAActive) {
+ mAudioManager.handleBluetoothA2dpActiveDeviceChange(
+ previousActiveDevice, BluetoothProfile.STATE_DISCONNECTED,
+ BluetoothProfile.A2DP, suppressNoisyIntent, -1);
}
- if (mAdapterService.isTwsPlusDevice(previousActiveDevice) &&
- mDummyDevice != null) {
- previousActiveDevice = mDummyDevice;
- mDummyDevice = null;
+ }
+ // 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");
+ }
+ }
+
+ /**
+ * Process a change in the silence mode for a {@link BluetoothDevice}.
+ *
+ * @param device the device to change silence mode
+ * @param silence true to enable silence mode, false to disable.
+ * @return true on success, false on error
+ */
+ @VisibleForTesting
+ public boolean setSilenceMode(BluetoothDevice device, boolean silence) {
+ if (DBG) {
+ Log.d(TAG, "setSilenceMode(" + device + "): " + silence);
+ }
+ if (silence && Objects.equals(mActiveDevice, device)) {
+ removeActiveDevice(true);
+ } else if (!silence && mActiveDevice == null) {
+ // Set the device as the active device if currently no active device.
+ setActiveDevice(device);
+ }
+ if (!mA2dpNativeInterface.setSilenceDevice(device, silence)) {
+ Log.e(TAG, "Cannot set " + device + " silence mode " + silence + " in native layer");
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Early notification that Hearing Aids will be the active device. This allows the A2DP to save
+ * its volume before the Audio Service starts changing its media stream.
+ */
+ public void earlyNotifyHearingAidActive() {
+ enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
+
+ synchronized (mStateMachines) {
+ // Switch active device from A2DP to Hearing Aids.
+ if (DBG) {
+ Log.d(TAG, "earlyNotifyHearingAidActive: Save volume for " + mActiveDevice);
}
- mDisconnectDelay =
- mAudioManager.setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(
- previousActiveDevice, BluetoothProfile.STATE_DISCONNECTED,
- BluetoothProfile.A2DP, suppressNoisyIntent, -1);
- if (mDisconnectDelay > 0) {
- mDisconnectTime = SystemClock.uptimeMillis();
- }
- // 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");
- }
+ storeActiveDeviceVolume();
}
}
@@ -655,23 +743,28 @@
*/
public boolean setActiveDevice(BluetoothDevice device) {
enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
+
+ if(mAvrcp_ext != null) {
+ return mAvrcp_ext.startSHO(device, false);
+ }
+
+ return false;
+ }
+
+ public boolean startSHO(BluetoothDevice device) {
synchronized (mActiveDeviceLock) {
return setActiveDeviceInternal(device);
}
}
+
private boolean setActiveDeviceInternal(BluetoothDevice device) {
boolean deviceChanged;
BluetoothCodecStatus codecStatus = null;
BluetoothDevice previousActiveDevice = mActiveDevice;
boolean isBAActive = false;
+ boolean tws_switch = false;
Log.w(TAG, "setActiveDevice(" + device + "): previous is " + previousActiveDevice);
- if (previousActiveDevice != null && AvrcpTargetService.get() != null) {
- AvrcpTargetService.get().storeVolumeForDevice(previousActiveDevice);
- } else if (previousActiveDevice != null && mAvrcp_ext != null) {
- //Store volume only if SHO is triggered or output device other than BT is selected
- mAvrcp_ext.storeVolumeForDevice(previousActiveDevice);
- }
synchronized (mBtA2dpLock) {
BATService mBatService = BATService.getBATService();
isBAActive = (mBatService != null) && (mBatService.isBATActive());
@@ -708,35 +801,44 @@
Log.d(TAG,"Ignore setActiveDevice request");
return false;
}
- if (!mA2dpNativeInterface.setActiveDevice(device)) {
- Log.e(TAG, "setActiveDevice(" + device + "): Cannot set as active in native layer");
- return false;
- }
codecStatus = sm.getCodecStatus();
- mActiveDevice = device;
+
+ if (deviceChanged) {
+ // Switch from one A2DP to another A2DP device
+ if (DBG) {
+ Log.d(TAG, "Switch A2DP devices to " + device + " from " + mActiveDevice);
+ }
+ storeActiveDeviceVolume();
+ }
+
// This needs to happen before we inform the audio manager that the device
- // disconnected. Please see comment in broadcastActiveDevice() for why.
- broadcastActiveDevice(mActiveDevice);
+ // disconnected. Please see comment in updateAndBroadcastActiveDevice() for why.
Log.w(TAG, "setActiveDevice coming out of mutex lock");
}
- if (deviceChanged &&
- (mDummyDevice == null || !mAdapterService.isTwsPlusDevice(mActiveDevice))) {
+
+ if (!mA2dpNativeInterface.setActiveDevice(device)) {
+ Log.e(TAG, "setActiveDevice(" + device + "): Cannot set as active in native layer");
+ return false;
+ }
+ updateAndBroadcastActiveDevice(device);
+ Log.d(TAG, "setActiveDevice(" + device + "): completed");
+
+ if (deviceChanged) {
if(mAvrcp_ext != null)
mAvrcp_ext.setActiveDevice(device);
- if (mAdapterService.isTwsPlusDevice(device) && mDummyDevice == null) {
- Log.d(TAG,"set dummy device for tws+");
- mDummyDevice = mAdapter.getRemoteDevice("FA:CE:FA:CE:FA:CE");
+ if (mAdapterService.isTwsPlusDevice(device) &&
+ (previousActiveDevice != null && mAdapterService.isTwsPlusDevice(previousActiveDevice))) {
+ Log.d(TAG,"TWS+ active device disconnected, setting other pair as active");
+ tws_switch = true;
}
// Send an intent with the active device codec config
if (codecStatus != null) {
broadcastCodecConfig(mActiveDevice, codecStatus);
}
int rememberedVolume = -1;
- if (AvrcpTargetService.get() != null) {
- AvrcpTargetService.get().volumeDeviceSwitched(device);
-
- rememberedVolume = AvrcpTargetService.get()
- .getRememberedVolumeForDevice(device);
+ if (mFactory.getAvrcpTargetService() != null) {
+ rememberedVolume = mFactory.getAvrcpTargetService()
+ .getRememberedVolumeForDevice(mActiveDevice);
} else if (mAdapterService.isVendorIntfEnabled()) {
rememberedVolume = mAvrcp_ext.getVolume(device);
Log.d(TAG,"volume = " + rememberedVolume);
@@ -745,70 +847,41 @@
// 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 (previousActiveDevice != null && !tws_switch) {
if (!mAudioManager.isStreamMute(AudioManager.STREAM_MUSIC)) {
mAudioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC,
AudioManager.ADJUST_MUTE,
mAudioManager.FLAG_BLUETOOTH_ABS_VOLUME);
wasMuted = true;
}
- if (mDummyDevice != null &&
- mAdapterService.isTwsPlusDevice(previousActiveDevice)) {
- mAudioManager.setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(
- mDummyDevice, BluetoothProfile.STATE_DISCONNECTED,
- BluetoothProfile.A2DP, true, -1);
- mDummyDevice = null;
- } else {
- mAudioManager.setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(
- previousActiveDevice, BluetoothProfile.STATE_DISCONNECTED,
- BluetoothProfile.A2DP, true, -1);
- }
- }
- // Check if ther is any delay set on audioservice for previous
- // disconnect, if so then need to serialise disconnect/connect
- // requests to audioservice, wait till prev disconnect is completed
- if (mDisconnectDelay > 0) {
- long currentTime = SystemClock.uptimeMillis();
- if (mDisconnectDelay > (currentTime - mDisconnectTime)) {
- try {
- Log.d(TAG, "Enter wait for previous disconnect");
- Thread.sleep(mDisconnectDelay - (currentTime - mDisconnectTime));
- Log.d(TAG, "Exiting Wait for previous disconnect");
- } catch (InterruptedException e) {
- Log.e(TAG, "setactive was interrupted");
- }
- }
- mDisconnectDelay = 0;
- mDisconnectTime = 0;
}
if (!isBAActive) {
- if (mDummyDevice == null) {
- mAudioManager.setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(
- mActiveDevice, BluetoothProfile.STATE_CONNECTED, BluetoothProfile.A2DP,
- true, rememberedVolume);
- } else {
- mAudioManager.setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(
- mDummyDevice, BluetoothProfile.STATE_CONNECTED, BluetoothProfile.A2DP,
- true, rememberedVolume);
- }
+ // Make sure the Audio Manager knows the previous
+ // Active device is disconnected, and the new Active
+ // device is connected.
+ // Also, provide information about codec used by
+ // new active device so that Audio Service
+ // can reset accordingly the audio feeding parameters
+ // in the Audio HAL to the Bluetooth stack.
+ mAudioManager.handleBluetoothA2dpActiveDeviceChange(
+ 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.
- String offloadSupported =
- SystemProperties.get("persist.vendor.btstack.enable.splita2dp");
- if (!(offloadSupported.isEmpty() || "true".equals(offloadSupported))) {
- mAudioManager.handleBluetoothA2dpDeviceConfigChange(device);
- }
+
if (wasMuted) {
mAudioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC,
AudioManager.ADJUST_UNMUTE,
mAudioManager.FLAG_BLUETOOTH_ABS_VOLUME);
}
- if(mAvrcp_ext != null)
+ if (mAvrcp_ext != null && !tws_switch) {
mAvrcp_ext.setAbsVolumeFlag(device);
+ }
+ tws_switch = false;
}
return true;
}
@@ -833,20 +906,18 @@
public boolean setPriority(BluetoothDevice device, int priority) {
enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
- Settings.Global.putInt(getContentResolver(),
- Settings.Global.getBluetoothA2dpSinkPriorityKey(device.getAddress()), priority);
if (DBG) {
Log.d(TAG, "Saved priority " + device + " = " + priority);
}
+ mAdapterService.getDatabase()
+ .setProfilePriority(device, BluetoothProfile.A2DP, priority);
return true;
}
public int getPriority(BluetoothDevice device) {
enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
- int priority = Settings.Global.getInt(getContentResolver(),
- Settings.Global.getBluetoothA2dpSinkPriorityKey(device.getAddress()),
- BluetoothProfile.PRIORITY_UNDEFINED);
- return priority;
+ return mAdapterService.getDatabase()
+ .getProfilePriority(device, BluetoothProfile.A2DP);
}
/* Absolute volume implementation */
@@ -860,8 +931,8 @@
public void setAvrcpAbsoluteVolume(int volume) {
// TODO (apanicke): Instead of using A2DP as a middleman for volume changes, add a binder
// service to the new AVRCP Profile and have the audio manager use that instead.
- if (AvrcpTargetService.get() != null) {
- AvrcpTargetService.get().sendVolumeChanged(volume);
+ if (mFactory.getAvrcpTargetService() != null) {
+ mFactory.getAvrcpTargetService().sendVolumeChanged(volume);
return;
}
@@ -920,6 +991,43 @@
}
}
+ private BluetoothCodecStatus getTwsPlusCodecStatus(BluetoothCodecStatus mCodecStatus) {
+ BluetoothCodecConfig mCodecConfig = mCodecStatus.getCodecConfig();
+ BluetoothCodecConfig mNewCodecConfig;
+ Log.d(TAG, "Return TWS codec status with " + mCodecConfig.getCodecName() + " codec");
+ if(mCodecConfig.getCodecType() == BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_ADAPTIVE) {
+ mNewCodecConfig = new BluetoothCodecConfig(
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_ADAPTIVE,
+ BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
+ BluetoothCodecConfig.SAMPLE_RATE_48000,
+ BluetoothCodecConfig.BITS_PER_SAMPLE_NONE,
+ BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+ 0, 0, 0, 0);
+ } else {
+ mNewCodecConfig = new BluetoothCodecConfig(
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX,
+ mCodecConfig.getCodecPriority(), mCodecConfig.getSampleRate(),
+ mCodecConfig.getBitsPerSample(), mCodecConfig.getChannelMode(),
+ mCodecConfig.getCodecSpecific1(), mCodecConfig.getCodecSpecific2(),
+ mCodecConfig.getCodecSpecific3(), mCodecConfig.getCodecSpecific4());
+ }
+ return (new BluetoothCodecStatus(mNewCodecConfig, mCodecStatus.getCodecsLocalCapabilities(),
+ mCodecStatus.getCodecsSelectableCapabilities()));
+ }
+
+ private BluetoothCodecStatus getBACodecStatus() {
+ BluetoothCodecConfig mNewCodecConfig =
+ new BluetoothCodecConfig(
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_CELT,
+ BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
+ BluetoothCodecConfig.SAMPLE_RATE_48000,
+ BluetoothCodecConfig.BITS_PER_SAMPLE_NONE,
+ BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+ 0, 0, 0, 0);
+ BluetoothCodecConfig[] mBACodecConfig = {mNewCodecConfig};
+ return (new BluetoothCodecStatus(mNewCodecConfig, mBACodecConfig, mBACodecConfig));
+ }
+
/**
* Gets the current codec status (configuration and capability).
*
@@ -933,6 +1041,10 @@
if (DBG) {
Log.d(TAG, "getCodecStatus(" + device + ")");
}
+ A2dpStateMachine sm = null;
+ boolean isBAActive = false;
+ BATService mBatService = BATService.getBATService();
+ isBAActive = (mBatService != null) && (mBatService.isBATActive());
synchronized (mBtA2dpLock) {
if (device == null) {
device = mActiveDevice;
@@ -940,7 +1052,17 @@
if (device == null) {
return null;
}
- A2dpStateMachine sm = mStateMachines.get(device);
+ if(isBAActive) {
+ Log.d(TAG, "getBACodecStatus(" + device + ")");
+ return getBACodecStatus();
+ }
+ if (Objects.equals(device, mDummyDevice)) {
+ sm = mStateMachines.get(mActiveDevice);
+ if (sm != null) {
+ return getTwsPlusCodecStatus(sm.getCodecStatus());
+ }
+ }
+ sm = mStateMachines.get(device);
if (sm != null) {
return sm.getCodecStatus();
}
@@ -967,25 +1089,47 @@
device = mActiveDevice;
}
if (device == null) {
- Log.e(TAG, "Cannot set codec config preference: no active A2DP device");
+ Log.e(TAG, "setCodecConfigPreference: Invalid device");
return;
}
- if((codecConfig.getCodecSpecific4() & AptxBLEScanMask) > 0) {
+ long cs4 = codecConfig.getCodecSpecific4();
GattService mGattService = GattService.getGattService();
- if(mGattService != null) {
- long mScanMode = codecConfig.getCodecSpecific4() & AptxBLEScanMask;
- if(mScanMode == Aptx_BLEScanEnable) {
- mGattService.setAptXLowLatencyMode(false);
- }
- else if(mScanMode == Aptx_BLEScanDisable) {
- Log.w(TAG, "Disable BLE scanning to support aptX LL Mode");
+ if(cs4 > 0 && mGattService != null) {
+ switch((int)(cs4 & APTX_MODE_MASK)) {
+ case APTX_HQ:
+ mGattService.setAptXLowLatencyMode(false);
+ break;
+
+ case APTX_LL:
+ case APTX_ULL:
+ if((cs4 & APTX_SCAN_FILTER_MASK) == APTX_SCAN_FILTER_MASK) {
mGattService.setAptXLowLatencyMode(true);
- }
+ } else {
+ mGattService.setAptXLowLatencyMode(false);
+ }
+ break;
+ default:
+ Log.e(TAG, cs4 + " is not a aptX profile mode feedback");
}
}
- mA2dpCodecConfig.setCodecConfigPreference(device, codecConfig);
+
+ if (codecConfig == null) {
+ Log.e(TAG, "setCodecConfigPreference: Codec config can't be null");
+ return;
+ }
+ BluetoothCodecStatus codecStatus = getCodecStatus(device);
+ if (codecStatus == null) {
+ Log.e(TAG, "setCodecConfigPreference: Codec status is null");
+ return;
+ }
+ if (mAdapterService.isTwsPlusDevice(device) && ( cs4 == 0 ||
+ codecConfig.getCodecType() != BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_ADAPTIVE )) {
+ Log.w(TAG, "Block un-supportive codec on TWS+ device: " + device);
+ return;
+ }
+ mA2dpCodecConfig.setCodecConfigPreference(device, codecStatus, codecConfig);
}
/**
@@ -1004,10 +1148,19 @@
device = mActiveDevice;
}
if (device == null) {
- Log.e(TAG, "Cannot enable optional codecs: no active A2DP device");
+ Log.e(TAG, "enableOptionalCodecs: Invalid device");
return;
}
- mA2dpCodecConfig.enableOptionalCodecs(device);
+ if (getSupportsOptionalCodecs(device) != BluetoothA2dp.OPTIONAL_CODECS_SUPPORTED) {
+ Log.e(TAG, "enableOptionalCodecs: No optional codecs");
+ return;
+ }
+ BluetoothCodecStatus codecStatus = getCodecStatus(device);
+ if (codecStatus == null) {
+ Log.e(TAG, "enableOptionalCodecs: Codec status is null");
+ return;
+ }
+ mA2dpCodecConfig.enableOptionalCodecs(device, codecStatus.getCodecConfig());
}
/**
@@ -1026,34 +1179,36 @@
device = mActiveDevice;
}
if (device == null) {
- Log.e(TAG, "Cannot disable optional codecs: no active A2DP device");
+ Log.e(TAG, "disableOptionalCodecs: Invalid device");
return;
}
- mA2dpCodecConfig.disableOptionalCodecs(device);
+ if (getSupportsOptionalCodecs(device) != BluetoothA2dp.OPTIONAL_CODECS_SUPPORTED) {
+ Log.e(TAG, "disableOptionalCodecs: No optional codecs");
+ return;
+ }
+ BluetoothCodecStatus codecStatus = getCodecStatus(device);
+ if (codecStatus == null) {
+ Log.e(TAG, "disableOptionalCodecs: Codec status is null");
+ return;
+ }
+ mA2dpCodecConfig.disableOptionalCodecs(device, codecStatus.getCodecConfig());
}
public int getSupportsOptionalCodecs(BluetoothDevice device) {
enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
- int support = Settings.Global.getInt(getContentResolver(),
- Settings.Global.getBluetoothA2dpSupportsOptionalCodecsKey(device.getAddress()),
- BluetoothA2dp.OPTIONAL_CODECS_SUPPORT_UNKNOWN);
- return support;
+ return mAdapterService.getDatabase().getA2dpSupportsOptionalCodecs(device);
}
public void setSupportsOptionalCodecs(BluetoothDevice device, boolean doesSupport) {
enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
int value = doesSupport ? BluetoothA2dp.OPTIONAL_CODECS_SUPPORTED
: BluetoothA2dp.OPTIONAL_CODECS_NOT_SUPPORTED;
- Settings.Global.putInt(getContentResolver(),
- Settings.Global.getBluetoothA2dpSupportsOptionalCodecsKey(device.getAddress()),
- value);
+ mAdapterService.getDatabase().setA2dpSupportsOptionalCodecs(device, value);
}
public int getOptionalCodecsEnabled(BluetoothDevice device) {
enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
- return Settings.Global.getInt(getContentResolver(),
- Settings.Global.getBluetoothA2dpOptionalCodecsEnabledKey(device.getAddress()),
- BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN);
+ return mAdapterService.getDatabase().getA2dpOptionalCodecsEnabled(device);
}
public void setOptionalCodecsEnabled(BluetoothDevice device, int value) {
@@ -1064,9 +1219,7 @@
Log.w(TAG, "Unexpected value passed to setOptionalCodecsEnabled:" + value);
return;
}
- Settings.Global.putInt(getContentResolver(),
- Settings.Global.getBluetoothA2dpOptionalCodecsEnabledKey(device.getAddress()),
- value);
+ mAdapterService.getDatabase().setA2dpOptionalCodecsEnabled(device, value);
}
// Handle messages from native (JNI) to Java
@@ -1091,6 +1244,7 @@
if (!connectionAllowedCheckMaxDevices(device)) {
Log.e(TAG, "Cannot connect to " + device
+ " : too many connected devices");
+ mA2dpNativeInterface.disconnectA2dp(device);
return;
}
sm = getOrCreateStateMachine(device);
@@ -1133,50 +1287,91 @@
* @param sameAudioFeedingParameters if true the audio feeding parameters
* haven't been changed
*/
- void codecConfigUpdated(BluetoothDevice device, BluetoothCodecStatus codecStatus,
+ @VisibleForTesting
+ public void codecConfigUpdated(BluetoothDevice device, BluetoothCodecStatus codecStatus,
boolean sameAudioFeedingParameters) {
Log.w(TAG, "codecConfigUpdated for device:" + device +
"sameAudioFeedingParameters: " + sameAudioFeedingParameters);
+
+ // Log codec config and capability metrics
+ BluetoothCodecConfig codecConfig = codecStatus.getCodecConfig();
+ StatsLog.write(StatsLog.BLUETOOTH_A2DP_CODEC_CONFIG_CHANGED,
+ mAdapterService.obfuscateAddress(device), codecConfig.getCodecType(),
+ codecConfig.getCodecPriority(), codecConfig.getSampleRate(),
+ codecConfig.getBitsPerSample(), codecConfig.getChannelMode(),
+ codecConfig.getCodecSpecific1(), codecConfig.getCodecSpecific2(),
+ codecConfig.getCodecSpecific3(), codecConfig.getCodecSpecific4());
+ BluetoothCodecConfig[] codecCapabilities = codecStatus.getCodecsSelectableCapabilities();
+ for (BluetoothCodecConfig codecCapability : codecCapabilities) {
+ StatsLog.write(StatsLog.BLUETOOTH_A2DP_CODEC_CAPABILITY_CHANGED,
+ mAdapterService.obfuscateAddress(device), codecCapability.getCodecType(),
+ codecCapability.getCodecPriority(), codecCapability.getSampleRate(),
+ codecCapability.getBitsPerSample(), codecCapability.getChannelMode(),
+ codecConfig.getCodecSpecific1(), codecConfig.getCodecSpecific2(),
+ codecConfig.getCodecSpecific3(), codecConfig.getCodecSpecific4());
+ }
+
broadcastCodecConfig(device, codecStatus);
// 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.
+ int rememberedVolume = -1;
if (isActiveDevice(device) && !sameAudioFeedingParameters) {
- mAudioManager.handleBluetoothA2dpDeviceConfigChange(device);
+ if (mAvrcp_ext != null)
+ rememberedVolume = mAvrcp_ext.getVolume(device);
+ mAudioManager.handleBluetoothA2dpActiveDeviceChange(device,
+ BluetoothProfile.STATE_CONNECTED, BluetoothProfile.A2DP,
+ true, rememberedVolume);
}
}
void updateTwsChannelMode(int state, BluetoothDevice device) {
- if (mIsTwsPlusMonoSupported) {
- BluetoothDevice peerTwsDevice = mAdapterService.getTwsPlusPeerDevice(device);
- Log.d(TAG, "TwsChannelMode: " + mTwsPlusChannelMode);
- if ((state == BluetoothA2dp.STATE_PLAYING) && ("mono".equals(mTwsPlusChannelMode))) {
- if ((peerTwsDevice!= null) && peerTwsDevice.isConnected() && isA2dpPlaying(peerTwsDevice)) {
- Log.d(TAG, "setparameters to Dual-Mono");
- mAudioManager.setParameters("TwsChannelConfig=dual-mono");
- mTwsPlusChannelMode = "dual-mono";
- }
- } else if ("dual-mono".equals(mTwsPlusChannelMode)) {
- if ((state == BluetoothA2dp.STATE_PLAYING) && (getConnectionState(peerTwsDevice) != BluetoothProfile.STATE_CONNECTED)) {
- Log.d(TAG, "updateTwsChannelMode: send delay message ");
- Message msg = mHandler.obtainMessage(SET_EBMONO_CFG);
- mHandler.sendMessageDelayed(msg, MonoCfg_Timeout);
+ if (mIsTwsPlusMonoSupported) {
+ BluetoothDevice peerTwsDevice = mAdapterService.getTwsPlusPeerDevice(device);
+ Log.d(TAG, "TwsChannelMode: " + mTwsPlusChannelMode);
+ synchronized(mBtTwsLock) {
+ if ("mono".equals(mTwsPlusChannelMode)) {
+ if ((state == BluetoothA2dp.STATE_PLAYING) && (peerTwsDevice!= null)
+ && peerTwsDevice.isConnected() && isA2dpPlaying(peerTwsDevice)) {
+ Log.d(TAG, "updateTwsChannelMode: send delay message to set dual-mono ");
+ Message msg = mHandler.obtainMessage(SET_EBDUALMONO_CFG);
+ mHandler.sendMessageDelayed(msg, DualMonoCfg_Timeout);
+ } else if (state == BluetoothA2dp.STATE_PLAYING) {
+ Log.d(TAG, "setparameters to Mono");
+ mAudioManager.setParameters("TwsChannelConfig=mono");
+ }
+ if ((state == BluetoothA2dp.STATE_NOT_PLAYING) &&
+ isA2dpPlaying(peerTwsDevice)) {
+ if (mHandler.hasMessages(SET_EBDUALMONO_CFG)) {
+ Log.d(TAG, "updateTwsChannelMode:remove delay message for dual-mono");
+ mHandler.removeMessages(SET_EBDUALMONO_CFG);
+ }
+ }
+ } else if ("dual-mono".equals(mTwsPlusChannelMode)) {
+ if ((state == BluetoothA2dp.STATE_PLAYING) &&
+ (getConnectionState(peerTwsDevice) != BluetoothProfile.STATE_CONNECTED
+ || !isA2dpPlaying(peerTwsDevice))) {
+ Log.d(TAG, "updateTwsChannelMode: send delay message to set mono");
+ Message msg = mHandler.obtainMessage(SET_EBMONO_CFG);
+ mHandler.sendMessageDelayed(msg, MonoCfg_Timeout);
+ }
+ if ((state == BluetoothA2dp.STATE_PLAYING) && isA2dpPlaying(peerTwsDevice)) {
+ if (mHandler.hasMessages(SET_EBMONO_CFG)) {
+ Log.d(TAG, "updateTwsChannelMode: remove delay message to set mono");
+ mHandler.removeMessages(SET_EBMONO_CFG);
+ }
+ }
+ if ((state == BluetoothA2dp.STATE_NOT_PLAYING)
+ && isA2dpPlaying(peerTwsDevice)) {
+ Log.d(TAG, "setparameters to Mono");
+ mAudioManager.setParameters("TwsChannelConfig=mono");
+ mTwsPlusChannelMode = "mono";
+ }
+ }
}
- if ((state == BluetoothA2dp.STATE_PLAYING) && isA2dpPlaying(peerTwsDevice)) {
- if (mHandler.hasMessages(SET_EBMONO_CFG)) {
- Log.d(TAG, "updateTwsChannelMode: remove delay message ");
- mHandler.removeMessages(SET_EBMONO_CFG);
- }
- }
- if ((state == BluetoothA2dp.STATE_NOT_PLAYING) && isA2dpPlaying(peerTwsDevice)) {
- Log.d(TAG, "setparameters to Mono");
- mAudioManager.setParameters("TwsChannelConfig=mono");
- mTwsPlusChannelMode = "mono";
- }
- }
- } else {
+ } else {
Log.d(TAG,"TWS+ L/R to M feature not supported");
- }
+ }
}
public void broadcastReconfigureA2dp() {
@@ -1219,11 +1414,24 @@
}
}
- private void broadcastActiveDevice(BluetoothDevice device) {
+ // This needs to run before any of the Audio Manager connection functions since
+ // AVRCP needs to be aware that the audio device is changed before the Audio Manager
+ // changes the volume of the output devices.
+ private void updateAndBroadcastActiveDevice(BluetoothDevice device) {
if (DBG) {
- Log.d(TAG, "broadcastActiveDevice(" + device + ")");
+ Log.d(TAG, "updateAndBroadcastActiveDevice(" + 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));
Intent intent = new Intent(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED);
intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
@@ -1283,6 +1491,10 @@
if (sm.getConnectionState() != BluetoothProfile.STATE_DISCONNECTED) {
return;
}
+ if (mFactory.getAvrcpTargetService() != null) {
+ mFactory.getAvrcpTargetService().removeStoredVolumeForDevice(device);
+ }
+
removeStateMachine(device);
}
}
@@ -1302,9 +1514,17 @@
}
}
- private void updateOptionalCodecsSupport(BluetoothDevice device) {
+
+ /**
+ * Update and initiate optional codec status change to native.
+ *
+ * @param device the device to change optional codec status
+ */
+ @VisibleForTesting
+ public void updateOptionalCodecsSupport(BluetoothDevice device) {
int previousSupport = getSupportsOptionalCodecs(device);
boolean supportsOptional = false;
+ boolean hasMandatoryCodec = false;
synchronized (mBtA2dpLock) {
A2dpStateMachine sm = mStateMachines.get(device);
@@ -1314,13 +1534,22 @@
BluetoothCodecStatus codecStatus = sm.getCodecStatus();
if (codecStatus != null) {
for (BluetoothCodecConfig config : codecStatus.getCodecsSelectableCapabilities()) {
- if (!config.isMandatoryCodec()) {
+ if (config.isMandatoryCodec()) {
+ hasMandatoryCodec = true;
+ } else {
supportsOptional = true;
- break;
}
}
}
}
+ if (!hasMandatoryCodec) {
+ // Mandatory codec(SBC) is not selectable. It could be caused by the remote device
+ // select codec before native finish get codec capabilities. Stop use this codec
+ // status as the reference to support/enable optional codecs.
+ Log.i(TAG, "updateOptionalCodecsSupport: Mandatory codec is not selectable.");
+ return;
+ }
+
if (previousSupport == BluetoothA2dp.OPTIONAL_CODECS_SUPPORT_UNKNOWN
|| supportsOptional != (previousSupport
== BluetoothA2dp.OPTIONAL_CODECS_SUPPORTED)) {
@@ -1328,10 +1557,17 @@
}
if (supportsOptional) {
int enabled = getOptionalCodecsEnabled(device);
- if (enabled == BluetoothA2dp.OPTIONAL_CODECS_PREF_ENABLED) {
- enableOptionalCodecs(device);
- } else if (enabled == BluetoothA2dp.OPTIONAL_CODECS_PREF_DISABLED) {
- disableOptionalCodecs(device);
+ switch (enabled) {
+ case BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN:
+ // Enable optional codec by default.
+ setOptionalCodecsEnabled(device, BluetoothA2dp.OPTIONAL_CODECS_PREF_ENABLED);
+ // Fall through intended
+ case BluetoothA2dp.OPTIONAL_CODECS_PREF_ENABLED:
+ enableOptionalCodecs(device);
+ break;
+ case BluetoothA2dp.OPTIONAL_CODECS_PREF_DISABLED:
+ disableOptionalCodecs(device);
+ break;
}
}
}
@@ -1342,15 +1578,16 @@
}
synchronized (mBtA2dpLock) {
if (toState == BluetoothProfile.STATE_CONNECTED) {
- // Each time a device connects, we want to re-check if it supports optional
- // codecs (perhaps it's had a firmware update, etc.) and save that state if
- // it differs from what we had saved before.
- updateOptionalCodecsSupport(device);
+ MetricsLogger.logProfileConnectionEvent(BluetoothMetricsProto.ProfileId.A2DP);
}
// 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);
}
}
@@ -1561,6 +1798,11 @@
if (service == null) {
return BluetoothA2dp.OPTIONAL_CODECS_SUPPORT_UNKNOWN;
}
+ AdapterService adService = AdapterService.getAdapterService();
+ if(adService.isTwsPlusDevice(device)) {
+ Log.w(TAG, "Disable optional codec support for TWS+ device");
+ return BluetoothA2dp.OPTIONAL_CODECS_NOT_SUPPORTED;
+ }
return service.getSupportsOptionalCodecs(device);
}
diff --git a/src/com/android/bluetooth/a2dp/A2dpStateMachine.java b/src/com/android/bluetooth/a2dp/A2dpStateMachine.java
index 56faad4..ceb6a4e 100644
--- a/src/com/android/bluetooth/a2dp/A2dpStateMachine.java
+++ b/src/com/android/bluetooth/a2dp/A2dpStateMachine.java
@@ -53,10 +53,11 @@
import android.content.Intent;
import android.os.Looper;
import android.os.Message;
-import android.support.annotation.VisibleForTesting;
import android.util.Log;
+import android.util.StatsLog;
import com.android.bluetooth.btservice.ProfileService;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.State;
import com.android.internal.util.StateMachine;
@@ -90,7 +91,8 @@
private A2dpService mA2dpService;
private A2dpNativeInterface mA2dpNativeInterface;
- private boolean mA2dpOffloadEnabled = false;
+ @VisibleForTesting
+ boolean mA2dpOffloadEnabled = false;
private final BluetoothDevice mDevice;
private boolean mIsPlaying = false;
private BluetoothCodecStatus mCodecStatus;
@@ -481,6 +483,10 @@
}
removeDeferredMessages(CONNECT);
+ // Each time a device connects, we want to re-check if it supports optional
+ // codecs (perhaps it's had a firmware update, etc.) and save that state if
+ // it differs from what we had saved before.
+ mA2dpService.updateOptionalCodecsSupport(mDevice);
broadcastConnectionState(mConnectionState, mLastConnectionState);
// Upon connected, the audio starts out as stopped
broadcastAudioState(BluetoothA2dp.STATE_NOT_PLAYING,
@@ -576,11 +582,11 @@
broadcastAudioState(BluetoothA2dp.STATE_PLAYING,
BluetoothA2dp.STATE_NOT_PLAYING);
Log.i(TAG,"state:AUDIO_STATE_STARTED");
- AdapterService adapterService = AdapterService.getAdapterService();
- if (adapterService.isVendorIntfEnabled() &&
- adapterService.isTwsPlusDevice(mDevice)) {
- mA2dpService.updateTwsChannelMode(BluetoothA2dp.STATE_PLAYING, mDevice);
- }
+ }
+ AdapterService adapterService = AdapterService.getAdapterService();
+ if (adapterService.isVendorIntfEnabled() &&
+ adapterService.isTwsPlusDevice(mDevice)) {
+ mA2dpService.updateTwsChannelMode(BluetoothA2dp.STATE_PLAYING, mDevice);
}
}
break;
@@ -630,20 +636,32 @@
}
// NOTE: This event is processed in any state
- private void processCodecConfigEvent(BluetoothCodecStatus newCodecStatus) {
+ @VisibleForTesting
+ void processCodecConfigEvent(BluetoothCodecStatus newCodecStatus) {
BluetoothCodecConfig prevCodecConfig = null;
+ BluetoothCodecStatus prevCodecStatus = mCodecStatus;
+
int new_codec_type = newCodecStatus.getCodecConfig().getCodecType();
- String offloadSupported =
- SystemProperties.get("persist.vendor.btstack.enable.splita2dp");
- if (DBG) Log.d(TAG, "START of A2dpService");
- Log.w(TAG,"processCodecConfigEvent: new_codec_type = " + new_codec_type);
+
// Split A2dp will be enabled by default
- if (offloadSupported.isEmpty() || "true".equals(offloadSupported)) {
+ boolean isSplitA2dpEnabled = true;
+ AdapterService adapterService = AdapterService.getAdapterService();
+
+ if (adapterService != null){
+ isSplitA2dpEnabled = adapterService.isSplitA2dpEnabled();
+ Log.v(TAG,"isSplitA2dpEnabled: " + isSplitA2dpEnabled);
+ } else {
+ Log.e(TAG,"adapterService is null");
+ }
+
+ Log.w(TAG,"processCodecConfigEvent: new_codec_type = " + new_codec_type);
+
+ if (isSplitA2dpEnabled) {
if (new_codec_type == BluetoothCodecConfig.SOURCE_CODEC_TYPE_MAX) {
- AdapterService adapterService = AdapterService.getAdapterService();
if (adapterService.isVendorIntfEnabled() &&
adapterService.isTwsPlusDevice(mDevice)) {
Log.d(TAG,"TWSP device streaming,not calling reconfig");
+ mCodecStatus = newCodecStatus;
return;
}
mA2dpService.broadcastReconfigureA2dp();
@@ -670,6 +688,12 @@
}
}
+ if (isConnected() && !sameSelectableCodec(prevCodecStatus, mCodecStatus)) {
+ // Remote selectable codec could be changed if codec config changed
+ // in connected state, we need to re-check optional codec status
+ // for this codec change event.
+ mA2dpService.updateOptionalCodecsSupport(mDevice);
+ }
if (mA2dpOffloadEnabled) {
boolean update = false;
BluetoothCodecConfig newCodecConfig = mCodecStatus.getCodecConfig();
@@ -691,7 +715,7 @@
return;
}
- if (!(offloadSupported.isEmpty() || "true".equals(offloadSupported))) {
+ if (!isSplitA2dpEnabled) {
boolean isUpdateRequired = false;
if ((prevCodecConfig != null) && (prevCodecConfig.getCodecType() != new_codec_type)) {
Log.d(TAG, "previous codec is differs from new codec");
@@ -732,7 +756,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);
Intent intent = new Intent(BluetoothA2dp.ACTION_PLAYING_STATE_CHANGED);
intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice);
intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState);
@@ -755,6 +779,16 @@
return builder.toString();
}
+ private static boolean sameSelectableCodec(BluetoothCodecStatus prevCodecStatus,
+ BluetoothCodecStatus newCodecStatus) {
+ if (prevCodecStatus == null) {
+ return false;
+ }
+ return BluetoothCodecStatus.sameCapabilities(
+ prevCodecStatus.getCodecsSelectableCapabilities(),
+ newCodecStatus.getCodecsSelectableCapabilities());
+ }
+
private static String messageWhatToString(int what) {
switch (what) {
case CONNECT:
@@ -808,7 +842,6 @@
ProfileService.println(sb, " mCodecConfig: " + mCodecStatus.getCodecConfig());
}
}
- ProfileService.println(sb, " StateMachine: " + this);
// 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 6502706..b51ffea 100644
--- a/src/com/android/bluetooth/a2dpsink/A2dpSinkService.java
+++ b/src/com/android/bluetooth/a2dpsink/A2dpSinkService.java
@@ -13,375 +13,114 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
package com.android.bluetooth.a2dpsink;
+import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothAudioConfig;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
-import android.bluetooth.BluetoothUuid;
import android.bluetooth.IBluetoothA2dpSink;
-import android.content.Context;
-import android.content.Intent;
-import android.media.AudioFormat;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Looper;
-import android.os.ParcelUuid;
-import android.os.SystemProperties;
-import android.provider.Settings;
import android.util.Log;
-import android.widget.Toast;
+import android.os.SystemProperties;
-import com.android.bluetooth.btservice.AdapterService;
+
import com.android.bluetooth.Utils;
-import com.android.bluetooth.a2dpsink.mbs.A2dpMediaBrowserService;
-import com.android.bluetooth.avrcpcontroller.AvrcpControllerService;
+import com.android.bluetooth.btservice.AdapterService;
import com.android.bluetooth.btservice.ProfileService;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.bluetooth.avrcpcontroller.AvrcpControllerService;
+
import java.util.ArrayList;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
+import java.util.Arrays;
import java.util.List;
-import java.util.Objects;
+import java.util.Map;
import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
/**
* Provides Bluetooth A2DP Sink profile, as a service in the Bluetooth application.
* @hide
*/
public class A2dpSinkService extends ProfileService {
- private static final boolean DBG = true;
private static final String TAG = "A2dpSinkService";
+ private static final boolean DBG = true;
+ //static final int MAX_ALLOWED_SINK_CONNECTIONS = 2;
- /* HashMap of A2dpSinkStateMachines for remote connected devices*/
- private final ConcurrentMap<BluetoothDevice, A2dpSinkStateMachine> mStateMachines =
- new ConcurrentHashMap<>();
- private HandlerThread mStateMachinesThread;
- private final Object mBtA2dpLock = new Object();
- private AdapterService mAdapterService;
+ private final BluetoothAdapter mAdapter;
+ protected static Map<BluetoothDevice, A2dpSinkStateMachine> mDeviceStateMap =
+ new ConcurrentHashMap<>(1);
- private A2dpSinkStateMachine mStateMachine;
- private static A2dpSinkService sA2dpSinkService;
+ private static final Object mBtA2dpLock = new Object();
+ private static A2dpSinkStreamHandler mA2dpSinkStreamHandler;
+ private static A2dpSinkService sService;
protected static BluetoothDevice mStreamingDevice;
private static int mMaxA2dpSinkConnections = 1;
public static final int MAX_ALLOWED_SINK_CONNECTIONS = 2;
-
static {
classInitNative();
}
@Override
- protected IProfileServiceBinder initBinder() {
- return new BluetoothA2dpSinkBinder(this);
- }
-
- @Override
protected boolean start() {
- if (DBG) {
- Log.d(TAG, "start()");
- }
-
initNative();
- mStateMachines.clear();
- mStateMachinesThread = new HandlerThread("A2dpSinkService.StateMachines");
- mStateMachinesThread.start();
-
- mAdapterService = Objects.requireNonNull(AdapterService.getAdapterService(),
- "AdapterService cannot be null when A2dpService starts");
-
+ sService = this;
+ mA2dpSinkStreamHandler = new A2dpSinkStreamHandler(this, this);
mMaxA2dpSinkConnections = Math.min(
SystemProperties.getInt("persist.vendor.bt.a2dp.sink_conn", 1),
MAX_ALLOWED_SINK_CONNECTIONS);
- // Start the media browser service.
- Intent startIntent = new Intent(this, A2dpMediaBrowserService.class);
- startService(startIntent);
- setA2dpSinkService(this);
return true;
}
@Override
protected boolean stop() {
- if (DBG) {
- Log.d(TAG, "stop()");
- }
- setA2dpSinkService(null);
-
- // Step 4: Destroy state machines and stop handler thread
synchronized (mBtA2dpLock) {
- for (A2dpSinkStateMachine sm : mStateMachines.values()) {
- sm.doQuit();
- sm.cleanup();
+ for (A2dpSinkStateMachine stateMachine : mDeviceStateMap.values()) {
+ stateMachine.quitNow();
}
- mStateMachines.clear();
- }
- mStateMachinesThread.quitSafely();
- mStateMachinesThread = null;
-
- Intent stopIntent = new Intent(this, A2dpMediaBrowserService.class);
- stopService(stopIntent);
+ }
+ sService = null;
return true;
}
+ public static A2dpSinkService getA2dpSinkService() {
+ return sService;
+ }
+
+ public A2dpSinkService() {
+ mAdapter = BluetoothAdapter.getDefaultAdapter();
+ }
+
+ protected A2dpSinkStateMachine newStateMachine(BluetoothDevice device) {
+ return new A2dpSinkStateMachine(device, this);
+ }
+
+ protected synchronized A2dpSinkStateMachine getStateMachine(BluetoothDevice device) {
+ return mDeviceStateMap.get(device);
+ }
+
+ /**
+ * Request audio focus such that the designated device can stream audio
+ */
+ public void requestAudioFocus(BluetoothDevice device, boolean request) {
+ A2dpSinkStateMachine stateMachine = null;
+ synchronized (mBtA2dpLock) {
+ stateMachine = mDeviceStateMap.get(device);
+ if (stateMachine == null) {
+ return;
+ }
+ }
+ mA2dpSinkStreamHandler.requestAudioFocus(request);
+ }
+
@Override
- protected void cleanup() {
- cleanupNative();
- }
-
- protected void removeStateMachine(BluetoothDevice device) {
- synchronized (mBtA2dpLock) {
- A2dpSinkStateMachine sm = mStateMachines.get(device);
- if (sm == null) {
- Log.e(TAG, "State Machine not found for device:" + device);
- return;
- }
- mStateMachines.remove(device);
- sm.doQuit();
- sm.cleanup();
- sm = null;
- }
- }
-
- //API Methods
-
- public static synchronized A2dpSinkService getA2dpSinkService() {
- if (sA2dpSinkService == null) {
- Log.w(TAG, "getA2dpSinkService(): service is null");
- return null;
- }
- if (!sA2dpSinkService.isAvailable()) {
- Log.w(TAG, "getA2dpSinkService(): service is not available ");
- return null;
- }
- return sA2dpSinkService;
- }
-
- private static synchronized void setA2dpSinkService(A2dpSinkService instance) {
- if (DBG) {
- Log.d(TAG, "setA2dpSinkService(): set to: " + instance);
- }
- sA2dpSinkService = instance;
- }
-
- public boolean connect(BluetoothDevice device) {
- enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
-
- if (DBG) Log.d(TAG, "connect(): " + device);
- if (getPriority(device) == BluetoothProfile.PRIORITY_OFF) {
- return false;
- }
- if (!BluetoothUuid.isUuidPresent(mAdapterService.getRemoteUuids(device),
- BluetoothUuid.AudioSource)) {
- Log.e(TAG, "Cannot connect to " + device + " : Remote does not have A2DP Source UUID");
- return false;
- }
-
- A2dpSinkStateMachine sm = null;
- synchronized (mBtA2dpLock) {
- sm = getOrCreateStateMachine(device);
- if (sm != null) {
- int connectionState = sm.getConnectionState();
- if (connectionState == BluetoothProfile.STATE_CONNECTED
- || connectionState == BluetoothProfile.STATE_CONNECTING) {
- Log.e(TAG, "Device (" + device + ") is already connected/connecting. Ignore");
- return false;
- }
- } else if (sm == null) {
- return false;
- }
- sm.sendMessage(A2dpSinkStateMachine.CONNECT);
- }
- return true;
- }
-
- boolean disconnect(BluetoothDevice device) {
- enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
-
- if (DBG) {
- Log.d(TAG, "disconnect(): " + device);
- }
-
- synchronized (mBtA2dpLock) {
- A2dpSinkStateMachine sm = mStateMachines.get(device);
- if (sm == null) {
- Log.e(TAG, "Ignored disconnect request for " + device + " : no state machine");
- return false;
- }
- // State check before Disconnect
- int connectionState = sm.getConnectionState();
- if (connectionState != BluetoothProfile.STATE_CONNECTED
- && connectionState != BluetoothProfile.STATE_CONNECTING) {
- return false;
- }
- sm.sendMessage(A2dpSinkStateMachine.DISCONNECT);
- return true;
- }
- }
-
- List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
- enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- List<BluetoothDevice> deviceList = new ArrayList<BluetoothDevice>();
- synchronized (mStateMachines) {
- Set<BluetoothDevice> bondedDevices = mAdapter.getBondedDevices();
- int connectionState;
-
- for (BluetoothDevice device : bondedDevices) {
- ParcelUuid[] featureUuids = device.getUuids();
- if (!BluetoothUuid.isUuidPresent(featureUuids, BluetoothUuid.AudioSource)) {
- continue;
- }
- connectionState = BluetoothProfile.STATE_DISCONNECTED;
- A2dpSinkStateMachine sm = mStateMachines.get(device);
- if (sm != null) {
- connectionState = sm.getConnectionState();
- }
- for (int i = 0; i < states.length; i++) {
- if (connectionState == states[i]) {
- deviceList.add(device);
- }
- }
- }
- }
- return deviceList;
- }
-
- public int getConnectionState(BluetoothDevice device) {
- enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- synchronized (mBtA2dpLock) {
- A2dpSinkStateMachine sm = mStateMachines.get(device);
- if (sm != null) {
- return sm.getConnectionState();
- }
- }
- return BluetoothProfile.STATE_DISCONNECTED;
- }
-
- public boolean setPriority(BluetoothDevice device, int priority) {
- enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
- Settings.Global.putInt(getContentResolver(),
- Settings.Global.getBluetoothA2dpSrcPriorityKey(device.getAddress()), priority);
- if (DBG) {
- Log.d(TAG, "Saved priority " + device + " = " + priority);
- }
- return true;
- }
-
- public int getPriority(BluetoothDevice device) {
- enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
- int priority = Settings.Global.getInt(getContentResolver(),
- Settings.Global.getBluetoothA2dpSrcPriorityKey(device.getAddress()),
- BluetoothProfile.PRIORITY_UNDEFINED);
- return priority;
- }
-
- /**
- * Called by AVRCP controller to provide information about the last user intent on CT.
- *
- * If the user has pressed play in the last attempt then A2DP Sink component will grant focus to
- * any incoming sound from the phone (and also retain focus for a few seconds before
- * relinquishing. On the other hand if the user has pressed pause/stop then the A2DP sink
- * component will take the focus away but also notify the stack to throw away incoming data.
- */
- public void informAvrcpPassThroughCmd(BluetoothDevice device, int keyCode, int keyState) {
- A2dpSinkStateMachine mStateMachine = null;
- synchronized (mBtA2dpLock) {
- mStateMachine = mStateMachines.get(device);
- if (mStateMachine == null) {
- Log.w(TAG, "state machine is not present for device:" + device);
- return;
- }
- }
- if (mStateMachine != null) {
- if (keyCode == AvrcpControllerService.PASS_THRU_CMD_ID_PLAY
- && keyState == AvrcpControllerService.KEY_STATE_RELEASED) {
- mStateMachine.sendMessage(A2dpSinkStateMachine.EVENT_AVRCP_CT_PLAY);
- } else if ((keyCode == AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE
- || keyCode == AvrcpControllerService.PASS_THRU_CMD_ID_STOP)
- && keyState == AvrcpControllerService.KEY_STATE_RELEASED) {
- mStateMachine.sendMessage(A2dpSinkStateMachine.EVENT_AVRCP_CT_PAUSE);
- }
- }
- }
-
- /**
- * Called by AVRCP controller to provide information about the last user intent on TG.
- *
- * Tf the user has pressed pause on the TG then we can preempt streaming music. This is opposed
- * to when the streaming stops abruptly (jitter) in which case we will wait for sometime before
- * stopping playback.
- */
- public void informTGStatePlaying(BluetoothDevice device, boolean isPlaying) {
- Log.d(TAG, "informTGStatePlaying: device: " + device
- + ", mStreamingDevice:" + mStreamingDevice);
- A2dpSinkStateMachine mStateMachine = null;
- synchronized (mBtA2dpLock) {
- mStateMachine = mStateMachines.get(device);
- if (mStateMachine == null) {
- return;
- }
- }
- if (mStateMachine != null) {
- if (!isPlaying) {
- mStateMachine.sendMessage(A2dpSinkStateMachine.EVENT_AVRCP_TG_PAUSE);
- } else {
- // Soft-Handoff from AVRCP Cmd (if received before AVDTP_START)
- initiateHandoffOperations(device);
- if (mStreamingDevice != null && !mStreamingDevice.equals(device)) {
- Log.d(TAG, "updating streaming device after avrcp status command");
- mStreamingDevice = device;
- }
- mStateMachine.sendMessage(A2dpSinkStateMachine.EVENT_AVRCP_TG_PLAY);
- }
- }
- }
-
- /**
- * Called by AVRCP controller to establish audio focus.
- *
- * In order to perform streaming the A2DP sink must have audio focus. This interface allows the
- * associated MediaSession to inform the sink of intent to play and then allows streaming to be
- * started from either the source or the sink endpoint.
- */
- public void requestAudioFocus(BluetoothDevice device, boolean request) {
- A2dpSinkStateMachine sm = null;
- synchronized (mBtA2dpLock) {
- sm = mStateMachines.get(device);
- if (sm == null) {
- return;
- }
- }
- sm.sendMessage(A2dpSinkStateMachine.EVENT_REQUEST_FOCUS);
- }
-
- synchronized boolean isA2dpPlaying(BluetoothDevice device) {
- enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- if (DBG) {
- Log.d(TAG, "isA2dpPlaying(" + device + ")");
- }
- synchronized (mBtA2dpLock) {
- A2dpSinkStateMachine mStateMachine = mStateMachines.get(device);
- if (mStateMachine == null) {
- return false;
- }
- }
- return mStateMachine.isPlaying(device);
- }
-
- BluetoothAudioConfig getAudioConfig(BluetoothDevice device) {
- enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- A2dpSinkStateMachine sm = null;
- synchronized (mBtA2dpLock) {
- sm = mStateMachines.get(device);
- if (sm == null) {
- return null;
- }
- }
- return sm.getAudioConfig(device);
+ protected IProfileServiceBinder initBinder() {
+ return new A2dpSinkServiceBinder(this);
}
//Binder object: Must be static class or memory leak may occur
- private static class BluetoothA2dpSinkBinder extends IBluetoothA2dpSink.Stub
+ private static class A2dpSinkServiceBinder extends IBluetoothA2dpSink.Stub
implements IProfileServiceBinder {
private A2dpSinkService mService;
@@ -391,13 +130,13 @@
return null;
}
- if (mService != null && mService.isAvailable()) {
+ if (mService != null) {
return mService;
}
return null;
}
- BluetoothA2dpSinkBinder(A2dpSinkService svc) {
+ A2dpSinkServiceBinder(A2dpSinkService svc) {
mService = svc;
}
@@ -452,15 +191,6 @@
}
@Override
- public boolean isA2dpPlaying(BluetoothDevice device) {
- A2dpSinkService service = getService();
- if (service == null) {
- return false;
- }
- return service.isA2dpPlaying(device);
- }
-
- @Override
public boolean setPriority(BluetoothDevice device, int priority) {
A2dpSinkService service = getService();
if (service == null) {
@@ -479,6 +209,15 @@
}
@Override
+ public boolean isA2dpPlaying(BluetoothDevice device) {
+ A2dpSinkService service = getService();
+ if (service == null) {
+ return false;
+ }
+ return service.isA2dpPlaying(device);
+ }
+
+ @Override
public BluetoothAudioConfig getAudioConfig(BluetoothDevice device) {
A2dpSinkService service = getService();
if (service == null) {
@@ -488,89 +227,154 @@
}
}
- ;
+ /* Generic Profile Code */
- @Override
- public void dump(StringBuilder sb) {
- super.dump(sb);
- if (mStateMachine != null) {
- mStateMachine.dump(sb);
+ /**
+ * Connect the given Bluetooth device.
+ *
+ * @return true if connection is successful, false otherwise.
+ */
+ public synchronized boolean connect(BluetoothDevice device) {
+ if (device == null) {
+ throw new IllegalArgumentException("Null device");
+ }
+ if (DBG) {
+ StringBuilder sb = new StringBuilder();
+ dump(sb);
+ 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");
+ return false;
+ }
+ A2dpSinkStateMachine stateMachine = getOrCreateStateMachine(device);
+ if (stateMachine != null) {
+ stateMachine.connect();
+ return true;
+ } else {
+ // a state machine instance doesn't exist yet, and the max has been reached.
+ Log.e(TAG, "Maxed out on the number of allowed MAP connections. "
+ + "Connect request rejected on " + device);
+ return false;
}
}
- /* Get Number of connected/connecting devices*/
+ /**
+ * Disconnect the given Bluetooth device.
+ *
+ * @return true if disconnect is successful, false otherwise.
+ */
+ public synchronized boolean disconnect(BluetoothDevice device) {
+ if (DBG) {
+ StringBuilder sb = new StringBuilder();
+ dump(sb);
+ Log.d(TAG, "A2DP disconnect device: " + device
+ + ", InstanceMap start state: " + sb.toString());
+ }
+ A2dpSinkStateMachine stateMachine = mDeviceStateMap.get(device);
+ // a state machine instance doesn't exist. maybe it is already gone?
+ if (stateMachine == null) {
+ return false;
+ }
+ int connectionState = stateMachine.getState();
+ if (connectionState == BluetoothProfile.STATE_DISCONNECTED
+ || connectionState == BluetoothProfile.STATE_DISCONNECTING) {
+ return false;
+ }
+ // upon completion of disconnect, the state machine will remove itself from the available
+ // devices map
+ stateMachine.disconnect();
+ return true;
+ }
+
+ void removeStateMachine(A2dpSinkStateMachine stateMachine) {
+ mDeviceStateMap.remove(stateMachine.getDevice());
+ }
+
public List<BluetoothDevice> getConnectedDevices() {
- enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- synchronized (mBtA2dpLock) {
- List<BluetoothDevice> devices = new ArrayList<>();
- for (A2dpSinkStateMachine sm : mStateMachines.values()) {
- if (sm.isConnected()) {
- devices.add(sm.getDevice());
- }
- }
- return devices;
- }
+ return getDevicesMatchingConnectionStates(new int[]{BluetoothAdapter.STATE_CONNECTED});
}
- /* This API returns existing state machine for remote device or creates new if not present.*/
- private A2dpSinkStateMachine getOrCreateStateMachine(BluetoothDevice device) {
+ protected A2dpSinkStateMachine getOrCreateStateMachine(BluetoothDevice device) {
if (device == null) {
Log.e(TAG, "getOrCreateStateMachine failed: device cannot be null");
return null;
}
synchronized (mBtA2dpLock) {
- A2dpSinkStateMachine sm = mStateMachines.get(device);
- if (sm != null) {
- Log.i(TAG, "Return existing state machine for device:" + device);
- return sm;
- }
- // Limit the maximum number of state machines to avoid DoS
- if (mStateMachines.size() >= mMaxA2dpSinkConnections) {
- Log.e(TAG, "Maximum number of A2DP Sink Connections reached: "
- + mMaxA2dpSinkConnections);
- return null;
- }
- if (DBG) {
+ A2dpSinkStateMachine stateMachine = mDeviceStateMap.get(device);
+ if (stateMachine == null) {
+ // Limit the maximum number of state machines to avoid DoS
+ if (mDeviceStateMap.size() >= mMaxA2dpSinkConnections) {
+ Log.e(TAG, "Maximum number of A2DP Sink Connections reached: "
+ + mMaxA2dpSinkConnections);
+ return null;
+ }
Log.d(TAG, "Creating a new state machine for " + device);
+ stateMachine = newStateMachine(device);
+ mDeviceStateMap.put(device, stateMachine);
+ stateMachine.start();
}
-
- sm = A2dpSinkStateMachine.make(this, mStateMachinesThread.getLooper(), device);
- mStateMachines.put(device, sm);
- return sm;
- }
+ return stateMachine;
+ }
}
public static BluetoothDevice getCurrentStreamingDevice() {
return mStreamingDevice;
}
+ public void informTGStatePlaying(BluetoothDevice device, boolean isPlaying) {
+ Log.d(TAG, "informTGStatePlaying: device: " + device
+ + ", mStreamingDevice:" + mStreamingDevice);
+ A2dpSinkStateMachine mStateMachine = null;
+ synchronized (mBtA2dpLock) {
+ mStateMachine = mDeviceStateMap.get(device);
+ if (mStateMachine == null) {
+ return;
+ }
+ }
+ if (mStateMachine != null) {
+ if (!isPlaying) {
+ //mStateMachine.sendMessage(A2dpSinkStateMachine.EVENT_AVRCP_TG_PAUSE);
+ mA2dpSinkStreamHandler.obtainMessage(
+ A2dpSinkStreamHandler.SRC_PAUSE).sendToTarget();
+ } else {
+ // Soft-Handoff from AVRCP Cmd (if received before AVDTP_START)
+ initiateHandoffOperations(device);
+ if (mStreamingDevice != null && !mStreamingDevice.equals(device)) {
+ Log.d(TAG, "updating streaming device after avrcp status command");
+ mStreamingDevice = device;
+ }
+ //mStateMachine.sendMessage(A2dpSinkStateMachine.EVENT_AVRCP_TG_PLAY);
+ mA2dpSinkStreamHandler.obtainMessage(
+ A2dpSinkStreamHandler.SRC_PLAY).sendToTarget();
+ }
+ }
+ }
+
/* This API performs all the operations required for doing soft-Handoff */
- public synchronized void initiateHandoffOperations(BluetoothDevice device) {
+ public synchronized void initiateHandoffOperations(BluetoothDevice device) {
if (mStreamingDevice != null && !mStreamingDevice.equals(device)) {
Log.d(TAG, "Soft-Handoff. Prev Device:" + mStreamingDevice + ", New: " + device);
- for (A2dpSinkStateMachine otherSm: mStateMachines.values()) {
+ for (A2dpSinkStateMachine otherSm: mDeviceStateMap.values()) {
BluetoothDevice otherDevice = otherSm.getDevice();
if (mStreamingDevice.equals(otherDevice)) {
Log.d(TAG, "Release Audio Focus for " + otherDevice);
- otherSm.sendMessage(A2dpSinkStateMachine.EVENT_RELEASE_FOCUS);
+ mA2dpSinkStreamHandler.obtainMessage(
+ A2dpSinkStreamHandler.RELEASE_FOCUS).sendToTarget();
// Send Passthrough Command for PAUSE
AvrcpControllerService avrcpService =
AvrcpControllerService.getAvrcpControllerService();
- avrcpService.sendPassThroughCmd(mStreamingDevice,
- AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE,
- AvrcpControllerService.KEY_STATE_PRESSED);
- avrcpService.sendPassThroughCmd(mStreamingDevice,
- AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE,
- AvrcpControllerService.KEY_STATE_RELEASED);
-
- // send intent for updated streaming device so that media session is updated
- Intent intent = new Intent(A2dpMediaBrowserService.ACTION_DEVICE_UPDATED);
- intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
- sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
-
+ avrcpService.sendPassThroughCommandNative(Utils.getByteAddress(mStreamingDevice),
+ AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE,
+ AvrcpControllerService.KEY_STATE_PRESSED);
+ avrcpService.sendPassThroughCommandNative(Utils.getByteAddress(mStreamingDevice),
+ AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE,
+ AvrcpControllerService.KEY_STATE_RELEASED);
/* set autoconnect priority of non-streaming device to PRIORITY_ON and priority
* of streaming device to PRIORITY_AUTO_CONNECT */
+ avrcpService.onDeviceUpdated(device);
setPriority(otherDevice, BluetoothProfile.PRIORITY_ON);
setPriority(device, BluetoothProfile.PRIORITY_AUTO_CONNECT);
break;
@@ -582,13 +386,140 @@
}
}
- /* JNI Changes for SINK SHO */
+ List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
+ if (DBG) Log.d(TAG, "getDevicesMatchingConnectionStates" + Arrays.toString(states));
+ List<BluetoothDevice> deviceList = new ArrayList<>();
+ Set<BluetoothDevice> bondedDevices = mAdapter.getBondedDevices();
+ int connectionState;
+ for (BluetoothDevice device : bondedDevices) {
+ connectionState = getConnectionState(device);
+ if (DBG) Log.d(TAG, "Device: " + device + "State: " + connectionState);
+ for (int i = 0; i < states.length; i++) {
+ if (connectionState == states[i]) {
+ deviceList.add(device);
+ }
+ }
+ }
+ if (DBG) Log.d(TAG, deviceList.toString());
+ Log.d(TAG, "GetDevicesDone");
+ return deviceList;
+ }
+
+ synchronized public int getConnectionState(BluetoothDevice device) {
+ A2dpSinkStateMachine stateMachine = mDeviceStateMap.get(device);
+ return (stateMachine == null) ? BluetoothProfile.STATE_DISCONNECTED
+ : stateMachine.getState();
+ }
+
+ /**
+ * Set the priority of the profile.
+ *
+ * @param device the remote device
+ * @param priority the priority of the profile
+ * @return true on success, otherwise false
+ */
+ public boolean setPriority(BluetoothDevice device, int priority) {
+ enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
+ if (DBG) {
+ Log.d(TAG, "Saved priority " + device + " = " + priority);
+ }
+ AdapterService.getAdapterService().getDatabase()
+ .setProfilePriority(device, BluetoothProfile.A2DP_SINK, priority);
+ return true;
+ }
+
+ /**
+ * Get the priority of the profile.
+ *
+ * @param device the remote device
+ * @return priority of the specified device
+ */
+ public int getPriority(BluetoothDevice device) {
+ enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
+ return AdapterService.getAdapterService().getDatabase()
+ .getProfilePriority(device, BluetoothProfile.A2DP_SINK);
+ }
+
+
+ @Override
+ public void dump(StringBuilder sb) {
+ super.dump(sb);
+ ProfileService.println(sb, "Devices Tracked = " + mDeviceStateMap.size());
+ for (A2dpSinkStateMachine stateMachine : mDeviceStateMap.values()) {
+ ProfileService.println(sb,
+ "==== StateMachine for " + stateMachine.getDevice() + " ====");
+ stateMachine.dump(sb);
+ }
+ }
+
+ /**
+ * Get the current Bluetooth Audio focus state
+ *
+ * @return focus
+ */
+ public static int getFocusState() {
+ return sService.mA2dpSinkStreamHandler.getFocusState();
+ }
+
+ boolean isA2dpPlaying(BluetoothDevice device) {
+ enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+ Log.d(TAG, "isA2dpPlaying(" + device + ")");
+ A2dpSinkStateMachine sm = null;
+ synchronized (mBtA2dpLock) {
+ sm = mDeviceStateMap.get(device);
+ if (sm == null) {
+ return false;
+ }
+ }
+ return mA2dpSinkStreamHandler.isPlaying();
+ }
+
+ BluetoothAudioConfig getAudioConfig(BluetoothDevice device) {
+ A2dpSinkStateMachine stateMachine = mDeviceStateMap.get(device);
+ // a state machine instance doesn't exist. maybe it is already gone?
+ if (stateMachine == null) {
+ return null;
+ }
+ return stateMachine.getAudioConfig();
+ }
+
+ /* JNI interfaces*/
+
+ private static native void classInitNative();
+
+ private native void initNative();
+
+ private native void cleanupNative();
+
+ native boolean connectA2dpNative(byte[] address);
+
+ native boolean disconnectA2dpNative(byte[] address);
+
+ /**
+ * inform A2DP decoder of the current audio focus
+ *
+ * @param focusGranted
+ */
+ @VisibleForTesting
+ public native void informAudioFocusStateNative(int focusGranted);
+
+ /**
+ * inform A2DP decoder the desired audio gain
+ *
+ * @param gain
+ */
+ @VisibleForTesting
+ public native void informAudioTrackGainNative(float gain);
+
private void onConnectionStateChanged(byte[] address, int state) {
BluetoothDevice device = getDevice(address);
Log.d(TAG, "onConnectionStateChanged. State = " + state + ", device:" + device
+ ", streaming:" + mStreamingDevice);
- A2dpSinkStateMachine sm = getOrCreateStateMachine(device);
- if (sm == null || device == null) {
+
+ StackEvent event = StackEvent.connectionStateChanged(getDevice(address), state);
+ A2dpSinkStateMachine stateMachine = getOrCreateStateMachine(event.mDevice);
+
+ if (stateMachine == null || device == null) {
Log.e(TAG, "State Machine not found for device:" + device + ". Return.");
return;
}
@@ -596,7 +527,8 @@
// If streaming device is disconnected, release audio focus and update mStreamingDevice
if (state == BluetoothProfile.STATE_DISCONNECTED && device.equals(mStreamingDevice)) {
Log.d(TAG, "Release Audio Focus for Streaming device: " + device);
- sm.sendMessage(A2dpSinkStateMachine.EVENT_RELEASE_FOCUS);
+ mA2dpSinkStreamHandler.obtainMessage(
+ A2dpSinkStreamHandler.RELEASE_FOCUS).sendToTarget();
mStreamingDevice = null;
}
@@ -610,66 +542,37 @@
mStreamingDevice = device;
}
}
-
- A2dpSinkStateMachine.StackEvent event =
- sm.new StackEvent(A2dpSinkStateMachine.EVENT_TYPE_CONNECTION_STATE_CHANGED);
- event.device = device;
- event.valueInt = state;
- sm.sendMessage(A2dpSinkStateMachine.STACK_EVENT, event);
+ stateMachine.sendMessage(A2dpSinkStateMachine.STACK_EVENT, event);
}
private void onAudioStateChanged(byte[] address, int state) {
BluetoothDevice device = getDevice(address);
Log.d(TAG, "onAudioStateChanged. Audio State = " + state + ", device:" + device);
- A2dpSinkStateMachine sm = mStateMachines.get(device);
- if (sm == null) {
+
+ A2dpSinkStateMachine stateMachine = mDeviceStateMap.get(device);
+ if (stateMachine == null) {
return;
}
- // Intiate Handoff operations if AUDIO_STATE_STARTED for other connected device
- if (state == A2dpSinkStateMachine.AUDIO_STATE_STARTED) {
+ if (state == StackEvent.AUDIO_STATE_STARTED) {
initiateHandoffOperations(device);
- mStreamingDevice = device; // mark playing device as streaming device
+ mStreamingDevice = device;
+ mA2dpSinkStreamHandler.obtainMessage(
+ A2dpSinkStreamHandler.SRC_STR_START).sendToTarget();
+ } else if (state == StackEvent.AUDIO_STATE_STOPPED) {
+ mA2dpSinkStreamHandler.obtainMessage(
+ A2dpSinkStreamHandler.SRC_STR_STOP).sendToTarget();
}
-
- A2dpSinkStateMachine.StackEvent event =
- sm.new StackEvent(A2dpSinkStateMachine.EVENT_TYPE_AUDIO_STATE_CHANGED);
- event.device = device;
- event.valueInt = state;
- sm.sendMessage(A2dpSinkStateMachine.STACK_EVENT, event);
}
private void onAudioConfigChanged(byte[] address, int sampleRate, int channelCount) {
BluetoothDevice device = getDevice(address);
Log.d(TAG, "onAudioConfigChanged:- device:" + device + " samplerate:" + sampleRate
+ ", channelCount:" + channelCount);
- A2dpSinkStateMachine sm = mStateMachines.get(device);
- if (sm == null) {
- return;
- }
- A2dpSinkStateMachine.StackEvent event =
- sm.new StackEvent(A2dpSinkStateMachine.EVENT_TYPE_AUDIO_CONFIG_CHANGED);
- event.device = device;
- int channelConfig =
- (channelCount == 1 ? AudioFormat.CHANNEL_IN_MONO : AudioFormat.CHANNEL_IN_STEREO);
- event.audioConfig =
- new BluetoothAudioConfig(sampleRate, channelConfig, AudioFormat.ENCODING_PCM_16BIT);
- sm.sendMessage(A2dpSinkStateMachine.STACK_EVENT, event);
+ StackEvent event = StackEvent.audioConfigChanged(getDevice(address), sampleRate,
+ channelCount);
+ A2dpSinkStateMachine stateMachine = getOrCreateStateMachine(event.mDevice);
+ stateMachine.sendMessage(A2dpSinkStateMachine.STACK_EVENT, event);
}
-
- private static native void classInitNative();
-
- private native void initNative();
-
- private native void cleanupNative();
-
- public static native boolean connectA2dpNative(byte[] address);
-
- public static native boolean disconnectA2dpNative(byte[] address);
-
- public static native void informAudioFocusStateNative(int focusGranted);
-
- public static native void informAudioTrackGainNative(float focusGranted);
-
}
diff --git a/src/com/android/bluetooth/a2dpsink/A2dpSinkStateMachine.java b/src/com/android/bluetooth/a2dpsink/A2dpSinkStateMachine.java
old mode 100755
new mode 100644
index fea7080..2bfbb2b
--- a/src/com/android/bluetooth/a2dpsink/A2dpSinkStateMachine.java
+++ b/src/com/android/bluetooth/a2dpsink/A2dpSinkStateMachine.java
@@ -13,114 +13,59 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
-/**
- * Bluetooth A2dp Sink StateMachine
- * (Disconnected)
- * | ^
- * CONNECT | | DISCONNECTED
- * V |
- * (Pending)
- * | ^
- * CONNECTED | | CONNECT
- * V |
- * (Connected -- See A2dpSinkStreamHandler)
- */
package com.android.bluetooth.a2dpsink;
import android.bluetooth.BluetoothA2dpSink;
-import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothAudioConfig;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
-import android.bluetooth.BluetoothUuid;
-import android.content.Context;
import android.content.Intent;
import android.media.AudioFormat;
-import android.media.AudioManager;
-import android.media.AudioSystem;
-import android.os.Handler;
-import android.os.Looper;
import android.os.Message;
-import android.os.ParcelUuid;
-import android.os.PowerManager;
import android.util.Log;
import com.android.bluetooth.BluetoothMetricsProto;
import com.android.bluetooth.Utils;
-import com.android.bluetooth.avrcpcontroller.AvrcpControllerService;
-import com.android.bluetooth.btservice.AdapterService;
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 java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Set;
public class A2dpSinkStateMachine extends StateMachine {
- private static final boolean DBG = false;
+ static final String TAG = "A2DPSinkStateMachine";
+ static final boolean DBG = true;
- static final int CONNECT = 1;
- static final int DISCONNECT = 2;
- protected static final int STACK_EVENT = 101;
- private static final int CONNECT_TIMEOUT = 201;
- public static final int EVENT_AVRCP_CT_PLAY = 301;
- public static final int EVENT_AVRCP_CT_PAUSE = 302;
- public static final int EVENT_AVRCP_TG_PLAY = 303;
- public static final int EVENT_AVRCP_TG_PAUSE = 304;
- public static final int EVENT_REQUEST_FOCUS = 305;
- public static final int EVENT_RELEASE_FOCUS = 306;
+ //0->99 Events from Outside
+ public static final int CONNECT = 1;
+ public static final int DISCONNECT = 2;
- private static final int IS_INVALID_DEVICE = 0;
- private static final int IS_VALID_DEVICE = 1;
- public static final int AVRC_ID_PLAY = 0x44;
- public static final int AVRC_ID_PAUSE = 0x46;
- public static final int KEY_STATE_PRESSED = 0;
- public static final int KEY_STATE_RELEASED = 1;
- private static final String BT_ADDR_KEY = "bt_addr";
+ //100->199 Internal Events
+ protected static final int CLEANUP = 100;
+ private static final int CONNECT_TIMEOUT = 101;
- // Connection states.
- // 1. Disconnected: The connection does not exist.
- // 2. Pending: The connection is being established.
- // 3. Connected: The connection is established. The audio connection is in Idle state.
- private Disconnected mDisconnected;
- private Connecting mConnecting;
- private Disconnecting mDisconnecting;
- private Connected mConnected;
+ //200->299 Events from Native
+ static final int STACK_EVENT = 200;
- private A2dpSinkService mService;
- private Context mContext;
- private BluetoothAdapter mAdapter;
+ static final int CONNECT_TIMEOUT_MS = 5000;
- private static final int MSG_CONNECTION_STATE_CHANGED = 0;
+ protected final BluetoothDevice mDevice;
+ protected final byte[] mDeviceAddress;
+ protected final A2dpSinkService mService;
+ protected final Disconnected mDisconnected;
+ protected final Connecting mConnecting;
+ protected final Connected mConnected;
+ protected final Disconnecting mDisconnecting;
- private final Object mLockForPatch = new Object();
+ protected int mMostRecentState = BluetoothProfile.STATE_DISCONNECTED;
+ protected BluetoothAudioConfig mAudioConfig = null;
- private A2dpSinkStreamHandler mStreaming = null;
-
- private final BluetoothDevice mDevice;
- private static final String TAG = "A2dpSinkStateMachine";
- private int mConnectionState = BluetoothProfile.STATE_DISCONNECTED;
- private boolean mIsPlaying = false;
- private Looper mLooper;
- private final HashMap<BluetoothDevice, BluetoothAudioConfig> mAudioConfigs =
- new HashMap<BluetoothDevice, BluetoothAudioConfig>();
- private int mLastConnectionState = -1;
-
- private A2dpSinkStateMachine(A2dpSinkService svc, Looper looper, BluetoothDevice device) {
- super(TAG, looper);
- mService = svc;
- mContext = svc;
- mLooper = looper;
+ A2dpSinkStateMachine(BluetoothDevice device, A2dpSinkService service) {
+ super(TAG);
mDevice = device;
- mAdapter = BluetoothAdapter.getDefaultAdapter();
-
- if (Looper.myLooper() == null)
- Looper.prepare();
+ mDeviceAddress = Utils.getByteAddress(mDevice);
+ mService = service;
+ if (DBG) Log.d(TAG, device.toString());
mDisconnected = new Disconnected();
mConnecting = new Connecting();
@@ -132,672 +77,224 @@
addState(mConnected);
addState(mDisconnecting);
- if (mAdapter != null) {
- String bdAddr = mAdapter.getAddress();
- AudioSystem.setParameters(BT_ADDR_KEY + "=" + bdAddr);
- log("AudioSystem.setParameters, Key: " + BT_ADDR_KEY + " Value: " + bdAddr);
- }
-
setInitialState(mDisconnected);
-
- PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
}
- static A2dpSinkStateMachine make(A2dpSinkService svc, Looper looper, BluetoothDevice device) {
- Log.d("A2dpSinkStateMachine", "make");
- A2dpSinkStateMachine a2dpSm = new A2dpSinkStateMachine(svc, looper, device);
- a2dpSm.start();
- return a2dpSm;
+ protected String getConnectionStateChangedIntent() {
+ return BluetoothA2dpSink.ACTION_CONNECTION_STATE_CHANGED;
}
- public void doQuit() {
- if (DBG) {
- Log.d("A2dpSinkStateMachine", "Quit");
- }
- if (mIsPlaying) {
- mIsPlaying = false;
- broadcastAudioState(mDevice, BluetoothA2dpSink.STATE_NOT_PLAYING,
- BluetoothA2dpSink.STATE_PLAYING);
- }
- synchronized (A2dpSinkStateMachine.this) {
- mStreaming = null;
- }
- quitNow();
+ /**
+ * Get the current connection state
+ *
+ * @return current State
+ */
+ public int getState() {
+ return mMostRecentState;
}
- public void cleanup() {
- mAudioConfigs.clear();
+ /**
+ * get current audio config
+ */
+ BluetoothAudioConfig getAudioConfig() {
+ return mAudioConfig;
}
- public void dump(StringBuilder sb) {
- ProfileService.println(sb, "mDevice: " + mDevice);
- ProfileService.println(sb, "StateMachine: " + this.toString());
- ProfileService.println(sb, "isPlaying: " + mIsPlaying);
- }
-
- private class Disconnected extends State {
- @Override
- public void enter() {
- log("Enter Disconnected (Device: " + mDevice + ") : " + getCurrentMessage().what);
- mConnectionState = BluetoothProfile.STATE_DISCONNECTED;
- if (mIsPlaying) {
- Log.i(TAG, "Disconnected: stopped playing: " + mDevice);
- mIsPlaying = false;
- broadcastAudioState(mDevice, BluetoothA2dpSink.STATE_NOT_PLAYING,
- BluetoothA2dpSink.STATE_PLAYING);
- }
- if (mLastConnectionState != -1) {
- log("Quit State Machine for device:" + mDevice);
- broadcastConnectionState(mDevice, mConnectionState, mLastConnectionState);
- mService.removeStateMachine(mDevice);
- }
- }
-
- @Override
- public boolean processMessage(Message message) {
- log("Disconnected (Device: " + mDevice + ") Process Message: " + message.what);
-
- boolean retValue = HANDLED;
- switch (message.what) {
- case CONNECT:
- if (mDevice == null) {
- Log.e(TAG, "State Machine for Null Device Reference, Return.");
- return NOT_HANDLED;
- }
-
- if (!A2dpSinkService.connectA2dpNative(getByteAddress(mDevice))) {
- break;
- }
-
- synchronized (A2dpSinkStateMachine.this) {
- transitionTo(mConnecting);
- }
- // TODO(BT) remove CONNECT_TIMEOUT when the stack
- // sends back events consistently
- sendMessageDelayed(CONNECT_TIMEOUT, 30000);
- break;
- case DISCONNECT:
- // ignore
- break;
- case STACK_EVENT:
- StackEvent event = (StackEvent) message.obj;
- switch (event.type) {
- case EVENT_TYPE_CONNECTION_STATE_CHANGED:
- processConnectionEvent(event.device, event.valueInt);
- break;
- case EVENT_TYPE_AUDIO_CONFIG_CHANGED:
- processAudioConfigEvent(event.device, event.audioConfig);
- break;
- default:
- loge("Unexpected stack event: " + event.type);
- break;
- }
- break;
- default:
- return NOT_HANDLED;
- }
- return retValue;
- }
-
- @Override
- public void exit() {
- log("Exit Disconnected: (" + mDevice + "): " + getCurrentMessage().what);
- mLastConnectionState = BluetoothProfile.STATE_DISCONNECTED;
- }
-
- // in Disconnected state
- private void processConnectionEvent(BluetoothDevice device, int state) {
- switch (state) {
- case CONNECTION_STATE_DISCONNECTED:
- logw("Device: " + device + " already in disconnected state. Ignore ");
- break;
- case CONNECTION_STATE_CONNECTING:
- if (okToConnect(device)) {
- logi("Incoming A2DP accepted");
- synchronized (A2dpSinkStateMachine.this) {
- transitionTo(mConnecting);
- }
- } else {
- //reject the connection and stay in Disconnected state itself
- logi("Incoming A2DP rejected");
- A2dpSinkService.disconnectA2dpNative(getByteAddress(device));
- }
- break;
- case CONNECTION_STATE_CONNECTED:
- logw("A2DP Connected from Disconnected state");
- if (okToConnect(device)) {
- logi("Incoming A2DP accepted");
- synchronized (A2dpSinkStateMachine.this) {
- transitionTo(mConnected);
- }
- } else {
- //reject the connection and stay in Disconnected state itself
- logi("Incoming A2DP rejected");
- A2dpSinkService.disconnectA2dpNative(getByteAddress(device));
- }
- break;
- case CONNECTION_STATE_DISCONNECTING:
- logw("Ignore HF DISCONNECTING event, device: " + device);
- break;
- default:
- loge("Incorrect state: " + state);
- break;
- }
- }
- }
-
- private class Connecting extends State {
- @Override
- public void enter() {
- log("Enter CONNECTING: " + getCurrentMessage().what);
- mConnectionState = BluetoothProfile.STATE_CONNECTING;
- broadcastConnectionState(mDevice, mConnectionState, mLastConnectionState);
- }
-
- @Override
- public void exit() {
- log("Exit CONNECTING: " + getCurrentMessage().what);
- mLastConnectionState = BluetoothProfile.STATE_CONNECTING;
- }
-
- @Override
- public boolean processMessage(Message message) {
- log("SM STATE: CONNECTING (" + mDevice + ") process message: " + message.what);
-
- boolean retValue = HANDLED;
- switch (message.what) {
- case CONNECT:
- logd("Connection is already in pregress.");
- break;
- case CONNECT_TIMEOUT:
- onConnectionStateChanged(getByteAddress(mDevice),
- CONNECTION_STATE_DISCONNECTED);
- break;
- case DISCONNECT:
- if (mDevice == null) {
- Log.e(TAG, "Message received for wrong device.");
- return NOT_HANDLED;
- }
- // cancel connection to the mDevice
- A2dpSinkService.disconnectA2dpNative(getByteAddress(mDevice)); //
- transitionTo(mDisconnected);
- break;
- case STACK_EVENT:
- StackEvent event = (StackEvent) message.obj;
- log("STACK_EVENT " + event.type);
- switch (event.type) {
- case EVENT_TYPE_CONNECTION_STATE_CHANGED:
- removeMessages(CONNECT_TIMEOUT);
- processConnectionEvent(event.device, event.valueInt);
- break;
- case EVENT_TYPE_AUDIO_CONFIG_CHANGED:
- processAudioConfigEvent(event.device, event.audioConfig);
- break;
- default:
- loge("Unexpected stack event: " + event.type);
- break;
- }
- break;
- default:
- return NOT_HANDLED;
- }
- return retValue;
- }
-
- // in Pending state
- private void processConnectionEvent(BluetoothDevice device, int state) {
- log("processConnectionEvent state " + state);
- switch (state) {
- case CONNECTION_STATE_DISCONNECTED:
- // connection failed
- mAudioConfigs.remove(device);
- synchronized (A2dpSinkStateMachine.this) {
- transitionTo(mDisconnected);
- }
- break;
- case CONNECTION_STATE_CONNECTED:
- // Connection completed
- synchronized (A2dpSinkStateMachine.this) {
- transitionTo(mConnected);
- }
- break;
- case CONNECTION_STATE_CONNECTING:
- log("current device tries to connect back. Ignore");
- break;
- case CONNECTION_STATE_DISCONNECTING:
- // remote trying to disconnect
- if (DBG) {
- log("stack is disconnecting mDevice");
- }
- transitionTo(mDisconnecting);
- break;
- default:
- loge("Incorrect state: " + state);
- break;
- }
- }
- }
-
- private class Disconnecting extends State {
- @Override
- public void enter() {
- log("Enter Disconnecting: " + getCurrentMessage().what);
- mConnectionState = BluetoothProfile.STATE_DISCONNECTING;
- broadcastConnectionState(mDevice, mConnectionState, mLastConnectionState);
- }
-
- @Override
- public boolean processMessage(Message message) {
- log("DISCONNECTING (" + mDevice + ") process message: " + message.what);
-
- boolean retValue = HANDLED;
- switch (message.what) {
- case CONNECT:
- logd("Disconnection is in progress. Try again later.");
- break;
- case CONNECT_TIMEOUT:
- onConnectionStateChanged(getByteAddress(mDevice),
- CONNECTION_STATE_DISCONNECTED);
- break;
- case DISCONNECT:
- Log.e(TAG, "Message received for wrong device.");
- break;
-
- case EVENT_RELEASE_FOCUS:
- mStreaming.obtainMessage(A2dpSinkStreamHandler.RELEASE_FOCUS).sendToTarget();
- break;
-
- case STACK_EVENT:
- StackEvent event = (StackEvent) message.obj;
- log("STACK_EVENT " + event.type);
- switch (event.type) {
- case EVENT_TYPE_CONNECTION_STATE_CHANGED:
- processConnectionEvent(event.device, event.valueInt);
- break;
- case EVENT_TYPE_AUDIO_CONFIG_CHANGED:
- processAudioConfigEvent(event.device, event.audioConfig);
- break;
- default:
- loge("Unexpected stack event: " + event.type);
- break;
- }
- break;
- default:
- return NOT_HANDLED;
- }
- return retValue;
- }
-
- // in Pending state
- private void processConnectionEvent(BluetoothDevice device, int state) {
- log("processConnectionEvent state " + state);
- switch (state) {
- case CONNECTION_STATE_DISCONNECTED:
- mAudioConfigs.remove(device);
- synchronized (A2dpSinkStateMachine.this) {
- transitionTo(mDisconnected);
- }
- break;
- case CONNECTION_STATE_CONNECTED:
- // disconnection failed
- synchronized (A2dpSinkStateMachine.this) {
- transitionTo(mConnected);
- }
- break;
- case CONNECTION_STATE_CONNECTING:
- log("Current device tries to connect back.");
- transitionTo(mConnecting);
- break;
- case CONNECTION_STATE_DISCONNECTING:
- // we already broadcasted the intent, doing nothing here
- if (DBG) {
- log("stack is disconnecting mDevice. Ignore.");
- }
- break;
- default:
- loge("Incorrect state: " + state);
- break;
- }
- }
-
- @Override
- public void exit() {
- log("Exit Disconnecting: (" + mDevice + "): " + getCurrentMessage().what);
- mLastConnectionState = BluetoothProfile.STATE_DISCONNECTING;
- }
- }
-
- private class Connected extends State {
- @Override
- public void enter() {
- log("Enter Connected (Device: "+ mDevice + ") " + getCurrentMessage().what);
- // Upon connected, the audio starts out as stopped
- mConnectionState = BluetoothProfile.STATE_CONNECTED;
- broadcastAudioState(mDevice, BluetoothA2dpSink.STATE_NOT_PLAYING,
- BluetoothA2dpSink.STATE_PLAYING);
- broadcastConnectionState(mDevice, mConnectionState, mLastConnectionState);
- synchronized (A2dpSinkStateMachine.this) {
- if (mStreaming == null) {
- if (DBG) {
- log("Creating New A2dpSinkStreamHandler");
- }
- mStreaming = new A2dpSinkStreamHandler(A2dpSinkStateMachine.this,
- mContext, mDevice);
- }
- }
- if (mStreaming.getAudioFocus() == AudioManager.AUDIOFOCUS_NONE) {
- A2dpSinkService.informAudioFocusStateNative(0);
- }
- }
-
- @Override
- public void exit() {
- log("Exit Connected: (" + mDevice + "): " + getCurrentMessage().what);
- mLastConnectionState = BluetoothProfile.STATE_CONNECTED;
- }
-
- @Override
- public boolean processMessage(Message message) {
- log("Connected (Device: "+ mDevice + ") process message: " + message.what);
- if (mDevice == null) {
- loge("ERROR: Current Device is null in Connected");
- return NOT_HANDLED;
- }
-
- switch (message.what) {
- case CONNECT:
- logd("Connect received in Connected State");
- break;
-
- case DISCONNECT: {
- if (!A2dpSinkService.disconnectA2dpNative(getByteAddress(mDevice))) {
- break;
- }
- mStreaming.obtainMessage(A2dpSinkStreamHandler.DISCONNECT).sendToTarget();
- transitionTo(mDisconnecting);
- }
- break;
-
- case STACK_EVENT:
- StackEvent event = (StackEvent) message.obj;
- switch (event.type) {
- case EVENT_TYPE_CONNECTION_STATE_CHANGED:
- processConnectionEvent(event.device, event.valueInt);
- break;
- case EVENT_TYPE_AUDIO_STATE_CHANGED:
- processAudioStateEvent(event.device, event.valueInt);
- break;
- case EVENT_TYPE_AUDIO_CONFIG_CHANGED:
- processAudioConfigEvent(event.device, event.audioConfig);
- break;
- default:
- loge("Unexpected stack event: " + event.type);
- break;
- }
- break;
-
- case EVENT_AVRCP_CT_PLAY:
- mStreaming.obtainMessage(A2dpSinkStreamHandler.SNK_PLAY).sendToTarget();
- break;
-
- case EVENT_AVRCP_TG_PLAY:
- mStreaming.obtainMessage(A2dpSinkStreamHandler.SRC_PLAY).sendToTarget();
- break;
-
- case EVENT_AVRCP_CT_PAUSE:
- mStreaming.obtainMessage(A2dpSinkStreamHandler.SNK_PAUSE).sendToTarget();
- break;
-
- case EVENT_AVRCP_TG_PAUSE:
- mStreaming.obtainMessage(A2dpSinkStreamHandler.SRC_PAUSE).sendToTarget();
- break;
-
- case EVENT_REQUEST_FOCUS:
- mStreaming.obtainMessage(A2dpSinkStreamHandler.REQUEST_FOCUS).sendToTarget();
- break;
-
- case EVENT_RELEASE_FOCUS:
- mStreaming.obtainMessage(A2dpSinkStreamHandler.RELEASE_FOCUS).sendToTarget();
- break;
-
- default:
- return NOT_HANDLED;
- }
- return HANDLED;
- }
-
- // in Connected state
- private void processConnectionEvent(BluetoothDevice device, int state) {
- if (mDevice != null && device != null && !mDevice.equals(device)) {
- Log.e(TAG, "Message received for wrong device.");
- return;
- }
- switch (state) {
- case CONNECTION_STATE_DISCONNECTED:
- mAudioConfigs.remove(device);
- if (mDevice.equals(device)) {
- synchronized (A2dpSinkStateMachine.this) {
- // Take care of existing audio focus in the streaming state machine.
- mStreaming.obtainMessage(A2dpSinkStreamHandler.DISCONNECT)
- .sendToTarget();
- transitionTo(mDisconnected);
- }
- } else {
- loge("Disconnected from unknown device: " + device);
- }
- break;
- default:
- loge("Connection State Device: " + device + " bad state: " + state);
- break;
- }
- }
-
- private void processAudioStateEvent(BluetoothDevice device, int state) {
- if (!mDevice.equals(device)) {
- loge("Audio State Device:" + device + "is different from ConnectedDevice:"
- + mDevice);
- return;
- }
- log(" processAudioStateEvent in state " + state);
- switch (state) {
- case AUDIO_STATE_STARTED:
- mIsPlaying = true;
- mStreaming.obtainMessage(A2dpSinkStreamHandler.SRC_STR_START).sendToTarget();
- broadcastAudioState(device, BluetoothA2dpSink.STATE_PLAYING,
- BluetoothA2dpSink.STATE_NOT_PLAYING);
- break;
- case AUDIO_STATE_REMOTE_SUSPEND:
- case AUDIO_STATE_STOPPED:
- mIsPlaying = false;
- if (mDevice.equals(A2dpSinkService.mStreamingDevice)) {
- mStreaming.obtainMessage(A2dpSinkStreamHandler.SRC_STR_STOP).sendToTarget();
- } else {
- Log.d(TAG, "other than streaming device. Ignore");
- }
- broadcastAudioState(device, BluetoothA2dpSink.STATE_NOT_PLAYING,
- BluetoothA2dpSink.STATE_PLAYING);
- break;
- default:
- loge("Audio State Device: " + device + " bad state: " + state);
- break;
- }
- }
- }
-
- private void processAudioConfigEvent(BluetoothDevice device, BluetoothAudioConfig audioConfig) {
- log("processAudioConfigEvent: " + device);
- mAudioConfigs.put(device, audioConfig);
- broadcastAudioConfig(device, audioConfig);
- }
-
- int getConnectionState() {
- return mConnectionState;
- }
-
- BluetoothAudioConfig getAudioConfig(BluetoothDevice device) {
- return mAudioConfigs.get(device);
- }
-
- List<BluetoothDevice> getConnectedDevices() {
- List<BluetoothDevice> devices = new ArrayList<BluetoothDevice>();
- synchronized (this) {
- if (getCurrentState() == mConnected) {
- devices.add(mDevice);
- }
- }
- return devices;
- }
-
- boolean isPlaying(BluetoothDevice device) {
- synchronized (this) {
- if ((mDevice != null) && (device.equals(mDevice))) {
- return mIsPlaying;
- }
- }
- return false;
- }
-
- boolean isConnected() {
- synchronized (this) {
- return (getCurrentState() == mConnected);
- }
- }
-
- // Utility Functions
- boolean okToConnect(BluetoothDevice device) {
- AdapterService adapterService = AdapterService.getAdapterService();
- int priority = mService.getPriority(device);
-
- // check priority and accept or reject the connection. if priority 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))) {
- return true;
- }
- logw("okToConnect not OK to connect " + device);
- return false;
- }
-
- private void broadcastConnectionState(BluetoothDevice device, int newState, int prevState) {
- if (prevState != newState && newState == BluetoothProfile.STATE_CONNECTED) {
- MetricsLogger.logProfileConnectionEvent(BluetoothMetricsProto.ProfileId.A2DP_SINK);
- }
- Intent intent = new Intent(BluetoothA2dpSink.ACTION_CONNECTION_STATE_CHANGED);
- intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState);
- intent.putExtra(BluetoothProfile.EXTRA_STATE, newState);
- intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
-//FIXME intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
- mContext.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
- Log.d(TAG, "Connection state " + device + ": " + prevState + "->" + newState);
- }
-
- private void broadcastAudioState(BluetoothDevice device, int state, int prevState) {
- Intent intent = new Intent(BluetoothA2dpSink.ACTION_PLAYING_STATE_CHANGED);
- intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
- intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState);
- intent.putExtra(BluetoothProfile.EXTRA_STATE, state);
-//FIXME intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
- mContext.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
-
- log("A2DP Playing state : device: " + device + " State:" + prevState + "->" + state);
- }
-
- private void broadcastAudioConfig(BluetoothDevice device, BluetoothAudioConfig audioConfig) {
- Intent intent = new Intent(BluetoothA2dpSink.ACTION_AUDIO_CONFIG_CHANGED);
- intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
- intent.putExtra(BluetoothA2dpSink.EXTRA_AUDIO_CONFIG, audioConfig);
-//FIXME intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
- mContext.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
-
- log("A2DP Audio Config : device: " + device + " config: " + audioConfig);
- }
-
- private byte[] getByteAddress(BluetoothDevice device) {
- return Utils.getBytesFromAddress(device.getAddress());
- }
-
- private void onConnectionStateChanged(byte[] address, int state) {
- StackEvent event = new StackEvent(EVENT_TYPE_CONNECTION_STATE_CHANGED);
- event.device = getDevice(address);
- event.valueInt = state;
- sendMessage(STACK_EVENT, event);
- }
-
- private void onAudioStateChanged(byte[] address, int state) {
- StackEvent event = new StackEvent(EVENT_TYPE_AUDIO_STATE_CHANGED);
- event.device = getDevice(address);
- event.valueInt = state;
- sendMessage(STACK_EVENT, event);
- }
-
- private void onAudioConfigChanged(byte[] address, int sampleRate, int channelCount) {
- StackEvent event = new StackEvent(EVENT_TYPE_AUDIO_CONFIG_CHANGED);
- event.device = getDevice(address);
- int channelConfig =
- (channelCount == 1 ? AudioFormat.CHANNEL_IN_MONO : AudioFormat.CHANNEL_IN_STEREO);
- event.audioConfig =
- new BluetoothAudioConfig(sampleRate, channelConfig, AudioFormat.ENCODING_PCM_16BIT);
- sendMessage(STACK_EVENT, event);
- }
-
- private BluetoothDevice getDevice(byte[] address) {
- return mAdapter.getRemoteDevice(Utils.getAddressStringFromByte(address));
- }
-
- public class StackEvent {
- public int type = EVENT_TYPE_NONE;
- public BluetoothDevice device = null;
- public int valueInt = 0;
- public BluetoothAudioConfig audioConfig = null;
-
- public StackEvent(int type) {
- this.type = type;
- }
- }
-
- public boolean sendPassThruPlay(BluetoothDevice mDevice) {
- log("sendPassThruPlay + ");
- AvrcpControllerService avrcpCtrlService =
- AvrcpControllerService.getAvrcpControllerService();
- if ((avrcpCtrlService != null) && (mDevice != null)
- && (avrcpCtrlService.getConnectedDevices().contains(mDevice))) {
- avrcpCtrlService.sendPassThroughCmd(mDevice,
- AvrcpControllerService.PASS_THRU_CMD_ID_PLAY,
- AvrcpControllerService.KEY_STATE_PRESSED);
- avrcpCtrlService.sendPassThroughCmd(mDevice,
- AvrcpControllerService.PASS_THRU_CMD_ID_PLAY,
- AvrcpControllerService.KEY_STATE_RELEASED);
- log(" sendPassThruPlay command sent - ");
- return true;
- } else {
- log("passthru command not sent, connection unavailable");
- return false;
- }
- }
-
- BluetoothDevice getDevice() {
+ /**
+ * Get the underlying device tracked by this state machine
+ *
+ * @return device in focus
+ */
+ public synchronized BluetoothDevice getDevice() {
return mDevice;
}
- // Event types for STACK_EVENT message
- protected static final int EVENT_TYPE_NONE = 0;
- protected static final int EVENT_TYPE_CONNECTION_STATE_CHANGED = 1;
- protected static final int EVENT_TYPE_AUDIO_STATE_CHANGED = 2;
- protected static final int EVENT_TYPE_AUDIO_CONFIG_CHANGED = 3;
+ /**
+ * send the Connect command asynchronously
+ */
+ public final void connect() {
+ sendMessage(CONNECT);
+ }
- // Do not modify without updating the HAL bt_av.h files.
+ /**
+ * send the Disconnect command asynchronously
+ */
+ public final void disconnect() {
+ sendMessage(DISCONNECT);
+ }
- // match up with btav_connection_state_t enum of bt_av.h
- static final int CONNECTION_STATE_DISCONNECTED = 0;
- static final int CONNECTION_STATE_CONNECTING = 1;
- static final int CONNECTION_STATE_CONNECTED = 2;
- static final int CONNECTION_STATE_DISCONNECTING = 3;
+ /**
+ * Dump the current State Machine to the string builder.
+ * @param sb output string
+ */
+ public void dump(StringBuilder sb) {
+ ProfileService.println(sb, "mDevice: " + mDevice.getAddress() + "("
+ + mDevice.getName() + ") " + this.toString());
+ }
- // match up with btav_audio_state_t enum of bt_av.h
- static final int AUDIO_STATE_REMOTE_SUSPEND = 0;
- static final int AUDIO_STATE_STOPPED = 1;
- static final int AUDIO_STATE_STARTED = 2;
+ @Override
+ protected void unhandledMessage(Message msg) {
+ Log.w(TAG, "unhandledMessage in state " + getCurrentState() + "msg.what=" + msg.what);
+ }
+ class Disconnected extends State {
+ @Override
+ public void enter() {
+ if (DBG) Log.d(TAG, "Enter Disconnected");
+ if (mMostRecentState != BluetoothProfile.STATE_DISCONNECTED) {
+ sendMessage(CLEANUP);
+ }
+ onConnectionStateChanged(BluetoothProfile.STATE_DISCONNECTED);
+ }
+
+ @Override
+ public boolean processMessage(Message message) {
+ switch (message.what) {
+ case STACK_EVENT:
+ processStackEvent((StackEvent) message.obj);
+ return true;
+ case CONNECT:
+ if (DBG) Log.d(TAG, "Connect");
+ transitionTo(mConnecting);
+ return true;
+ case CLEANUP:
+ mService.removeStateMachine(A2dpSinkStateMachine.this);
+ return true;
+ }
+ return false;
+ }
+
+ void processStackEvent(StackEvent event) {
+ switch (event.mType) {
+ case StackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED:
+ switch (event.mState) {
+ case StackEvent.CONNECTION_STATE_CONNECTED:
+ transitionTo(mConnected);
+ break;
+ case StackEvent.CONNECTION_STATE_DISCONNECTED:
+ sendMessage(CLEANUP);
+ break;
+ }
+ }
+ }
+ }
+
+ class Connecting extends State {
+ boolean mIncommingConnection = false;
+
+ @Override
+ public void enter() {
+ if (DBG) Log.d(TAG, "Enter Connecting");
+ onConnectionStateChanged(BluetoothProfile.STATE_CONNECTING);
+ sendMessageDelayed(CONNECT_TIMEOUT, CONNECT_TIMEOUT_MS);
+
+ if (!mIncommingConnection) {
+ mService.connectA2dpNative(mDeviceAddress);
+ }
+
+ super.enter();
+ }
+
+ @Override
+ public boolean processMessage(Message message) {
+ switch (message.what) {
+ case STACK_EVENT:
+ processStackEvent((StackEvent) message.obj);
+ return true;
+ case CONNECT_TIMEOUT:
+ transitionTo(mDisconnected);
+ return true;
+ }
+ return false;
+ }
+
+ void processStackEvent(StackEvent event) {
+ switch (event.mType) {
+ case StackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED:
+ switch (event.mState) {
+ case StackEvent.CONNECTION_STATE_CONNECTED:
+ transitionTo(mConnected);
+ break;
+ case StackEvent.CONNECTION_STATE_DISCONNECTED:
+ transitionTo(mDisconnected);
+ break;
+ }
+ }
+ }
+ @Override
+ public void exit() {
+ removeMessages(CONNECT_TIMEOUT);
+ }
+
+ }
+
+ class Connected extends State {
+ @Override
+ public void enter() {
+ if (DBG) Log.d(TAG, "Enter Connected");
+ onConnectionStateChanged(BluetoothProfile.STATE_CONNECTED);
+ }
+
+ @Override
+ public boolean processMessage(Message message) {
+ switch (message.what) {
+ case DISCONNECT:
+ transitionTo(mDisconnecting);
+ mService.disconnectA2dpNative(mDeviceAddress);
+ return true;
+ case STACK_EVENT:
+ processStackEvent((StackEvent) message.obj);
+ return true;
+ }
+ return false;
+ }
+
+ void processStackEvent(StackEvent event) {
+ switch (event.mType) {
+ case StackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED:
+ switch (event.mState) {
+ case StackEvent.CONNECTION_STATE_DISCONNECTING:
+ transitionTo(mDisconnecting);
+ break;
+ case StackEvent.CONNECTION_STATE_DISCONNECTED:
+ transitionTo(mDisconnected);
+ break;
+ }
+ break;
+ case StackEvent.EVENT_TYPE_AUDIO_CONFIG_CHANGED:
+ mAudioConfig = new BluetoothAudioConfig(event.mSampleRate, event.mChannelCount,
+ AudioFormat.ENCODING_PCM_16BIT);
+ break;
+ }
+ }
+ }
+
+ protected class Disconnecting extends State {
+ @Override
+ public void enter() {
+ if (DBG) Log.d(TAG, "Enter Disconnecting");
+ onConnectionStateChanged(BluetoothProfile.STATE_DISCONNECTING);
+ transitionTo(mDisconnected);
+ }
+ }
+
+ protected void onConnectionStateChanged(int currentState) {
+ if (mMostRecentState == currentState) {
+ return;
+ }
+ if (currentState == BluetoothProfile.STATE_CONNECTED) {
+ MetricsLogger.logProfileConnectionEvent(BluetoothMetricsProto.ProfileId.A2DP_SINK);
+ }
+ if (DBG) {
+ Log.d(TAG, "Connection state " + mDevice + ": " + mMostRecentState + "->"
+ + currentState);
+ }
+ Intent intent = new Intent(BluetoothA2dpSink.ACTION_CONNECTION_STATE_CHANGED);
+ intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, mMostRecentState);
+ intent.putExtra(BluetoothProfile.EXTRA_STATE, currentState);
+ intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice);
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+ mMostRecentState = currentState;
+ mService.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
+ }
}
diff --git a/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandler.java b/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandler.java
index 571d5d6..daa5312 100644
--- a/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandler.java
+++ b/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandler.java
@@ -23,13 +23,14 @@
import android.media.AudioFocusRequest;
import android.media.AudioManager;
import android.media.AudioManager.OnAudioFocusChangeListener;
+import android.media.MediaPlayer;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import com.android.bluetooth.R;
-import com.android.bluetooth.avrcpcontroller.AvrcpControllerService;
-import com.android.bluetooth.avrcpcontroller.CoverArtUtils;
+import com.android.bluetooth.avrcpcontroller.BluetoothMediaBrowserService;
+import com.android.bluetooth.hfpclient.HeadsetClientService;
import java.util.List;
@@ -52,12 +53,12 @@
* restored.
*/
public class A2dpSinkStreamHandler extends Handler {
- private static final boolean DBG = true;
private static final String TAG = "A2dpSinkStreamHandler";
+ private static final boolean DBG = true;
// Configuration Variables
private static final int DEFAULT_DUCK_PERCENT = 25;
- private static final int SETTLE_TIMEOUT = 1000;
+ private static final int SETTLE_TIMEOUT = 400;
// Incoming events.
public static final int SRC_STR_START = 0; // Audio stream from remote device started
@@ -69,8 +70,8 @@
public static final int DISCONNECT = 6; // Remote device was disconnected
public static final int AUDIO_FOCUS_CHANGE = 7; // Audio focus callback with associated change
public static final int REQUEST_FOCUS = 8; // Request focus when the media service is active
- public static final int DELAYED_RESUME = 9; // If a call just ended allow stack time to settle
- public static final int RELEASE_FOCUS = 10; // Release focus when requested
+ public static final int DELAYED_PAUSE = 9; // If a call just started allow stack time to settle
+ public static final int RELEASE_FOCUS = 10;
// Used to indicate focus lost
private static final int STATE_FOCUS_LOST = 0;
@@ -78,7 +79,7 @@
private static final int STATE_FOCUS_GRANTED = 1;
// Private variables.
- private A2dpSinkStateMachine mA2dpSinkSm;
+ private A2dpSinkService mA2dpSinkService;
private Context mContext;
private AudioManager mAudioManager;
// Keep track if the remote device is providing audio
@@ -86,7 +87,17 @@
private boolean mSentPause = false;
// Keep track of the relevant audio focus (None, Transient, Gain)
private int mAudioFocus = AudioManager.AUDIOFOCUS_NONE;
- private BluetoothDevice mDevice;
+
+ // In order for Bluetooth to be considered as an audio source capable of receiving media key
+ // events (In the eyes of MediaSessionService), we need an active MediaPlayer in addition to a
+ // MediaSession. Because of this, the media player below plays an incredibly short, silent audio
+ // sample so that MediaSessionService and AudioPlaybackStateMonitor will believe that we're the
+ // current active player and send the Bluetooth process media events. This allows AVRCP
+ // controller to create a MediaSession and handle the events if it would like. The player and
+ // session requirement is a restriction currently imposed by the media framework code and could
+ // be reconsidered in the future.
+ private MediaPlayer mMediaPlayer = null;
+
// Focus changes when we are currently holding focus.
private OnAudioFocusChangeListener mAudioFocusListener = new OnAudioFocusChangeListener() {
@Override
@@ -99,80 +110,82 @@
}
};
- public A2dpSinkStreamHandler(A2dpSinkStateMachine a2dpSinkSm, Context context,
- BluetoothDevice device) {
- mA2dpSinkSm = a2dpSinkSm;
+ public A2dpSinkStreamHandler(A2dpSinkService a2dpSinkService, Context context) {
+ mA2dpSinkService = a2dpSinkService;
mContext = context;
- mDevice = device;
mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
}
+ void requestAudioFocus(boolean request) {
+ obtainMessage(REQUEST_FOCUS, request).sendToTarget();
+ }
+
+ int getFocusState() {
+ return mAudioFocus;
+ }
+
+ boolean isPlaying() {
+ return (mStreamAvailable
+ && (mAudioFocus == AudioManager.AUDIOFOCUS_GAIN
+ || mAudioFocus == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK));
+ }
+
@Override
public void handleMessage(Message message) {
if (DBG) {
- Log.d(TAG, " process message: " + message.what + ", device:" + mDevice);
+ Log.d(TAG, " process message: " + message.what);
+ Log.d(TAG, " audioFocus = " + mAudioFocus);
}
-
switch (message.what) {
case SRC_STR_START:
mStreamAvailable = true;
- // Always request audio focus if on TV.
- if (isTvDevice()) {
- if (mAudioFocus == AudioManager.AUDIOFOCUS_NONE) {
- requestAudioFocus();
- }
+ Log.d(TAG, " isTvDevice = " + isTvDevice() +
+ "shouldRequestFocus = " + shouldRequestFocus());
+ if (isTvDevice() || shouldRequestFocus()) {
+ requestAudioFocusIfNone();
}
+
// Audio stream has started, stop it if we don't have focus.
if (mAudioFocus == AudioManager.AUDIOFOCUS_NONE) {
requestAudioFocus();
}
- startAvrcpUpdates();
break;
case SRC_STR_STOP:
// Audio stream has stopped, maintain focus but stop avrcp updates.
- mStreamAvailable = false;
- stopAvrcpUpdates();
break;
case SNK_PLAY:
// Local play command, gain focus and start avrcp updates.
- if (mAudioFocus == AudioManager.AUDIOFOCUS_NONE) {
- requestAudioFocus();
- }
- startAvrcpUpdates();
+ requestAudioFocusIfNone();
break;
case SNK_PAUSE:
+ mStreamAvailable = false;
// Local pause command, maintain focus but stop avrcp updates.
- stopAvrcpUpdates();
break;
case SRC_PLAY:
+ mStreamAvailable = true;
// Remote play command.
- // If is an iot device gain focus and start avrcp updates.
- if (isIotDevice() || isTvDevice()) {
- if (mAudioFocus == AudioManager.AUDIOFOCUS_NONE) {
- requestAudioFocus();
- }
- startAvrcpUpdates();
+ if (isIotDevice() || isTvDevice() || shouldRequestFocus()) {
+ requestAudioFocusIfNone();
break;
}
+
+ // Audio stream has started, stop it if we don't have focus.
if (mAudioFocus == AudioManager.AUDIOFOCUS_NONE) {
requestAudioFocus();
}
- startAvrcpUpdates();
break;
case SRC_PAUSE:
+ mStreamAvailable = false;
// Remote pause command, stop avrcp updates.
- stopAvrcpUpdates();
break;
case REQUEST_FOCUS:
- if (mAudioFocus == AudioManager.AUDIOFOCUS_NONE) {
- requestAudioFocus();
- }
+ requestAudioFocusIfNone();
break;
case RELEASE_FOCUS:
@@ -181,7 +194,6 @@
case DISCONNECT:
// Remote device has disconnected, restore everything to default state.
- stopAvrcpUpdates();
mSentPause = false;
break;
@@ -189,11 +201,12 @@
// message.obj is the newly granted audio focus.
switch ((int) message.obj) {
case AudioManager.AUDIOFOCUS_GAIN:
+ removeMessages(DELAYED_PAUSE);
// Begin playing audio, if we paused the remote, send a play now.
- startAvrcpUpdates();
startFluorideStreaming();
if (mSentPause) {
- sendMessageDelayed(obtainMessage(DELAYED_RESUME), SETTLE_TIMEOUT);
+ sendAvrcpPlay();
+ mSentPause = false;
}
break;
@@ -215,11 +228,8 @@
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
// Temporary loss of focus, if we are actively streaming pause the remote
// and make sure we resume playback when we regain focus.
- if (mStreamAvailable) {
- sendAvrcpPause();
- mSentPause = true;
- }
- stopFluorideStreaming();
+ sendMessageDelayed(obtainMessage(DELAYED_PAUSE), SETTLE_TIMEOUT);
+ setFluorideAudioTrackGain(0);
break;
case AudioManager.AUDIOFOCUS_LOSS:
@@ -231,13 +241,14 @@
}
break;
- case DELAYED_RESUME:
- // Resume playback after source and sink states settle.
- sendAvrcpPlay();
- mSentPause = false;
+ case DELAYED_PAUSE:
+ if (mStreamAvailable && !inCallFromStreamingDevice()) {
+ sendAvrcpPause();
+ mSentPause = true;
+ mStreamAvailable = false;
+ }
break;
-
default:
Log.w(TAG, "Received unexpected event: " + message.what);
}
@@ -246,10 +257,21 @@
/**
* Utility functions.
*/
+ private void requestAudioFocusIfNone() {
+ if (DBG) Log.d(TAG, "requestAudioFocusIfNone()");
+ if (mAudioFocus == AudioManager.AUDIOFOCUS_NONE) {
+ Log.d(TAG, " mAudioFocus = " + mAudioFocus);
+ 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() {
- Log.d(TAG, "requestAudioFocus");
// Bluetooth A2DP may carry Music, Audio Books, Navigation, or other sounds so mark content
// type unknown.
+ Log.d(TAG, " requestAudioFocus() ");
AudioAttributes streamAttributes =
new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_MEDIA)
.setContentType(AudioAttributes.CONTENT_TYPE_UNKNOWN)
@@ -263,120 +285,116 @@
.setOnAudioFocusChangeListener(mAudioFocusListener, this)
.build();
int focusRequestStatus = mAudioManager.requestAudioFocus(focusRequest);
- Log.d(TAG, "focusRequestStatus = " + focusRequestStatus);
+ Log.d(TAG, " focusRequestStatus = " + focusRequestStatus +
+ " focusRequest: " + focusRequest);
// If the request is granted begin streaming immediately and schedule an upgrade.
if (focusRequestStatus == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
- startAvrcpUpdates();
startFluorideStreaming();
mAudioFocus = AudioManager.AUDIOFOCUS_GAIN;
}
return focusRequestStatus;
}
+ /**
+ * Creates a MediaPlayer that plays a silent audio sample so that MediaSessionService will be
+ * aware of the fact that Bluetooth is playing audio.
+ *
+ * This allows the MediaSession in AVRCP Controller to be routed media key events, if we've
+ * chosen to use it.
+ */
+ 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;
+ }
+
+ 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);
+ }
private synchronized void abandonAudioFocus() {
stopFluorideStreaming();
if (mAudioFocus != AudioManager.AUDIOFOCUS_NONE) {
Log.d(TAG, "abandoning audio focus");
- mAudioManager.abandonAudioFocus(mAudioFocusListener);
+ AudioAttributes streamAttributes =
+ new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_MEDIA)
+ .setContentType(AudioAttributes.CONTENT_TYPE_UNKNOWN)
+ .build();
+ AudioFocusRequest mfocusRequest =
+ new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN).setAudioAttributes(
+ streamAttributes)
+ .setWillPauseWhenDucked(true)
+ .setOnAudioFocusChangeListener(mAudioFocusListener, this)
+ .build();
+ mAudioManager.abandonAudioFocusRequest(mfocusRequest);
mAudioFocus = AudioManager.AUDIOFOCUS_NONE;
}
}
+ /**
+ * Destroys the silent audio sample MediaPlayer, notifying MediaSessionService of the fact
+ * we're no longer playing audio.
+ */
+ private synchronized void releaseMediaKeyFocus() {
+ if (DBG) Log.d(TAG, "releaseMediaKeyFocus()");
+ if (mMediaPlayer == null) {
+ return;
+ }
+ BluetoothMediaBrowserService.setActive(false);
+ mMediaPlayer.stop();
+ mMediaPlayer.release();
+ mMediaPlayer = null;
+ }
+
private void startFluorideStreaming() {
- A2dpSinkService.informAudioFocusStateNative(STATE_FOCUS_GRANTED);
- A2dpSinkService.informAudioTrackGainNative(1.0f);
+ mA2dpSinkService.informAudioFocusStateNative(STATE_FOCUS_GRANTED);
+ mA2dpSinkService.informAudioTrackGainNative(1.0f);
}
private void stopFluorideStreaming() {
- A2dpSinkService.informAudioFocusStateNative(STATE_FOCUS_LOST);
+ mA2dpSinkService.informAudioFocusStateNative(STATE_FOCUS_LOST);
}
private void setFluorideAudioTrackGain(float gain) {
- A2dpSinkService.informAudioTrackGainNative(gain);
- }
-
- private void startAvrcpUpdates() {
- // Since AVRCP gets started after A2DP we may need to request it later in cycle.
- AvrcpControllerService avrcpService = AvrcpControllerService.getAvrcpControllerService();
-
- if (DBG) {
- Log.d(TAG, "startAvrcpUpdates");
- }
- if (avrcpService != null && avrcpService.getConnectedDevices().size() >= 1) {
- avrcpService.startAvrcpUpdates(mDevice);
- } else {
- Log.e(TAG, "startAvrcpUpdates failed because of connection.");
- }
- }
-
- private void stopAvrcpUpdates() {
- // Since AVRCP gets started after A2DP we may need to request it later in cycle.
- AvrcpControllerService avrcpService = AvrcpControllerService.getAvrcpControllerService();
-
- if (DBG) {
- Log.d(TAG, "stopAvrcpUpdates");
- }
- if (avrcpService != null && avrcpService.getConnectedDevices().size() >= 1) {
- avrcpService.stopAvrcpUpdates(mDevice);
- } else {
- Log.e(TAG, "stopAvrcpUpdates failed because of connection.");
- }
-
- CoverArtUtils coverArt = new CoverArtUtils();
- coverArt.broadcastInValidHandle(mContext ,avrcpService, mStreamAvailable);
+ mA2dpSinkService.informAudioTrackGainNative(gain);
}
private void sendAvrcpPause() {
- // Since AVRCP gets started after A2DP we may need to request it later in cycle.
- AvrcpControllerService avrcpService = AvrcpControllerService.getAvrcpControllerService();
-
- if (DBG) {
- Log.d(TAG, "sendAvrcpPause");
- }
- if (avrcpService != null) {
- List<BluetoothDevice> connectedDevices = avrcpService.getConnectedDevices();
- if (!connectedDevices.isEmpty()) {
- BluetoothDevice targetDevice = connectedDevices.get(0);
- if (DBG) {
- Log.d(TAG, "Pausing AVRCP.");
- }
- avrcpService.sendPassThroughCmd(targetDevice,
- AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE,
- AvrcpControllerService.KEY_STATE_PRESSED);
- avrcpService.sendPassThroughCmd(targetDevice,
- AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE,
- AvrcpControllerService.KEY_STATE_RELEASED);
- }
- } else {
- Log.e(TAG, "Passthrough not sent, connection un-available.");
- }
+ BluetoothMediaBrowserService.pause();
}
private void sendAvrcpPlay() {
- // Since AVRCP gets started after A2DP we may need to request it later in cycle.
- AvrcpControllerService avrcpService = AvrcpControllerService.getAvrcpControllerService();
+ BluetoothMediaBrowserService.play();
+ }
- if (DBG) {
- Log.d(TAG, "sendAvrcpPlay");
+ private boolean inCallFromStreamingDevice() {
+ BluetoothDevice targetDevice = null;
+ List<BluetoothDevice> connectedDevices = mA2dpSinkService.getConnectedDevices();
+ if (!connectedDevices.isEmpty()) {
+ targetDevice = connectedDevices.get(0);
}
- if (avrcpService != null) {
- List<BluetoothDevice> connectedDevices = avrcpService.getConnectedDevices();
- if (!connectedDevices.isEmpty()) {
- BluetoothDevice targetDevice = connectedDevices.get(0);
- if (DBG) {
- Log.d(TAG, "Playing AVRCP.");
- }
- avrcpService.sendPassThroughCmd(targetDevice,
- AvrcpControllerService.PASS_THRU_CMD_ID_PLAY,
- AvrcpControllerService.KEY_STATE_PRESSED);
- avrcpService.sendPassThroughCmd(targetDevice,
- AvrcpControllerService.PASS_THRU_CMD_ID_PLAY,
- AvrcpControllerService.KEY_STATE_RELEASED);
- }
- } else {
- Log.e(TAG, "Passthrough not sent, connection un-available.");
+ HeadsetClientService headsetClientService = HeadsetClientService.getHeadsetClientService();
+ if (targetDevice != null && headsetClientService != null) {
+ return headsetClientService.getCurrentCalls(targetDevice).size() > 0;
}
+ return false;
}
synchronized int getAudioFocus() {
@@ -391,4 +409,9 @@
return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK);
}
+ private boolean shouldRequestFocus() {
+ return mContext.getResources()
+ .getBoolean(R.bool.a2dp_sink_automatically_request_audio_focus);
+ }
+
}
diff --git a/src/com/android/bluetooth/a2dpsink/StackEvent.java b/src/com/android/bluetooth/a2dpsink/StackEvent.java
new file mode 100644
index 0000000..68a6ce9
--- /dev/null
+++ b/src/com/android/bluetooth/a2dpsink/StackEvent.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2018 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.a2dpsink;
+
+import android.bluetooth.BluetoothDevice;
+
+final class StackEvent {
+ // Event types for STACK_EVENT message
+ static final int EVENT_TYPE_NONE = 0;
+ static final int EVENT_TYPE_CONNECTION_STATE_CHANGED = 1;
+ static final int EVENT_TYPE_AUDIO_STATE_CHANGED = 2;
+ static final int EVENT_TYPE_AUDIO_CONFIG_CHANGED = 3;
+
+ // match up with btav_connection_state_t enum of bt_av.h
+ static final int CONNECTION_STATE_DISCONNECTED = 0;
+ static final int CONNECTION_STATE_CONNECTING = 1;
+ static final int CONNECTION_STATE_CONNECTED = 2;
+ static final int CONNECTION_STATE_DISCONNECTING = 3;
+
+ // match up with btav_audio_state_t enum of bt_av.h
+ static final int AUDIO_STATE_REMOTE_SUSPEND = 0;
+ static final int AUDIO_STATE_STOPPED = 1;
+ static final int AUDIO_STATE_STARTED = 2;
+
+ int mType = EVENT_TYPE_NONE;
+ BluetoothDevice mDevice = null;
+ int mState = 0;
+ int mSampleRate = 0;
+ int mChannelCount = 0;
+
+ private StackEvent(int type) {
+ this.mType = type;
+ }
+
+ @Override
+ public String toString() {
+ switch (mType) {
+ case EVENT_TYPE_CONNECTION_STATE_CHANGED:
+ return "EVENT_TYPE_CONNECTION_STATE_CHANGED " + mState;
+ case EVENT_TYPE_AUDIO_STATE_CHANGED:
+ return "EVENT_TYPE_AUDIO_STATE_CHANGED " + mState;
+ case EVENT_TYPE_AUDIO_CONFIG_CHANGED:
+ return "EVENT_TYPE_AUDIO_CONFIG_CHANGED " + mSampleRate + ":" + mChannelCount;
+ default:
+ return "Unknown";
+ }
+ }
+
+ static StackEvent connectionStateChanged(BluetoothDevice device, int state) {
+ StackEvent event = new StackEvent(StackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
+ event.mDevice = device;
+ event.mState = state;
+ return event;
+ }
+
+ static StackEvent audioStateChanged(BluetoothDevice device, int state) {
+ StackEvent event = new StackEvent(StackEvent.EVENT_TYPE_AUDIO_STATE_CHANGED);
+ event.mDevice = device;
+ event.mState = state;
+ return event;
+ }
+
+ static StackEvent audioConfigChanged(BluetoothDevice device, int sampleRate,
+ int channelCount) {
+ StackEvent event = new StackEvent(StackEvent.EVENT_TYPE_AUDIO_CONFIG_CHANGED);
+ event.mDevice = device;
+ event.mSampleRate = sampleRate;
+ event.mChannelCount = channelCount;
+ return event;
+ }
+}
diff --git a/src/com/android/bluetooth/a2dpsink/mbs/A2dpMediaBrowserService.java b/src/com/android/bluetooth/a2dpsink/mbs/A2dpMediaBrowserService.java
deleted file mode 100644
index 8e072ba..0000000
--- a/src/com/android/bluetooth/a2dpsink/mbs/A2dpMediaBrowserService.java
+++ /dev/null
@@ -1,586 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.bluetooth.a2dpsink.mbs;
-
-import android.bluetooth.BluetoothAvrcpController;
-import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothProfile;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.media.MediaMetadata;
-import android.media.browse.MediaBrowser;
-import android.media.browse.MediaBrowser.MediaItem;
-import android.media.session.MediaController;
-import android.media.session.MediaSession;
-import android.media.session.PlaybackState;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.os.Parcelable;
-import android.service.media.MediaBrowserService;
-import android.util.Log;
-import android.util.Pair;
-
-import com.android.bluetooth.R;
-import com.android.bluetooth.a2dpsink.A2dpSinkService;
-import com.android.bluetooth.avrcpcontroller.AvrcpControllerService;
-import com.android.bluetooth.avrcpcontroller.BrowseTree;
-
-import java.lang.ref.WeakReference;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-/**
- * Implements the MediaBrowserService interface to AVRCP and A2DP
- *
- * This service provides a means for external applications to access A2DP and AVRCP.
- * The applications are expected to use MediaBrowser (see API) and all the music
- * browsing/playback/metadata can be controlled via MediaBrowser and MediaController.
- *
- * The current behavior of MediaSession exposed by this service is as follows:
- * 1. MediaSession is active (i.e. SystemUI and other overview UIs can see updates) when device is
- * connected and first starts playing. Before it starts playing we do not active the session.
- * 1.1 The session is active throughout the duration of connection.
- * 2. The session is de-activated when the device disconnects. It will be connected again when (1)
- * happens.
- */
-public class A2dpMediaBrowserService extends MediaBrowserService {
- private static final String TAG = "A2dpMediaBrowserService";
- private static final boolean DBG = false;
- private static final boolean VDBG = false;
-
- private static final String UNKNOWN_BT_AUDIO = "__UNKNOWN_BT_AUDIO__";
- private static final float PLAYBACK_SPEED = 1.0f;
-
- // Message sent when A2DP device is disconnected.
- private static final int MSG_DEVICE_DISCONNECT = 0;
- // Message sent when A2DP device is connected.
- private static final int MSG_DEVICE_CONNECT = 2;
- // Message sent when we recieve a TRACK update from AVRCP profile over a connected A2DP device.
- private static final int MSG_TRACK = 4;
- // Internal message sent to trigger a AVRCP action.
- private static final int MSG_AVRCP_PASSTHRU = 5;
- // Internal message to trigger a getplaystatus command to remote.
- private static final int MSG_AVRCP_GET_PLAY_STATUS_NATIVE = 6;
- // Message sent when AVRCP browse is connected.
- private static final int MSG_DEVICE_BROWSE_CONNECT = 7;
- // Message sent when AVRCP browse is disconnected.
- private static final int MSG_DEVICE_BROWSE_DISCONNECT = 8;
- // Message sent when folder list is fetched.
- private static final int MSG_FOLDER_LIST = 9;
- // Message sent when streaming device is updated after soft-handoff
- private static final int MSG_DEVICE_UPDATED = 10;
-
- // Custom actions for PTS testing.
- private static final String CUSTOM_ACTION_VOL_UP =
- "com.android.bluetooth.a2dpsink.mbs.CUSTOM_ACTION_VOL_UP";
- private static final String CUSTOM_ACTION_VOL_DN =
- "com.android.bluetooth.a2dpsink.mbs.CUSTOM_ACTION_VOL_DN";
- private static final String CUSTOM_ACTION_GET_PLAY_STATUS_NATIVE =
- "com.android.bluetooth.a2dpsink.mbs.CUSTOM_ACTION_GET_PLAY_STATUS_NATIVE";
-
- private MediaSession mSession;
- private MediaMetadata mA2dpMetadata;
-
- private AvrcpControllerService mAvrcpCtrlSrvc;
- private boolean mBrowseConnected = false;
- private BluetoothDevice mA2dpDevice = null;
- private A2dpSinkService mA2dpSinkService = null;
- private Handler mAvrcpCommandQueue;
- private final Map<String, Result<List<MediaItem>>> mParentIdToRequestMap = new HashMap<>();
-
- // Browsing related structures.
- private List<MediaItem> mNowPlayingList = null;
-
- private long mTransportControlFlags = PlaybackState.ACTION_PAUSE | PlaybackState.ACTION_PLAY
- | PlaybackState.ACTION_SKIP_TO_NEXT | PlaybackState.ACTION_SKIP_TO_PREVIOUS;
-
- public static final String ACTION_DEVICE_UPDATED =
- "android.bluetooth.a2dp.mbs.action.DeviceUpdated";
-
- private static final class AvrcpCommandQueueHandler extends Handler {
- WeakReference<A2dpMediaBrowserService> mInst;
-
- AvrcpCommandQueueHandler(Looper looper, A2dpMediaBrowserService sink) {
- super(looper);
- mInst = new WeakReference<A2dpMediaBrowserService>(sink);
- }
-
- @Override
- public void handleMessage(Message msg) {
- A2dpMediaBrowserService inst = mInst.get();
- if (inst == null) {
- Log.e(TAG, "Parent class has died; aborting.");
- return;
- }
-
- switch (msg.what) {
- case MSG_DEVICE_CONNECT:
- inst.msgDeviceConnect((BluetoothDevice) msg.obj);
- break;
- case MSG_DEVICE_DISCONNECT:
- inst.msgDeviceDisconnect((BluetoothDevice) msg.obj);
- break;
- case MSG_TRACK:
- Pair<PlaybackState, MediaMetadata> pair =
- (Pair<PlaybackState, MediaMetadata>) (msg.obj);
- inst.msgTrack(pair.first, pair.second);
- break;
- case MSG_AVRCP_PASSTHRU:
- inst.msgPassThru((int) msg.obj);
- break;
- case MSG_AVRCP_GET_PLAY_STATUS_NATIVE:
- inst.msgGetPlayStatusNative();
- break;
- case MSG_DEVICE_BROWSE_CONNECT:
- inst.msgDeviceBrowseConnect((BluetoothDevice) msg.obj);
- break;
- case MSG_DEVICE_BROWSE_DISCONNECT:
- inst.msgDeviceBrowseDisconnect((BluetoothDevice) msg.obj);
- break;
- case MSG_FOLDER_LIST:
- inst.msgFolderList((Intent) msg.obj);
- break;
- case MSG_DEVICE_UPDATED:
- inst.msgDeviceUpdated((BluetoothDevice) msg.obj);
- default:
- Log.e(TAG, "Message not handled " + msg);
- }
- }
- }
-
- @Override
- public void onCreate() {
- if (DBG) Log.d(TAG, "onCreate");
- super.onCreate();
-
- mSession = new MediaSession(this, TAG);
- setSessionToken(mSession.getSessionToken());
- mSession.setCallback(mSessionCallbacks);
- mSession.setFlags(MediaSession.FLAG_HANDLES_MEDIA_BUTTONS
- | MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS);
- mSession.setActive(true);
- mAvrcpCommandQueue = new AvrcpCommandQueueHandler(Looper.getMainLooper(), this);
-
- refreshInitialPlayingState();
-
- IntentFilter filter = new IntentFilter();
- filter.addAction(BluetoothAvrcpController.ACTION_CONNECTION_STATE_CHANGED);
- filter.addAction(AvrcpControllerService.ACTION_BROWSE_CONNECTION_STATE_CHANGED);
- filter.addAction(AvrcpControllerService.ACTION_TRACK_EVENT);
- filter.addAction(AvrcpControllerService.ACTION_FOLDER_LIST);
- filter.addAction(A2dpMediaBrowserService.ACTION_DEVICE_UPDATED);
- registerReceiver(mBtReceiver, filter);
-
- synchronized (this) {
- mParentIdToRequestMap.clear();
- }
- }
-
- @Override
- public void onDestroy() {
- if (DBG) Log.d(TAG, "onDestroy");
- mSession.release();
- unregisterReceiver(mBtReceiver);
- super.onDestroy();
- }
-
- @Override
- public BrowserRoot onGetRoot(String clientPackageName, int clientUid, Bundle rootHints) {
- return new BrowserRoot(BrowseTree.ROOT, null);
- }
-
- @Override
- public synchronized void onLoadChildren(final String parentMediaId,
- final Result<List<MediaItem>> result) {
- if (mAvrcpCtrlSrvc == null) {
- Log.w(TAG, "AVRCP not yet connected.");
- result.sendResult(Collections.emptyList());
- return;
- }
-
- if (DBG) Log.d(TAG, "onLoadChildren parentMediaId=" + parentMediaId);
- if (!mAvrcpCtrlSrvc.getChildren(mA2dpDevice, parentMediaId, 0, 0xff)) {
- result.sendResult(Collections.emptyList());
- return;
- }
-
- // Since we are using this thread from a binder thread we should make sure that
- // we synchronize against other such asynchronous calls.
- synchronized (this) {
- mParentIdToRequestMap.put(parentMediaId, result);
- }
- result.detach();
- }
-
- @Override
- public void onLoadItem(String itemId, Result<MediaBrowser.MediaItem> result) {
- }
-
- // Media Session Stuff.
- private MediaSession.Callback mSessionCallbacks = new MediaSession.Callback() {
- @Override
- public void onPlay() {
- if (DBG) Log.d(TAG, "onPlay");
- mAvrcpCommandQueue.obtainMessage(MSG_AVRCP_PASSTHRU,
- AvrcpControllerService.PASS_THRU_CMD_ID_PLAY).sendToTarget();
- // TRACK_EVENT should be fired eventually and the UI should be hence updated.
- }
-
- @Override
- public void onPause() {
- if (DBG) Log.d(TAG, "onPause");
- mAvrcpCommandQueue.obtainMessage(MSG_AVRCP_PASSTHRU,
- AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE).sendToTarget();
- // TRACK_EVENT should be fired eventually and the UI should be hence updated.
- }
-
- @Override
- public void onSkipToNext() {
- if (DBG) Log.d(TAG, "onSkipToNext");
- mAvrcpCommandQueue.obtainMessage(MSG_AVRCP_PASSTHRU,
- AvrcpControllerService.PASS_THRU_CMD_ID_FORWARD).sendToTarget();
- // TRACK_EVENT should be fired eventually and the UI should be hence updated.
- }
-
- @Override
- public void onSkipToPrevious() {
- if (DBG) Log.d(TAG, "onSkipToPrevious");
- mAvrcpCommandQueue.obtainMessage(MSG_AVRCP_PASSTHRU,
- AvrcpControllerService.PASS_THRU_CMD_ID_BACKWARD).sendToTarget();
- // TRACK_EVENT should be fired eventually and the UI should be hence updated.
- }
-
- @Override
- public void onStop() {
- if (DBG) Log.d(TAG, "onStop");
- mAvrcpCommandQueue.obtainMessage(MSG_AVRCP_PASSTHRU,
- AvrcpControllerService.PASS_THRU_CMD_ID_STOP).sendToTarget();
- }
-
- @Override
- public void onPrepare() {
- if (DBG) Log.d(TAG, "onPrepare");
- if (mA2dpSinkService != null) {
- mA2dpSinkService.requestAudioFocus(mA2dpDevice, true);
- }
- }
-
- @Override
- public void onRewind() {
- if (DBG) Log.d(TAG, "onRewind");
- mAvrcpCommandQueue.obtainMessage(MSG_AVRCP_PASSTHRU,
- AvrcpControllerService.PASS_THRU_CMD_ID_REWIND).sendToTarget();
- // TRACK_EVENT should be fired eventually and the UI should be hence updated.
- }
-
- @Override
- public void onFastForward() {
- if (DBG) Log.d(TAG, "onFastForward");
- mAvrcpCommandQueue.obtainMessage(MSG_AVRCP_PASSTHRU,
- AvrcpControllerService.PASS_THRU_CMD_ID_FF).sendToTarget();
- // TRACK_EVENT should be fired eventually and the UI should be hence updated.
- }
-
- @Override
- public void onPlayFromMediaId(String mediaId, Bundle extras) {
- synchronized (A2dpMediaBrowserService.this) {
- // Play the item if possible.
- mAvrcpCtrlSrvc.fetchAttrAndPlayItem(mA2dpDevice, mediaId);
-
- // Since we request explicit playback here we should start the updates to UI.
- mAvrcpCtrlSrvc.startAvrcpUpdates(mA2dpDevice);
- }
-
- // TRACK_EVENT should be fired eventually and the UI should be hence updated.
- }
-
- // Support VOL UP and VOL DOWN events for PTS testing.
- @Override
- public void onCustomAction(String action, Bundle extras) {
- if (DBG) Log.d(TAG, "onCustomAction " + action);
- if (CUSTOM_ACTION_VOL_UP.equals(action)) {
- mAvrcpCommandQueue.obtainMessage(MSG_AVRCP_PASSTHRU,
- AvrcpControllerService.PASS_THRU_CMD_ID_VOL_UP).sendToTarget();
- } else if (CUSTOM_ACTION_VOL_DN.equals(action)) {
- mAvrcpCommandQueue.obtainMessage(MSG_AVRCP_PASSTHRU,
- AvrcpControllerService.PASS_THRU_CMD_ID_VOL_DOWN).sendToTarget();
- } else if (CUSTOM_ACTION_GET_PLAY_STATUS_NATIVE.equals(action)) {
- mAvrcpCommandQueue.obtainMessage(MSG_AVRCP_GET_PLAY_STATUS_NATIVE).sendToTarget();
- } else {
- Log.w(TAG, "Custom action " + action + " not supported.");
- }
- }
- };
-
- private BroadcastReceiver mBtReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (DBG) Log.d(TAG, "onReceive intent=" + intent);
- String action = intent.getAction();
- BluetoothDevice btDev =
- (BluetoothDevice) intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
- int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1);
-
- if (BluetoothAvrcpController.ACTION_CONNECTION_STATE_CHANGED.equals(action)) {
- if (DBG) {
- Log.d(TAG, "handleConnectionStateChange: newState="
- + state + " btDev=" + btDev);
- }
-
- // Connected state will be handled when AVRCP BluetoothProfile gets connected.
- if (state == BluetoothProfile.STATE_CONNECTED) {
- mAvrcpCommandQueue.obtainMessage(MSG_DEVICE_CONNECT, btDev).sendToTarget();
- } else if (state == BluetoothProfile.STATE_DISCONNECTED) {
- // Set the playback state to unconnected.
- mAvrcpCommandQueue.obtainMessage(MSG_DEVICE_DISCONNECT, btDev).sendToTarget();
- // If we have been pushing updates via the session then stop sending them since
- // we are not connected anymore.
- if (mSession.isActive()) {
- mSession.setActive(false);
- }
- }
- } else if (AvrcpControllerService.ACTION_BROWSE_CONNECTION_STATE_CHANGED.equals(
- action)) {
- if (state == BluetoothProfile.STATE_CONNECTED) {
- mAvrcpCommandQueue.obtainMessage(MSG_DEVICE_BROWSE_CONNECT, btDev)
- .sendToTarget();
- } else if (state == BluetoothProfile.STATE_DISCONNECTED) {
- mAvrcpCommandQueue.obtainMessage(MSG_DEVICE_BROWSE_DISCONNECT, btDev)
- .sendToTarget();
- }
- } else if (AvrcpControllerService.ACTION_TRACK_EVENT.equals(action)) {
- PlaybackState pbb =
- intent.getParcelableExtra(AvrcpControllerService.EXTRA_PLAYBACK);
- MediaMetadata mmd =
- intent.getParcelableExtra(AvrcpControllerService.EXTRA_METADATA);
- mAvrcpCommandQueue.obtainMessage(MSG_TRACK,
- new Pair<PlaybackState, MediaMetadata>(pbb, mmd)).sendToTarget();
- } else if (AvrcpControllerService.ACTION_FOLDER_LIST.equals(action)) {
- mAvrcpCommandQueue.obtainMessage(MSG_FOLDER_LIST, intent).sendToTarget();
- } else if (ACTION_DEVICE_UPDATED.equals(action)) {
- mAvrcpCommandQueue.obtainMessage(MSG_DEVICE_UPDATED, btDev).sendToTarget();
- }
- }
- };
-
- private synchronized void msgDeviceConnect(BluetoothDevice device) {
- if (DBG) Log.d(TAG, "msgDeviceConnect");
- // We are connected to a new device via A2DP now.
- mA2dpDevice = device;
- mAvrcpCtrlSrvc = AvrcpControllerService.getAvrcpControllerService();
- if (mAvrcpCtrlSrvc == null) {
- Log.e(TAG, "!!!AVRCP Controller cannot be null");
- return;
- }
- refreshInitialPlayingState();
- }
-
- private synchronized void msgDeviceUpdated(BluetoothDevice device) {
- if (device != null && device.equals(mA2dpDevice)) {
- return;
- }
- Log.d(TAG, "msgDeviceUpdated. Previous: " + mA2dpDevice + " New: " + device);
- // We are connected to a new device via A2DP now.
- mA2dpDevice = device;
- mAvrcpCtrlSrvc = AvrcpControllerService.getAvrcpControllerService();
- if (mAvrcpCtrlSrvc == null) {
- Log.e(TAG, "!!!AVRCP Controller cannot be null");
- return;
- }
- if (mAvrcpCtrlSrvc.isBrowsingConnected(device))
- notifyChildrenChanged("__ROOT__");
- refreshInitialPlayingState();
- }
-
- // Refresh the UI if we have a connected device and AVRCP is initialized.
- private synchronized void refreshInitialPlayingState() {
- if (mA2dpDevice == null) {
- if (DBG) Log.d(TAG, "device " + mA2dpDevice);
- return;
- }
-
- List<BluetoothDevice> devices = mAvrcpCtrlSrvc.getConnectedDevices();
- if (devices.size() == 0) {
- Log.w(TAG, "No devices connected yet");
- return;
- }
-
- if (mA2dpDevice != null && !devices.contains(mA2dpDevice)) {
- Log.w(TAG, "A2dp device : " + mA2dpDevice + " avrcp device " + devices.get(0));
- return;
- }
- mA2dpSinkService = A2dpSinkService.getA2dpSinkService();
-
- PlaybackState playbackState = mAvrcpCtrlSrvc.getPlaybackState(mA2dpDevice);
- // Add actions required for playback and rebuild the object.
- PlaybackState.Builder pbb = new PlaybackState.Builder(playbackState);
- playbackState = pbb.setActions(mTransportControlFlags).build();
-
- MediaMetadata mediaMetadata = mAvrcpCtrlSrvc.getMetaData(mA2dpDevice);
- if (VDBG) {
- Log.d(TAG, "Media metadata " + mediaMetadata + " playback state " + playbackState);
- }
- mSession.setMetadata(mAvrcpCtrlSrvc.getMetaData(mA2dpDevice));
- mSession.setPlaybackState(playbackState);
- }
-
- private void msgDeviceDisconnect(BluetoothDevice device) {
- if (DBG) Log.d(TAG, "msgDeviceDisconnect");
- if (mA2dpDevice == null) {
- Log.w(TAG, "Already disconnected - nothing to do here.");
- return;
- } else if (!mA2dpDevice.equals(device)) {
- Log.e(TAG,
- "Not the right device to disconnect current " + mA2dpDevice + " dc " + device);
- return;
- }
-
- // Unset the session.
- PlaybackState.Builder pbb = new PlaybackState.Builder();
- pbb = pbb.setState(PlaybackState.STATE_ERROR, PlaybackState.PLAYBACK_POSITION_UNKNOWN,
- PLAYBACK_SPEED)
- .setActions(mTransportControlFlags)
- .setErrorMessage(getString(R.string.bluetooth_disconnected));
- mSession.setPlaybackState(pbb.build());
-
- // Set device to null.
- mA2dpDevice = null;
- mBrowseConnected = false;
- // update playerList.
- notifyChildrenChanged("__ROOT__");
- }
-
- private void msgTrack(PlaybackState pb, MediaMetadata mmd) {
- if (VDBG) Log.d(TAG, "msgTrack: playback: " + pb + " mmd: " + mmd);
- // Log the current track position/content.
- MediaController controller = mSession.getController();
- PlaybackState prevPS = controller.getPlaybackState();
- MediaMetadata prevMM = controller.getMetadata();
-
- if (prevPS != null) {
- Log.d(TAG, "prevPS " + prevPS);
- }
-
- if (prevMM != null) {
- String title = prevMM.getString(MediaMetadata.METADATA_KEY_TITLE);
- long trackLen = prevMM.getLong(MediaMetadata.METADATA_KEY_DURATION);
- if (VDBG) Log.d(TAG, "prev MM title " + title + " track len " + trackLen);
- }
-
- if (mmd != null) {
- if (VDBG) Log.d(TAG, "msgTrack() mmd " + mmd.getDescription());
- mSession.setMetadata(mmd);
- }
-
- if (pb != null) {
- if (DBG) Log.d(TAG, "msgTrack() playbackstate " + pb);
- PlaybackState.Builder pbb = new PlaybackState.Builder(pb);
- pb = pbb.setActions(mTransportControlFlags).build();
- mSession.setPlaybackState(pb);
-
- // If we are now playing then we should start pushing updates via MediaSession so that
- // external UI (such as SystemUI) can show the currently playing music.
- if (pb.getState() == PlaybackState.STATE_PLAYING && !mSession.isActive()) {
- mSession.setActive(true);
- }
- }
- }
-
- private synchronized void msgPassThru(int cmd) {
- if (DBG) Log.d(TAG, "msgPassThru " + cmd);
- if (mA2dpDevice == null) {
- // We should have already disconnected - ignore this message.
- Log.w(TAG, "Already disconnected ignoring.");
- return;
- }
-
- // Send the pass through.
- mAvrcpCtrlSrvc.sendPassThroughCmd(mA2dpDevice, cmd,
- AvrcpControllerService.KEY_STATE_PRESSED);
- mAvrcpCtrlSrvc.sendPassThroughCmd(mA2dpDevice, cmd,
- AvrcpControllerService.KEY_STATE_RELEASED);
- }
-
- private synchronized void msgGetPlayStatusNative() {
- if (DBG) Log.d(TAG, "msgGetPlayStatusNative");
- if (mA2dpDevice == null) {
- // We should have already disconnected - ignore this message.
- Log.w(TAG, "Already disconnected ignoring.");
- return;
- }
-
- // Ask for a non cached version.
- mAvrcpCtrlSrvc.getPlaybackState(mA2dpDevice, false);
- }
-
- private void msgDeviceBrowseConnect(BluetoothDevice device) {
- if (DBG) Log.d(TAG, "msgDeviceBrowseConnect device " + device);
- // We should already be connected to this device over A2DP.
- if (!device.equals(mA2dpDevice)) {
- Log.e(TAG, "Browse connected over different device a2dp " + mA2dpDevice + " browse "
- + device);
- return;
- }
- mBrowseConnected = true;
- // update playerList
- notifyChildrenChanged("__ROOT__");
- }
-
- private void msgFolderList(Intent intent) {
- // Parse the folder list for children list and id.
- List<Parcelable> extraParcelableList =
- (ArrayList<Parcelable>) intent.getParcelableArrayListExtra(
- AvrcpControllerService.EXTRA_FOLDER_LIST);
- List<MediaItem> folderList = new ArrayList<MediaItem>();
- for (Parcelable p : extraParcelableList) {
- folderList.add((MediaItem) p);
- }
-
- String id = intent.getStringExtra(AvrcpControllerService.EXTRA_FOLDER_ID);
- if (VDBG) Log.d(TAG, "Parent: " + id + " Folder list: " + folderList);
- synchronized (this) {
- // If we have a result object then we should send the result back
- // to client since it is blocking otherwise we may have gotten more items
- // from remote device, hence let client know to fetch again.
- Result<List<MediaItem>> results = mParentIdToRequestMap.remove(id);
- if (results == null) {
- Log.w(TAG, "Request no longer exists, notifying that children changed.");
- notifyChildrenChanged(id);
- } else {
- results.sendResult(folderList);
- }
- }
- }
-
- private void msgDeviceBrowseDisconnect(BluetoothDevice device) {
- if (DBG) Log.d(TAG, "msgDeviceBrowseDisconnect device " + device);
- // Disconnect only if mA2dpDevice is non null
- if (!device.equals(mA2dpDevice)) {
- Log.w(TAG, "Browse disconnecting from different device a2dp " + mA2dpDevice + " browse "
- + device);
- return;
- }
- mBrowseConnected = false;
- }
-}
diff --git a/src/com/android/bluetooth/avrcp/AddressedMediaPlayer.java b/src/com/android/bluetooth/avrcp/AddressedMediaPlayer.java
index 178ed07..f8a9cf6 100644
--- a/src/com/android/bluetooth/avrcp/AddressedMediaPlayer.java
+++ b/src/com/android/bluetooth/avrcp/AddressedMediaPlayer.java
@@ -370,7 +370,7 @@
/* Set display name for current item */
folderDataNative.mDisplayNames[itemIndex] =
- getAttrValue(AvrcpConstants.ATTRID_TITLE, item, mediaController);
+ getAttrValue(bdaddr, AvrcpConstants.ATTRID_TITLE, item, mediaController);
int maxAttributesRequested = 0;
boolean isAllAttribRequested = false;
@@ -394,7 +394,7 @@
int attribId =
isAllAttribRequested ? (idx + 1) : folderItemsReqObj.mAttrIDs[idx];
- value = getAttrValue(attribId, item, mediaController);
+ value = getAttrValue(bdaddr, attribId, item, mediaController);
if (value != null) {
attrArray.add(value);
attrId.add(attribId);
@@ -432,7 +432,7 @@
mMediaInterface.folderItemsRsp(bdaddr, AvrcpConstants.RSP_NO_ERROR, rspObj);
}
- private String getAttrValue(int attr, MediaSession.QueueItem item,
+ private String getAttrValue(byte []bdaddr, int attr, MediaSession.QueueItem item,
@Nullable MediaController mediaController) {
String attrValue = null;
if (item == null) {
@@ -506,7 +506,12 @@
break;
case AvrcpConstants.ATTRID_COVER_ART:
- attrValue = Avrcp_ext.getImgHandleFromTitle(desc.getTitle().toString());
+ if (mAvrcp != null) {
+ attrValue = mAvrcp.getImgHandleFromTitle(bdaddr,
+ desc.getTitle().toString());
+ } else {
+ if (DEBUG) Log.d(TAG, " mAvrcp null ");
+ }
break;
default:
@@ -563,7 +568,7 @@
/* lookup and copy values of attributes for ids requested above */
for (int idx = 0; idx < attrTempId.size(); idx++) {
/* check if media player provided requested attributes */
- String value = getAttrValue(attrTempId.get(idx), mediaItem, mediaController);
+ String value = getAttrValue(bdaddr, attrTempId.get(idx), mediaItem, mediaController);
if (value != null) {
attrArray.add(value);
attrId.add(attrTempId.get(idx));
@@ -611,19 +616,19 @@
sb.append("#");
sb.append(item.getQueueId());
sb.append(": ");
- sb.append(Utils.ellipsize(getAttrValue(AvrcpConstants.ATTRID_TITLE, item, null)));
+ sb.append(Utils.ellipsize(getAttrValue(null, AvrcpConstants.ATTRID_TITLE, item, null)));
sb.append(" - ");
- sb.append(Utils.ellipsize(getAttrValue(AvrcpConstants.ATTRID_ALBUM, item, null)));
+ sb.append(Utils.ellipsize(getAttrValue(null, AvrcpConstants.ATTRID_ALBUM, item, null)));
sb.append(" by ");
- sb.append(Utils.ellipsize(getAttrValue(AvrcpConstants.ATTRID_ARTIST, item, null)));
+ sb.append(Utils.ellipsize(getAttrValue(null, AvrcpConstants.ATTRID_ARTIST, item, null)));
sb.append(" (");
- sb.append(getAttrValue(AvrcpConstants.ATTRID_PLAY_TIME, item, null));
+ sb.append(getAttrValue(null, AvrcpConstants.ATTRID_PLAY_TIME, item, null));
sb.append(" ");
- sb.append(getAttrValue(AvrcpConstants.ATTRID_TRACK_NUM, item, null));
+ sb.append(getAttrValue(null, AvrcpConstants.ATTRID_TRACK_NUM, item, null));
sb.append("/");
- sb.append(getAttrValue(AvrcpConstants.ATTRID_NUM_TRACKS, item, null));
+ sb.append(getAttrValue(null, AvrcpConstants.ATTRID_NUM_TRACKS, item, null));
sb.append(") ");
- sb.append(getAttrValue(AvrcpConstants.ATTRID_GENRE, item, null));
+ sb.append(getAttrValue(null, AvrcpConstants.ATTRID_GENRE, item, null));
return sb.toString();
}
diff --git a/src/com/android/bluetooth/avrcp/Avrcp.java b/src/com/android/bluetooth/avrcp/Avrcp.java
index 2938582..b507e67 100644
--- a/src/com/android/bluetooth/avrcp/Avrcp.java
+++ b/src/com/android/bluetooth/avrcp/Avrcp.java
@@ -33,12 +33,14 @@
import android.content.pm.ResolveInfo;
import android.content.res.Resources;
import android.media.AudioManager;
+import android.media.AudioAttributes;
import android.media.AudioPlaybackConfiguration;
import android.media.MediaDescription;
import android.media.MediaMetadata;
import android.media.session.MediaSession;
import android.media.session.MediaSessionManager;
import android.media.session.PlaybackState;
+import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
@@ -72,7 +74,6 @@
import java.util.TreeMap;
import com.android.bluetooth.hfp.HeadsetService;
-import android.os.SystemProperties;
import com.android.bluetooth.hfp.HeadsetService;
import com.android.bluetooth.avrcpcontroller.AvrcpControllerService;
@@ -212,8 +213,6 @@
/* Manage browsed players */
private AvrcpBrowseManager mAvrcpBrowseManager;
- /* BIP Responder */
- static private AvrcpBipRsp mAvrcpBipRsp;
/* Broadcast receiver for device connections intent broadcasts */
private final BroadcastReceiver mAvrcpReceiver = new AvrcpServiceBroadcastReceiver();
private final BroadcastReceiver mBootReceiver = new AvrcpServiceBootReceiver();
@@ -308,7 +307,6 @@
mContext = context;
mLastUsedPlayerID = 0;
mAddressedMediaPlayer = null;
- mAvrcpBipRsp = null;
initNative();
mMediaSessionManager =
@@ -353,6 +351,8 @@
NotificationManager.IMPORTANCE_DEFAULT);
mChannel.setDescription(mContext.getString(R.string.bluetooth_advanced_feat_description));
mChannel.enableLights(true);
+ mChannel.enableVibration(false);
+ mChannel.setSound(Uri.EMPTY, Notification.AUDIO_ATTRIBUTES_DEFAULT);
mChannel.setLightColor(Color.GREEN);
mNotificationManager.createNotificationChannel(mChannel);
@@ -380,27 +380,6 @@
}
mPackageManager = mContext.getApplicationContext().getPackageManager();
- boolean isCoverArtSupported = false;
- AdapterService adapterService = AdapterService.getAdapterService();
- if ((adapterService != null) && (adapterService.isVendorIntfEnabled())) {
- if (adapterService.getProfileInfo(AbstractionLayer.AVRCP, AbstractionLayer.AVRCP_0103_SUPPORT))
- {
- isCoverArtSupported = false;
- } else if(adapterService.getProfileInfo(AbstractionLayer.AVRCP, AbstractionLayer.AVRCP_COVERART_SUPPORT)){
- isCoverArtSupported = true;
- }
- }
- String avrcpVersion = SystemProperties.get(AVRCP_VERSION_PROPERTY, AVRCP_1_4_STRING);
- if (DEBUG) Log.d(TAG, "avrcpVersion: " + avrcpVersion
- + " isCoverArtSupported :"+isCoverArtSupported);
- /* Enable Cover Art support is version is 1.6 and flag is set in config */
- if (isCoverArtSupported && avrcpVersion != null &&
- avrcpVersion.equals(AVRCP_1_6_STRING)) {
- mAvrcpBipRsp = new AvrcpBipRsp(mContext);
- mAvrcpBipRsp.start();
- if (DEBUG) Log.d(TAG, "Starting AVRCP BIP Responder Service");
- }
-
/* create object to communicate with addressed player */
mAddressedMediaPlayer = new AddressedMediaPlayer(mAvrcpMediaRsp);
@@ -453,10 +432,6 @@
if (looper != null) {
looper.quitSafely();
}
- if (mAvrcpBipRsp != null) {
- mAvrcpBipRsp.stop();
- mAvrcpBipRsp = null;
- }
mAudioManagerPlaybackHandler = null;
mContext.unregisterReceiver(mAvrcpReceiver);
mContext.unregisterReceiver(mBootReceiver);
@@ -587,13 +562,12 @@
if ((mFeatures & BTRC_FEAT_AVRC_UI_UPDATE) != 0) {
int NOTIFICATION_ID = android.R.drawable.stat_sys_data_bluetooth;
- Notification notification = new Notification.Builder(mContext)
+ Notification notification = new Notification.Builder(mContext, AVRCP_NOTIFICATION_ID)
.setContentTitle(mContext.getString(R.string.bluetooth_rc_feat_title))
.setContentText(mContext.getString(R.string.bluetooth_rc_feat_content))
.setSubText(mContext.getString(R.string.bluetooth_rc_feat_subtext))
.setSmallIcon(android.R.drawable.stat_sys_data_bluetooth)
.setChannelId(AVRCP_NOTIFICATION_ID)
- .setDefaults(Notification.DEFAULT_ALL)
.build();
if (mNotificationManager != null )
@@ -1036,11 +1010,7 @@
longStringOrBlank(data.getLong(MediaMetadata.METADATA_KEY_NUM_TRACKS));
mGenre = stringOrBlank(data.getString(MediaMetadata.METADATA_KEY_GENRE));
mPlayingTimeMs = data.getLong(MediaMetadata.METADATA_KEY_DURATION);
- if (mAvrcpBipRsp != null) {
- coverArt = stringOrBlank(mAvrcpBipRsp.getImgHandle(mAlbumName));
- } else {
- coverArt = stringOrBlank(null);
- }
+ coverArt = stringOrBlank(null);
// Try harder for the title.
mTitle = data.getString(MediaMetadata.METADATA_KEY_TITLE);
@@ -1113,14 +1083,7 @@
case ATTR_PLAYING_TIME_MS:
return Long.toString(mPlayingTimeMs);
case ATTR_COVER_ART:
- if (mAvrcpBipRsp != null) {
- /* Fetch coverArt Handle now in case OBEX channel is established just
- * before retrieving get element attribute. */
- coverArt = stringOrBlank(mAvrcpBipRsp.getImgHandle(mAlbumName));
- } else {
- coverArt = stringOrBlank(null);
- }
- return coverArt;
+ return stringOrBlank(null);
default:
return new String();
}
@@ -1318,13 +1281,18 @@
if (param <= 0)
param = 1;
- boolean isSplitA2dpEnabled = false;
long update_interval = 0L;
- String offloadSupported = SystemProperties.get("persist.vendor.btstack.enable.splita2dp");
- if (offloadSupported.isEmpty() || "true".equals(offloadSupported)) {
- isSplitA2dpEnabled = true;
- Log.v(TAG,"split enabled");
+ // Split A2dp will be enabled by default
+ boolean isSplitA2dpEnabled = true;
+ AdapterService adapterService = AdapterService.getAdapterService();
+
+ if (adapterService != null){
+ isSplitA2dpEnabled = adapterService.isSplitA2dpEnabled();
+ Log.v(TAG,"isSplitA2dpEnabled: " + isSplitA2dpEnabled);
+ } else {
+ Log.e(TAG,"adapterService is null");
}
+
update_interval = (isSplitA2dpEnabled) ?
SystemProperties.getLong("persist.vendor.btstack.avrcp.pos_time", 3000L):
SystemProperties.getLong("persist.vendor.btstack.avrcp.pos_time", 1000L);
@@ -2390,8 +2358,6 @@
featureBitsList.add(AvrcpConstants.AVRC_PF_UID_UNIQUE_BIT_NO);
featureBitsList.add(AvrcpConstants.AVRC_PF_NOW_PLAY_BIT_NO);
featureBitsList.add(AvrcpConstants.AVRC_PF_GET_NUM_OF_ITEMS_BIT_NO);
- if (mAvrcpBipRsp != null)
- featureBitsList.add(AvrcpConstants.AVRC_PF_COVER_ART_BIT_NO);
}
// converting arraylist to array for response
@@ -3139,7 +3105,7 @@
}
KeyEvent event = new KeyEvent(action, code);
- if (!KeyEvent.isMediaKey(code)) {
+ if (!KeyEvent.isMediaSessionKey(code)) {
Log.w(TAG, "Passthrough non-media key " + op + " (code " + code + ") state " + state);
} else {
if (code == KeyEvent.KEYCODE_MEDIA_FAST_FORWARD) {
diff --git a/src/com/android/bluetooth/newavrcp/AvrcpNativeInterface.java b/src/com/android/bluetooth/avrcp/AvrcpNativeInterface.java
similarity index 98%
rename from src/com/android/bluetooth/newavrcp/AvrcpNativeInterface.java
rename to src/com/android/bluetooth/avrcp/AvrcpNativeInterface.java
index 4976237..a4e8b66 100644
--- a/src/com/android/bluetooth/newavrcp/AvrcpNativeInterface.java
+++ b/src/com/android/bluetooth/avrcp/AvrcpNativeInterface.java
@@ -27,7 +27,7 @@
* data.
*/
public class AvrcpNativeInterface {
- private static final String TAG = "NewAvrcpNativeInterface";
+ private static final String TAG = "AvrcpNativeInterface";
private static final boolean DEBUG = true;
private static AvrcpNativeInterface sInstance;
diff --git a/src/com/android/bluetooth/newavrcp/AvrcpTargetService.java b/src/com/android/bluetooth/avrcp/AvrcpTargetService.java
similarity index 93%
rename from src/com/android/bluetooth/newavrcp/AvrcpTargetService.java
rename to src/com/android/bluetooth/avrcp/AvrcpTargetService.java
index 7a1c5da..b48f353 100644
--- a/src/com/android/bluetooth/newavrcp/AvrcpTargetService.java
+++ b/src/com/android/bluetooth/avrcp/AvrcpTargetService.java
@@ -35,6 +35,7 @@
import com.android.bluetooth.a2dp.A2dpService;
import com.android.bluetooth.btservice.MetricsLogger;
import com.android.bluetooth.btservice.ProfileService;
+import com.android.bluetooth.btservice.ServiceFactory;
import java.util.List;
import java.util.Objects;
@@ -44,7 +45,7 @@
* @hide
*/
public class AvrcpTargetService extends ProfileService {
- private static final String TAG = "NewAvrcpTargetService";
+ private static final String TAG = "AvrcpTargetService";
private static final boolean DEBUG = true;
private static final String AVRCP_ENABLE_PROPERTY = "persist.bluetooth.enablenewavrcp";
@@ -57,6 +58,7 @@
private AvrcpBroadcastReceiver mReceiver;
private AvrcpNativeInterface mNativeInterface;
private AvrcpVolumeManager mVolumeManager;
+ private ServiceFactory mFactory = new ServiceFactory();
// Only used to see if the metadata has changed from its previous value
private MediaData mCurrentData;
@@ -144,11 +146,6 @@
mAdapterService = Objects.requireNonNull(AdapterService.getAdapterService(),
"AdapterService cannot be null when A2dpService starts");
- mReceiver = new AvrcpBroadcastReceiver();
- IntentFilter filter = new IntentFilter();
- filter.addAction(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED);
- registerReceiver(mReceiver, filter);
-
if(mAdapterService.isVendorIntfEnabled()) {
Log.i(TAG, "Vendor Stack is enabled, using legacy implementation");
SystemProperties.set(AVRCP_ENABLE_PROPERTY, "false");
@@ -165,15 +162,20 @@
mMediaPlayerList = new MediaPlayerList(Looper.myLooper(), this);
+ mNativeInterface = AvrcpNativeInterface.getInterface();
+ mNativeInterface.init(AvrcpTargetService.this);
+
+ mVolumeManager = new AvrcpVolumeManager(this, mAudioManager, mNativeInterface);
+
UserManager userManager = UserManager.get(getApplicationContext());
if (userManager.isUserUnlocked()) {
mMediaPlayerList.init(new ListCallback());
}
- mNativeInterface = AvrcpNativeInterface.getInterface();
- mNativeInterface.init(AvrcpTargetService.this);
-
- mVolumeManager = new AvrcpVolumeManager(this, mAudioManager, mNativeInterface);
+ mReceiver = new AvrcpBroadcastReceiver();
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED);
+ registerReceiver(mReceiver, filter);
// Only allow the service to be used once it is initialized
sInstance = this;
@@ -236,17 +238,35 @@
public void storeVolumeForDevice(BluetoothDevice device) {
if (device == null) return;
+ List<BluetoothDevice> HAActiveDevices = null;
+ if (mFactory.getHearingAidService() != null) {
+ HAActiveDevices = mFactory.getHearingAidService().getActiveDevices();
+ }
+ if (HAActiveDevices != null
+ && (HAActiveDevices.get(0) != null || HAActiveDevices.get(1) != null)) {
+ Log.d(TAG, "Do not store volume when Hearing Aid devices is active");
+ return;
+ }
mVolumeManager.storeVolumeForDevice(device);
}
/**
+ * Remove the stored volume for a device.
+ */
+ public void removeStoredVolumeForDevice(BluetoothDevice device) {
+ if (device == null) return;
+
+ mVolumeManager.removeStoredVolumeForDevice(device);
+ }
+
+ /**
* Retrieve the remembered volume for a device. Returns -1 if there is no volume for the
* device.
*/
public int getRememberedVolumeForDevice(BluetoothDevice device) {
if (device == null) return -1;
- return mVolumeManager.getVolume(device, -1);
+ return mVolumeManager.getVolume(device, mVolumeManager.getNewDeviceVolume());
}
// TODO (apanicke): Add checks to blacklist Absolute Volume devices if they behave poorly.
diff --git a/src/com/android/bluetooth/newavrcp/AvrcpVolumeManager.java b/src/com/android/bluetooth/avrcp/AvrcpVolumeManager.java
similarity index 90%
rename from src/com/android/bluetooth/newavrcp/AvrcpVolumeManager.java
rename to src/com/android/bluetooth/avrcp/AvrcpVolumeManager.java
index 2e2ce42..fea5210 100644
--- a/src/com/android/bluetooth/newavrcp/AvrcpVolumeManager.java
+++ b/src/com/android/bluetooth/avrcp/AvrcpVolumeManager.java
@@ -32,7 +32,7 @@
import java.util.Objects;
class AvrcpVolumeManager extends AudioDeviceCallback {
- public static final String TAG = "NewAvrcpVolumeManager";
+ public static final String TAG = "AvrcpVolumeManager";
public static final boolean DEBUG = true;
// All volumes are stored at system volume values, not AVRCP values
@@ -115,20 +115,35 @@
volumeMapEditor.apply();
}
- void storeVolumeForDevice(BluetoothDevice device) {
+ synchronized void storeVolumeForDevice(@NonNull BluetoothDevice device) {
+ 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);
pref.putInt(device.getAddress(), storeVolume);
-
// Always use apply() since it is asynchronous, otherwise the call can hang waiting for
// storage to be written.
pref.apply();
}
- int getVolume(@NonNull BluetoothDevice device, int defaultValue) {
+ synchronized void removeStoredVolumeForDevice(@NonNull BluetoothDevice device) {
+ if (device.getBondState() != BluetoothDevice.BOND_NONE) {
+ return;
+ }
+ SharedPreferences.Editor pref = getVolumeMap().edit();
+ Log.i(TAG, "RemoveStoredVolume: Remove stored stream volume level for device " + device);
+ mVolumeMap.remove(device);
+ pref.remove(device.getAddress());
+ // Always use apply() since it is asynchronous, otherwise the call can hang waiting for
+ // storage to be written.
+ pref.apply();
+ }
+
+ synchronized int getVolume(@NonNull BluetoothDevice device, int defaultValue) {
if (!mVolumeMap.containsKey(device)) {
Log.w(TAG, "getVolume: Couldn't find volume preference for device: " + device);
return defaultValue;
@@ -138,6 +153,10 @@
return mVolumeMap.get(device);
}
+ public int getNewDeviceVolume() {
+ return sNewDeviceVolume;
+ }
+
@Override
public synchronized void onAudioDevicesAdded(AudioDeviceInfo[] addedDevices) {
if (mCurrentDevice == null) {
diff --git a/src/com/android/bluetooth/newavrcp/BrowsablePlayerConnector.java b/src/com/android/bluetooth/avrcp/BrowsablePlayerConnector.java
similarity index 90%
rename from src/com/android/bluetooth/newavrcp/BrowsablePlayerConnector.java
rename to src/com/android/bluetooth/avrcp/BrowsablePlayerConnector.java
index ab23dbf..b5886c2 100644
--- a/src/com/android/bluetooth/newavrcp/BrowsablePlayerConnector.java
+++ b/src/com/android/bluetooth/avrcp/BrowsablePlayerConnector.java
@@ -39,7 +39,7 @@
* when constructing BrowsedPlayerWrappers by hand.
*/
public class BrowsablePlayerConnector {
- private static final String TAG = "NewAvrcpBrowsablePlayerConnector";
+ private static final String TAG = "AvrcpBrowsablePlayerConnector";
private static final boolean DEBUG = true;
private static final long CONNECT_TIMEOUT_MS = 10000; // Time in ms to wait for a connection
@@ -152,11 +152,7 @@
case MSG_TIMEOUT: {
Log.v(TAG, "Timed out waiting for players");
- for (BrowsedPlayerWrapper wrapper : mPendingPlayers) {
- if (DEBUG) Log.d(TAG, "Disconnecting " + wrapper.getPackageName());
- wrapper.disconnect();
- }
- mPendingPlayers.clear();
+ removePendingPlayers();
} break;
}
@@ -169,4 +165,21 @@
}
};
}
+
+ private void removePendingPlayers() {
+ for (BrowsedPlayerWrapper wrapper : mPendingPlayers) {
+ if (DEBUG) Log.d(TAG, "Disconnecting " + wrapper.getPackageName());
+ wrapper.disconnect();
+ }
+ mPendingPlayers.clear();
+ }
+
+ void cleanup() {
+ if (mPendingPlayers.size() != 0) {
+ Log.i(TAG, "Bluetooth turn off with " + mPendingPlayers.size() + " pending player(s)");
+ mHandler.removeMessages(MSG_TIMEOUT);
+ removePendingPlayers();
+ mHandler = null;
+ }
+ }
}
diff --git a/src/com/android/bluetooth/avrcp/BrowsedMediaPlayer.java b/src/com/android/bluetooth/avrcp/BrowsedMediaPlayer.java
index 0ed0f59..9a3a240 100644
--- a/src/com/android/bluetooth/avrcp/BrowsedMediaPlayer.java
+++ b/src/com/android/bluetooth/avrcp/BrowsedMediaPlayer.java
@@ -26,10 +26,6 @@
import android.media.session.MediaSession;
import android.os.Bundle;
import android.util.Log;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Looper;
-import android.os.Message;
import java.math.BigInteger;
import java.util.ArrayList;
@@ -51,11 +47,6 @@
private static final int DISCONNECTED = 0;
private static final int CONNECTED = 1;
private static final int SUSPENDED = 2;
- private static final int MSG_CONNECT_PLAYER = 1;
- private static final int MSG_DISCONNECT_PLAYER = 2;
- private static final int MSG_TIMEOUT = 3;
-
- private static final int TIMEOUT = 3000;
private static final int BROWSED_ITEM_ID_INDEX = 2;
private static final int BROWSED_FOLDER_ID_INDEX = 4;
@@ -71,8 +62,6 @@
private String mCurrentBrowsePackage;
private String mCurrentBrowseClass;
- private BrowseMediaHandler mHandler = null;
- private HandlerThread mHandlerThread;
/* Object used to connect to MediaBrowseService of Media Player */
private MediaBrowser mMediaBrowser = null;
@@ -100,45 +89,6 @@
/* store result of getfolderitems with scope="vfs" */
private List<MediaBrowser.MediaItem> mFolderItems = null;
- private List<String> mBrowsablePlayerList = new ArrayList<String>();
-
- class BrowseMediaHandler extends Handler {
- BrowseMediaHandler(Looper looper) {
- super(looper);
- }
-
- @Override
- public void handleMessage(Message msg)
- {
- Log.w(TAG, "handleMessage " + msg.what + " obj " + msg.obj);
- switch (msg.what) {
- case MSG_CONNECT_PLAYER:
- Bundle data = msg.getData();
- String packageName = data.getCharSequence("package").toString();
- String cls = data.getCharSequence("class").toString();
- Log.w(TAG, "package = " + packageName + " svc class " + cls);
- MediaConnectionCallback callback = new MediaConnectionCallback(packageName);
- MediaBrowser tempBrowser = new MediaBrowser(
- mContext, new ComponentName(packageName, cls), callback, null);
- callback.setBrowser(tempBrowser);
- tempBrowser.connect();
- Log.w(TAG, "TryconnectMBS with Browser service");
- Message m = mHandler.obtainMessage(MSG_TIMEOUT, 0, 0, packageName);
- mHandler.sendMessageDelayed(m, TIMEOUT);
- break;
- case MSG_DISCONNECT_PLAYER:
- MediaBrowser mb = (MediaBrowser)msg.obj;
- mb.disconnect();
- Log.w(TAG, "Trigger disconnect for MediaBrowser " + mb);
- break;
- case MSG_TIMEOUT:
- Log.w(TAG, "MSG_TIMEOUT");
- break;
- default:
- break;
- }
- }
- };
/* Connection state callback handler */
class MediaConnectionCallback extends MediaBrowser.ConnectionCallback {
@@ -150,21 +100,15 @@
}
public void setBrowser(MediaBrowser b) {
- Log.d(TAG, "setBrowser " + b);
mBrowser = b;
}
@Override
public void onConnected() {
- if ((mHandler != null) && !mBrowsablePlayerList.contains(mCallbackPackageName)) {
- Log.d(TAG, "Add " + mCallbackPackageName + " to MBS List " + mBrowser);
- mBrowsablePlayerList.add(mCallbackPackageName);
- mHandler.removeMessages(MSG_TIMEOUT, mCallbackPackageName);
- Message msg = mHandler.obtainMessage(MSG_DISCONNECT_PLAYER, 0, 0, mBrowser);
- mHandler.sendMessage(msg);
- }
mConnState = CONNECTED;
- Log.d(TAG, "mediaBrowser CONNECTED to " + mPackageName);
+ if (DEBUG) {
+ Log.d(TAG, "mediaBrowser CONNECTED to " + mPackageName);
+ }
/* perform init tasks and set player as browsed player on successful connection */
onBrowseConnect(mCallbackPackageName, mBrowser);
@@ -174,23 +118,17 @@
@Override
public void onConnectionFailed() {
- if ((mHandler != null) && !mBrowsablePlayerList.contains(mCallbackPackageName)) {
- mHandler.removeMessages(MSG_TIMEOUT, mCallbackPackageName);
- }
mConnState = DISCONNECTED;
// Remove what could be a circular dependency causing GC to never happen on this object
mBrowser = null;
Log.e(TAG, "mediaBrowser Connection failed with " + mPackageName
+ ", Sending fail response!");
- mMediaInterface.setBrowsedPlayerRsp(mBDAddr, AvrcpConstants.RSP_PLAY_NOT_BROW,
+ mMediaInterface.setBrowsedPlayerRsp(mBDAddr, AvrcpConstants.RSP_INTERNAL_ERR,
(byte) 0x00, 0, null);
}
@Override
public void onConnectionSuspended() {
- if ((mHandler != null) && !mBrowsablePlayerList.contains(mCallbackPackageName)) {
- mHandler.removeMessages(MSG_TIMEOUT, mCallbackPackageName);
- }
mBrowser = null;
mConnState = SUSPENDED;
Log.e(TAG, "mediaBrowser SUSPENDED connection with " + mPackageName);
@@ -320,7 +258,7 @@
/* lookup and copy values of attributes for ids requested above */
for (int attrId : attrReqIds) {
/* check if media player provided requested attributes */
- String value = getAttrValue(attrId, mediaItem);
+ String value = getAttrValue(mBDAddr, attrId, mediaItem);
if (value != null) {
attrIdArray.add(attrId);
attrValueArray.add(value);
@@ -540,60 +478,6 @@
Log.w(TAG, "Reconnected with Browser service");
}
- public void CheckMBSConnection(String packageName, String cls) {
- Log.w(TAG, "TryconnectMBS with Browser service for package = " + packageName);
- Message msg = mHandler.obtainMessage(MSG_CONNECT_PLAYER);
- Bundle data = new Bundle();
- data.putCharSequence("package", packageName);
- data.putCharSequence("class", cls);
- msg.setData(data);
- mHandler.sendMessage(msg);
- Log.w(TAG, "Exit MSG_CONNECT_PLAYER for package = " + packageName);
- }
-
- public void updateBrowsablePlayerList(String packageName) {
- if (packageName == null || packageName.isEmpty())
- return;
- if (mBrowsablePlayerList.contains(packageName)) {
- Log.w(TAG, "Remove pkg" + packageName + "from list" + mBrowsablePlayerList);
- mBrowsablePlayerList.remove(packageName);
- }
- }
-
- public boolean isPackageInMBSList(String packageName) {
- if (packageName == null || packageName.isEmpty())
- return false;
- Log.w(TAG, "isPlayerConnectedMBS for package = " + packageName);
-
- // Wait while pending messages are in queue
- while ((mHandler != null) && (mHandler.hasMessages(MSG_CONNECT_PLAYER)
- || mHandler.hasMessages(MSG_TIMEOUT))) {
- try {
- Log.d(TAG, "Connection with MBS ongoing, sleep for 200 ms and recheck");
- Thread.sleep(200);
- } catch (InterruptedException e) {
- Log.w(TAG, "Interrupt sleep caught Exception");
- }
- }
-
- Log.w(TAG, "isPlayerConnectedMBS for package = " + mBrowsablePlayerList);
- for (String pkg : mBrowsablePlayerList) {
- if (packageName.equals(pkg))
- return true;
- }
- return false;
- }
-
- public void start() {
- if (mHandler == null) {
- Log.w(TAG, "start");
- mHandlerThread = new HandlerThread("BrowseMediaHandler");
- mHandlerThread.start();
- mHandler = new BrowseMediaHandler(mHandlerThread.getLooper());
- }
- Log.w(TAG, "start exit");
- }
-
public void setCurrentPackage(String packageName, String cls) {
Log.w(TAG, "Set current Browse based on Addr Player as " + packageName);
mCurrentBrowsePackage = packageName;
@@ -602,27 +486,9 @@
/* called when connection to media player is closed */
public void cleanup() {
- disconnect();
if (DEBUG) {
Log.d(TAG, "cleanup");
}
- mBrowsablePlayerList.clear();
- if (mHandler != null) {
- Log.d(TAG, "cleanup handlers");
- mHandler.removeCallbacksAndMessages(null);
- Looper looper = mHandler.getLooper();
- if (looper != null)
- looper.quit();
- }
- if (mHandlerThread != null) {
- mHandlerThread.quitSafely();
- }
- }
-
- public void disconnect() {
- if (DEBUG) {
- Log.d(TAG, "disconnect");
- }
if (mConnState != DISCONNECTED) {
if (mMediaBrowser != null) mMediaBrowser.disconnect();
@@ -930,7 +796,7 @@
/* Set display name for current item */
folderDataNative.mDisplayNames[itemIndex] =
- getAttrValue(AvrcpConstants.ATTRID_TITLE, item);
+ getAttrValue(bdaddr, AvrcpConstants.ATTRID_TITLE, item);
int maxAttributesRequested = 0;
boolean isAllAttribRequested = false;
@@ -954,7 +820,7 @@
int attribId =
isAllAttribRequested ? (idx + 1) : mFolderItemsReqObj.mAttrIDs[idx];
- value = getAttrValue(attribId, resultItems.get(itemIndex));
+ value = getAttrValue(bdaddr, attribId, resultItems.get(itemIndex));
if (value != null) {
attrArray.add(value);
attrId.add(attribId);
@@ -986,7 +852,7 @@
mMediaInterface.folderItemsRsp(bdaddr, AvrcpConstants.RSP_NO_ERROR, rspObj);
}
- public static String getAttrValue(int attr, MediaBrowser.MediaItem item) {
+ public String getAttrValue(byte []bdaddr, int attr, MediaBrowser.MediaItem item) {
String attrValue = null;
try {
MediaDescription desc = item.getDescription();
@@ -1022,7 +888,8 @@
break;
case AvrcpConstants.ATTRID_COVER_ART:
- attrValue = Avrcp_ext.getImgHandleFromTitle(desc.getTitle().toString());
+ attrValue = Avrcp_ext.getImgHandleFromTitle(bdaddr,
+ desc.getTitle().toString());
break;
default:
diff --git a/src/com/android/bluetooth/newavrcp/BrowsedPlayerWrapper.java b/src/com/android/bluetooth/avrcp/BrowsedPlayerWrapper.java
similarity index 99%
rename from src/com/android/bluetooth/newavrcp/BrowsedPlayerWrapper.java
rename to src/com/android/bluetooth/avrcp/BrowsedPlayerWrapper.java
index 93afd17..b704aae 100644
--- a/src/com/android/bluetooth/newavrcp/BrowsedPlayerWrapper.java
+++ b/src/com/android/bluetooth/avrcp/BrowsedPlayerWrapper.java
@@ -33,7 +33,7 @@
* Right now this is ok because the BrowsablePlayerConnector will handle timeouts.
*/
class BrowsedPlayerWrapper {
- private static final String TAG = "NewAvrcpBrowsedPlayerWrapper";
+ private static final String TAG = "AvrcpBrowsedPlayerWrapper";
private static final boolean DEBUG = true;
enum ConnectionState {
diff --git a/src/com/android/bluetooth/newavrcp/GPMWrapper.java b/src/com/android/bluetooth/avrcp/GPMWrapper.java
similarity index 97%
rename from src/com/android/bluetooth/newavrcp/GPMWrapper.java
rename to src/com/android/bluetooth/avrcp/GPMWrapper.java
index a7f41fb..87e5ec0 100644
--- a/src/com/android/bluetooth/newavrcp/GPMWrapper.java
+++ b/src/com/android/bluetooth/avrcp/GPMWrapper.java
@@ -25,7 +25,7 @@
* methods to allow Google Play Music to match the default behaviour of MediaPlayerWrapper.
*/
class GPMWrapper extends MediaPlayerWrapper {
- private static final String TAG = "NewAvrcpGPMWrapper";
+ private static final String TAG = "AvrcpGPMWrapper";
private static final boolean DEBUG = true;
@Override
diff --git a/src/com/android/bluetooth/newavrcp/MediaPlayerList.java b/src/com/android/bluetooth/avrcp/MediaPlayerList.java
similarity index 97%
rename from src/com/android/bluetooth/newavrcp/MediaPlayerList.java
rename to src/com/android/bluetooth/avrcp/MediaPlayerList.java
index 74ea039..10d99bf 100644
--- a/src/com/android/bluetooth/newavrcp/MediaPlayerList.java
+++ b/src/com/android/bluetooth/avrcp/MediaPlayerList.java
@@ -57,7 +57,7 @@
* player would effectively cause player switch by sending a play command to that player.
*/
public class MediaPlayerList {
- private static final String TAG = "NewAvrcpMediaPlayerList";
+ private static final String TAG = "AvrcpMediaPlayerList";
private static final boolean DEBUG = true;
static boolean sTesting = false;
@@ -89,6 +89,7 @@
private int mActivePlayerId = NO_ACTIVE_PLAYER;
private AvrcpTargetService.ListCallback mCallback;
+ private BrowsablePlayerConnector mBrowsablePlayerConnector;
interface MediaUpdateCallback {
void run(MediaData data);
@@ -140,8 +141,8 @@
.getPackageManager()
.queryIntentServices(intent, PackageManager.MATCH_ALL);
- BrowsablePlayerConnector.connectToPlayers(mContext, mLooper, playerList,
- (List<BrowsedPlayerWrapper> players) -> {
+ mBrowsablePlayerConnector = BrowsablePlayerConnector.connectToPlayers(mContext, mLooper,
+ playerList, (List<BrowsedPlayerWrapper> players) -> {
Log.i(TAG, "init: Browsable Player list size is " + players.size());
// Check to see if the list has been cleaned up before this completed
@@ -196,6 +197,9 @@
}
mMediaPlayers.clear();
+ if (mBrowsablePlayerConnector != null) {
+ mBrowsablePlayerConnector.cleanup();
+ }
for (BrowsedPlayerWrapper player : mBrowsablePlayers.values()) {
player.disconnect();
}
@@ -207,8 +211,10 @@
}
int getFreeMediaPlayerId() {
- int id = 0;
- while (mMediaPlayerIds.containsValue(++id)) {}
+ int id = 1;
+ while (mMediaPlayerIds.containsValue(id)) {
+ id++;
+ }
return id;
}
diff --git a/src/com/android/bluetooth/newavrcp/MediaPlayerWrapper.java b/src/com/android/bluetooth/avrcp/MediaPlayerWrapper.java
similarity index 95%
rename from src/com/android/bluetooth/newavrcp/MediaPlayerWrapper.java
rename to src/com/android/bluetooth/avrcp/MediaPlayerWrapper.java
index 256e771..e4176b3 100644
--- a/src/com/android/bluetooth/newavrcp/MediaPlayerWrapper.java
+++ b/src/com/android/bluetooth/avrcp/MediaPlayerWrapper.java
@@ -23,10 +23,11 @@
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
-import android.support.annotation.GuardedBy;
-import android.support.annotation.VisibleForTesting;
import android.util.Log;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+
import java.util.List;
import java.util.Objects;
@@ -38,8 +39,8 @@
* with that.
*/
class MediaPlayerWrapper {
- private static final String TAG = "NewAvrcpMediaPlayerWrapper";
- private static final boolean DEBUG = true;
+ private static final String TAG = "AvrcpMediaPlayerWrapper";
+ private static final boolean DEBUG = false;
static boolean sTesting = false;
private MediaController mMediaController;
@@ -61,14 +62,18 @@
void mediaUpdatedCallback(MediaData data);
}
- boolean isReady() {
+ boolean isPlaybackStateReady() {
if (getPlaybackState() == null) {
- d("isReady(): PlaybackState is null");
+ d("isPlaybackStateReady(): PlaybackState is null");
return false;
}
+ return true;
+ }
+
+ boolean isMetadataReady() {
if (getMetadata() == null) {
- d("isReady(): Metadata is null");
+ d("isMetadataReady(): Metadata is null");
return false;
}
@@ -371,13 +376,16 @@
@Override
public void onMetadataChanged(@Nullable MediaMetadata metadata) {
- if (!isReady()) {
+ if (!isMetadataReady()) {
Log.v(TAG, "onMetadataChanged(): " + mPackageName
+ " tried to update with no queue");
return;
}
- Log.v(TAG, "onMetadataChanged(): " + mPackageName + " : " + Util.toMetadata(metadata));
+ if (DEBUG) {
+ Log.v(TAG, "onMetadataChanged(): " + mPackageName + " : "
+ + Util.toMetadata(metadata));
+ }
if (!Objects.equals(metadata, getMetadata())) {
e("The callback metadata doesn't match controller metadata");
@@ -402,7 +410,7 @@
@Override
public void onPlaybackStateChanged(@Nullable PlaybackState state) {
- if (!isReady()) {
+ if (!isPlaybackStateReady()) {
Log.v(TAG, "onPlaybackStateChanged(): " + mPackageName
+ " tried to update with no queue");
return;
@@ -431,7 +439,7 @@
@Override
public void onQueueChanged(@Nullable List<MediaSession.QueueItem> queue) {
- if (!isReady()) {
+ if (!isPlaybackStateReady() || !isMetadataReady()) {
Log.v(TAG, "onQueueChanged(): " + mPackageName
+ " tried to update with no queue");
return;
diff --git a/src/com/android/bluetooth/newavrcp/helpers/AvrcpPassthrough.java b/src/com/android/bluetooth/avrcp/helpers/AvrcpPassthrough.java
similarity index 100%
rename from src/com/android/bluetooth/newavrcp/helpers/AvrcpPassthrough.java
rename to src/com/android/bluetooth/avrcp/helpers/AvrcpPassthrough.java
diff --git a/src/com/android/bluetooth/newavrcp/helpers/Folder.java b/src/com/android/bluetooth/avrcp/helpers/Folder.java
similarity index 100%
rename from src/com/android/bluetooth/newavrcp/helpers/Folder.java
rename to src/com/android/bluetooth/avrcp/helpers/Folder.java
diff --git a/src/com/android/bluetooth/newavrcp/helpers/ListItem.java b/src/com/android/bluetooth/avrcp/helpers/ListItem.java
similarity index 100%
rename from src/com/android/bluetooth/newavrcp/helpers/ListItem.java
rename to src/com/android/bluetooth/avrcp/helpers/ListItem.java
diff --git a/src/com/android/bluetooth/newavrcp/helpers/MediaData.java b/src/com/android/bluetooth/avrcp/helpers/MediaData.java
similarity index 100%
rename from src/com/android/bluetooth/newavrcp/helpers/MediaData.java
rename to src/com/android/bluetooth/avrcp/helpers/MediaData.java
diff --git a/src/com/android/bluetooth/newavrcp/helpers/Metadata.java b/src/com/android/bluetooth/avrcp/helpers/Metadata.java
similarity index 100%
rename from src/com/android/bluetooth/newavrcp/helpers/Metadata.java
rename to src/com/android/bluetooth/avrcp/helpers/Metadata.java
diff --git a/src/com/android/bluetooth/newavrcp/helpers/PlayStatus.java b/src/com/android/bluetooth/avrcp/helpers/PlayStatus.java
similarity index 100%
rename from src/com/android/bluetooth/newavrcp/helpers/PlayStatus.java
rename to src/com/android/bluetooth/avrcp/helpers/PlayStatus.java
diff --git a/src/com/android/bluetooth/newavrcp/helpers/PlayerInfo.java b/src/com/android/bluetooth/avrcp/helpers/PlayerInfo.java
similarity index 100%
rename from src/com/android/bluetooth/newavrcp/helpers/PlayerInfo.java
rename to src/com/android/bluetooth/avrcp/helpers/PlayerInfo.java
diff --git a/src/com/android/bluetooth/newavrcp/helpers/Util.java b/src/com/android/bluetooth/avrcp/helpers/Util.java
similarity index 99%
rename from src/com/android/bluetooth/newavrcp/helpers/Util.java
rename to src/com/android/bluetooth/avrcp/helpers/Util.java
index eb84b0a..e09377b 100644
--- a/src/com/android/bluetooth/newavrcp/helpers/Util.java
+++ b/src/com/android/bluetooth/avrcp/helpers/Util.java
@@ -29,7 +29,7 @@
import java.util.List;
class Util {
- public static String TAG = "NewAvrcpUtil";
+ public static String TAG = "AvrcpUtil";
public static boolean DEBUG = false;
private static final String GPM_KEY = "com.google.android.music.mediasession.music_metadata";
diff --git a/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerService.java b/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerService.java
index bf60f38..a0fd09c 100644
--- a/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerService.java
+++ b/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerService.java
@@ -20,41 +20,35 @@
import android.bluetooth.BluetoothAvrcpPlayerSettings;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
-import android.bluetooth.BluetoothUuid;
import android.bluetooth.IBluetoothAvrcpController;
+import android.content.Intent;
import android.media.MediaDescription;
-import android.media.MediaMetadata;
-import android.media.browse.MediaBrowser;
import android.media.browse.MediaBrowser.MediaItem;
import android.media.session.PlaybackState;
import android.os.Bundle;
-import android.os.Message;
-import android.os.SystemProperties;
import android.util.Log;
-import com.android.bluetooth.a2dpsink.A2dpSinkService;
-import com.android.bluetooth.btservice.AdapterService;
-import com.android.bluetooth.btservice.ProfileService;
import com.android.bluetooth.Utils;
+import com.android.bluetooth.btservice.ProfileService;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
-import java.util.Objects;
+import java.util.Map;
import java.util.Set;
import java.util.UUID;
-
import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
/**
* Provides Bluetooth AVRCP Controller profile, as a service in the Bluetooth application.
*/
public class AvrcpControllerService extends ProfileService {
static final String TAG = "AvrcpControllerService";
- static final String LOG_TAG = "AvrcpController";
+ static final int MAXIMUM_CONNECTED_DEVICES = 5;
static final boolean DBG = true;
- static final boolean VDBG = Log.isLoggable(LOG_TAG, Log.VERBOSE);
+ static final boolean VDBG = true;
+
+ public static final String MEDIA_ITEM_UID_KEY = "media-item-uid-key";
/*
* Play State Values from JNI
*/
@@ -65,97 +59,19 @@
private static final byte JNI_PLAY_STATUS_REV_SEEK = 0x04;
private static final byte JNI_PLAY_STATUS_ERROR = -1;
- /*
- * Browsing Media Item Attribute IDs
- * This should be kept in sync with BTRC_MEDIA_ATTR_ID_* in bt_rc.h
+ /* Folder/Media Item scopes.
+ * Keep in sync with AVRCP 1.6 sec. 6.10.1
*/
- private static final int JNI_MEDIA_ATTR_ID_INVALID = -1;
- private static final int JNI_MEDIA_ATTR_ID_TITLE = 0x00000001;
- private static final int JNI_MEDIA_ATTR_ID_ARTIST = 0x00000002;
- private static final int JNI_MEDIA_ATTR_ID_ALBUM = 0x00000003;
- private static final int JNI_MEDIA_ATTR_ID_TRACK_NUM = 0x00000004;
- private static final int JNI_MEDIA_ATTR_ID_NUM_TRACKS = 0x00000005;
- private static final int JNI_MEDIA_ATTR_ID_GENRE = 0x00000006;
- private static final int JNI_MEDIA_ATTR_ID_PLAYING_TIME = 0x00000007;
+ public static final byte BROWSE_SCOPE_PLAYER_LIST = 0x00;
+ public static final byte BROWSE_SCOPE_VFS = 0x01;
+ public static final byte BROWSE_SCOPE_SEARCH = 0x02;
+ public static final byte BROWSE_SCOPE_NOW_PLAYING = 0x03;
- /*
- * Browsing folder types
- * This should be kept in sync with BTRC_FOLDER_TYPE_* in bt_rc.h
+ /* Folder navigation directions
+ * This is borrowed from AVRCP 1.6 spec and must be kept with same values
*/
- private static final int JNI_FOLDER_TYPE_TITLES = 0x01;
- private static final int JNI_FOLDER_TYPE_ALBUMS = 0x02;
- private static final int JNI_FOLDER_TYPE_ARTISTS = 0x03;
- private static final int JNI_FOLDER_TYPE_GENRES = 0x04;
- private static final int JNI_FOLDER_TYPE_PLAYLISTS = 0x05;
- private static final int JNI_FOLDER_TYPE_YEARS = 0x06;
-
- /*
- * AVRCP Error types as defined in spec. Also they should be in sync with btrc_status_t.
- * NOTE: Not all may be defined.
- */
- private static final int JNI_AVRC_STS_NO_ERROR = 0x04;
- private static final int JNI_AVRC_INV_RANGE = 0x0b;
-
- /**
- * Intent used to broadcast the change in browse connection state of the AVRCP Controller
- * profile.
- *
- * <p>This intent will have 2 extras:
- * <ul>
- * <li> {@link BluetoothProfile#EXTRA_STATE} - The current state of the profile. </li>
- * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li>
- * </ul>
- *
- * <p>{@link #EXTRA_STATE} can be any of
- * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING},
- * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}.
- *
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
- * receive.
- */
- public static final String ACTION_BROWSE_CONNECTION_STATE_CHANGED =
- "android.bluetooth.avrcp-controller.profile.action.BROWSE_CONNECTION_STATE_CHANGED";
-
- /**
- * intent used to broadcast the change in metadata state of playing track on the avrcp
- * ag.
- *
- * <p>this intent will have the two extras:
- * <ul>
- * <li> {@link #extra_metadata} - {@link mediametadata} containing the current metadata.</li>
- * <li> {@link #extra_playback} - {@link playbackstate} containing the current playback
- * state. </li>
- * </ul>
- */
- public static final String ACTION_TRACK_EVENT =
- "android.bluetooth.avrcp-controller.profile.action.TRACK_EVENT";
-
- /**
- * Intent used to broadcast the change of folder list.
- *
- * <p>This intent will have the one extra:
- * <ul>
- * <li> {@link #EXTRA_FOLDER_LIST} - array of {@link MediaBrowser#MediaItem}
- * containing the folder listing of currently selected folder.
- * </ul>
- */
- public static final String ACTION_FOLDER_LIST =
- "android.bluetooth.avrcp-controller.profile.action.FOLDER_LIST";
-
- public static final String EXTRA_FOLDER_LIST =
- "android.bluetooth.avrcp-controller.profile.extra.FOLDER_LIST";
-
- public static final String EXTRA_FOLDER_ID = "com.android.bluetooth.avrcp.EXTRA_FOLDER_ID";
- public static final String EXTRA_FOLDER_BT_ID =
- "com.android.bluetooth.avrcp-controller.EXTRA_FOLDER_BT_ID";
-
- public static final String EXTRA_METADATA =
- "android.bluetooth.avrcp-controller.profile.extra.METADATA";
-
- public static final String EXTRA_PLAYBACK =
- "android.bluetooth.avrcp-controller.profile.extra.PLAYBACK";
-
- public static final String MEDIA_ITEM_UID_KEY = "media-item-uid-key";
+ public static final byte FOLDER_NAVIGATION_DIRECTION_UP = 0x00;
+ public static final byte FOLDER_NAVIGATION_DIRECTION_DOWN = 0x01;
/*
* KeyCoded for Pass Through Commands
@@ -174,575 +90,117 @@
public static final int KEY_STATE_PRESSED = 0;
public static final int KEY_STATE_RELEASED = 1;
- /* Group Navigation Key Codes */
- public static final int PASS_THRU_CMD_ID_NEXT_GRP = 0x00;
- public static final int PASS_THRU_CMD_ID_PREV_GRP = 0x01;
+ public static final String ACTION_TRACK_EVENT =
+ "android.bluetooth.avrcp-controller.profile.action.TRACK_EVENT";
+ public static final String EXTRA_PLAYBACK =
+ "android.bluetooth.avrcp-controller.profile.extra.PLAYBACK";
- /* Folder navigation directions
- * This is borrowed from AVRCP 1.6 spec and must be kept with same values
- */
- public static final int FOLDER_NAVIGATION_DIRECTION_UP = 0x00;
- public static final int FOLDER_NAVIGATION_DIRECTION_DOWN = 0x01;
- /* Folder/Media Item scopes.
- * Keep in sync with AVRCP 1.6 sec. 6.10.1
- */
- public static final int BROWSE_SCOPE_PLAYER_LIST = 0x00;
- public static final int BROWSE_SCOPE_VFS = 0x01;
- public static final int BROWSE_SCOPE_SEARCH = 0x02;
- public static final int BROWSE_SCOPE_NOW_PLAYING = 0x03;
- private static AvrcpControllerService sAvrcpControllerService;
- private AdapterService mAdapterService;
- // UID size is 8 bytes (AVRCP 1.6 spec)
- private static final byte[] EMPTY_UID = {0, 0, 0, 0, 0, 0, 0, 0};
+ static BrowseTree sBrowseTree;
+ private static AvrcpControllerService sService;
+ private final BluetoothAdapter mAdapter;
- // We only support one device.
- private BluetoothDevice mConnectedDevice = null;
- // If browse is supported (only valid if mConnectedDevice != null).
- private boolean mBrowseConnected = false;
- // Caches the current browse folder. If this is null then root is the currently browsed folder
- // (which also has no UID).
- private String mCurrentBrowseFolderUID = null;
-
- // HashMap of state machine of AVRCP Controller Connection
- private static final ConcurrentMap<BluetoothDevice,
- AvrcpControllerStateMachine> mStateMachines = new ConcurrentHashMap<>();
-
- private static int mMaxAllowedAvrcpConnection = 1;
+ protected Map<BluetoothDevice, AvrcpControllerStateMachine> mDeviceStateMap =
+ new ConcurrentHashMap<>(1);
static {
classInitNative();
}
public AvrcpControllerService() {
- initNative();
- }
-
- @Override
- protected IProfileServiceBinder initBinder() {
- return new BluetoothAvrcpControllerBinder(this);
+ super();
+ mAdapter = BluetoothAdapter.getDefaultAdapter();
}
@Override
protected boolean start() {
- Log.d(TAG, "start()");
+ initNative();
+ sBrowseTree = new BrowseTree(null);
+ sService = this;
- mAdapterService = Objects.requireNonNull(AdapterService.getAdapterService(),
- "AdapterService cansinkstatenot be null when A2dpService starts");
- mMaxAllowedAvrcpConnection = Math.min(
- SystemProperties.getInt("persist.vendor.bt.a2dp.sink_conn", 1),
- A2dpSinkService.MAX_ALLOWED_SINK_CONNECTIONS);
- setAvrcpControllerService(this);
+ // Start the media browser service.
+ Intent startIntent = new Intent(this, BluetoothMediaBrowserService.class);
+ startService(startIntent);
return true;
}
@Override
protected boolean stop() {
- setAvrcpControllerService(null);
- synchronized (mStateMachines) {
- for (AvrcpControllerStateMachine mAvrcpCtSm : mStateMachines.values()) {
- mAvrcpCtSm.doQuit();
- }
- mStateMachines.clear();
+ Intent stopIntent = new Intent(this, BluetoothMediaBrowserService.class);
+ stopService(stopIntent);
+ for (AvrcpControllerStateMachine stateMachine : mDeviceStateMap.values()) {
+ stateMachine.doQuit();
+ stateMachine.quitNow();
}
+
+ sService = null;
+ sBrowseTree = null;
return true;
}
- //API Methods
-
- public static synchronized AvrcpControllerService getAvrcpControllerService() {
- if (sAvrcpControllerService == null) {
- Log.w(TAG, "getAvrcpControllerService(): service is null");
- return null;
- }
- if (!sAvrcpControllerService.isAvailable()) {
- Log.w(TAG, "getAvrcpControllerService(): service is not available ");
- return null;
- }
- return sAvrcpControllerService;
+ public static AvrcpControllerService getAvrcpControllerService() {
+ return sService;
}
- private static synchronized void setAvrcpControllerService(AvrcpControllerService instance) {
- if (DBG) {
- Log.d(TAG, "setAvrcpControllerService(): set to: " + instance);
- }
- sAvrcpControllerService = instance;
+ protected AvrcpControllerStateMachine newStateMachine(BluetoothDevice device) {
+ return new AvrcpControllerStateMachine(device, this);
}
- public synchronized List<BluetoothDevice> getConnectedDevices() {
- enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- List<BluetoothDevice> devices = new ArrayList<BluetoothDevice>();
- for (AvrcpControllerStateMachine mAvrcpSm: mStateMachines.values()) {
- devices.add(mAvrcpSm.getDevice());
+ private void refreshContents(BrowseTree.BrowseNode node) {
+ if (node.mDevice == null) {
+ return;
}
- return devices;
+ AvrcpControllerStateMachine stateMachine = getStateMachine(node.mDevice);
+ if (stateMachine != null) {
+ stateMachine.requestContents(node);
+ }
}
+ /*Java API*/
+
/**
- * This function only supports STATE_CONNECTED
+ * Get a List of MediaItems that are children of the specified media Id
+ *
+ * @param parentMediaId The player or folder to get the contents of
+ * @return List of Children if available, an empty list if there are none,
+ * or null if a search must be performed.
*/
- public synchronized List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
- enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- List<BluetoothDevice> devices = new ArrayList<>();
- Set<BluetoothDevice> bondedDevices = mAdapter.getBondedDevices();
- synchronized (mStateMachines) {
- for (BluetoothDevice device : bondedDevices) {
- if (!BluetoothUuid.isUuidPresent(mAdapterService.getRemoteUuids(device),
- BluetoothUuid.AvrcpTarget)) {
- continue;
- }
- AvrcpControllerStateMachine sm = mStateMachines.get(device);
- if (sm != null) {
- for (int i = 0; i < states.length; i++) {
- if (BluetoothProfile.STATE_CONNECTED == states[i]) {
- devices.add(device);
- }
- }
+ public synchronized List<MediaItem> getContents(String parentMediaId) {
+ if (DBG) Log.d(TAG, "getContents(" + parentMediaId + ")");
+
+ BrowseTree.BrowseNode requestedNode = sBrowseTree.findBrowseNodeByID(parentMediaId);
+ if (requestedNode == null) {
+ for (AvrcpControllerStateMachine stateMachine : mDeviceStateMap.values()) {
+ requestedNode = stateMachine.findNode(parentMediaId);
+ if (requestedNode != null) {
+ Log.d(TAG, "Found a node");
+ break;
}
}
- return devices;
- }
- }
-
- public synchronized int getConnectionState(BluetoothDevice device) {
- enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- return (mStateMachines.get(device) != null ? BluetoothProfile.STATE_CONNECTED
- : BluetoothProfile.STATE_DISCONNECTED);
- }
-
- public synchronized void sendGroupNavigationCmd(BluetoothDevice device, int keyCode,
- int keyState) {
- Log.v(TAG, "sendGroupNavigationCmd keyCode: " + keyCode + " keyState: " + keyState
- + ", device:" + device);
- if (device == null) {
- Log.e(TAG, "sendGroupNavigationCmd device is null");
}
- if (!(mStateMachines.containsKey(device))) {
- Log.e(TAG, " Device " + device + "does not match connected devices");
- return;
- }
- enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- AvrcpControllerStateMachine mAvrcpCtSm = getAvrcpCtStateMachine(device);
- if (mAvrcpCtSm == null)
- return;
- Message msg = mAvrcpCtSm.obtainMessage(
- AvrcpControllerStateMachine.MESSAGE_SEND_GROUP_NAVIGATION_CMD,
- keyCode, keyState, device);
- mAvrcpCtSm.sendMessage(msg);
- }
-
- public synchronized void sendPassThroughCmd(BluetoothDevice device, int keyCode, int keyState) {
- Log.v(TAG, "sendPassThroughCmd keyCode: " + keyCode + " keyState: " + keyState
- + ", To: " + device);
- if (device == null) {
- Log.e(TAG, "sendPassThroughCmd: Device is null");
- return;
- }
-
- if (!mStateMachines.containsKey(device)) {
- Log.e(TAG, " Device " + device + " does not match connected devices");
- return;
- }
-
- enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- AvrcpControllerStateMachine mAvrcpCtSm = getAvrcpCtStateMachine(device);
- if (mAvrcpCtSm == null)
- return;
- Message msg =
- mAvrcpCtSm.obtainMessage(AvrcpControllerStateMachine.MESSAGE_SEND_PASS_THROUGH_CMD,
- keyCode, keyState, device);
- mAvrcpCtSm.sendMessage(msg);
- }
-
- public void startAvrcpUpdates(BluetoothDevice device) {
- AvrcpControllerStateMachine mAvrcpCtSm = getAvrcpCtStateMachine(device);
- if (mAvrcpCtSm == null)
- return;
- mAvrcpCtSm.obtainMessage(AvrcpControllerStateMachine.MESSAGE_START_METADATA_BROADCASTS)
- .sendToTarget();
- }
-
- public void stopAvrcpUpdates(BluetoothDevice device) {
- AvrcpControllerStateMachine mAvrcpCtSm = getAvrcpCtStateMachine(device);
- if (mAvrcpCtSm == null)
- return;
- mAvrcpCtSm.obtainMessage(AvrcpControllerStateMachine.MESSAGE_STOP_METADATA_BROADCASTS)
- .sendToTarget();
- }
-
- public synchronized MediaMetadata getMetaData(BluetoothDevice device) {
- if (DBG) {
- Log.d(TAG, "getMetaData");
- }
- enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- if (device == null) {
- Log.e(TAG, "getMetadata device is null");
+ if (requestedNode == null) {
+ if (DBG) Log.d(TAG, "Didn't find a node");
return null;
- }
-
- if (!mStateMachines.containsKey(device)) {
- return null;
- }
- AvrcpControllerStateMachine mAvrcpCtSm = getAvrcpCtStateMachine(device);
- if (mAvrcpCtSm == null)
- return null;
- return mAvrcpCtSm.getCurrentMetaData();
- }
-
- public PlaybackState getPlaybackState(BluetoothDevice device) {
- // Get the cached state by default.
- return getPlaybackState(device, true);
- }
-
- // cached can be used to force a getPlaybackState command. Useful for PTS testing.
- public synchronized PlaybackState getPlaybackState(BluetoothDevice device, boolean cached) {
- if (DBG) {
- Log.d(TAG, "getPlayBackState device = " + device);
- }
-
- if (device == null) {
- Log.e(TAG, "getPlaybackState device is null");
- return null;
- }
-
- if (!mStateMachines.containsKey(device)) {
- Log.e(TAG, "Device " + device + " does not match connected devices");
- return null;
-
- }
- enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- AvrcpControllerStateMachine mAvrcpCtSm = getAvrcpCtStateMachine(device);
- if (mAvrcpCtSm == null)
- return null;
- return mAvrcpCtSm.getCurrentPlayBackState(cached);
- }
-
- public synchronized BluetoothAvrcpPlayerSettings getPlayerSettings(BluetoothDevice device) {
- if (DBG) {
- Log.d(TAG, "getPlayerApplicationSetting ");
- }
-
- if (device == null) {
- Log.e(TAG, "getPlayerSettings device is null");
- return null;
- }
-
- if (!mStateMachines.containsKey(device)) {
- Log.e(TAG, " Device " + device + "does not match connected devices");
- return null;
- }
-
- enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-
- /* Do nothing */
- return null;
- }
-
- public boolean setPlayerApplicationSetting(BluetoothAvrcpPlayerSettings plAppSetting) {
- if (DBG) {
- Log.d(TAG, "getPlayerApplicationSetting");
- }
-
- /* Do nothing */
- return false;
- }
-
- /**
- * Fetches the list of children for the parentID node.
- *
- * This function manages the overall tree for browsing structure.
- *
- * Arguments:
- * device - Device to browse content for.
- * parentMediaId - ID of the parent that we need to browse content for. Since most
- * of the players are database unware, fetching a root invalidates all the children.
- * start - number of item to start scanning from
- * items - number of items to fetch
- */
- public synchronized boolean getChildren(BluetoothDevice device, String parentMediaId, int start,
- int items) {
- if (DBG) {
- Log.d(TAG, "getChildren device = " + device + " parent " + parentMediaId);
- }
-
- if (device == null) {
- Log.e(TAG, "getChildren device is null");
- return false;
- }
-
- if (!mStateMachines.containsKey(device)) {
- Log.e(TAG, "getChildren device " + device + " does not match connected devices");
- return false;
- }
-
- if (!mBrowseConnected) {
- Log.e(TAG, "getChildren browse not yet connected");
- return false;
- }
- AvrcpControllerStateMachine mAvrcpCtSm = getAvrcpCtStateMachine(device);
- if (mAvrcpCtSm == null)
- return false;
-
- if (!mAvrcpCtSm.isConnected()) {
- return false;
- }
- mAvrcpCtSm.getChildren(parentMediaId, start, items);
- return true;
- }
-
- public synchronized boolean getNowPlayingList(BluetoothDevice device, String id, int start,
- int items) {
- if (DBG) {
- Log.d(TAG, "getNowPlayingList device = " + device + " start = " + start + "items = "
- + items);
- }
-
- if (device == null) {
- Log.e(TAG, "getNowPlayingList device is null");
- return false;
- }
-
- if (!mStateMachines.containsKey(device)) {
- Log.e(TAG,
- "getNowPlayingList device " + device + " does not match connected devices");
- return false;
- }
-
- if (!mBrowseConnected) {
- Log.e(TAG, "getNowPlayingList browse not yet connected");
- return false;
- }
-
- enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- AvrcpControllerStateMachine mAvrcpCtSm = getAvrcpCtStateMachine(device);
- if (mAvrcpCtSm == null)
- return false;
- Message msg =
- mAvrcpCtSm.obtainMessage(AvrcpControllerStateMachine.MESSAGE_GET_NOW_PLAYING_LIST,
- start, items, id);
- mAvrcpCtSm.sendMessage(msg);
- return true;
- }
-
- public synchronized boolean getFolderList(BluetoothDevice device, String id, int start,
- int items) {
- if (DBG) {
- Log.d(TAG, "getFolderListing device = " + device + " start = " + start + "items = "
- + items);
- }
-
- if (device == null) {
- Log.e(TAG, "getFolderListing device is null");
- return false;
- }
-
- if (!mStateMachines.containsKey(device)) {
- Log.e(TAG, "getFolderListing device " + device + " does not match Connected Devices");
- return false;
- }
-
- if (!mBrowseConnected) {
- Log.e(TAG, "getFolderListing browse not yet connected");
- return false;
- }
-
- enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- AvrcpControllerStateMachine mAvrcpCtSm = getAvrcpCtStateMachine(device);
- if (mAvrcpCtSm == null)
- return false;
- Message msg =
- mAvrcpCtSm.obtainMessage(AvrcpControllerStateMachine.MESSAGE_GET_FOLDER_LIST, start,
- items, id);
- mAvrcpCtSm.sendMessage(msg);
- return true;
- }
-
- public synchronized boolean getPlayerList(BluetoothDevice device, int start, int items) {
- if (DBG) {
- Log.d(TAG,
- "getPlayerList device = " + device + " start = " + start + "items = " + items);
- }
-
- if (device == null) {
- Log.e(TAG, "getPlayerList device is null");
- return false;
- }
-
- if (!mStateMachines.containsKey(device)) {
- Log.e(TAG, "getPlayerList device " + device + " does not match Connected Devices");
- return false;
- }
-
- if (!mBrowseConnected) {
- Log.e(TAG, "getPlayerList browse not yet connected");
- return false;
- }
-
- enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- AvrcpControllerStateMachine mAvrcpCtSm = getAvrcpCtStateMachine(device);
- if (mAvrcpCtSm == null)
- return false;
- Message msg =
- mAvrcpCtSm.obtainMessage(AvrcpControllerStateMachine.MESSAGE_GET_PLAYER_LIST, start,
- items);
- mAvrcpCtSm.sendMessage(msg);
- return true;
- }
-
- public synchronized boolean changeFolderPath(BluetoothDevice device, int direction, String uid,
- String fid) {
- if (DBG) {
- Log.d(TAG, "changeFolderPath device = " + device + " direction " + direction + " uid "
- + uid);
- }
-
- if (device == null) {
- Log.e(TAG, "changeFolderPath device is null");
- return false;
- }
-
- if (!mStateMachines.containsKey(device)) {
- Log.e(TAG, "changeFolderPath device " + device + " does not match Connected Devices");
- return false;
- }
-
- if (!mBrowseConnected) {
- Log.e(TAG, "changeFolderPath browse not yet connected");
- return false;
- }
-
- enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- AvrcpControllerStateMachine mAvrcpCtSm = getAvrcpCtStateMachine(device);
- if (mAvrcpCtSm == null)
- return false;
- Bundle b = new Bundle();
- b.putString(EXTRA_FOLDER_ID, fid);
- b.putString(EXTRA_FOLDER_BT_ID, uid);
- Message msg =
- mAvrcpCtSm.obtainMessage(AvrcpControllerStateMachine.MESSAGE_CHANGE_FOLDER_PATH,
- direction, 0, b);
- mAvrcpCtSm.sendMessage(msg);
- return true;
- }
-
- public synchronized boolean setBrowsedPlayer(BluetoothDevice device, int id, String fid) {
- if (DBG) {
- Log.d(TAG, "setBrowsedPlayer device = " + device + " id" + id + " fid " + fid);
- }
-
- if (device == null) {
- Log.e(TAG, "setBrowsedPlayer device is null");
- return false;
- }
-
- if (!mStateMachines.containsKey(device)) {
- Log.e(TAG, "changeFolderPath device " + device + " does not match Connected Devices");
- return false;
- }
-
- if (!mBrowseConnected) {
- Log.e(TAG, "setBrowsedPlayer browse not yet connected");
- return false;
- }
-
- enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- AvrcpControllerStateMachine mAvrcpCtSm = getAvrcpCtStateMachine(device);
- if (mAvrcpCtSm == null)
- return false;
- Message msg =
- mAvrcpCtSm.obtainMessage(AvrcpControllerStateMachine.MESSAGE_SET_BROWSED_PLAYER, id,
- 0, fid);
- mAvrcpCtSm.sendMessage(msg);
- return true;
- }
-
- public synchronized void fetchAttrAndPlayItem(BluetoothDevice device, String uid) {
- if (DBG) {
- Log.d(TAG, "fetchAttrAndPlayItem device = " + device + " uid " + uid);
- }
-
- if (device == null) {
- Log.e(TAG, "fetchAttrAndPlayItem device is null");
- return;
- }
-
- if (!mStateMachines.containsKey(device)) {
- Log.e(TAG, "fetchAttrAndPlayItem device " + device + " does not match "
- + "Connected Devices");
- return;
- }
-
- if (!mBrowseConnected) {
- Log.e(TAG, "fetchAttrAndPlayItem browse not yet connected");
- return;
- }
- AvrcpControllerStateMachine mAvrcpCtSm = getAvrcpCtStateMachine(device);
- if (mAvrcpCtSm == null)
- return;
- mAvrcpCtSm.fetchAttrAndPlayItem(uid);
- }
-
- // get state AvrcpControllerStateMachine corresponding to Bluetooth Device
- public AvrcpControllerStateMachine getAvrcpCtStateMachine(BluetoothDevice device) {
- if (device == null) {
- return null;
- }
- AvrcpControllerStateMachine avrcpCtSm = mStateMachines.get(device);
- if (avrcpCtSm == null) {
- Log.d(TAG, "State Machine not found for device : " + device);
- }
- return avrcpCtSm;
- }
-
- // create state machine for new connection or return if already existing
- public AvrcpControllerStateMachine getOrCreateAvrcpCtStateMachine(BluetoothDevice device) {
- AvrcpControllerStateMachine avrcpCtSm = null;
- synchronized (mStateMachines) {
- avrcpCtSm = mStateMachines.get(device);
- if (avrcpCtSm != null) {
- Log.d(TAG, "State Machine is already present for device : " + device);
- return avrcpCtSm;
+ } else {
+ if (!requestedNode.isCached()) {
+ if (DBG) Log.d(TAG, "node is not cached");
+ refreshContents(requestedNode);
}
- if (mStateMachines.size() >= mMaxAllowedAvrcpConnection) {
- Log.e(TAG, "Max Allowed AVRCP Connections already reached, Return.");
- return null;
- }
- avrcpCtSm = new AvrcpControllerStateMachine(sAvrcpControllerService, device);
- avrcpCtSm.start();
- mStateMachines.put(device, avrcpCtSm);
- }
- return avrcpCtSm;
- }
-
- protected static void removeStateMachine(BluetoothDevice device) {
- synchronized (mStateMachines) {
- AvrcpControllerStateMachine sm = mStateMachines.get(device);
- if (sm == null) {
- Log.e(TAG, "State Machine already removed for device:" + device);
- return;
- }
- Log.d(TAG, "State Machine removed for device:" + device);
- mStateMachines.remove(device);
- sm.doQuit();
- sm = null;
+ if (DBG) Log.d(TAG, "Returning contents");
+ return requestedNode.getContents();
}
}
- // check if Browsing is connected for device
- public boolean isBrowsingConnected(BluetoothDevice device) {
- AvrcpControllerStateMachine avrcpCtSm = getAvrcpCtStateMachine(device);
- if (avrcpCtSm == null) {
- return false;
- }
- return avrcpCtSm.isBrowsingConnected();
+ @Override
+ protected IProfileServiceBinder initBinder() {
+ return new AvrcpControllerServiceBinder(this);
}
//Binder object: Must be static class or memory leak may occur
- private static class BluetoothAvrcpControllerBinder extends IBluetoothAvrcpController.Stub
+ private static class AvrcpControllerServiceBinder extends IBluetoothAvrcpController.Stub
implements IProfileServiceBinder {
-
private AvrcpControllerService mService;
private AvrcpControllerService getService() {
@@ -751,14 +209,14 @@
return null;
}
- if (mService != null && mService.isAvailable()) {
+ if (mService != null) {
return mService;
}
return null;
}
- BluetoothAvrcpControllerBinder(AvrcpControllerService svc) {
- mService = svc;
+ AvrcpControllerServiceBinder(AvrcpControllerService service) {
+ mService = service;
}
@Override
@@ -790,160 +248,106 @@
if (service == null) {
return BluetoothProfile.STATE_DISCONNECTED;
}
-
- if (device == null) {
- throw new IllegalStateException("Device cannot be null!");
- }
-
return service.getConnectionState(device);
}
@Override
public void sendGroupNavigationCmd(BluetoothDevice device, int keyCode, int keyState) {
- Log.v(TAG, "Binder Call: sendGroupNavigationCmd");
- AvrcpControllerService service = getService();
- if (service == null) {
- return;
- }
+ Log.w(TAG, "sendGroupNavigationCmd not implemented");
+ return;
+ }
- if (device == null) {
- throw new IllegalStateException("Device cannot be null!");
- }
-
- service.sendGroupNavigationCmd(device, keyCode, keyState);
+ @Override
+ public boolean setPlayerApplicationSetting(BluetoothAvrcpPlayerSettings settings) {
+ Log.w(TAG, "setPlayerApplicationSetting not implemented");
+ return false;
}
@Override
public BluetoothAvrcpPlayerSettings getPlayerSettings(BluetoothDevice device) {
- Log.v(TAG, "Binder Call: getPlayerApplicationSetting ");
- AvrcpControllerService service = getService();
- if (service == null) {
- return null;
- }
-
- if (device == null) {
- throw new IllegalStateException("Device cannot be null!");
- }
-
- return service.getPlayerSettings(device);
- }
-
- @Override
- public boolean setPlayerApplicationSetting(BluetoothAvrcpPlayerSettings plAppSetting) {
- Log.v(TAG, "Binder Call: setPlayerApplicationSetting ");
- AvrcpControllerService service = getService();
- if (service == null) {
- return false;
- }
- return service.setPlayerApplicationSetting(plAppSetting);
+ Log.w(TAG, "getPlayerSettings not implemented");
+ return null;
}
}
+
+ /* JNI API*/
// Called by JNI when a passthrough key was received.
private void handlePassthroughRsp(int id, int keyState, byte[] address) {
- Log.d(TAG,
- "passthrough response received as: key: " + id + " state: " + keyState + "address:"
- + address);
+ if (DBG) {
+ Log.d(TAG, "passthrough response received as: key: " + id
+ + " state: " + keyState + "address:" + address);
+ }
}
private void handleGroupNavigationRsp(int id, int keyState) {
- Log.d(TAG, "group navigation response received as: key: " + id + " state: " + keyState);
+ if (DBG) {
+ Log.d(TAG, "group navigation response received as: key: " + id + " state: "
+ + keyState);
+ }
}
// Called by JNI when a device has connected or disconnected.
- private synchronized void onConnectionStateChanged(boolean rcConnected, boolean brConnected,
- byte[] address) {
+ private synchronized void onConnectionStateChanged(boolean remoteControlConnected,
+ boolean browsingConnected, byte[] address) {
BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
- Log.d(TAG, "onConnectionStateChanged: RC = " + rcConnected + ", BR = " + brConnected
- + ", for: " + device);
+ if (DBG) {
+ Log.d(TAG, "onConnectionStateChanged " + remoteControlConnected + " "
+ + browsingConnected + device);
+ }
if (device == null) {
Log.e(TAG, "onConnectionStateChanged Device is null");
return;
}
- // Adjust the AVRCP connection state.
- int oldState = (mStateMachines.containsKey(device) ? BluetoothProfile.STATE_CONNECTED
- : BluetoothProfile.STATE_DISCONNECTED);
- int newState = (rcConnected ? BluetoothProfile.STATE_CONNECTED
- : BluetoothProfile.STATE_DISCONNECTED);
-
- AvrcpControllerStateMachine mAvrcpCtSm = getOrCreateAvrcpCtStateMachine(device);
- if (mAvrcpCtSm == null) {
- return;
- }
-
- if (rcConnected && oldState == BluetoothProfile.STATE_DISCONNECTED) {
- Message msg = mAvrcpCtSm.obtainMessage(
- AvrcpControllerStateMachine.MESSAGE_PROCESS_CONNECTION_CHANGE, newState,
- oldState, device);
- mAvrcpCtSm.sendMessage(msg);
- } else if (!rcConnected && oldState == BluetoothProfile.STATE_CONNECTED) {
- Message msg = mAvrcpCtSm.obtainMessage(
- AvrcpControllerStateMachine.MESSAGE_PROCESS_CONNECTION_CHANGE, newState,
- oldState, device);
- mAvrcpCtSm.sendMessage(msg);
- }
-
- // Adjust the browse connection state. If RC is connected we should have already sent the
- // connection status out.
- if (rcConnected && brConnected) {
- mBrowseConnected = true;
- Message msg = mAvrcpCtSm.obtainMessage(
- AvrcpControllerStateMachine.MESSAGE_PROCESS_BROWSE_CONNECTION_CHANGE);
- msg.arg1 = 1;
- msg.obj = device;
- mAvrcpCtSm.sendMessage(msg);
+ StackEvent event =
+ StackEvent.connectionStateChanged(remoteControlConnected, browsingConnected);
+ AvrcpControllerStateMachine stateMachine = getOrCreateStateMachine(device);
+ if (remoteControlConnected || browsingConnected) {
+ stateMachine.connect(event);
+ } else {
+ stateMachine.disconnect();
}
}
// Called by JNI to notify Avrcp of features supported by the Remote device.
private void getRcFeatures(byte[] address, int features, int caPsm) {
- Log.i(TAG, " getRcFeatures caPsm :" + caPsm);
+ /*Log.i(TAG, " getRcFeatures caPsm :" + caPsm);
BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
- AvrcpControllerStateMachine mAvrcpCtSm = getAvrcpCtStateMachine(device);
- if (mAvrcpCtSm == null)
- return;
Message msg = mAvrcpCtSm.obtainMessage(
AvrcpControllerStateMachine.MESSAGE_PROCESS_RC_FEATURES, features, caPsm, device);
- mAvrcpCtSm.sendMessage(msg);
+ mAvrcpCtSm.sendMessage(msg); */
}
// Called by JNI
private void setPlayerAppSettingRsp(byte[] address, byte accepted) {
- /* Do Nothing. */
+ /* Do Nothing. */
}
// Called by JNI when remote wants to receive absolute volume notifications.
private synchronized void handleRegisterNotificationAbsVol(byte[] address, byte label) {
- Log.d(TAG, "handleRegisterNotificationAbsVol ");
- BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
- if (device != null && !mStateMachines.containsKey(device)) {
- Log.e(TAG, "handleRegisterNotificationAbsVol device not found " + address);
- return;
+ if (DBG) {
+ Log.d(TAG, "handleRegisterNotificationAbsVol");
}
- AvrcpControllerStateMachine mAvrcpCtSm = getAvrcpCtStateMachine(device);
- if (mAvrcpCtSm == null)
- return;
- Message msg = mAvrcpCtSm.obtainMessage(
- AvrcpControllerStateMachine.MESSAGE_PROCESS_REGISTER_ABS_VOL_NOTIFICATION,
- (int) label, 0);
- mAvrcpCtSm.sendMessage(msg);
+ BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
+ AvrcpControllerStateMachine stateMachine = getStateMachine(device);
+ if (stateMachine != null) {
+ stateMachine.sendMessage(
+ AvrcpControllerStateMachine.MESSAGE_PROCESS_REGISTER_ABS_VOL_NOTIFICATION);
+ }
}
// Called by JNI when remote wants to set absolute volume.
private synchronized void handleSetAbsVolume(byte[] address, byte absVol, byte label) {
- Log.d(TAG, "handleSetAbsVolume ");
- BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
- if (device != null && !mStateMachines.containsKey(device)) {
- Log.e(TAG, "handleSetAbsVolume device not found " + address);
- return;
+ if (DBG) {
+ Log.d(TAG, "handleSetAbsVolume ");
}
- AvrcpControllerStateMachine mAvrcpCtSm = getAvrcpCtStateMachine(device);
- if (mAvrcpCtSm == null)
- return;
- Message msg = mAvrcpCtSm.obtainMessage(
- AvrcpControllerStateMachine.MESSAGE_PROCESS_SET_ABS_VOL_CMD, absVol, label);
- mAvrcpCtSm.sendMessage(msg);
+ BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
+ AvrcpControllerStateMachine stateMachine = getStateMachine(device);
+ if (stateMachine != null) {
+ stateMachine.sendMessage(AvrcpControllerStateMachine.MESSAGE_PROCESS_SET_ABS_VOL_CMD,
+ absVol, label);
+ }
}
// Called by JNI when a track changes and local AvrcpController is registered for updates.
@@ -952,39 +356,20 @@
if (DBG) {
Log.d(TAG, "onTrackChanged");
}
+
BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
- if (device != null && !mStateMachines.containsKey(device)) {
- Log.e(TAG, "onTrackChanged device not found " + address);
- return;
+ AvrcpControllerStateMachine stateMachine = getStateMachine(device);
+ if (stateMachine != null) {
+ stateMachine.sendMessage(AvrcpControllerStateMachine.MESSAGE_PROCESS_TRACK_CHANGED,
+ TrackInfo.getMetadata(attributes, attribVals));
}
- List<Integer> attrList = new ArrayList<>();
- for (int attr : attributes) {
- attrList.add(attr);
- }
- List<String> attrValList = Arrays.asList(attribVals);
- TrackInfo trackInfo = new TrackInfo(attrList, attrValList);
- if (VDBG) {
- Log.d(TAG, "onTrackChanged " + trackInfo);
- }
- AvrcpControllerStateMachine mAvrcpCtSm = getAvrcpCtStateMachine(device);
- if (mAvrcpCtSm == null)
- return;
- Message msg = mAvrcpCtSm.obtainMessage(
- AvrcpControllerStateMachine.MESSAGE_PROCESS_TRACK_CHANGED, trackInfo);
- mAvrcpCtSm.sendMessage(msg);
}
- private void onElementAttributeUpdate(byte[] address, byte numAttributes, int[] attributes,
+ private void onElementAttributeUpdate(byte[] address, byte numAttributes, int[] attributes,
String[] attribVals) {
- BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
- AvrcpControllerStateMachine mAvrcpCtSm = getAvrcpCtStateMachine(device);
- if (mAvrcpCtSm == null)
- return;
- CoverArtUtils coverArtUtils= new CoverArtUtils();
- coverArtUtils.onElementAttributeUpdate(address, numAttributes, attributes, attribVals,
- device, mAvrcpCtSm);
- }
+
+ }
// Called by JNI periodically based upon timer to update play position
private synchronized void onPlayPositionChanged(byte[] address, int songLen,
@@ -993,17 +378,12 @@
Log.d(TAG, "onPlayPositionChanged pos " + currSongPosition);
}
BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
- if (device != null && !mStateMachines.containsKey(device)) {
- Log.e(TAG, "onPlayPositionChanged not found device not found " + address);
- return;
+ AvrcpControllerStateMachine stateMachine = getStateMachine(device);
+ if (stateMachine != null) {
+ stateMachine.sendMessage(
+ AvrcpControllerStateMachine.MESSAGE_PROCESS_PLAY_POS_CHANGED,
+ songLen, currSongPosition);
}
- AvrcpControllerStateMachine mAvrcpCtSm = getAvrcpCtStateMachine(device);
- if (mAvrcpCtSm == null)
- return;
- Message msg = mAvrcpCtSm.obtainMessage(
- AvrcpControllerStateMachine.MESSAGE_PROCESS_PLAY_POS_CHANGED,
- songLen, currSongPosition);
- mAvrcpCtSm.sendMessage(msg);
}
// Called by JNI on changes of play status
@@ -1011,11 +391,6 @@
if (DBG) {
Log.d(TAG, "onPlayStatusChanged " + playStatus);
}
- BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
- if (device != null && !mStateMachines.containsKey(device)) {
- Log.e(TAG, "onPlayStatusChanged not found device not found " + address);
- return;
- }
int playbackState = PlaybackState.STATE_NONE;
switch (playStatus) {
case JNI_PLAY_STATUS_STOPPED:
@@ -1031,17 +406,17 @@
playbackState = PlaybackState.STATE_FAST_FORWARDING;
break;
case JNI_PLAY_STATUS_REV_SEEK:
- playbackState = PlaybackState.STATE_FAST_FORWARDING;
+ playbackState = PlaybackState.STATE_REWINDING;
break;
default:
playbackState = PlaybackState.STATE_NONE;
}
- AvrcpControllerStateMachine mAvrcpCtSm = getAvrcpCtStateMachine(device);
- if (mAvrcpCtSm == null)
- return;
- Message msg = mAvrcpCtSm.obtainMessage(
- AvrcpControllerStateMachine.MESSAGE_PROCESS_PLAY_STATUS_CHANGED, playbackState);
- mAvrcpCtSm.sendMessage(msg);
+ BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
+ AvrcpControllerStateMachine stateMachine = getStateMachine(device);
+ if (stateMachine != null) {
+ stateMachine.sendMessage(
+ AvrcpControllerStateMachine.MESSAGE_PROCESS_PLAY_STATUS_CHANGED, playbackState);
+ }
}
// Called by JNI to report remote Player's capabilities
@@ -1051,13 +426,13 @@
Log.d(TAG, "handlePlayerAppSetting rspLen = " + rspLen);
}
BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
- if (device != null && !mStateMachines.containsKey(device)) {
- Log.e(TAG, "handlePlayerAppSetting not found device not found " + address);
- return;
+ AvrcpControllerStateMachine stateMachine = getStateMachine(device);
+ if (stateMachine != null) {
+ PlayerApplicationSettings supportedSettings =
+ PlayerApplicationSettings.makeSupportedSettings(playerAttribRsp);
}
- PlayerApplicationSettings supportedSettings =
- PlayerApplicationSettings.makeSupportedSettings(playerAttribRsp);
/* Do nothing */
+
}
private synchronized void onPlayerAppSettingChanged(byte[] address, byte[] playerAttribRsp,
@@ -1066,63 +441,48 @@
Log.d(TAG, "onPlayerAppSettingChanged ");
}
BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
- if (device != null && !mStateMachines.containsKey(device)) {
- Log.e(TAG, "onPlayerAppSettingChanged: device not found " + address);
- return;
+ AvrcpControllerStateMachine stateMachine = getStateMachine(device);
+ if (stateMachine != null) {
+
+ PlayerApplicationSettings desiredSettings =
+ PlayerApplicationSettings.makeSettings(playerAttribRsp);
}
- PlayerApplicationSettings desiredSettings =
- PlayerApplicationSettings.makeSettings(playerAttribRsp);
/* Do nothing */
}
// Browsing related JNI callbacks.
- void handleGetFolderItemsRsp(int status, MediaItem[] items, byte[] address) {
+ void handleGetFolderItemsRsp(byte[] address, int status, MediaItem[] items) {
if (DBG) {
Log.d(TAG, "handleGetFolderItemsRsp called with status " + status + " items "
+ items.length + " items.");
}
-
- BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
- Log.d(TAG, "handleGetFolderItemsRsp device:" + device);
- AvrcpControllerStateMachine mAvrcpCtSm = getAvrcpCtStateMachine(device);
- if (mAvrcpCtSm == null) {
- return;
- }
-
- if (status == JNI_AVRC_INV_RANGE) {
- Log.w(TAG, "Sending out of range message.");
- // Send a special message since this could be used by state machine
- // to take as a signal that fetch is finished.
- Message msg = mAvrcpCtSm.obtainMessage(
- AvrcpControllerStateMachine.MESSAGE_PROCESS_GET_FOLDER_ITEMS_OUT_OF_RANGE);
- mAvrcpCtSm.sendMessage(msg);
- return;
- }
-
for (MediaItem item : items) {
if (VDBG) {
- Log.d(TAG, "media item: " + item + " uid: " + item.getDescription().getMediaId());
+ Log.d(TAG, "media item: " + item + " uid: "
+ + item.getDescription().getMediaId());
}
}
ArrayList<MediaItem> itemsList = new ArrayList<>();
for (MediaItem item : items) {
itemsList.add(item);
}
- Message msg = mAvrcpCtSm.obtainMessage(
- AvrcpControllerStateMachine.MESSAGE_PROCESS_GET_FOLDER_ITEMS, itemsList);
- mAvrcpCtSm.sendMessage(msg);
+
+ BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
+
+ AvrcpControllerStateMachine stateMachine = getStateMachine(device);
+ if (stateMachine != null) {
+
+ stateMachine.sendMessage(AvrcpControllerStateMachine.MESSAGE_PROCESS_GET_FOLDER_ITEMS,
+ itemsList);
+ }
}
- void handleGetPlayerItemsRsp(AvrcpPlayer[] items, byte[] address) {
- BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
- if (DBG) {
- Log.d(TAG, "handleGetFolderItemsRsp called with " + items.length
- + " items. Device: " + device);
+
+ void handleGetPlayerItemsRsp(byte[] address, AvrcpPlayer[] items) {
+ if (DBG) {
+ Log.d(TAG, "handleGetFolderItemsRsp called with " + items.length + " items.");
}
- AvrcpControllerStateMachine mAvrcpCtSm = getAvrcpCtStateMachine(device);
- if (mAvrcpCtSm == null) {
- return;
- }
+
for (AvrcpPlayer item : items) {
if (VDBG) {
Log.d(TAG, "bt player item: " + item);
@@ -1133,28 +493,31 @@
itemsList.add(p);
}
- Message msg = mAvrcpCtSm.obtainMessage(
- AvrcpControllerStateMachine.MESSAGE_PROCESS_GET_PLAYER_ITEMS, itemsList);
- mAvrcpCtSm.sendMessage(msg);
+ BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
+ AvrcpControllerStateMachine stateMachine = getStateMachine(device);
+ if (stateMachine != null) {
+ stateMachine.sendMessage(AvrcpControllerStateMachine.MESSAGE_PROCESS_GET_PLAYER_ITEMS,
+ itemsList);
+ }
}
// JNI Helper functions to convert native objects to java.
- MediaItem createFromNativeMediaItem(byte[] uid, int type, String name, int[] attrIds,
+ MediaItem createFromNativeMediaItem(long uid, int type, String name, int[] attrIds,
String[] attrVals) {
if (VDBG) {
- Log.d(TAG, "createFromNativeMediaItem uid: " + uid + " type " + type + " name " + name
- + " attrids " + attrIds + " attrVals " + attrVals);
+ Log.d(TAG, "createFromNativeMediaItem uid: " + uid + " type " + type + " name "
+ + name + " attrids " + attrIds + " attrVals " + attrVals);
}
MediaDescription.Builder mdb = new MediaDescription.Builder();
Bundle mdExtra = new Bundle();
- mdExtra.putString(MEDIA_ITEM_UID_KEY, byteUIDToHexString(uid));
+ mdExtra.putLong(MEDIA_ITEM_UID_KEY, uid);
mdb.setExtras(mdExtra);
+
// Generate a random UUID. We do this since database unaware TGs can send multiple
// items with same MEDIA_ITEM_UID_KEY.
mdb.setMediaId(UUID.randomUUID().toString());
-
// Concise readable name.
mdb.setTitle(name);
@@ -1164,23 +527,20 @@
return new MediaItem(mdb.build(), MediaItem.FLAG_PLAYABLE);
}
- MediaItem createFromNativeFolderItem(byte[] uid, int type, String name, int playable) {
+ MediaItem createFromNativeFolderItem(long uid, int type, String name, int playable) {
if (VDBG) {
- Log.d(TAG, "createFromNativeFolderItem uid: " + uid + " type " + type + " name " + name
- + " playable " + playable);
+ Log.d(TAG, "createFromNativeFolderItem uid: " + uid + " type " + type + " name "
+ + name + " playable " + playable);
}
MediaDescription.Builder mdb = new MediaDescription.Builder();
- // Covert the byte to a hex string. The coversion can be done back here to a
- // byte array when needed.
Bundle mdExtra = new Bundle();
- mdExtra.putString(MEDIA_ITEM_UID_KEY, byteUIDToHexString(uid));
+ mdExtra.putLong(MEDIA_ITEM_UID_KEY, uid);
mdb.setExtras(mdExtra);
// Generate a random UUID. We do this since database unaware TGs can send multiple
// items with same MEDIA_ITEM_UID_KEY.
mdb.setMediaId(UUID.randomUUID().toString());
-
// Concise readable name.
mdb.setTitle(name);
@@ -1191,96 +551,188 @@
int playStatus, int playerType) {
if (VDBG) {
Log.d(TAG,
- "createFromNativePlayerItem name: " + name + " transportFlags " + transportFlags
- + " play status " + playStatus + " player type " + playerType);
+ "createFromNativePlayerItem name: " + name + " transportFlags "
+ + transportFlags + " play status " + playStatus + " player type "
+ + playerType);
}
- AvrcpPlayer player = new AvrcpPlayer(id, name, 0, playStatus, playerType);
+ AvrcpPlayer player = new AvrcpPlayer(id, name, transportFlags, playStatus, playerType);
return player;
}
- private void handleChangeFolderRsp(int count, byte[] address) {
- BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
+ private void handleChangeFolderRsp(byte[] address, int count) {
if (DBG) {
- Log.d(TAG, "handleChangeFolderRsp count: " + count + ", device: " + device);
+ Log.d(TAG, "handleChangeFolderRsp count: " + count);
}
- AvrcpControllerStateMachine mAvrcpCtSm = getAvrcpCtStateMachine(device);
- if (mAvrcpCtSm == null) {
- return;
+ BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
+ AvrcpControllerStateMachine stateMachine = getStateMachine(device);
+ if (stateMachine != null) {
+ stateMachine.sendMessage(AvrcpControllerStateMachine.MESSAGE_PROCESS_FOLDER_PATH,
+ count);
}
- Message msg =
- mAvrcpCtSm.obtainMessage(AvrcpControllerStateMachine.MESSAGE_PROCESS_FOLDER_PATH,
- count);
- mAvrcpCtSm.sendMessage(msg);
}
- private void handleSetBrowsedPlayerRsp(int items, int depth, byte[] address) {
- BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
+ private void handleSetBrowsedPlayerRsp(byte[] address, int items, int depth) {
if (DBG) {
- Log.d(TAG, "handleSetBrowsedPlayerRsp depth: " + depth + ", device: " + device);
+ Log.d(TAG, "handleSetBrowsedPlayerRsp depth: " + depth);
}
- AvrcpControllerStateMachine mAvrcpCtSm = getAvrcpCtStateMachine(device);
- if (mAvrcpCtSm == null) {
- return;
+ BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
+
+ AvrcpControllerStateMachine stateMachine = getStateMachine(device);
+ if (stateMachine != null) {
+ stateMachine.sendMessage(AvrcpControllerStateMachine.MESSAGE_PROCESS_SET_BROWSED_PLAYER,
+ items, depth);
}
- Message msg = mAvrcpCtSm.obtainMessage(
- AvrcpControllerStateMachine.MESSAGE_PROCESS_SET_BROWSED_PLAYER, items, depth);
- mAvrcpCtSm.sendMessage(msg);
}
- private void handleSetAddressedPlayerRsp(int status, byte[] address) {
- BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
+ private void handleSetAddressedPlayerRsp(byte[] address, int status) {
if (DBG) {
- Log.d(TAG, "handleSetAddressedPlayerRsp status: " + status + ", device :" + device);
+ Log.d(TAG, "handleSetAddressedPlayerRsp status: " + status);
}
- AvrcpControllerStateMachine mAvrcpCtSm = getAvrcpCtStateMachine(device);
- if (mAvrcpCtSm == null) {
- return;
+ BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
+
+ AvrcpControllerStateMachine stateMachine = getStateMachine(device);
+ if (stateMachine != null) {
+ stateMachine.sendMessage(
+ AvrcpControllerStateMachine.MESSAGE_PROCESS_SET_ADDRESSED_PLAYER);
}
- Message msg = mAvrcpCtSm.obtainMessage(
- AvrcpControllerStateMachine.MESSAGE_PROCESS_SET_ADDRESSED_PLAYER);
- mAvrcpCtSm.sendMessage(msg);
+ }
+
+ private void handleAddressedPlayerChanged(byte[] address, int id) {
+ if (DBG) {
+ Log.d(TAG, "handleAddressedPlayerChanged id: " + id);
+ }
+ BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
+
+ AvrcpControllerStateMachine stateMachine = getStateMachine(device);
+ if (stateMachine != null) {
+ stateMachine.sendMessage(
+ AvrcpControllerStateMachine.MESSAGE_PROCESS_ADDRESSED_PLAYER_CHANGED, id);
+ }
+ }
+
+ private void handleNowPlayingContentChanged(byte[] address) {
+ if (DBG) {
+ Log.d(TAG, "handleNowPlayingContentChanged");
+ }
+ BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
+
+ AvrcpControllerStateMachine stateMachine = getStateMachine(device);
+ if (stateMachine != null) {
+ stateMachine.nowPlayingContentChanged();
+ }
+ }
+
+ /* Generic Profile Code */
+
+ /**
+ * Disconnect the given Bluetooth device.
+ *
+ * @return true if disconnect is successful, false otherwise.
+ */
+ public synchronized boolean disconnect(BluetoothDevice device) {
+ if (DBG) {
+ StringBuilder sb = new StringBuilder();
+ dump(sb);
+ Log.d(TAG, "MAP disconnect device: " + device
+ + ", InstanceMap start state: " + sb.toString());
+ }
+ AvrcpControllerStateMachine stateMachine = mDeviceStateMap.get(device);
+ // a map state machine instance doesn't exist. maybe it is already gone?
+ if (stateMachine == null) {
+ return false;
+ }
+ int connectionState = stateMachine.getState();
+ if (connectionState != BluetoothProfile.STATE_CONNECTED
+ && connectionState != BluetoothProfile.STATE_CONNECTING) {
+ return false;
+ }
+ stateMachine.disconnect();
+ if (DBG) {
+ StringBuilder sb = new StringBuilder();
+ dump(sb);
+ Log.d(TAG, "MAP disconnect device: " + device
+ + ", InstanceMap start state: " + sb.toString());
+ }
+ return true;
+ }
+
+ void removeStateMachine(AvrcpControllerStateMachine stateMachine) {
+ mDeviceStateMap.remove(stateMachine.getDevice());
+ }
+
+ public List<BluetoothDevice> getConnectedDevices() {
+ return getDevicesMatchingConnectionStates(new int[]{BluetoothAdapter.STATE_CONNECTED});
+ }
+
+ protected AvrcpControllerStateMachine getStateMachine(BluetoothDevice device) {
+ return mDeviceStateMap.get(device);
+ }
+
+ protected AvrcpControllerStateMachine getOrCreateStateMachine(BluetoothDevice device) {
+ AvrcpControllerStateMachine stateMachine = mDeviceStateMap.get(device);
+ if (stateMachine == null) {
+ stateMachine = newStateMachine(device);
+ mDeviceStateMap.put(device, stateMachine);
+ stateMachine.start();
+ }
+ return stateMachine;
+ }
+
+ List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
+ if (DBG) Log.d(TAG, "getDevicesMatchingConnectionStates" + Arrays.toString(states));
+ List<BluetoothDevice> deviceList = new ArrayList<>();
+ Set<BluetoothDevice> bondedDevices = mAdapter.getBondedDevices();
+ int connectionState;
+ for (BluetoothDevice device : bondedDevices) {
+ connectionState = getConnectionState(device);
+ if (DBG) Log.d(TAG, "Device: " + device + "State: " + connectionState);
+ for (int i = 0; i < states.length; i++) {
+ if (connectionState == states[i]) {
+ deviceList.add(device);
+ }
+ }
+ }
+ if (DBG) Log.d(TAG, deviceList.toString());
+ Log.d(TAG, "GetDevicesDone");
+ return deviceList;
+ }
+
+ synchronized int getConnectionState(BluetoothDevice device) {
+ AvrcpControllerStateMachine stateMachine = mDeviceStateMap.get(device);
+ return (stateMachine == null) ? BluetoothProfile.STATE_DISCONNECTED
+ : stateMachine.getState();
+ }
+
+ public void onDeviceUpdated(BluetoothDevice device) {
+ AvrcpControllerStateMachine stateMachine = mDeviceStateMap.get(device);
+ if (stateMachine != null) {
+ Log.d(TAG, "Send device: " + device + " updated meassage to Avrcpstatemachine");
+ stateMachine.sendMessage(AvrcpControllerStateMachine.MSG_DEVICE_UPDATED,
+ device);
+ }
}
@Override
public void dump(StringBuilder sb) {
super.dump(sb);
- for (AvrcpControllerStateMachine mAvrcpCtSm: mStateMachines.values())
- mAvrcpCtSm.dump(sb);
+ ProfileService.println(sb, "Devices Tracked = " + mDeviceStateMap.size());
+
+ for (AvrcpControllerStateMachine stateMachine : mDeviceStateMap.values()) {
+ ProfileService.println(sb,
+ "==== StateMachine for " + stateMachine.getDevice() + " ====");
+ stateMachine.dump(sb);
+ }
+ sb.append("\n sBrowseTree: " + sBrowseTree.toString());
}
- public static String byteUIDToHexString(byte[] uid) {
- StringBuilder sb = new StringBuilder();
- for (byte b : uid) {
- sb.append(String.format("%02X", b));
- }
- return sb.toString();
- }
-
- public static byte[] hexStringToByteUID(String uidStr) {
- if (uidStr == null) {
- Log.e(TAG, "Null hex string.");
- return EMPTY_UID;
- } else if (uidStr.length() % 2 == 1) {
- // Odd length strings should not be possible.
- Log.e(TAG, "Odd length hex string " + uidStr);
- return EMPTY_UID;
- }
- int len = uidStr.length();
- byte[] data = new byte[len / 2];
- for (int i = 0; i < len; i += 2) {
- data[i / 2] = (byte) ((Character.digit(uidStr.charAt(i), 16) << 4) + Character.digit(
- uidStr.charAt(i + 1), 16));
- }
- return data;
- }
-
+ /*JNI*/
private static native void classInitNative();
private native void initNative();
private native void cleanupNative();
- static native boolean sendPassThroughCommandNative(byte[] address, int keyCode, int keyState);
+ public native boolean sendPassThroughCommandNative(byte[] address, int keyCode, int keyState);
static native boolean sendGroupNavigationCommandNative(byte[] address, int keyCode,
int keyState);
@@ -1308,9 +760,9 @@
static native void getPlayerListNative(byte[] address, int start, int end);
/* API used to change the folder */
- static native void changeFolderPathNative(byte[] address, byte direction, byte[] uid);
+ static native void changeFolderPathNative(byte[] address, byte direction, long uid);
- static native void playItemNative(byte[] address, byte scope, byte[] uid, int uidCounter);
+ static native void playItemNative(byte[] address, byte scope, long uid, int uidCounter);
static native void setBrowsedPlayerNative(byte[] address, int playerId);
diff --git a/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java b/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java
index 0ac58d5..dc9f76a 100644
--- a/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java
+++ b/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java
@@ -16,24 +16,26 @@
package com.android.bluetooth.avrcpcontroller;
+import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothAvrcpController;
-import android.bluetooth.BluetoothAvrcpPlayerSettings;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
import android.content.BroadcastReceiver;
+import android.content.IntentFilter;
import android.content.Context;
import android.content.Intent;
-import android.content.IntentFilter;
import android.media.AudioManager;
-import android.media.MediaDescription;
import android.media.MediaMetadata;
import android.media.browse.MediaBrowser.MediaItem;
+import android.media.session.MediaSession;
import android.media.session.PlaybackState;
import android.os.Bundle;
import android.os.Message;
import android.util.Log;
+import android.util.SparseArray;
import com.android.bluetooth.BluetoothMetricsProto;
+import com.android.bluetooth.R;
import com.android.bluetooth.Utils;
import com.android.bluetooth.a2dpsink.A2dpSinkService;
import com.android.bluetooth.btservice.MetricsLogger;
@@ -43,59 +45,49 @@
import java.util.ArrayList;
import java.util.List;
-
/**
* Provides Bluetooth AVRCP Controller State Machine responsible for all remote control connections
* and interactions with a remote controlable device.
*/
class AvrcpControllerStateMachine extends StateMachine {
+ static final String TAG = "AvrcpControllerStateMachine";
+ static final boolean DBG = true;
- // commands from Binder service
- static final int MESSAGE_SEND_PASS_THROUGH_CMD = 1;
- static final int MESSAGE_SEND_GROUP_NAVIGATION_CMD = 3;
- static final int MESSAGE_GET_NOW_PLAYING_LIST = 5;
- static final int MESSAGE_GET_FOLDER_LIST = 6;
- static final int MESSAGE_GET_PLAYER_LIST = 7;
- static final int MESSAGE_CHANGE_FOLDER_PATH = 8;
- static final int MESSAGE_FETCH_ATTR_AND_PLAY_ITEM = 9;
- static final int MESSAGE_SET_BROWSED_PLAYER = 10;
+ //0->99 Events from Outside
+ public static final int CONNECT = 1;
+ public static final int DISCONNECT = 2;
+ public static final int MSG_DEVICE_UPDATED = 3;
- // commands from native layer
- static final int MESSAGE_PROCESS_SET_ABS_VOL_CMD = 103;
- static final int MESSAGE_PROCESS_REGISTER_ABS_VOL_NOTIFICATION = 104;
- static final int MESSAGE_PROCESS_TRACK_CHANGED = 105;
- static final int MESSAGE_PROCESS_PLAY_POS_CHANGED = 106;
- static final int MESSAGE_PROCESS_PLAY_STATUS_CHANGED = 107;
- static final int MESSAGE_PROCESS_VOLUME_CHANGED_NOTIFICATION = 108;
- static final int MESSAGE_PROCESS_GET_FOLDER_ITEMS = 109;
- static final int MESSAGE_PROCESS_GET_FOLDER_ITEMS_OUT_OF_RANGE = 110;
- static final int MESSAGE_PROCESS_GET_PLAYER_ITEMS = 111;
- static final int MESSAGE_PROCESS_FOLDER_PATH = 112;
- static final int MESSAGE_PROCESS_SET_BROWSED_PLAYER = 113;
- static final int MESSAGE_PROCESS_SET_ADDRESSED_PLAYER = 114;
+ //100->199 Internal Events
+ protected static final int CLEANUP = 100;
+ private static final int CONNECT_TIMEOUT = 101;
- // commands from A2DP sink
- static final int MESSAGE_STOP_METADATA_BROADCASTS = 201;
- static final int MESSAGE_START_METADATA_BROADCASTS = 202;
+ //200->299 Events from Native
+ static final int STACK_EVENT = 200;
+ static final int MESSAGE_INTERNAL_CMD_TIMEOUT = 201;
- // commands for connection
- static final int MESSAGE_PROCESS_RC_FEATURES = 301;
- static final int MESSAGE_PROCESS_CONNECTION_CHANGE = 302;
- static final int MESSAGE_PROCESS_BROWSE_CONNECTION_CHANGE = 303;
+ static final int MESSAGE_PROCESS_SET_ABS_VOL_CMD = 203;
+ static final int MESSAGE_PROCESS_REGISTER_ABS_VOL_NOTIFICATION = 204;
+ static final int MESSAGE_PROCESS_TRACK_CHANGED = 205;
+ static final int MESSAGE_PROCESS_PLAY_POS_CHANGED = 206;
+ static final int MESSAGE_PROCESS_PLAY_STATUS_CHANGED = 207;
+ static final int MESSAGE_PROCESS_VOLUME_CHANGED_NOTIFICATION = 208;
+ static final int MESSAGE_PROCESS_GET_FOLDER_ITEMS = 209;
+ static final int MESSAGE_PROCESS_GET_FOLDER_ITEMS_OUT_OF_RANGE = 210;
+ static final int MESSAGE_PROCESS_GET_PLAYER_ITEMS = 211;
+ static final int MESSAGE_PROCESS_FOLDER_PATH = 212;
+ static final int MESSAGE_PROCESS_SET_BROWSED_PLAYER = 213;
+ static final int MESSAGE_PROCESS_SET_ADDRESSED_PLAYER = 214;
+ static final int MESSAGE_PROCESS_ADDRESSED_PLAYER_CHANGED = 215;
+ static final int MESSAGE_PROCESS_NOW_PLAYING_CONTENTS_CHANGED = 216;
- // Interal messages
- static final int MESSAGE_INTERNAL_BROWSE_DEPTH_INCREMENT = 401;
- static final int MESSAGE_INTERNAL_MOVE_N_LEVELS_UP = 402;
- static final int MESSAGE_INTERNAL_CMD_TIMEOUT = 403;
+ //300->399 Events for Browsing
+ static final int MESSAGE_GET_FOLDER_ITEMS = 300;
+ static final int MESSAGE_PLAY_ITEM = 301;
+ static final int MSG_AVRCP_PASSTHRU = 302;
+
static final int MESSAGE_INTERNAL_ABS_VOL_TIMEOUT = 404;
- static final int ABS_VOL_TIMEOUT_MILLIS = 1000; //1s
- static final int CMD_TIMEOUT_MILLIS = 5000; // 5s
- // Fetch only 20 items at a time.
- static final int GET_FOLDER_ITEMS_PAGINATION_SIZE = 20;
- // Fetch no more than 1000 items per directory.
- static final int MAX_FOLDER_ITEMS = 1000;
-
/*
* Base value for absolute volume from JNI
*/
@@ -107,577 +99,540 @@
private static final byte NOTIFICATION_RSP_TYPE_INTERIM = 0x00;
private static final byte NOTIFICATION_RSP_TYPE_CHANGED = 0x01;
-
- private static final String TAG = "AvrcpControllerSM";
- private static final boolean DBG = true;
- private static final boolean VDBG = AvrcpControllerService.VDBG;
-
- private final Context mContext;
private final AudioManager mAudioManager;
- private AvrcpControllerBipStateMachine mBipStateMachine;
- private static CoverArtUtils mCoveArtUtils;
- private final State mDisconnected;
- private final State mConnected;
- private final SetBrowsedPlayer mSetBrowsedPlayer;
- private final SetAddresedPlayerAndPlayItem mSetAddrPlayer;
- private final ChangeFolderPath mChangeFolderPath;
- private final GetFolderList mGetFolderList;
- private final GetPlayerListing mGetPlayerListing;
- private final MoveToRoot mMoveToRoot;
+ protected final BluetoothDevice mDevice;
+ private BluetoothDevice mA2dpDevice = null;
+ protected final byte[] mDeviceAddress;
+ protected final AvrcpControllerService mService;
+ protected final Disconnected mDisconnected;
+ protected final Connecting mConnecting;
+ protected final Connected mConnected;
+ protected final Disconnecting mDisconnecting;
+ private A2dpSinkService mA2dpSinkService;
- private final Object mLock = new Object();
- private static final ArrayList<MediaItem> EMPTY_MEDIA_ITEM_LIST = new ArrayList<>();
- private static final MediaMetadata EMPTY_MEDIA_METADATA = new MediaMetadata.Builder().build();
+ protected int mMostRecentState = BluetoothProfile.STATE_DISCONNECTED;
- // APIs exist to access these so they must be thread safe
- private Boolean mIsConnected = false;
+ boolean mRemoteControlConnected = false;
+ boolean mBrowsingConnected = false;
+ final BrowseTree mBrowseTree;
+ private AvrcpPlayer mAddressedPlayer = new AvrcpPlayer();
private RemoteDevice mRemoteDevice;
- private AvrcpPlayer mAddressedPlayer;
-
+ private int mPreviousPercentageVol = -1;
+ private int mAddressedPlayerId = -1;
+ private SparseArray<AvrcpPlayer> mAvailablePlayerList = new SparseArray<AvrcpPlayer>();
// Only accessed from State Machine processMessage
private int mVolumeChangedNotificationsToIgnore = 0;
- private int mPreviousPercentageVol = -1;
- // Depth from root of current browsing. This can be used to move to root directly.
- private int mBrowseDepth = 0;
+ GetFolderList mGetFolderList = null;
- // Browse tree.
- private BrowseTree mBrowseTree = new BrowseTree();
+ //Number of items to get in a single fetch
+ static final int ITEM_PAGE_SIZE = 20;
+ static final int CMD_TIMEOUT_MILLIS = 10000;
+ static final int ABS_VOL_TIMEOUT_MILLIS = 1000; //1s
- private final BluetoothDevice mDevice;
- protected boolean mBrowsingConnected = false;
- protected int prevState = -1;
-
- AvrcpControllerStateMachine(Context context, BluetoothDevice device) {
+ AvrcpControllerStateMachine(BluetoothDevice device, AvrcpControllerService service) {
super(TAG);
- mContext = context;
mDevice = device;
+ mDeviceAddress = Utils.getByteAddress(mDevice);
+ mService = service;
+ logD(device.toString());
- mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
- IntentFilter filter = new IntentFilter(AudioManager.VOLUME_CHANGED_ACTION);
- mContext.registerReceiver(mBroadcastReceiver, filter);
-
+ mBrowseTree = new BrowseTree(mDevice);
mDisconnected = new Disconnected();
+ mConnecting = new Connecting();
mConnected = new Connected();
-
- // Used to change folder path and fetch the new folder listing.
- mSetBrowsedPlayer = new SetBrowsedPlayer();
- mSetAddrPlayer = new SetAddresedPlayerAndPlayItem();
- mChangeFolderPath = new ChangeFolderPath();
- mGetFolderList = new GetFolderList();
- mGetPlayerListing = new GetPlayerListing();
- mMoveToRoot = new MoveToRoot();
+ mDisconnecting = new Disconnecting();
addState(mDisconnected);
+ addState(mConnecting);
addState(mConnected);
+ addState(mDisconnecting);
- // Any action that needs blocking other requests to the state machine will be implemented as
- // a separate substate of the mConnected state. Once transtition to the sub-state we should
- // only handle the messages that are relevant to the sub-action. Everything else should be
- // deferred so that once we transition to the mConnected we can process them hence.
- addState(mSetBrowsedPlayer, mConnected);
- addState(mSetAddrPlayer, mConnected);
- addState(mChangeFolderPath, mConnected);
+ mGetFolderList = new GetFolderList();
addState(mGetFolderList, mConnected);
- addState(mGetPlayerListing, mConnected);
- addState(mMoveToRoot, mConnected);
- mCoveArtUtils = new CoverArtUtils();
+
+ mRemoteDevice = new RemoteDevice(device);
+
+ mAudioManager = (AudioManager) service.getSystemService(Context.AUDIO_SERVICE);
+ IntentFilter filter = new IntentFilter(AudioManager.VOLUME_CHANGED_ACTION);
+ mService.registerReceiver(mBroadcastReceiver, filter);
+
setInitialState(mDisconnected);
- mBipStateMachine = AvrcpControllerBipStateMachine.make(this, getHandler(), context);
}
- class Disconnected extends State {
+ BrowseTree.BrowseNode findNode(String parentMediaId) {
+ logD("FindNode");
+ return mBrowseTree.findBrowseNodeByID(parentMediaId);
+ }
+ /**
+ * Get the current connection state
+ *
+ * @return current State
+ */
+ public int getState() {
+ return mMostRecentState;
+ }
+
+ /**
+ * Get the underlying device tracked by this state machine
+ *
+ * @return device in focus
+ */
+ public synchronized BluetoothDevice getDevice() {
+ return mDevice;
+ }
+
+ /**
+ * send the connection event asynchronously
+ */
+ public boolean connect(StackEvent event) {
+ if (event.mBrowsingConnected) {
+ onBrowsingConnected();
+ }
+ mRemoteControlConnected = event.mRemoteControlConnected;
+ sendMessage(CONNECT);
+ return true;
+ }
+
+ /**
+ * send the Disconnect command asynchronously
+ */
+ public void disconnect() {
+ sendMessage(DISCONNECT);
+ }
+
+ /**
+ * Dump the current State Machine to the string builder.
+ *
+ * @param sb output string
+ */
+ public void dump(StringBuilder sb) {
+ ProfileService.println(sb, "mDevice: " + mDevice.getAddress() + "("
+ + mDevice.getName() + ") " + this.toString());
+ }
+
+ @Override
+ protected void unhandledMessage(Message msg) {
+ Log.w(TAG, "Unhandled message in state " + getCurrentState() + "msg.what=" + msg.what);
+ }
+
+ private static void logD(String message) {
+ if (DBG) {
+ Log.d(TAG, message);
+ }
+ }
+
+ synchronized void onBrowsingConnected() {
+ if (mBrowsingConnected) return;
+ mService.sBrowseTree.mRootNode.addChild(mBrowseTree.mRootNode);
+ BluetoothMediaBrowserService.notifyChanged(mService
+ .sBrowseTree.mRootNode);
+ BluetoothMediaBrowserService.notifyChanged(mAddressedPlayer.getPlaybackState());
+ mBrowsingConnected = true;
+ }
+
+ synchronized void onBrowsingDisconnected() {
+ if (!mBrowsingConnected) return;
+ mAddressedPlayer.setPlayStatus(PlaybackState.STATE_ERROR);
+ mAddressedPlayer.updateCurrentTrack(null);
+ if (mBrowseTree != null && mBrowseTree.mNowPlayingNode != null) {
+ mBrowseTree.mNowPlayingNode.setCached(false);
+ BluetoothMediaBrowserService.notifyChanged(mBrowseTree.mNowPlayingNode);
+ }
+ PlaybackState.Builder pbb = new PlaybackState.Builder();
+ pbb.setState(PlaybackState.STATE_ERROR, PlaybackState.PLAYBACK_POSITION_UNKNOWN,
+ 1.0f).setActions(0);
+ pbb.setErrorMessage(mService.getString(R.string.bluetooth_disconnected));
+ BluetoothMediaBrowserService.notifyChanged(pbb.build());
+ if (mBrowseTree != null && mBrowseTree.mRootNode != null) {
+ mService.sBrowseTree.mRootNode.removeChild(
+ mBrowseTree.mRootNode);
+ BluetoothMediaBrowserService.notifyChanged(mService.sBrowseTree.mRootNode);
+ }
+ BluetoothMediaBrowserService.trackChanged(null);
+ mBrowsingConnected = false;
+ }
+
+ private void notifyChanged(BrowseTree.BrowseNode node) {
+ BluetoothMediaBrowserService.notifyChanged(node);
+ }
+
+ void requestContents(BrowseTree.BrowseNode node) {
+ sendMessage(MESSAGE_GET_FOLDER_ITEMS, node);
+
+ logD("Fetching " + node);
+ }
+
+ void nowPlayingContentChanged() {
+ mBrowseTree.mNowPlayingNode.setCached(false);
+ sendMessage(MESSAGE_GET_FOLDER_ITEMS, mBrowseTree.mNowPlayingNode);
+ }
+
+ protected class Disconnected extends State {
@Override
public void enter() {
- Log.d(TAG, "Enter State: Disconnected mDevice: " + mDevice);
- mBrowsingConnected = false;
- if (prevState != -1) {
- AvrcpControllerService.removeStateMachine(mDevice);
- }
+ logD("Enter Disconnected");
+ if (mMostRecentState != BluetoothProfile.STATE_DISCONNECTED) {
+ sendMessage(CLEANUP);
+ }
+ mA2dpDevice = null;
+ broadcastConnectionStateChanged(BluetoothProfile.STATE_DISCONNECTED);
}
@Override
- public boolean processMessage(Message msg) {
- if (DBG) Log.d(TAG, " HandleMessage: " + dumpMessageString(msg.what)
- + ", mDevice: " + mDevice);
- switch (msg.what) {
- case MESSAGE_PROCESS_CONNECTION_CHANGE:
- if (msg.arg1 == BluetoothProfile.STATE_CONNECTED) {
- mBrowseTree.init();
- transitionTo(mConnected);
- BluetoothDevice rtDevice = (BluetoothDevice) msg.obj;
- synchronized (mLock) {
- mRemoteDevice = new RemoteDevice(rtDevice);
- mAddressedPlayer = new AvrcpPlayer();
- mIsConnected = true;
- }
- MetricsLogger.logProfileConnectionEvent(
- BluetoothMetricsProto.ProfileId.AVRCP_CONTROLLER);
- Intent intent = new Intent(
- BluetoothAvrcpController.ACTION_CONNECTION_STATE_CHANGED);
- intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE,
- BluetoothProfile.STATE_DISCONNECTED);
- intent.putExtra(BluetoothProfile.EXTRA_STATE,
- BluetoothProfile.STATE_CONNECTED);
- intent.putExtra(BluetoothDevice.EXTRA_DEVICE, rtDevice);
- mContext.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
- }
+ public boolean processMessage(Message message) {
+ switch (message.what) {
+ case CONNECT:
+ logD("Connect");
+ transitionTo(mConnecting);
break;
-
- default:
- Log.w(TAG,
- "Currently Disconnected not handling " + dumpMessageString(msg.what));
- return false;
+ case CLEANUP:
+ mService.removeStateMachine(AvrcpControllerStateMachine.this);
+ doQuit();
+ break;
}
return true;
}
+ }
+ protected class Connecting extends State {
@Override
- public void exit() {
- prevState = BluetoothProfile.STATE_DISCONNECTED;
- log("Exit State: Disconnected: ");
+ public void enter() {
+ logD("Enter Connecting");
+ broadcastConnectionStateChanged(BluetoothProfile.STATE_CONNECTING);
+ transitionTo(mConnected);
}
}
+
class Connected extends State {
- @Override
- public boolean processMessage(Message msg) {
- if (DBG) Log.d(TAG, " HandleMessage: " + dumpMessageString(msg.what));
- A2dpSinkService a2dpSinkService = A2dpSinkService.getA2dpSinkService();
- synchronized (mLock) {
- switch (msg.what) {
- case MESSAGE_SEND_PASS_THROUGH_CMD:
- BluetoothDevice device = (BluetoothDevice) msg.obj;
- AvrcpControllerService.sendPassThroughCommandNative(
- Utils.getByteAddress(device), msg.arg1, msg.arg2);
- if (a2dpSinkService != null) {
- if (DBG) Log.d(TAG, " inform AVRCP Commands to A2DP Sink ");
- a2dpSinkService.informAvrcpPassThroughCmd(device, msg.arg1, msg.arg2);
- }
- break;
-
- case MESSAGE_SEND_GROUP_NAVIGATION_CMD:
- AvrcpControllerService.sendGroupNavigationCommandNative(
- mRemoteDevice.getBluetoothAddress(), msg.arg1, msg.arg2);
- break;
-
- case MESSAGE_GET_NOW_PLAYING_LIST:
- mGetFolderList.setFolder((String) msg.obj);
- mGetFolderList.setBounds((int) msg.arg1, (int) msg.arg2);
- mGetFolderList.setScope(AvrcpControllerService.BROWSE_SCOPE_NOW_PLAYING);
- transitionTo(mGetFolderList);
- break;
-
- case MESSAGE_GET_FOLDER_LIST:
- // Whenever we transition we set the information for folder we need to
- // return result.
- mGetFolderList.setBounds(msg.arg1, msg.arg2);
- mGetFolderList.setFolder((String) msg.obj);
- mGetFolderList.setScope(AvrcpControllerService.BROWSE_SCOPE_VFS);
- transitionTo(mGetFolderList);
- break;
-
- case MESSAGE_GET_PLAYER_LIST:
- AvrcpControllerService.getPlayerListNative(
- mRemoteDevice.getBluetoothAddress(), (byte) msg.arg1,
- (byte) msg.arg2);
- transitionTo(mGetPlayerListing);
- sendMessageDelayed(MESSAGE_INTERNAL_CMD_TIMEOUT, CMD_TIMEOUT_MILLIS);
- break;
-
- case MESSAGE_CHANGE_FOLDER_PATH: {
- int direction = msg.arg1;
- Bundle b = (Bundle) msg.obj;
- String uid = b.getString(AvrcpControllerService.EXTRA_FOLDER_BT_ID);
- String fid = b.getString(AvrcpControllerService.EXTRA_FOLDER_ID);
-
- // String is encoded as a Hex String (mostly for display purposes)
- // hence convert this back to real byte string.
- AvrcpControllerService.changeFolderPathNative(
- mRemoteDevice.getBluetoothAddress(), (byte) msg.arg1,
- AvrcpControllerService.hexStringToByteUID(uid));
- mChangeFolderPath.setFolder(fid);
- transitionTo(mChangeFolderPath);
- sendMessage(MESSAGE_INTERNAL_BROWSE_DEPTH_INCREMENT, (byte) msg.arg1);
- sendMessageDelayed(MESSAGE_INTERNAL_CMD_TIMEOUT, CMD_TIMEOUT_MILLIS);
- break;
- }
-
- case MESSAGE_FETCH_ATTR_AND_PLAY_ITEM: {
- int scope = msg.arg1;
- String playItemUid = (String) msg.obj;
- BrowseTree.BrowseNode currBrPlayer = mBrowseTree.getCurrentBrowsedPlayer();
- BrowseTree.BrowseNode currAddrPlayer =
- mBrowseTree.getCurrentAddressedPlayer();
- if (DBG) {
- Log.d(TAG, "currBrPlayer " + currBrPlayer + " currAddrPlayer "
- + currAddrPlayer);
- }
-
- if (currBrPlayer == null || currBrPlayer.equals(currAddrPlayer)) {
- // String is encoded as a Hex String (mostly for display purposes)
- // hence convert this back to real byte string.
- // NOTE: It may be possible that sending play while the same item is
- // playing leads to reset of track.
- AvrcpControllerService.playItemNative(
- mRemoteDevice.getBluetoothAddress(), (byte) scope,
- AvrcpControllerService.hexStringToByteUID(playItemUid),
- (int) 0);
- } else {
- // Send out the request for setting addressed player.
- AvrcpControllerService.setAddressedPlayerNative(
- mRemoteDevice.getBluetoothAddress(),
- currBrPlayer.getPlayerID());
- mSetAddrPlayer.setItemAndScope(currBrPlayer.getID(), playItemUid,
- scope);
- transitionTo(mSetAddrPlayer);
- }
- break;
- }
-
- case MESSAGE_SET_BROWSED_PLAYER: {
- AvrcpControllerService.setBrowsedPlayerNative(
- mRemoteDevice.getBluetoothAddress(), (int) msg.arg1);
- mSetBrowsedPlayer.setFolder((String) msg.obj);
- transitionTo(mSetBrowsedPlayer);
- break;
- }
-
- case MESSAGE_PROCESS_SET_ADDRESSED_PLAYER:
- AvrcpControllerService.getPlayerListNative(
- mRemoteDevice.getBluetoothAddress(), 0, 255);
- transitionTo(mGetPlayerListing);
- sendMessageDelayed(MESSAGE_INTERNAL_CMD_TIMEOUT, CMD_TIMEOUT_MILLIS);
- break;
-
-
- case MESSAGE_PROCESS_CONNECTION_CHANGE:
- if (msg.arg1 == BluetoothProfile.STATE_DISCONNECTED) {
- mCoveArtUtils.msgDisconnectBip(mBipStateMachine,mRemoteDevice);
- synchronized (mLock) {
- mIsConnected = false;
- mRemoteDevice = null;
- }
- mBrowseTree.clear();
- transitionTo(mDisconnected);
- BluetoothDevice rtDevice = (BluetoothDevice) msg.obj;
- Intent intent = new Intent(
- BluetoothAvrcpController.ACTION_CONNECTION_STATE_CHANGED);
- intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE,
- BluetoothProfile.STATE_CONNECTED);
- intent.putExtra(BluetoothProfile.EXTRA_STATE,
- BluetoothProfile.STATE_DISCONNECTED);
- intent.putExtra(BluetoothDevice.EXTRA_DEVICE, rtDevice);
- mContext.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
- }
- break;
-
- case MESSAGE_PROCESS_BROWSE_CONNECTION_CHANGE:
- // Service tells us if the browse is connected or disconnected.
- // This is useful only for deciding whether to send browse commands rest of
- // the connection state handling should be done via the message
- // MESSAGE_PROCESS_CONNECTION_CHANGE.
- Intent intent = new Intent(
- AvrcpControllerService.ACTION_BROWSE_CONNECTION_STATE_CHANGED);
- intent.putExtra(BluetoothDevice.EXTRA_DEVICE, (BluetoothDevice) msg.obj);
- if (DBG) {
- Log.d(TAG, "Browse connection state " + msg.arg1);
- }
- if (msg.arg1 == 1) {
- intent.putExtra(BluetoothProfile.EXTRA_STATE,
- BluetoothProfile.STATE_CONNECTED);
- mBrowsingConnected = true;
- } else if (msg.arg1 == 0) {
- intent.putExtra(BluetoothProfile.EXTRA_STATE,
- BluetoothProfile.STATE_DISCONNECTED);
- // If browse is disconnected, the next time we connect we should
- // be at the ROOT.
- mBrowseDepth = 0;
- mBrowsingConnected = false;
- } else {
- Log.w(TAG, "Incorrect browse state " + msg.arg1);
- }
-
- mContext.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
- break;
-
- case MESSAGE_PROCESS_RC_FEATURES:
- mRemoteDevice.setRemoteFeatures(msg.arg1);
- if (msg.arg2 > 0) {
- mCoveArtUtils.msgProcessRcFeatures
- (mBipStateMachine, mRemoteDevice,msg.arg2);
- }
- break;
-
- case MESSAGE_PROCESS_SET_ABS_VOL_CMD:
- mVolumeChangedNotificationsToIgnore++;
- removeMessages(MESSAGE_INTERNAL_ABS_VOL_TIMEOUT);
- sendMessageDelayed(MESSAGE_INTERNAL_ABS_VOL_TIMEOUT,
- ABS_VOL_TIMEOUT_MILLIS);
- setAbsVolume(msg.arg1, msg.arg2);
- break;
-
- case MESSAGE_PROCESS_REGISTER_ABS_VOL_NOTIFICATION: {
- mRemoteDevice.setNotificationLabel(msg.arg1);
- mRemoteDevice.setAbsVolNotificationRequested(true);
- int percentageVol = getVolumePercentage();
- if (DBG) {
- Log.d(TAG, " Sending Interim Response = " + percentageVol + " label "
- + msg.arg1);
- }
- AvrcpControllerService.sendRegisterAbsVolRspNative(
- mRemoteDevice.getBluetoothAddress(), NOTIFICATION_RSP_TYPE_INTERIM,
- percentageVol, mRemoteDevice.getNotificationLabel());
- }
- break;
-
- case MESSAGE_PROCESS_VOLUME_CHANGED_NOTIFICATION: {
- if (mVolumeChangedNotificationsToIgnore > 0) {
- mVolumeChangedNotificationsToIgnore--;
- if (mVolumeChangedNotificationsToIgnore == 0) {
- removeMessages(MESSAGE_INTERNAL_ABS_VOL_TIMEOUT);
- }
- } else {
- if (mRemoteDevice.getAbsVolNotificationRequested()) {
- int percentageVol = getVolumePercentage();
- if (percentageVol != mPreviousPercentageVol) {
- AvrcpControllerService.sendRegisterAbsVolRspNative(
- mRemoteDevice.getBluetoothAddress(),
- NOTIFICATION_RSP_TYPE_CHANGED, percentageVol,
- mRemoteDevice.getNotificationLabel());
- mPreviousPercentageVol = percentageVol;
- mRemoteDevice.setAbsVolNotificationRequested(false);
- }
- }
- }
- }
- break;
-
- case MESSAGE_INTERNAL_ABS_VOL_TIMEOUT:
- // Volume changed notifications should come back promptly from the
- // AudioManager, if for some reason some notifications were squashed don't
- // prevent future notifications.
- if (DBG) Log.d(TAG, "Timed out on volume changed notification");
- mVolumeChangedNotificationsToIgnore = 0;
- break;
-
- case MESSAGE_PROCESS_TRACK_CHANGED:
- // Music start playing automatically and update Metadata
- mAddressedPlayer.updateCurrentTrack((TrackInfo) msg.obj);
- if (mCoveArtUtils.msgTrackChanged(mContext, mBipStateMachine,
- mAddressedPlayer,mRemoteDevice)) {
- broadcastMetaDataChanged(
- mAddressedPlayer.getCurrentTrack().getMediaMetaData());
- }
- break;
-
- case MESSAGE_PROCESS_PLAY_POS_CHANGED:
- if (msg.arg2 != -1) {
- mAddressedPlayer.setPlayTime(msg.arg2);
- broadcastPlayBackStateChanged(getCurrentPlayBackState());
- }
- break;
-
- case MESSAGE_PROCESS_PLAY_STATUS_CHANGED:
- int status = msg.arg1;
- mAddressedPlayer.setPlayStatus(status);
- broadcastPlayBackStateChanged(getCurrentPlayBackState());
- if (status == PlaybackState.STATE_PLAYING) {
- a2dpSinkService.informTGStatePlaying(mRemoteDevice.mBTDevice, true);
- } else if (status == PlaybackState.STATE_PAUSED
- || status == PlaybackState.STATE_STOPPED) {
- a2dpSinkService.informTGStatePlaying(mRemoteDevice.mBTDevice, false);
- }
- break;
-
- case CoverArtUtils.MESSAGE_BIP_CONNECTED:
- case CoverArtUtils.MESSAGE_BIP_DISCONNECTED:
- case CoverArtUtils.MESSAGE_BIP_IMAGE_FETCHED:
- case CoverArtUtils.MESSAGE_BIP_THUMB_NAIL_FETCHED:
- mCoveArtUtils.processBipAction(mContext, mAddressedPlayer,
- mRemoteDevice, msg.what, msg);
- break;
- default:
- return false;
- }
- }
- return true;
- }
-
- @Override
- public void exit() {
- prevState = BluetoothProfile.STATE_CONNECTED;
- log("Exit State: Connected: " + mDevice + ", currentMsg: " + getCurrentMessage().what);
- }
- }
-
- // Handle the change folder path meta-action.
- // a) Send Change folder command
- // b) Once successful transition to folder fetch state.
- class ChangeFolderPath extends CmdState {
- private static final String STATE_TAG = "AVRCPSM.ChangeFolderPath";
- private int mTmpIncrDirection;
- private String mID = "";
-
- public void setFolder(String id) {
- mID = id;
- }
+ private static final String STATE_TAG = "Avrcp.ConnectedAvrcpController";
+ private int mCurrentlyHeldKey = 0;
@Override
public void enter() {
+ if (mMostRecentState == BluetoothProfile.STATE_CONNECTING) {
+ BluetoothDevice device =
+ BluetoothAdapter.getDefaultAdapter().getRemoteDevice(mDeviceAddress);
+ mA2dpDevice = device;
+ broadcastConnectionStateChanged(BluetoothProfile.STATE_CONNECTED);
+ BluetoothMediaBrowserService.addressedPlayerChanged(mSessionCallbacks);
+ } else {
+ logD("ReEnteringConnected");
+ }
super.enter();
- mTmpIncrDirection = -1;
}
@Override
public boolean processMessage(Message msg) {
- if (DBG) Log.d(STATE_TAG, "processMessage " + msg.what);
+ logD(STATE_TAG + " processMessage " + msg.what);
switch (msg.what) {
- case MESSAGE_INTERNAL_BROWSE_DEPTH_INCREMENT:
- mTmpIncrDirection = msg.arg1;
- break;
-
- case MESSAGE_PROCESS_FOLDER_PATH: {
- // Fetch the listing of objects in this folder.
- if (DBG) {
- Log.d(STATE_TAG,
- "MESSAGE_PROCESS_FOLDER_PATH returned " + msg.arg1 + " elements");
- }
-
- // Update the folder depth.
- if (mTmpIncrDirection
- == AvrcpControllerService.FOLDER_NAVIGATION_DIRECTION_UP) {
- mBrowseDepth -= 1;
- } else if (mTmpIncrDirection
- == AvrcpControllerService.FOLDER_NAVIGATION_DIRECTION_DOWN) {
- mBrowseDepth += 1;
- } else {
- throw new IllegalStateException("incorrect nav " + mTmpIncrDirection);
- }
- if (DBG) Log.d(STATE_TAG, "New browse depth " + mBrowseDepth);
-
- if (msg.arg1 > 0) {
- sendMessage(MESSAGE_GET_FOLDER_LIST, 0, msg.arg1 - 1, mID);
- } else {
- // Return an empty response to the upper layer.
- broadcastFolderList(mID, EMPTY_MEDIA_ITEM_LIST);
- }
- mBrowseTree.setCurrentBrowsedFolder(mID);
- transitionTo(mConnected);
- break;
- }
-
- case MESSAGE_INTERNAL_CMD_TIMEOUT:
- // We timed out changing folders. It is imperative we tell
- // the upper layers that we failed by giving them an empty list.
- Log.e(STATE_TAG, "change folder failed, sending empty list.");
- broadcastFolderList(mID, EMPTY_MEDIA_ITEM_LIST);
- transitionTo(mConnected);
- break;
-
- case MESSAGE_SEND_PASS_THROUGH_CMD:
- case MESSAGE_SEND_GROUP_NAVIGATION_CMD:
case MESSAGE_PROCESS_SET_ABS_VOL_CMD:
- case MESSAGE_PROCESS_REGISTER_ABS_VOL_NOTIFICATION:
+ mVolumeChangedNotificationsToIgnore++;
+ removeMessages(MESSAGE_INTERNAL_ABS_VOL_TIMEOUT);
+ sendMessageDelayed(MESSAGE_INTERNAL_ABS_VOL_TIMEOUT,
+ ABS_VOL_TIMEOUT_MILLIS);
+ setAbsVolume(msg.arg1, msg.arg2);
+ return true;
+
+ case MESSAGE_GET_FOLDER_ITEMS:
+ transitionTo(mGetFolderList);
+ return true;
+
+ case MESSAGE_PLAY_ITEM:
+ //Set Addressed Player
+ playItem((BrowseTree.BrowseNode) msg.obj);
+ return true;
+
+ case MSG_AVRCP_PASSTHRU:
+ passThru(msg.arg1);
+ return true;
+
case MESSAGE_PROCESS_TRACK_CHANGED:
- case MESSAGE_PROCESS_PLAY_POS_CHANGED:
+ mAddressedPlayer.updateCurrentTrack((MediaMetadata) msg.obj);
+ BluetoothMediaBrowserService.trackChanged((MediaMetadata) msg.obj);
+ return true;
+
case MESSAGE_PROCESS_PLAY_STATUS_CHANGED:
- case MESSAGE_PROCESS_VOLUME_CHANGED_NOTIFICATION:
- case MESSAGE_STOP_METADATA_BROADCASTS:
- case MESSAGE_START_METADATA_BROADCASTS:
- case MESSAGE_PROCESS_CONNECTION_CHANGE:
- case MESSAGE_PROCESS_BROWSE_CONNECTION_CHANGE:
- // All of these messages should be handled by parent state immediately.
- return false;
+ int status = msg.arg1;
+ mAddressedPlayer.setPlayStatus(status);
+ BluetoothMediaBrowserService.notifyChanged(mAddressedPlayer.getPlaybackState());
+ broadcastPlayBackStateChanged(mAddressedPlayer.getPlaybackState());
+ BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(mDeviceAddress);
+ mA2dpSinkService = A2dpSinkService.getA2dpSinkService();
+ if (mA2dpSinkService == null) {
+ Log.e(TAG, "mA2dpSinkService is null, return");
+ }
+ if (status == PlaybackState.STATE_PLAYING) {
+ mA2dpSinkService.informTGStatePlaying(device, true);
+ } else if (status == PlaybackState.STATE_PAUSED
+ || status == PlaybackState.STATE_STOPPED) {
+ mA2dpSinkService.informTGStatePlaying(device, false);
+ }
+ return true;
+
+ case MESSAGE_PROCESS_PLAY_POS_CHANGED:
+ if (msg.arg2 != -1) {
+ mAddressedPlayer.setPlayTime(msg.arg2);
+
+ BluetoothMediaBrowserService.notifyChanged(
+ mAddressedPlayer.getPlaybackState());
+ }
+ return true;
+
+ case MESSAGE_PROCESS_ADDRESSED_PLAYER_CHANGED:
+ mAddressedPlayerId = msg.arg1;
+ logD("AddressedPlayer = " + mAddressedPlayerId);
+ AvrcpPlayer updatedPlayer = mAvailablePlayerList.get(mAddressedPlayerId);
+ if (updatedPlayer != null) {
+ mAddressedPlayer = updatedPlayer;
+ logD("AddressedPlayer = " + mAddressedPlayer.getName());
+ } else {
+ mBrowseTree.mRootNode.setCached(false);
+ mBrowseTree.mRootNode.setExpectedChildren(255);
+ BluetoothMediaBrowserService.notifyChanged(mBrowseTree.mRootNode);
+ }
+ return true;
+
+ case DISCONNECT:
+ transitionTo(mDisconnecting);
+ return true;
+
+ case MSG_DEVICE_UPDATED:
+ msgDeviceUpdated((BluetoothDevice)msg.obj);
+ return true;
+
+ case MESSAGE_PROCESS_REGISTER_ABS_VOL_NOTIFICATION: {
+ mRemoteDevice.setNotificationLabel(msg.arg1);
+ mRemoteDevice.setAbsVolNotificationRequested(true);
+ int percentageVol = getVolumePercentage();
+ if (DBG) {
+ Log.d(TAG, " Sending Interim Response = " + percentageVol + " label "
+ + msg.arg1);
+ }
+ AvrcpControllerService.sendRegisterAbsVolRspNative(
+ mRemoteDevice.getBluetoothAddress(), NOTIFICATION_RSP_TYPE_INTERIM,
+ percentageVol, mRemoteDevice.getNotificationLabel());
+ }
+ return true;
+
+ case MESSAGE_PROCESS_VOLUME_CHANGED_NOTIFICATION: {
+ if (mVolumeChangedNotificationsToIgnore > 0) {
+ mVolumeChangedNotificationsToIgnore--;
+ if (mVolumeChangedNotificationsToIgnore == 0) {
+ removeMessages(MESSAGE_INTERNAL_ABS_VOL_TIMEOUT);
+ }
+ } else {
+ if (mRemoteDevice.getAbsVolNotificationRequested()) {
+ int percentageVol = getVolumePercentage();
+ Log.d(TAG, " percentageVol = " + percentageVol);
+ if (percentageVol != mPreviousPercentageVol) {
+ if (DBG) {
+ Log.d(TAG, " Sending Changed Response = " + percentageVol +
+ " label: " + msg.arg1 + " mPreviousPercentageVol: " +
+ mPreviousPercentageVol);
+ }
+ AvrcpControllerService.sendRegisterAbsVolRspNative(
+ mRemoteDevice.getBluetoothAddress(),
+ NOTIFICATION_RSP_TYPE_CHANGED, percentageVol,
+ mRemoteDevice.getNotificationLabel());
+ mPreviousPercentageVol = percentageVol;
+ mRemoteDevice.setAbsVolNotificationRequested(false);
+ }
+ }
+ }
+ }
+ return true;
+
+ case MESSAGE_INTERNAL_ABS_VOL_TIMEOUT:
+ // Volume changed notifications should come back promptly from the
+ // AudioManager, if for some reason some notifications were squashed don't
+ // prevent future notifications.
+ if (DBG) Log.d(TAG, "Timed out on volume changed notification");
+ mVolumeChangedNotificationsToIgnore = 0;
+ return true;
default:
- if (DBG) {
- Log.d(STATE_TAG, "deferring message " + msg.what + " to Connected state.");
- }
- deferMessage(msg);
+ return super.processMessage(msg);
}
- return true;
+
+ }
+
+ private void playItem(BrowseTree.BrowseNode node) {
+ if (node == null) {
+ Log.w(TAG, "Invalid item to play");
+ } else {
+ mService.playItemNative(
+ mDeviceAddress, node.getScope(),
+ node.getBluetoothID(), 0);
+ }
+ }
+
+ private synchronized void passThru(int cmd) {
+ logD("msgPassThru " + cmd);
+ // Some keys should be held until the next event.
+ if (mCurrentlyHeldKey != 0) {
+ mService.sendPassThroughCommandNative(
+ mDeviceAddress, mCurrentlyHeldKey,
+ AvrcpControllerService.KEY_STATE_RELEASED);
+
+ if (mCurrentlyHeldKey == cmd) {
+ // Return to prevent starting FF/FR operation again
+ mCurrentlyHeldKey = 0;
+ return;
+ } else {
+ // FF/FR is in progress and other operation is desired
+ // so after stopping FF/FR, not returning so that command
+ // can be sent for the desired operation.
+ mCurrentlyHeldKey = 0;
+ }
+ }
+
+ // Send the pass through.
+ mService.sendPassThroughCommandNative(mDeviceAddress, cmd,
+ AvrcpControllerService.KEY_STATE_PRESSED);
+
+ if (isHoldableKey(cmd)) {
+ // Release cmd next time a command is sent.
+ mCurrentlyHeldKey = cmd;
+ } else {
+ mService.sendPassThroughCommandNative(mDeviceAddress,
+ cmd, AvrcpControllerService.KEY_STATE_RELEASED);
+ }
+ }
+
+ private synchronized void msgDeviceUpdated(BluetoothDevice device) {
+ if (device != null && device.equals(mA2dpDevice)) {
+ return;
+ }
+ Log.d(TAG, "msgDeviceUpdated. Previous: " + mA2dpDevice + " New: " + device);
+ // We are connected to a new device via A2DP now.
+ mA2dpDevice = device;
+ Log.w(TAG, "mA2dpDevice: " + mA2dpDevice +
+ " mBrowsingConnected: " + mBrowsingConnected);
+ if (mBrowsingConnected) {
+ //To do
+ BluetoothMediaBrowserService.notifyChanged(mService.sBrowseTree.mRootNode);
+ //BluetoothMediaBrowserService.notifyChanged(BrowseTree.ROOT);
+ }
+
+ int Playstate = mAddressedPlayer.getPlayStatus();
+ MediaMetadata mediaMetadata = mAddressedPlayer.getCurrentTrack();
+ Log.d(TAG, "Media metadata " + mediaMetadata + " playback state " + Playstate);
+ mAddressedPlayer.setPlayStatus(Playstate);
+ mAddressedPlayer.updateCurrentTrack(mediaMetadata);
+ }
+
+ private boolean isHoldableKey(int cmd) {
+ return (cmd == AvrcpControllerService.PASS_THRU_CMD_ID_REWIND)
+ || (cmd == AvrcpControllerService.PASS_THRU_CMD_ID_FF);
}
}
// Handle the get folder listing action
// a) Fetch the listing of folders
// b) Once completed return the object listing
- class GetFolderList extends CmdState {
- private static final String STATE_TAG = "AVRCPSM.GetFolderList";
+ class GetFolderList extends State {
+ private static final String STATE_TAG = "Avrcp.GetFolderList";
- String mID = "";
- int mStartInd;
- int mEndInd;
- int mCurrInd;
- int mScope;
- private ArrayList<MediaItem> mFolderList = new ArrayList<>();
+ boolean mAbort;
+ BrowseTree.BrowseNode mBrowseNode;
+ BrowseTree.BrowseNode mNextStep;
@Override
public void enter() {
+ logD(STATE_TAG + " Entering GetFolderList");
// Setup the timeouts.
+ sendMessageDelayed(MESSAGE_INTERNAL_CMD_TIMEOUT, CMD_TIMEOUT_MILLIS);
super.enter();
- mCurrInd = 0;
- mFolderList.clear();
- callNativeFunctionForScope(mStartInd,
- Math.min(mEndInd, mStartInd + GET_FOLDER_ITEMS_PAGINATION_SIZE - 1));
- }
-
- public void setScope(int scope) {
- mScope = scope;
- }
-
- public void setFolder(String id) {
- if (DBG) Log.d(STATE_TAG, "Setting folder to " + id);
- mID = id;
- }
-
- public void setBounds(int startInd, int endInd) {
- if (DBG) {
- Log.d(STATE_TAG, "startInd " + startInd + " endInd " + endInd);
+ mAbort = false;
+ Message msg = getCurrentMessage();
+ if (msg.what == MESSAGE_GET_FOLDER_ITEMS) {
+ {
+ logD(STATE_TAG + " new Get Request");
+ mBrowseNode = (BrowseTree.BrowseNode) msg.obj;
+ }
}
- mStartInd = startInd;
- mEndInd = Math.min(endInd, MAX_FOLDER_ITEMS);
+
+ if (mBrowseNode == null) {
+ transitionTo(mConnected);
+ } else {
+ navigateToFolderOrRetrieve(mBrowseNode);
+ }
}
@Override
public boolean processMessage(Message msg) {
- Log.d(STATE_TAG, "processMessage " + msg.what);
+ logD(STATE_TAG + " processMessage " + msg.what);
switch (msg.what) {
case MESSAGE_PROCESS_GET_FOLDER_ITEMS:
ArrayList<MediaItem> folderList = (ArrayList<MediaItem>) msg.obj;
- mFolderList.addAll(folderList);
- if (DBG) {
- Log.d(STATE_TAG,
- "Start " + mStartInd + " End " + mEndInd + " Curr " + mCurrInd
- + " received " + folderList.size());
- }
- mCurrInd += folderList.size();
+ int endIndicator = mBrowseNode.getExpectedChildren() - 1;
+ logD("GetFolderItems: End " + endIndicator
+ + " received " + folderList.size());
// Always update the node so that the user does not wait forever
// for the list to populate.
- sendFolderBroadcastAndUpdateNode();
+ mBrowseNode.addChildren(folderList);
+ notifyChanged(mBrowseNode);
- if (mCurrInd > mEndInd || folderList.size() == 0) {
+ if (mBrowseNode.getChildrenCount() >= endIndicator || folderList.size() == 0
+ || mAbort) {
// If we have fetched all the elements or if the remotes sends us 0 elements
// (which can lead us into a loop since mCurrInd does not proceed) we simply
// abort.
+ mBrowseNode.setCached(true);
transitionTo(mConnected);
} else {
// Fetch the next set of items.
- callNativeFunctionForScope(mCurrInd, Math.min(mEndInd,
- mCurrInd + GET_FOLDER_ITEMS_PAGINATION_SIZE - 1));
+ fetchContents(mBrowseNode);
// Reset the timeout message since we are doing a new fetch now.
removeMessages(MESSAGE_INTERNAL_CMD_TIMEOUT);
sendMessageDelayed(MESSAGE_INTERNAL_CMD_TIMEOUT, CMD_TIMEOUT_MILLIS);
}
break;
+ case MESSAGE_PROCESS_SET_BROWSED_PLAYER:
+ mBrowseTree.setCurrentBrowsedPlayer(mNextStep.getID(), msg.arg1, msg.arg2);
+ removeMessages(MESSAGE_INTERNAL_CMD_TIMEOUT);
+ sendMessageDelayed(MESSAGE_INTERNAL_CMD_TIMEOUT, CMD_TIMEOUT_MILLIS);
+ navigateToFolderOrRetrieve(mBrowseNode);
+ break;
+
+ case MESSAGE_PROCESS_FOLDER_PATH:
+ mBrowseTree.setCurrentBrowsedFolder(mNextStep.getID());
+ mBrowseTree.getCurrentBrowsedFolder().setExpectedChildren(msg.arg1);
+
+ if (mAbort) {
+ transitionTo(mConnected);
+ } else {
+ removeMessages(MESSAGE_INTERNAL_CMD_TIMEOUT);
+ sendMessageDelayed(MESSAGE_INTERNAL_CMD_TIMEOUT, CMD_TIMEOUT_MILLIS);
+ navigateToFolderOrRetrieve(mBrowseNode);
+ }
+ break;
+
+ case MESSAGE_PROCESS_GET_PLAYER_ITEMS:
+ BrowseTree.BrowseNode rootNode = mBrowseTree.mRootNode;
+ if (!rootNode.isCached()) {
+ List<AvrcpPlayer> playerList = (List<AvrcpPlayer>) msg.obj;
+ mAvailablePlayerList.clear();
+ for (AvrcpPlayer player : playerList) {
+ mAvailablePlayerList.put(player.getId(), player);
+ }
+ rootNode.addChildren(playerList);
+ mBrowseTree.setCurrentBrowsedFolder(BrowseTree.ROOT);
+ rootNode.setExpectedChildren(playerList.size());
+ rootNode.setCached(true);
+ notifyChanged(rootNode);
+ }
+ transitionTo(mConnected);
+ break;
case MESSAGE_INTERNAL_CMD_TIMEOUT:
// We have timed out to execute the request, we should simply send
// whatever listing we have gotten until now.
- sendFolderBroadcastAndUpdateNode();
+ Log.w(TAG, "TIMEOUT");
transitionTo(mConnected);
break;
@@ -685,594 +640,158 @@
// If we have gotten an error for OUT OF RANGE we have
// already sent all the items to the client hence simply
// transition to Connected state here.
+ mBrowseNode.setCached(true);
transitionTo(mConnected);
break;
- case MESSAGE_CHANGE_FOLDER_PATH:
- case MESSAGE_FETCH_ATTR_AND_PLAY_ITEM:
- case MESSAGE_GET_PLAYER_LIST:
- case MESSAGE_GET_NOW_PLAYING_LIST:
- case MESSAGE_SET_BROWSED_PLAYER:
- // A new request has come in, no need to fetch more.
- mEndInd = 0;
- deferMessage(msg);
+ case MESSAGE_GET_FOLDER_ITEMS:
+ if (!mBrowseNode.equals(msg.obj)) {
+ if (shouldAbort(mBrowseNode.getScope(),
+ ((BrowseTree.BrowseNode) msg.obj).getScope())) {
+ mAbort = true;
+ }
+ deferMessage(msg);
+ logD("GetFolderItems: Go Get Another Directory");
+ } else {
+ logD("GetFolderItems: Get The Same Directory, ignore");
+ }
break;
- case MESSAGE_SEND_PASS_THROUGH_CMD:
- case MESSAGE_SEND_GROUP_NAVIGATION_CMD:
+ case CONNECT:
+ case DISCONNECT:
+ case MSG_AVRCP_PASSTHRU:
case MESSAGE_PROCESS_SET_ABS_VOL_CMD:
case MESSAGE_PROCESS_REGISTER_ABS_VOL_NOTIFICATION:
case MESSAGE_PROCESS_TRACK_CHANGED:
case MESSAGE_PROCESS_PLAY_POS_CHANGED:
case MESSAGE_PROCESS_PLAY_STATUS_CHANGED:
case MESSAGE_PROCESS_VOLUME_CHANGED_NOTIFICATION:
- case MESSAGE_STOP_METADATA_BROADCASTS:
- case MESSAGE_START_METADATA_BROADCASTS:
- case MESSAGE_PROCESS_CONNECTION_CHANGE:
- case MESSAGE_PROCESS_BROWSE_CONNECTION_CHANGE:
+ case MESSAGE_PLAY_ITEM:
+ case MESSAGE_PROCESS_ADDRESSED_PLAYER_CHANGED:
// All of these messages should be handled by parent state immediately.
return false;
default:
- if (DBG) Log.d(STATE_TAG, "deferring message " + msg.what + " to connected!");
+ logD(STATE_TAG + " deferring message " + msg.what
+ + " to connected!");
deferMessage(msg);
}
return true;
}
- private void sendFolderBroadcastAndUpdateNode() {
- BrowseTree.BrowseNode bn = mBrowseTree.findBrowseNodeByID(mID);
- if (bn == null) {
- Log.e(TAG, "Can not find BrowseNode by ID: " + mID);
- return;
+ /**
+ * shouldAbort calculates the cases where fetching the current directory is no longer
+ * necessary.
+ *
+ * @return true: a new folder in the same scope
+ * a new player while fetching contents of a folder
+ * false: other cases, specifically Now Playing while fetching a folder
+ */
+ private boolean shouldAbort(int currentScope, int fetchScope) {
+ if ((currentScope == fetchScope)
+ || (currentScope == AvrcpControllerService.BROWSE_SCOPE_VFS
+ && fetchScope == AvrcpControllerService.BROWSE_SCOPE_PLAYER_LIST)) {
+ return true;
}
- if (bn.isPlayer()) {
- // Add the now playing folder.
- MediaDescription.Builder mdb = new MediaDescription.Builder();
- mdb.setMediaId(BrowseTree.NOW_PLAYING_PREFIX + ":" + bn.getPlayerID());
- mdb.setTitle(BrowseTree.NOW_PLAYING_PREFIX);
- Bundle mdBundle = new Bundle();
- mdBundle.putString(AvrcpControllerService.MEDIA_ITEM_UID_KEY,
- BrowseTree.NOW_PLAYING_PREFIX + ":" + bn.getID());
- mdb.setExtras(mdBundle);
- mFolderList.add(new MediaItem(mdb.build(), MediaItem.FLAG_BROWSABLE));
- }
- mBrowseTree.refreshChildren(bn, mFolderList);
- broadcastFolderList(mID, mFolderList);
-
- // For now playing we need to set the current browsed folder here.
- // For normal folders it is set after ChangeFolderPath.
- if (mScope == AvrcpControllerService.BROWSE_SCOPE_NOW_PLAYING) {
- mBrowseTree.setCurrentBrowsedFolder(mID);
- }
+ return false;
}
- private void callNativeFunctionForScope(int start, int end) {
- switch (mScope) {
+ private void fetchContents(BrowseTree.BrowseNode target) {
+ int start = target.getChildrenCount();
+ int end = Math.min(target.getExpectedChildren(), target.getChildrenCount()
+ + ITEM_PAGE_SIZE) - 1;
+ switch (target.getScope()) {
+ case AvrcpControllerService.BROWSE_SCOPE_PLAYER_LIST:
+ AvrcpControllerService.getPlayerListNative(mDeviceAddress,
+ start, end);
+ break;
case AvrcpControllerService.BROWSE_SCOPE_NOW_PLAYING:
AvrcpControllerService.getNowPlayingListNative(
- mRemoteDevice.getBluetoothAddress(), start, end);
+ mDeviceAddress, start, end);
break;
case AvrcpControllerService.BROWSE_SCOPE_VFS:
- AvrcpControllerService.getFolderListNative(mRemoteDevice.getBluetoothAddress(),
+ AvrcpControllerService.getFolderListNative(mDeviceAddress,
start, end);
break;
default:
- Log.e(STATE_TAG, "Scope " + mScope + " cannot be handled here.");
+ Log.e(TAG, STATE_TAG + " Scope " + target.getScope()
+ + " cannot be handled here.");
}
}
- }
- // Handle the get player listing action
- // a) Fetch the listing of players
- // b) Once completed return the object listing
- class GetPlayerListing extends CmdState {
- private static final String STATE_TAG = "AVRCPSM.GetPlayerList";
-
- @Override
- public boolean processMessage(Message msg) {
- if (DBG) Log.d(STATE_TAG, "processMessage " + msg.what);
- switch (msg.what) {
- case MESSAGE_PROCESS_GET_PLAYER_ITEMS:
- List<AvrcpPlayer> playerList = (List<AvrcpPlayer>) msg.obj;
- mBrowseTree.refreshChildren(BrowseTree.ROOT, playerList);
- ArrayList<MediaItem> mediaItemList = new ArrayList<>();
- for (BrowseTree.BrowseNode c : mBrowseTree.findBrowseNodeByID(BrowseTree.ROOT)
- .getChildren()) {
- mediaItemList.add(c.getMediaItem());
- }
- broadcastFolderList(BrowseTree.ROOT, mediaItemList);
- mBrowseTree.setCurrentBrowsedFolder(BrowseTree.ROOT);
+ /* One of several things can happen when trying to get a folder list
+ *
+ *
+ * 0: The folder handle is no longer valid
+ * 1: The folder contents can be retrieved directly (NowPlaying, Root, Current)
+ * 2: The folder is a browsable player
+ * 3: The folder is a non browsable player
+ * 4: The folder is not a child of the current folder
+ * 5: The folder is a child of the current folder
+ *
+ */
+ private void navigateToFolderOrRetrieve(BrowseTree.BrowseNode target) {
+ mNextStep = mBrowseTree.getNextStepToFolder(target);
+ logD("NAVIGATING From "
+ + mBrowseTree.getCurrentBrowsedFolder().toString());
+ logD("NAVIGATING Toward " + target.toString());
+ if (mNextStep == null) {
+ return;
+ } else if (target.equals(mBrowseTree.mNowPlayingNode)
+ || target.equals(mBrowseTree.mRootNode)
+ || mNextStep.equals(mBrowseTree.getCurrentBrowsedFolder())) {
+ fetchContents(mNextStep);
+ } else if (mNextStep.isPlayer()) {
+ logD("NAVIGATING Player " + mNextStep.toString());
+ if (mNextStep.isBrowsable()) {
+ AvrcpControllerService.setBrowsedPlayerNative(
+ mDeviceAddress, (int) mNextStep.getBluetoothID());
+ } else {
+ logD("Player doesn't support browsing");
+ mNextStep.setCached(true);
transitionTo(mConnected);
- break;
+ }
+ } else if (mNextStep.equals(mBrowseTree.mNavigateUpNode)) {
+ logD("NAVIGATING UP " + mNextStep.toString());
+ mNextStep = mBrowseTree.getCurrentBrowsedFolder().getParent();
+ mBrowseTree.getCurrentBrowsedFolder().setCached(false);
- case MESSAGE_INTERNAL_CMD_TIMEOUT:
- // We have timed out to execute the request.
- // Send an empty list here.
- broadcastFolderList(BrowseTree.ROOT, EMPTY_MEDIA_ITEM_LIST);
- transitionTo(mConnected);
- break;
+ AvrcpControllerService.changeFolderPathNative(
+ mDeviceAddress,
+ AvrcpControllerService.FOLDER_NAVIGATION_DIRECTION_UP,
+ 0);
- case MESSAGE_SEND_PASS_THROUGH_CMD:
- case MESSAGE_SEND_GROUP_NAVIGATION_CMD:
- case MESSAGE_PROCESS_SET_ABS_VOL_CMD:
- case MESSAGE_PROCESS_REGISTER_ABS_VOL_NOTIFICATION:
- case MESSAGE_PROCESS_TRACK_CHANGED:
- case MESSAGE_PROCESS_PLAY_POS_CHANGED:
- case MESSAGE_PROCESS_PLAY_STATUS_CHANGED:
- case MESSAGE_PROCESS_VOLUME_CHANGED_NOTIFICATION:
- case MESSAGE_STOP_METADATA_BROADCASTS:
- case MESSAGE_START_METADATA_BROADCASTS:
- case MESSAGE_PROCESS_CONNECTION_CHANGE:
- case MESSAGE_PROCESS_BROWSE_CONNECTION_CHANGE:
- // All of these messages should be handled by parent state immediately.
- return false;
-
- default:
- if (DBG) Log.d(STATE_TAG, "deferring message " + msg.what + " to connected!");
- deferMessage(msg);
+ } else {
+ logD("NAVIGATING DOWN " + mNextStep.toString());
+ AvrcpControllerService.changeFolderPathNative(
+ mDeviceAddress,
+ AvrcpControllerService.FOLDER_NAVIGATION_DIRECTION_DOWN,
+ mNextStep.getBluetoothID());
}
- return true;
- }
- }
-
- class MoveToRoot extends CmdState {
- private static final String STATE_TAG = "AVRCPSM.MoveToRoot";
- private String mID = "";
-
- public void setFolder(String id) {
- if (DBG) Log.d(STATE_TAG, "setFolder " + id);
- mID = id;
- }
-
- @Override
- public void enter() {
- // Setup the timeouts.
- super.enter();
-
- // We need to move mBrowseDepth levels up. The following message is
- // completely internal to this state.
- sendMessage(MESSAGE_INTERNAL_MOVE_N_LEVELS_UP);
- }
-
- @Override
- public boolean processMessage(Message msg) {
- if (DBG) {
- Log.d(STATE_TAG, "processMessage " + msg.what + " browse depth " + mBrowseDepth);
- }
- switch (msg.what) {
- case MESSAGE_INTERNAL_MOVE_N_LEVELS_UP:
- if (mBrowseDepth == 0) {
- Log.w(STATE_TAG, "Already in root!");
- transitionTo(mConnected);
- sendMessage(MESSAGE_GET_FOLDER_LIST, 0, 0xff, mID);
- } else {
- AvrcpControllerService.changeFolderPathNative(
- mRemoteDevice.getBluetoothAddress(),
- (byte) AvrcpControllerService.FOLDER_NAVIGATION_DIRECTION_UP,
- AvrcpControllerService.hexStringToByteUID(null));
- }
- break;
-
- case MESSAGE_PROCESS_FOLDER_PATH:
- mBrowseDepth -= 1;
- if (DBG) Log.d(STATE_TAG, "New browse depth " + mBrowseDepth);
- if (mBrowseDepth < 0) {
- throw new IllegalArgumentException("Browse depth negative!");
- }
-
- sendMessage(MESSAGE_INTERNAL_MOVE_N_LEVELS_UP);
- break;
-
- case MESSAGE_INTERNAL_CMD_TIMEOUT:
- broadcastFolderList(BrowseTree.ROOT, EMPTY_MEDIA_ITEM_LIST);
- transitionTo(mConnected);
- break;
-
- case MESSAGE_SEND_PASS_THROUGH_CMD:
- case MESSAGE_SEND_GROUP_NAVIGATION_CMD:
- case MESSAGE_PROCESS_SET_ABS_VOL_CMD:
- case MESSAGE_PROCESS_REGISTER_ABS_VOL_NOTIFICATION:
- case MESSAGE_PROCESS_TRACK_CHANGED:
- case MESSAGE_PROCESS_PLAY_POS_CHANGED:
- case MESSAGE_PROCESS_PLAY_STATUS_CHANGED:
- case MESSAGE_PROCESS_VOLUME_CHANGED_NOTIFICATION:
- case MESSAGE_STOP_METADATA_BROADCASTS:
- case MESSAGE_START_METADATA_BROADCASTS:
- case MESSAGE_PROCESS_CONNECTION_CHANGE:
- case MESSAGE_PROCESS_BROWSE_CONNECTION_CHANGE:
- // All of these messages should be handled by parent state immediately.
- return false;
-
- default:
- if (DBG) Log.d(STATE_TAG, "deferring message " + msg.what + " to connected!");
- deferMessage(msg);
- }
- return true;
- }
- }
-
- class SetBrowsedPlayer extends CmdState {
- private static final String STATE_TAG = "AVRCPSM.SetBrowsedPlayer";
- String mID = "";
-
- public void setFolder(String id) {
- mID = id;
- }
-
- @Override
- public boolean processMessage(Message msg) {
- if (DBG) Log.d(STATE_TAG, "processMessage " + msg.what);
- switch (msg.what) {
- case MESSAGE_PROCESS_SET_BROWSED_PLAYER:
- // Set the new depth.
- if (DBG) Log.d(STATE_TAG, "player depth " + msg.arg2);
- mBrowseDepth = msg.arg2;
-
- // If we already on top of player and there is no content.
- // This should very rarely happen.
- if (mBrowseDepth == 0 && msg.arg1 == 0) {
- broadcastFolderList(mID, EMPTY_MEDIA_ITEM_LIST);
- transitionTo(mConnected);
- } else {
- // Otherwise move to root and fetch the listing.
- // the MoveToRoot#enter() function takes care of fetch.
- mMoveToRoot.setFolder(mID);
- transitionTo(mMoveToRoot);
- }
- mBrowseTree.setCurrentBrowsedFolder(mID);
- // Also set the browsed player here.
- mBrowseTree.setCurrentBrowsedPlayer(mID);
- break;
-
- case MESSAGE_INTERNAL_CMD_TIMEOUT:
- broadcastFolderList(mID, EMPTY_MEDIA_ITEM_LIST);
- transitionTo(mConnected);
- break;
-
- case MESSAGE_SEND_PASS_THROUGH_CMD:
- case MESSAGE_SEND_GROUP_NAVIGATION_CMD:
- case MESSAGE_PROCESS_SET_ABS_VOL_CMD:
- case MESSAGE_PROCESS_REGISTER_ABS_VOL_NOTIFICATION:
- case MESSAGE_PROCESS_TRACK_CHANGED:
- case MESSAGE_PROCESS_PLAY_POS_CHANGED:
- case MESSAGE_PROCESS_PLAY_STATUS_CHANGED:
- case MESSAGE_PROCESS_VOLUME_CHANGED_NOTIFICATION:
- case MESSAGE_STOP_METADATA_BROADCASTS:
- case MESSAGE_START_METADATA_BROADCASTS:
- case MESSAGE_PROCESS_CONNECTION_CHANGE:
- case MESSAGE_PROCESS_BROWSE_CONNECTION_CHANGE:
- // All of these messages should be handled by parent state immediately.
- return false;
-
- default:
- if (DBG) Log.d(STATE_TAG, "deferring message " + msg.what + " to connected!");
- deferMessage(msg);
- }
- return true;
- }
- }
-
- class SetAddresedPlayerAndPlayItem extends CmdState {
- private static final String STATE_TAG = "AVRCPSM.SetAddresedPlayerAndPlayItem";
- int mScope;
- String mPlayItemId;
- String mAddrPlayerId;
-
- public void setItemAndScope(String addrPlayerId, String playItemId, int scope) {
- mAddrPlayerId = addrPlayerId;
- mPlayItemId = playItemId;
- mScope = scope;
- }
-
- @Override
- public boolean processMessage(Message msg) {
- if (DBG) Log.d(STATE_TAG, "processMessage " + msg.what);
- switch (msg.what) {
- case MESSAGE_PROCESS_SET_ADDRESSED_PLAYER:
- // Set the new addressed player.
- mBrowseTree.setCurrentAddressedPlayer(mAddrPlayerId);
-
- // And now play the item.
- AvrcpControllerService.playItemNative(mRemoteDevice.getBluetoothAddress(),
- (byte) mScope, AvrcpControllerService.hexStringToByteUID(mPlayItemId),
- (int) 0);
-
- // Transition to connected state here.
- transitionTo(mConnected);
- break;
-
- case MESSAGE_INTERNAL_CMD_TIMEOUT:
- transitionTo(mConnected);
- break;
-
- case MESSAGE_SEND_PASS_THROUGH_CMD:
- case MESSAGE_SEND_GROUP_NAVIGATION_CMD:
- case MESSAGE_PROCESS_SET_ABS_VOL_CMD:
- case MESSAGE_PROCESS_REGISTER_ABS_VOL_NOTIFICATION:
- case MESSAGE_PROCESS_TRACK_CHANGED:
- case MESSAGE_PROCESS_PLAY_POS_CHANGED:
- case MESSAGE_PROCESS_PLAY_STATUS_CHANGED:
- case MESSAGE_PROCESS_VOLUME_CHANGED_NOTIFICATION:
- case MESSAGE_STOP_METADATA_BROADCASTS:
- case MESSAGE_START_METADATA_BROADCASTS:
- case MESSAGE_PROCESS_CONNECTION_CHANGE:
- case MESSAGE_PROCESS_BROWSE_CONNECTION_CHANGE:
- // All of these messages should be handled by parent state immediately.
- return false;
-
- default:
- if (DBG) Log.d(STATE_TAG, "deferring message " + msg.what + " to connected!");
- deferMessage(msg);
- }
- return true;
- }
- }
-
- // Class template for commands. Each state should do the following:
- // (a) In enter() send a timeout message which could be tracked in the
- // processMessage() stage.
- // (b) In exit() remove all the timeouts.
- //
- // Essentially the lifecycle of a timeout should be bounded to a CmdState always.
- abstract class CmdState extends State {
- @Override
- public void enter() {
- sendMessageDelayed(MESSAGE_INTERNAL_CMD_TIMEOUT, CMD_TIMEOUT_MILLIS);
}
@Override
public void exit() {
removeMessages(MESSAGE_INTERNAL_CMD_TIMEOUT);
+ mBrowseNode = null;
+ super.exit();
}
}
- // Interface APIs
- boolean isConnected() {
- synchronized (mLock) {
- return mIsConnected;
+ protected class Disconnecting extends State {
+ @Override
+ public void enter() {
+ onBrowsingDisconnected();
+ broadcastConnectionStateChanged(BluetoothProfile.STATE_DISCONNECTING);
+ transitionTo(mDisconnected);
}
}
- void doQuit() {
- try {
- mContext.unregisterReceiver(mBroadcastReceiver);
- } catch (IllegalArgumentException expected) {
- // If the receiver was never registered unregister will throw an
- // IllegalArgumentException.
- }
- mCoveArtUtils.closeBip(mBipStateMachine);
- // we should disacrd, all currently queuedup messages.
- quitNow();
- }
-
- void dump(StringBuilder sb) {
- ProfileService.println(sb, "StateMachine: " + this.toString());
- }
-
- MediaMetadata getCurrentMetaData() {
- synchronized (mLock) {
- if (mAddressedPlayer != null && mAddressedPlayer.getCurrentTrack() != null) {
- MediaMetadata mmd = mAddressedPlayer.getCurrentTrack().getMediaMetaData();
- if (DBG) {
- Log.d(TAG, "getCurrentMetaData mmd " + mmd);
- }
- }
- return EMPTY_MEDIA_METADATA;
- }
- }
-
- PlaybackState getCurrentPlayBackState() {
- return getCurrentPlayBackState(true);
- }
-
- PlaybackState getCurrentPlayBackState(boolean cached) {
- if (cached) {
- synchronized (mLock) {
- if (mAddressedPlayer == null) {
- return new PlaybackState.Builder().setState(PlaybackState.STATE_ERROR,
- PlaybackState.PLAYBACK_POSITION_UNKNOWN, 0).build();
- }
- return mAddressedPlayer.getPlaybackState();
- }
- } else {
- // Issue a native request, we return NULL since this is only for PTS.
- AvrcpControllerService.getPlaybackStateNative(mRemoteDevice.getBluetoothAddress());
- return null;
- }
- }
-
- // Entry point to the state machine where the services should call to fetch children
- // for a specific node. It checks if the currently browsed node is the same as the one being
- // asked for, in that case it returns the currently cached children. This saves bandwidth and
- // also if we are already fetching elements for a current folder (since we need to batch
- // fetches) then we should not submit another request but simply return what we have fetched
- // until now.
- //
- // It handles fetches to all VFS, Now Playing and Media Player lists.
- void getChildren(String parentMediaId, int start, int items) {
- BrowseTree.BrowseNode bn = mBrowseTree.findBrowseNodeByID(parentMediaId);
- if (bn == null) {
- Log.e(TAG, "Invalid folder to browse " + mBrowseTree);
- broadcastFolderList(parentMediaId, EMPTY_MEDIA_ITEM_LIST);
- return;
- }
-
- if (DBG) {
- Log.d(TAG, "To Browse folder " + bn + " is cached " + bn.isCached() + " current folder "
- + mBrowseTree.getCurrentBrowsedFolder());
- }
- if (bn.equals(mBrowseTree.getCurrentBrowsedFolder()) && bn.isCached()) {
- if (DBG) {
- Log.d(TAG, "Same cached folder -- returning existing children.");
- }
- BrowseTree.BrowseNode n = mBrowseTree.findBrowseNodeByID(parentMediaId);
- ArrayList<MediaItem> childrenList = new ArrayList<MediaItem>();
- for (BrowseTree.BrowseNode cn : n.getChildren()) {
- childrenList.add(cn.getMediaItem());
- }
- broadcastFolderList(parentMediaId, childrenList);
- return;
- }
-
- Message msg = null;
- int btDirection = mBrowseTree.getDirection(parentMediaId);
- BrowseTree.BrowseNode currFol = mBrowseTree.getCurrentBrowsedFolder();
- if (DBG) {
- Log.d(TAG, "Browse direction parent " + mBrowseTree.getCurrentBrowsedFolder() + " req "
- + parentMediaId + " direction " + btDirection);
- }
- if (BrowseTree.ROOT.equals(parentMediaId)) {
- // Root contains the list of players.
- msg = obtainMessage(AvrcpControllerStateMachine.MESSAGE_GET_PLAYER_LIST, start, items);
- } else if (bn.isPlayer() && btDirection != BrowseTree.DIRECTION_SAME) {
- // Set browsed (and addressed player) as the new player.
- // This should fetch the list of folders.
- msg = obtainMessage(AvrcpControllerStateMachine.MESSAGE_SET_BROWSED_PLAYER,
- bn.getPlayerID(), 0, bn.getID());
- } else if (bn.isNowPlaying()) {
- // Issue a request to fetch the items.
- msg = obtainMessage(AvrcpControllerStateMachine.MESSAGE_GET_NOW_PLAYING_LIST, start,
- items, parentMediaId);
- } else {
- // Only change folder if desired. If an app refreshes a folder
- // (because it resumed etc) and current folder does not change
- // then we can simply fetch list.
-
- // We exempt two conditions from change folder:
- // a) If the new folder is the same as current folder (refresh of UI)
- // b) If the new folder is ROOT and current folder is NOW_PLAYING (or vice-versa)
- // In this condition we 'fake' child-parent hierarchy but it does not exist in
- // bluetooth world.
- boolean isNowPlayingToRoot =
- currFol.isNowPlaying() && bn.getID().equals(BrowseTree.ROOT);
- if (!isNowPlayingToRoot) {
- // Find the direction of traversal.
- int direction = -1;
- if (DBG) Log.d(TAG, "Browse direction " + currFol + " " + bn + " = " + btDirection);
- if (btDirection == BrowseTree.DIRECTION_UNKNOWN) {
- Log.w(TAG, "parent " + bn + " is not a direct "
- + "successor or predeccessor of current folder " + currFol);
- broadcastFolderList(parentMediaId, EMPTY_MEDIA_ITEM_LIST);
- return;
- }
-
- if (btDirection == BrowseTree.DIRECTION_DOWN) {
- direction = AvrcpControllerService.FOLDER_NAVIGATION_DIRECTION_DOWN;
- } else if (btDirection == BrowseTree.DIRECTION_UP) {
- direction = AvrcpControllerService.FOLDER_NAVIGATION_DIRECTION_UP;
- }
-
- Bundle b = new Bundle();
- b.putString(AvrcpControllerService.EXTRA_FOLDER_ID, bn.getID());
- b.putString(AvrcpControllerService.EXTRA_FOLDER_BT_ID, bn.getFolderUID());
- msg = obtainMessage(AvrcpControllerStateMachine.MESSAGE_CHANGE_FOLDER_PATH,
- direction, 0, b);
- } else {
- // Fetch the listing without changing paths.
- msg = obtainMessage(AvrcpControllerStateMachine.MESSAGE_GET_FOLDER_LIST, start,
- items, bn.getFolderUID());
- }
- }
-
- if (msg != null) {
- sendMessage(msg);
- }
- }
-
- public void fetchAttrAndPlayItem(String uid) {
- BrowseTree.BrowseNode currItem = mBrowseTree.findFolderByIDLocked(uid);
- BrowseTree.BrowseNode currFolder = mBrowseTree.getCurrentBrowsedFolder();
- if (DBG) Log.d(TAG, "fetchAttrAndPlayItem mediaId=" + uid + " node=" + currItem);
- if (currItem != null) {
- int scope = currFolder.isNowPlaying() ? AvrcpControllerService.BROWSE_SCOPE_NOW_PLAYING
- : AvrcpControllerService.BROWSE_SCOPE_VFS;
- Message msg =
- obtainMessage(AvrcpControllerStateMachine.MESSAGE_FETCH_ATTR_AND_PLAY_ITEM,
- scope, 0, currItem.getFolderUID());
- sendMessage(msg);
- }
- }
-
- private void broadcastMetaDataChanged(MediaMetadata metadata) {
- Intent intent = new Intent(AvrcpControllerService.ACTION_TRACK_EVENT);
- intent.putExtra(AvrcpControllerService.EXTRA_METADATA, metadata);
- if (VDBG) {
- Log.d(TAG, " broadcastMetaDataChanged = " + metadata.getDescription());
- }
- mContext.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
- }
-
- private void broadcastFolderList(String id, ArrayList<MediaItem> items) {
- Intent intent = new Intent(AvrcpControllerService.ACTION_FOLDER_LIST);
- if (VDBG) Log.d(TAG, "broadcastFolderList id " + id + " items " + items);
- intent.putExtra(AvrcpControllerService.EXTRA_FOLDER_ID, id);
- intent.putParcelableArrayListExtra(AvrcpControllerService.EXTRA_FOLDER_LIST, items);
- mContext.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
- }
-
- private void broadcastPlayBackStateChanged(PlaybackState state) {
- Intent intent = new Intent(AvrcpControllerService.ACTION_TRACK_EVENT);
- intent.putExtra(AvrcpControllerService.EXTRA_PLAYBACK, state);
- if (DBG) {
- Log.d(TAG, " broadcastPlayBackStateChanged = " + state.toString());
- }
- mContext.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
- }
-
- private void setAbsVolume(int absVol, int label) {
- int maxVolume = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
- int currIndex = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
- /* If SetAbsVolume Control Cmd is received from non-Streaming device then the
- * requested volume level will not be set fot rendering and current Abs vol level
- * at DUT (sink: rendering device) will be sent in response. */
- Log.d(TAG, "Streaming device: " + A2dpSinkService.getCurrentStreamingDevice()
- + " Device:" + mDevice);
- if (!mDevice.equals(A2dpSinkService.getCurrentStreamingDevice())) {
- absVol = (currIndex * ABS_VOL_BASE) / maxVolume;
- Log.w(TAG, "Volume change request came from non-streaming device," +
- "respond with current absVol: " + absVol);
- AvrcpControllerService.sendAbsVolRspNative(mRemoteDevice.getBluetoothAddress(), absVol,
- label);
- return;
- }
- // Ignore first volume command since phone may not know difference between stream volume
- // and amplifier volume.
- if (mRemoteDevice.getFirstAbsVolCmdRecvd()) {
- int newIndex = (maxVolume * absVol) / ABS_VOL_BASE;
- if (DBG) {
- Log.d(TAG, " setAbsVolume =" + absVol + " maxVol = " + maxVolume
- + " cur = " + currIndex + " new = " + newIndex);
- }
- /*
- * In some cases change in percentage is not sufficient enough to warrant
- * change in index values which are in range of 0-15. For such cases
- * no action is required
- */
- if (newIndex != currIndex) {
- mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, newIndex,
- AudioManager.FLAG_SHOW_UI);
- }
- } else {
- mRemoteDevice.setFirstAbsVolCmdRecvd();
- absVol = (currIndex * ABS_VOL_BASE) / maxVolume;
- if (DBG) Log.d(TAG, " SetAbsVol recvd for first time, respond with " + absVol);
- }
- AvrcpControllerService.sendAbsVolRspNative(mRemoteDevice.getBluetoothAddress(), absVol,
- label);
- }
-
- private int getVolumePercentage() {
- int maxVolume = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
- int currIndex = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
- int percentageVol = ((currIndex * ABS_VOL_BASE) / maxVolume);
- return percentageVol;
- }
-
private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
+ Log.d(TAG, "onReceive(): action: " + action);
if (action.equals(AudioManager.VOLUME_CHANGED_ACTION)) {
int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
if (streamType == AudioManager.STREAM_MUSIC) {
@@ -1282,84 +801,168 @@
}
};
- public static String dumpMessageString(int message) {
- String str = "UNKNOWN";
- switch (message) {
- case MESSAGE_SEND_PASS_THROUGH_CMD:
- str = "REQ_PASS_THROUGH_CMD";
- break;
- case MESSAGE_SEND_GROUP_NAVIGATION_CMD:
- str = "REQ_GRP_NAV_CMD";
- break;
- case MESSAGE_PROCESS_SET_ABS_VOL_CMD:
- str = "CB_SET_ABS_VOL_CMD";
- break;
- case MESSAGE_PROCESS_REGISTER_ABS_VOL_NOTIFICATION:
- str = "CB_REGISTER_ABS_VOL";
- break;
- case MESSAGE_PROCESS_TRACK_CHANGED:
- str = "CB_TRACK_CHANGED";
- break;
- case MESSAGE_PROCESS_PLAY_POS_CHANGED:
- str = "CB_PLAY_POS_CHANGED";
- break;
- case MESSAGE_PROCESS_PLAY_STATUS_CHANGED:
- str = "CB_PLAY_STATUS_CHANGED";
- break;
- case MESSAGE_PROCESS_RC_FEATURES:
- str = "CB_RC_FEATURES";
- break;
- case MESSAGE_PROCESS_CONNECTION_CHANGE:
- str = "CB_CONN_CHANGED";
- break;
- case CoverArtUtils.MESSAGE_BIP_CONNECTED:
- case CoverArtUtils.MESSAGE_BIP_DISCONNECTED:
- case CoverArtUtils.MESSAGE_BIP_IMAGE_FETCHED:
- case CoverArtUtils.MESSAGE_BIP_THUMB_NAIL_FETCHED:
- str = mCoveArtUtils.dumpMessageString(message);
- break;
- default:
- str = Integer.toString(message);
- break;
+ void doQuit() {
+ Log.d(TAG, "doQuit()");
+ try {
+ mService.unregisterReceiver(mBroadcastReceiver);
+ } catch (IllegalArgumentException expected) {
+ // If the receiver was never registered unregister will throw an
+ // IllegalArgumentException.
}
- return str;
+ // we should disacrd, all currently queuedup messages.
+ quitNow();
}
- public static String displayBluetoothAvrcpSettings(BluetoothAvrcpPlayerSettings mSett) {
- StringBuffer sb = new StringBuffer();
- int supportedSetting = mSett.getSettings();
- if (VDBG) {
- Log.d(TAG, " setting: " + supportedSetting);
+ private void setAbsVolume(int absVol, int label) {
+ int maxVolume = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
+ int currIndex = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
+
+ /* If SetAbsVolume Control Cmd is received from non-Streaming device then the
+ * requested volume level will not be set fot rendering and current Abs vol level
+ * at DUT (sink: rendering device) will be sent in response. */
+ Log.d(TAG, "Streaming device: " + A2dpSinkService.getCurrentStreamingDevice()
+ + " Device: " + mDevice + " absVol: " + absVol + " label: " + label);
+ if (!mDevice.equals(A2dpSinkService.getCurrentStreamingDevice())) {
+ absVol = (currIndex * ABS_VOL_BASE) / maxVolume;
+ Log.w(TAG, "Volume change request came from non-streaming device," +
+ "respond with current absVol: " + absVol);
+ AvrcpControllerService.sendAbsVolRspNative(mRemoteDevice.getBluetoothAddress(), absVol,
+ label);
+ int percentageVol = getVolumePercentage();
+ AvrcpControllerService.sendRegisterAbsVolRspNative(mRemoteDevice.getBluetoothAddress(),
+ NOTIFICATION_RSP_TYPE_CHANGED, percentageVol, mRemoteDevice.getNotificationLabel());
+ mPreviousPercentageVol = percentageVol;
+ return;
}
- if ((supportedSetting & BluetoothAvrcpPlayerSettings.SETTING_EQUALIZER) != 0) {
- sb.append(" EQ : ");
- sb.append(Integer.toString(mSett.getSettingValue(
- BluetoothAvrcpPlayerSettings.SETTING_EQUALIZER)));
+ int newIndex = (maxVolume * absVol) / ABS_VOL_BASE;
+ logD(" setAbsVolume =" + absVol + " maxVol = " + maxVolume
+ + " cur = " + currIndex + " new = " + newIndex);
+ /*
+ * In some cases change in percentage is not sufficient enough to warrant
+ * change in index values which are in range of 0-15. For such cases
+ * no action is required
+ */
+ if (newIndex != currIndex) {
+ mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, newIndex,
+ AudioManager.FLAG_SHOW_UI);
}
- if ((supportedSetting & BluetoothAvrcpPlayerSettings.SETTING_REPEAT) != 0) {
- sb.append(" REPEAT : ");
- sb.append(Integer.toString(mSett.getSettingValue(
- BluetoothAvrcpPlayerSettings.SETTING_REPEAT)));
- }
- if ((supportedSetting & BluetoothAvrcpPlayerSettings.SETTING_SHUFFLE) != 0) {
- sb.append(" SHUFFLE : ");
- sb.append(Integer.toString(mSett.getSettingValue(
- BluetoothAvrcpPlayerSettings.SETTING_SHUFFLE)));
- }
- if ((supportedSetting & BluetoothAvrcpPlayerSettings.SETTING_SCAN) != 0) {
- sb.append(" SCAN : ");
- sb.append(Integer.toString(mSett.getSettingValue(
- BluetoothAvrcpPlayerSettings.SETTING_SCAN)));
- }
- return sb.toString();
+ AvrcpControllerService.sendAbsVolRspNative(mDeviceAddress, absVol, label);
}
- BluetoothDevice getDevice() {
- return mDevice;
+ private int getVolumePercentage() {
+ int maxVolume = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
+ int currIndex = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
+ int percentageVol = ((currIndex * ABS_VOL_BASE) / maxVolume);
+ logD("maxVolume: " + maxVolume + " currIndex: " + currIndex +
+ " percentageVol: " + percentageVol);
+ return percentageVol;
}
- boolean isBrowsingConnected() {
- Log.d(TAG, "mBrowsingConnected = " + mBrowsingConnected);
- return mBrowsingConnected;
+ MediaSession.Callback mSessionCallbacks = new MediaSession.Callback() {
+ @Override
+ public void onPlay() {
+ logD("onPlay");
+ onPrepare();
+ sendMessage(MSG_AVRCP_PASSTHRU, AvrcpControllerService.PASS_THRU_CMD_ID_PLAY);
+ }
+
+ @Override
+ public void onPause() {
+ logD("onPause");
+ sendMessage(MSG_AVRCP_PASSTHRU, AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE);
+ }
+
+ @Override
+ public void onSkipToNext() {
+ logD("onSkipToNext");
+ onPrepare();
+ sendMessage(MSG_AVRCP_PASSTHRU, AvrcpControllerService.PASS_THRU_CMD_ID_FORWARD);
+ }
+
+ @Override
+ public void onSkipToPrevious() {
+ logD("onSkipToPrevious");
+ onPrepare();
+ sendMessage(MSG_AVRCP_PASSTHRU, AvrcpControllerService.PASS_THRU_CMD_ID_BACKWARD);
+ }
+
+ @Override
+ public void onSkipToQueueItem(long id) {
+ logD("onSkipToQueueItem" + id);
+ onPrepare();
+ BrowseTree.BrowseNode node = mBrowseTree.getTrackFromNowPlayingList((int) id);
+ if (node != null) {
+ sendMessage(MESSAGE_PLAY_ITEM, node);
+ }
+ }
+
+ @Override
+ public void onStop() {
+ logD("onStop");
+ sendMessage(MSG_AVRCP_PASSTHRU, AvrcpControllerService.PASS_THRU_CMD_ID_STOP);
+ }
+
+ @Override
+ public void onPrepare() {
+ logD("onPrepare");
+ A2dpSinkService a2dpSinkService = A2dpSinkService.getA2dpSinkService();
+ if (a2dpSinkService != null) {
+ a2dpSinkService.requestAudioFocus(mDevice, true);
+ }
+ }
+
+ @Override
+ public void onRewind() {
+ logD("onRewind");
+ sendMessage(MSG_AVRCP_PASSTHRU, AvrcpControllerService.PASS_THRU_CMD_ID_REWIND);
+ }
+
+ @Override
+ public void onFastForward() {
+ logD("onFastForward");
+ sendMessage(MSG_AVRCP_PASSTHRU, AvrcpControllerService.PASS_THRU_CMD_ID_FF);
+ }
+
+ @Override
+ public void onPlayFromMediaId(String mediaId, Bundle extras) {
+ logD("onPlayFromMediaId");
+ // Play the item if possible.
+ onPrepare();
+ BrowseTree.BrowseNode node = mBrowseTree.findBrowseNodeByID(mediaId);
+ sendMessage(MESSAGE_PLAY_ITEM, node);
+ }
+ };
+
+ protected void broadcastConnectionStateChanged(int currentState) {
+ if (mMostRecentState == currentState) {
+ return;
+ }
+ if (currentState == BluetoothProfile.STATE_CONNECTED) {
+ MetricsLogger.logProfileConnectionEvent(
+ BluetoothMetricsProto.ProfileId.AVRCP_CONTROLLER);
+ }
+ logD("Connection state " + mDevice + ": " + mMostRecentState + "->" + currentState);
+ Intent intent = new Intent(BluetoothAvrcpController.ACTION_CONNECTION_STATE_CHANGED);
+ intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, mMostRecentState);
+ intent.putExtra(BluetoothProfile.EXTRA_STATE, currentState);
+ intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice);
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+ mMostRecentState = currentState;
+ mService.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
+ }
+
+ private void broadcastPlayBackStateChanged(PlaybackState state) {
+ Intent intent = new Intent(AvrcpControllerService.ACTION_TRACK_EVENT);
+ intent.putExtra(AvrcpControllerService.EXTRA_PLAYBACK, state);
+ if (DBG) {
+ Log.d(TAG, " broadcastPlayBackStateChanged = " + state.toString());
+ }
+ mService.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
+ }
+
+
+ private boolean shouldRequestFocus() {
+ return mService.getResources()
+ .getBoolean(R.bool.a2dp_sink_automatically_request_audio_focus);
}
}
diff --git a/src/com/android/bluetooth/avrcpcontroller/AvrcpPlayer.java b/src/com/android/bluetooth/avrcpcontroller/AvrcpPlayer.java
index d9e4924..ab0e987 100644
--- a/src/com/android/bluetooth/avrcpcontroller/AvrcpPlayer.java
+++ b/src/com/android/bluetooth/avrcpcontroller/AvrcpPlayer.java
@@ -16,9 +16,13 @@
package com.android.bluetooth.avrcpcontroller;
+import android.media.MediaMetadata;
import android.media.session.PlaybackState;
+import android.os.SystemClock;
import android.util.Log;
+import java.util.Arrays;
+
/*
* Contains information about remote player
*/
@@ -28,21 +32,48 @@
public static final int INVALID_ID = -1;
+ public static final int FEATURE_PLAY = 40;
+ public static final int FEATURE_STOP = 41;
+ public static final int FEATURE_PAUSE = 42;
+ public static final int FEATURE_REWIND = 44;
+ public static final int FEATURE_FAST_FORWARD = 45;
+ public static final int FEATURE_FORWARD = 47;
+ public static final int FEATURE_PREVIOUS = 48;
+ public static final int FEATURE_BROWSING = 59;
+
private int mPlayStatus = PlaybackState.STATE_NONE;
private long mPlayTime = PlaybackState.PLAYBACK_POSITION_UNKNOWN;
+ private long mPlayTimeUpdate = 0;
+ private float mPlaySpeed = 1;
private int mId;
private String mName = "";
private int mPlayerType;
- private TrackInfo mCurrentTrack = new TrackInfo();
+ private byte[] mPlayerFeatures;
+ private long mAvailableActions;
+ private MediaMetadata mCurrentTrack;
+ private PlaybackState mPlaybackState;
AvrcpPlayer() {
mId = INVALID_ID;
+ //Set Default Actions in case Player data isn't available.
+ mAvailableActions = PlaybackState.ACTION_PAUSE | PlaybackState.ACTION_PLAY
+ | PlaybackState.ACTION_SKIP_TO_NEXT | PlaybackState.ACTION_SKIP_TO_PREVIOUS
+ | PlaybackState.ACTION_STOP;
+ PlaybackState.Builder playbackStateBuilder = new PlaybackState.Builder()
+ .setActions(mAvailableActions);
+ mPlaybackState = playbackStateBuilder.build();
}
- AvrcpPlayer(int id, String name, int transportFlags, int playStatus, int playerType) {
+ AvrcpPlayer(int id, String name, byte[] playerFeatures, int playStatus, int playerType) {
mId = id;
mName = name;
+ mPlayStatus = playStatus;
mPlayerType = playerType;
+ mPlayerFeatures = Arrays.copyOf(playerFeatures, playerFeatures.length);
+ updateAvailableActions();
+ PlaybackState.Builder playbackStateBuilder = new PlaybackState.Builder()
+ .setActions(mAvailableActions);
+ mPlaybackState = playbackStateBuilder.build();
}
public int getId() {
@@ -55,6 +86,9 @@
public void setPlayTime(int playTime) {
mPlayTime = playTime;
+ mPlayTimeUpdate = SystemClock.elapsedRealtime();
+ mPlaybackState = new PlaybackState.Builder(mPlaybackState).setState(mPlayStatus, mPlayTime,
+ mPlaySpeed).build();
}
public long getPlayTime() {
@@ -62,39 +96,83 @@
}
public void setPlayStatus(int playStatus) {
+ mPlayTime += mPlaySpeed * (SystemClock.elapsedRealtime()
+ - mPlaybackState.getLastPositionUpdateTime());
mPlayStatus = playStatus;
+ switch (mPlayStatus) {
+ case PlaybackState.STATE_STOPPED:
+ mPlaySpeed = 0;
+ break;
+ case PlaybackState.STATE_PLAYING:
+ mPlaySpeed = 1;
+ break;
+ case PlaybackState.STATE_PAUSED:
+ mPlaySpeed = 0;
+ break;
+ case PlaybackState.STATE_FAST_FORWARDING:
+ mPlaySpeed = 3;
+ break;
+ case PlaybackState.STATE_REWINDING:
+ mPlaySpeed = -3;
+ break;
+ }
+
+ mPlaybackState = new PlaybackState.Builder(mPlaybackState).setState(mPlayStatus, mPlayTime,
+ mPlaySpeed).build();
+ }
+
+ public int getPlayStatus() {
+ return mPlayStatus;
+ }
+
+ public boolean supportsFeature(int featureId) {
+ int byteNumber = featureId / 8;
+ byte bitMask = (byte) (1 << (featureId % 8));
+ return (mPlayerFeatures[byteNumber] & bitMask) == bitMask;
}
public PlaybackState getPlaybackState() {
if (DBG) {
Log.d(TAG, "getPlayBackState state " + mPlayStatus + " time " + mPlayTime);
}
-
- long position = mPlayTime;
- float speed = 1;
- switch (mPlayStatus) {
- case PlaybackState.STATE_STOPPED:
- position = 0;
- speed = 0;
- break;
- case PlaybackState.STATE_PAUSED:
- speed = 0;
- break;
- case PlaybackState.STATE_FAST_FORWARDING:
- speed = 3;
- break;
- case PlaybackState.STATE_REWINDING:
- speed = -3;
- break;
- }
- return new PlaybackState.Builder().setState(mPlayStatus, position, speed).build();
+ return mPlaybackState;
}
- public synchronized void updateCurrentTrack(TrackInfo update) {
+ public synchronized void updateCurrentTrack(MediaMetadata update) {
+ if (update != null) {
+ long trackNumber = update.getLong(MediaMetadata.METADATA_KEY_TRACK_NUMBER);
+ mPlaybackState = new PlaybackState.Builder(mPlaybackState).setActiveQueueItemId(
+ trackNumber - 1).build();
+ }
mCurrentTrack = update;
}
- public synchronized TrackInfo getCurrentTrack() {
+ public synchronized MediaMetadata getCurrentTrack() {
return mCurrentTrack;
}
+
+ private void updateAvailableActions() {
+ if (supportsFeature(FEATURE_PLAY)) {
+ mAvailableActions = mAvailableActions | PlaybackState.ACTION_PLAY;
+ }
+ if (supportsFeature(FEATURE_STOP)) {
+ mAvailableActions = mAvailableActions | PlaybackState.ACTION_STOP;
+ }
+ if (supportsFeature(FEATURE_PAUSE)) {
+ mAvailableActions = mAvailableActions | PlaybackState.ACTION_PAUSE;
+ }
+ if (supportsFeature(FEATURE_REWIND)) {
+ mAvailableActions = mAvailableActions | PlaybackState.ACTION_REWIND;
+ }
+ if (supportsFeature(FEATURE_FAST_FORWARD)) {
+ mAvailableActions = mAvailableActions | PlaybackState.ACTION_FAST_FORWARD;
+ }
+ if (supportsFeature(FEATURE_FORWARD)) {
+ mAvailableActions = mAvailableActions | PlaybackState.ACTION_SKIP_TO_NEXT;
+ }
+ if (supportsFeature(FEATURE_PREVIOUS)) {
+ mAvailableActions = mAvailableActions | PlaybackState.ACTION_SKIP_TO_PREVIOUS;
+ }
+ if (DBG) Log.d(TAG, "Supported Actions = " + mAvailableActions);
+ }
}
diff --git a/src/com/android/bluetooth/avrcpcontroller/BluetoothMediaBrowserService.java b/src/com/android/bluetooth/avrcpcontroller/BluetoothMediaBrowserService.java
new file mode 100644
index 0000000..ae5b769
--- /dev/null
+++ b/src/com/android/bluetooth/avrcpcontroller/BluetoothMediaBrowserService.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.avrcpcontroller;
+
+import android.media.MediaMetadata;
+import android.media.browse.MediaBrowser.MediaItem;
+import android.media.session.MediaSession;
+import android.media.session.PlaybackState;
+import android.os.Bundle;
+import android.service.media.MediaBrowserService;
+import android.util.Log;
+
+import com.android.bluetooth.R;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Implements the MediaBrowserService interface to AVRCP and A2DP
+ *
+ * This service provides a means for external applications to access A2DP and AVRCP.
+ * The applications are expected to use MediaBrowser (see API) and all the music
+ * browsing/playback/metadata can be controlled via MediaBrowser and MediaController.
+ *
+ * The current behavior of MediaSession exposed by this service is as follows:
+ * 1. MediaSession is active (i.e. SystemUI and other overview UIs can see updates) when device is
+ * connected and first starts playing. Before it starts playing we do not active the session.
+ * 1.1 The session is active throughout the duration of connection.
+ * 2. The session is de-activated when the device disconnects. It will be connected again when (1)
+ * happens.
+ */
+public class BluetoothMediaBrowserService extends MediaBrowserService {
+ private static final String TAG = "BluetoothMediaBrowserService";
+ private static final boolean DBG = true;
+
+ private static BluetoothMediaBrowserService sBluetoothMediaBrowserService;
+
+ private MediaSession mSession;
+
+ // Browsing related structures.
+ private List<MediaSession.QueueItem> mMediaQueue = new ArrayList<>();
+
+
+ /**
+ * Initialize this BluetoothMediaBrowserService, creating our MediaSession, MediaPlayer and
+ * MediaMetaData, and setting up mechanisms to talk with the AvrcpControllerService.
+ */
+ @Override
+ public void onCreate() {
+ if (DBG) Log.d(TAG, "onCreate");
+ super.onCreate();
+
+ // Create and configure the MediaSession
+ mSession = new MediaSession(this, TAG);
+ setSessionToken(mSession.getSessionToken());
+ mSession.setFlags(MediaSession.FLAG_HANDLES_MEDIA_BUTTONS
+ | MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS);
+ mSession.setQueueTitle(getString(R.string.bluetooth_a2dp_sink_queue_name));
+ mSession.setQueue(mMediaQueue);
+ PlaybackState.Builder playbackStateBuilder = new PlaybackState.Builder();
+ playbackStateBuilder.setState(PlaybackState.STATE_ERROR,
+ PlaybackState.PLAYBACK_POSITION_UNKNOWN, 1.0f).setActions(0);
+ playbackStateBuilder.setErrorMessage(getString(R.string.bluetooth_disconnected));
+ mSession.setPlaybackState(playbackStateBuilder.build());
+ sBluetoothMediaBrowserService = this;
+ }
+
+ List<MediaItem> getContents(final String parentMediaId) {
+ AvrcpControllerService avrcpControllerService =
+ AvrcpControllerService.getAvrcpControllerService();
+ if (avrcpControllerService == null) {
+ return new ArrayList(0);
+ } else {
+ return avrcpControllerService.getContents(parentMediaId);
+ }
+ }
+
+ @Override
+ public synchronized void onLoadChildren(final String parentMediaId,
+ final Result<List<MediaItem>> result) {
+ if (DBG) Log.d(TAG, "onLoadChildren parentMediaId=" + parentMediaId);
+ List<MediaItem> contents = getContents(parentMediaId);
+ if (contents == null) {
+ result.detach();
+ } else {
+ result.sendResult(contents);
+ }
+ }
+
+ @Override
+ public BrowserRoot onGetRoot(String clientPackageName, int clientUid, Bundle rootHints) {
+ if (DBG) Log.d(TAG, "onGetRoot");
+ return new BrowserRoot(BrowseTree.ROOT, null);
+ }
+
+ private void updateNowPlayingQueue(BrowseTree.BrowseNode node) {
+ List<MediaItem> songList = node.getContents();
+ mMediaQueue.clear();
+ if (songList != null) {
+ for (MediaItem song : songList) {
+ mMediaQueue.add(new MediaSession.QueueItem(song.getDescription(),
+ mMediaQueue.size()));
+ }
+ }
+ mSession.setQueue(mMediaQueue);
+ }
+
+ static synchronized void notifyChanged(BrowseTree.BrowseNode node) {
+ if (sBluetoothMediaBrowserService != null) {
+ if (node.getScope() == AvrcpControllerService.BROWSE_SCOPE_NOW_PLAYING) {
+ sBluetoothMediaBrowserService.updateNowPlayingQueue(node);
+ } else {
+ sBluetoothMediaBrowserService.notifyChildrenChanged(node.getID());
+ }
+ }
+ }
+
+ static synchronized void addressedPlayerChanged(MediaSession.Callback callback) {
+ if (sBluetoothMediaBrowserService != null) {
+ sBluetoothMediaBrowserService.mSession.setCallback(callback);
+ } else {
+ Log.w(TAG, "addressedPlayerChanged Unavailable");
+ }
+ }
+
+ static synchronized void trackChanged(MediaMetadata mediaMetadata) {
+ if (sBluetoothMediaBrowserService != null) {
+ sBluetoothMediaBrowserService.mSession.setMetadata(mediaMetadata);
+ } else {
+ Log.w(TAG, "trackChanged Unavailable");
+ }
+ }
+
+ static synchronized void notifyChanged(PlaybackState playbackState) {
+ Log.d(TAG, "notifyChanged PlaybackState" + playbackState);
+ if (sBluetoothMediaBrowserService != null) {
+ sBluetoothMediaBrowserService.mSession.setPlaybackState(playbackState);
+ } else {
+ Log.w(TAG, "notifyChanged Unavailable");
+ }
+ }
+
+ /**
+ * Send AVRCP Play command
+ */
+ public static synchronized void play() {
+ if (sBluetoothMediaBrowserService != null) {
+ sBluetoothMediaBrowserService.mSession.getController().getTransportControls().play();
+ } else {
+ Log.w(TAG, "play Unavailable");
+ }
+ }
+
+ /**
+ * Send AVRCP Pause command
+ */
+ public static synchronized void pause() {
+ if (sBluetoothMediaBrowserService != null) {
+ sBluetoothMediaBrowserService.mSession.getController().getTransportControls().pause();
+ } else {
+ Log.w(TAG, "pause Unavailable");
+ }
+ }
+
+ /**
+ * Set Media session active whenever we have Focus of any kind
+ */
+ public static synchronized void setActive(boolean active) {
+ if (sBluetoothMediaBrowserService != null) {
+ sBluetoothMediaBrowserService.mSession.setActive(active);
+ } else {
+ Log.w(TAG, "setActive Unavailable");
+ }
+ }
+}
diff --git a/src/com/android/bluetooth/avrcpcontroller/BrowseTree.java b/src/com/android/bluetooth/avrcpcontroller/BrowseTree.java
index 46addcf..accea2a 100644
--- a/src/com/android/bluetooth/avrcpcontroller/BrowseTree.java
+++ b/src/com/android/bluetooth/avrcpcontroller/BrowseTree.java
@@ -16,16 +16,17 @@
package com.android.bluetooth.avrcpcontroller;
+import android.bluetooth.BluetoothDevice;
import android.media.MediaDescription;
import android.media.browse.MediaBrowser;
import android.media.browse.MediaBrowser.MediaItem;
import android.os.Bundle;
-import android.service.media.MediaBrowserService.Result;
import android.util.Log;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
+import java.util.UUID;
// Browsing hierarchy.
// Root:
@@ -40,15 +41,11 @@
// ....
public class BrowseTree {
private static final String TAG = "BrowseTree";
- private static final boolean DBG = false;
- private static final boolean VDBG = AvrcpControllerService.VDBG;
-
- public static final int DIRECTION_DOWN = 0;
- public static final int DIRECTION_UP = 1;
- public static final int DIRECTION_SAME = 2;
- public static final int DIRECTION_UNKNOWN = -1;
+ private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
+ private static final boolean VDBG = Log.isLoggable(TAG, Log.VERBOSE);
public static final String ROOT = "__ROOT__";
+ public static final String UP = "__UP__";
public static final String NOW_PLAYING_PREFIX = "NOW_PLAYING";
public static final String PLAYER_PREFIX = "PLAYER";
@@ -57,19 +54,39 @@
private BrowseNode mCurrentBrowseNode;
private BrowseNode mCurrentBrowsedPlayer;
private BrowseNode mCurrentAddressedPlayer;
+ private int mDepth = 0;
+ final BrowseNode mRootNode;
+ final BrowseNode mNavigateUpNode;
+ final BrowseNode mNowPlayingNode;
- BrowseTree() {
- }
+ BrowseTree(BluetoothDevice device) {
+ if (device == null) {
+ mRootNode = new BrowseNode(new MediaItem(new MediaDescription.Builder()
+ .setMediaId(ROOT).setTitle(ROOT).build(), MediaItem.FLAG_BROWSABLE));
+ mRootNode.setCached(true);
+ } else {
+ mRootNode = new BrowseNode(new MediaItem(new MediaDescription.Builder()
+ .setMediaId(ROOT + device.getAddress().toString()).setTitle(
+ device.getName()).build(), MediaItem.FLAG_BROWSABLE));
+ mRootNode.mDevice = device;
- public void init() {
- MediaDescription.Builder mdb = new MediaDescription.Builder();
- mdb.setMediaId(ROOT);
- mdb.setTitle(ROOT);
- Bundle mdBundle = new Bundle();
- mdBundle.putString(AvrcpControllerService.MEDIA_ITEM_UID_KEY, ROOT);
- mdb.setExtras(mdBundle);
- mBrowseMap.put(ROOT, new BrowseNode(new MediaItem(mdb.build(), MediaItem.FLAG_BROWSABLE)));
- mCurrentBrowseNode = mBrowseMap.get(ROOT);
+ }
+ mRootNode.mBrowseScope = AvrcpControllerService.BROWSE_SCOPE_PLAYER_LIST;
+ mRootNode.setExpectedChildren(255);
+
+ mNavigateUpNode = new BrowseNode(new MediaItem(new MediaDescription.Builder()
+ .setMediaId(UP).setTitle(UP).build(),
+ MediaItem.FLAG_BROWSABLE));
+
+ mNowPlayingNode = new BrowseNode(new MediaItem(new MediaDescription.Builder()
+ .setMediaId(NOW_PLAYING_PREFIX)
+ .setTitle(NOW_PLAYING_PREFIX).build(), MediaItem.FLAG_BROWSABLE));
+ mNowPlayingNode.mBrowseScope = AvrcpControllerService.BROWSE_SCOPE_NOW_PLAYING;
+ mNowPlayingNode.setExpectedChildren(255);
+ mBrowseMap.put(ROOT, mRootNode);
+ mBrowseMap.put(NOW_PLAYING_PREFIX, mNowPlayingNode);
+
+ mCurrentBrowseNode = mRootNode;
}
public void clear() {
@@ -77,11 +94,23 @@
mBrowseMap.clear();
}
+ void onConnected(BluetoothDevice device) {
+ BrowseNode browseNode = new BrowseNode(device);
+ mRootNode.addChild(browseNode);
+ }
+
+ BrowseNode getTrackFromNowPlayingList(int trackNumber) {
+ return mNowPlayingNode.mChildren.get(trackNumber);
+ }
+
// Each node of the tree is represented by Folder ID, Folder Name and the children.
class BrowseNode {
// MediaItem to store the media related details.
MediaItem mItem;
+ BluetoothDevice mDevice;
+ long mBluetoothId;
+
// Type of this browse node.
// Since Media APIs do not define the player separately we define that
// distinction here.
@@ -91,15 +120,19 @@
// without doing another fetch.
boolean mCached = false;
- // Result object if this node is not loaded yet. This result object will be used
- // once loading is finished.
- Result<List<MediaItem>> mResult = null;
+ byte mBrowseScope = AvrcpControllerService.BROWSE_SCOPE_VFS;
// List of children.
- final List<BrowseNode> mChildren = new ArrayList<BrowseNode>();
+ private BrowseNode mParent;
+ private final List<BrowseNode> mChildren = new ArrayList<BrowseNode>();
+ private int mExpectedChildrenCount;
BrowseNode(MediaItem item) {
mItem = item;
+ Bundle extras = mItem.getDescription().getExtras();
+ if (extras != null) {
+ mBluetoothId = extras.getLong(AvrcpControllerService.MEDIA_ITEM_UID_KEY);
+ }
}
BrowseNode(AvrcpPlayer player) {
@@ -107,34 +140,120 @@
// Transform the player into a item.
MediaDescription.Builder mdb = new MediaDescription.Builder();
- Bundle mdExtra = new Bundle();
String playerKey = PLAYER_PREFIX + player.getId();
- mdExtra.putString(AvrcpControllerService.MEDIA_ITEM_UID_KEY, playerKey);
- mdb.setExtras(mdExtra);
- mdb.setMediaId(playerKey);
+ mBluetoothId = player.getId();
+
+ mdb.setMediaId(UUID.randomUUID().toString());
mdb.setTitle(player.getName());
+ int mediaItemFlags = player.supportsFeature(AvrcpPlayer.FEATURE_BROWSING)
+ ? MediaBrowser.MediaItem.FLAG_BROWSABLE : 0;
+ mItem = new MediaBrowser.MediaItem(mdb.build(), mediaItemFlags);
+ }
+
+ BrowseNode(BluetoothDevice device) {
+ boolean mIsPlayer = true;
+ mDevice = device;
+ MediaDescription.Builder mdb = new MediaDescription.Builder();
+ String playerKey = PLAYER_PREFIX + device.getAddress().toString();
+ mdb.setMediaId(playerKey);
+ mdb.setTitle(device.getName());
+ int mediaItemFlags = MediaBrowser.MediaItem.FLAG_BROWSABLE;
+ mItem = new MediaBrowser.MediaItem(mdb.build(), mediaItemFlags);
+ }
+
+ private BrowseNode(String name) {
+ MediaDescription.Builder mdb = new MediaDescription.Builder();
+ mdb.setMediaId(name);
+ mdb.setTitle(name);
mItem = new MediaBrowser.MediaItem(mdb.build(), MediaBrowser.MediaItem.FLAG_BROWSABLE);
}
+ synchronized void setExpectedChildren(int count) {
+ mExpectedChildrenCount = count;
+ }
+
+ synchronized int getExpectedChildren() {
+ return mExpectedChildrenCount;
+ }
+
+ synchronized <E> int addChildren(List<E> newChildren) {
+ for (E child : newChildren) {
+ BrowseNode currentNode = null;
+ if (child instanceof MediaItem) {
+ currentNode = new BrowseNode((MediaItem) child);
+ } else if (child instanceof AvrcpPlayer) {
+ currentNode = new BrowseNode((AvrcpPlayer) child);
+ }
+ addChild(currentNode);
+ }
+ return newChildren.size();
+ }
+
+ synchronized boolean addChild(BrowseNode node) {
+ if (node != null) {
+ node.mParent = this;
+ if (this.mBrowseScope == AvrcpControllerService.BROWSE_SCOPE_NOW_PLAYING) {
+ node.mBrowseScope = this.mBrowseScope;
+ }
+ if (node.mDevice == null) {
+ node.mDevice = this.mDevice;
+ }
+ mChildren.add(node);
+ mBrowseMap.put(node.getID(), node);
+ return true;
+ }
+ return false;
+ }
+
+ synchronized void removeChild(BrowseNode node) {
+ mChildren.remove(node);
+ mBrowseMap.remove(node.getID());
+ }
+
+ synchronized int getChildrenCount() {
+ return mChildren.size();
+ }
+
synchronized List<BrowseNode> getChildren() {
return mChildren;
}
- synchronized boolean isChild(BrowseNode node) {
- for (BrowseNode bn : mChildren) {
- if (bn.equals(node)) {
- return true;
+ synchronized BrowseNode getParent() {
+ return mParent;
+ }
+
+ synchronized List<MediaItem> getContents() {
+ if (mChildren.size() > 0 || mCached) {
+ List<MediaItem> contents = new ArrayList<MediaItem>(mChildren.size());
+ for (BrowseNode child : mChildren) {
+ contents.add(child.getMediaItem());
}
+ return contents;
}
- return false;
+ return null;
+ }
+
+ synchronized boolean isChild(BrowseNode node) {
+ return mChildren.contains(node);
}
synchronized boolean isCached() {
return mCached;
}
+ synchronized boolean isBrowsable() {
+ return mItem.isBrowsable();
+ }
+
synchronized void setCached(boolean cached) {
+ if (DBG) Log.d(TAG, "Set Cache" + cached + "Node" + toString());
mCached = cached;
+ if (!cached) {
+ for (BrowseNode child : mChildren) {
+ mBrowseMap.remove(child.getID());
+ }
+ mChildren.clear();
+ }
}
// Fetch the Unique UID for this item, this is unique across all elements in the tree.
@@ -147,13 +266,19 @@
return Integer.parseInt(getID().replace(PLAYER_PREFIX, ""));
}
+ synchronized byte getScope() {
+ return mBrowseScope;
+ }
+
// Fetch the Folder UID that can be used to fetch folder listing via bluetooth.
// This may not be unique hence this combined with direction will define the
// browsing here.
synchronized String getFolderUID() {
- return mItem.getDescription()
- .getExtras()
- .getString(AvrcpControllerService.MEDIA_ITEM_UID_KEY);
+ return getID();
+ }
+
+ synchronized long getBluetoothID() {
+ return mBluetoothId;
}
synchronized MediaItem getMediaItem() {
@@ -178,52 +303,24 @@
}
@Override
- public String toString() {
+ public synchronized String toString() {
if (VDBG) {
- return "ID: " + getID() + " desc: " + mItem;
+ String serialized = "[ Name: " + mItem.getDescription().getTitle()
+ + " Scope:" + mBrowseScope + " expected Children: "
+ + mExpectedChildrenCount + "] ";
+ for (BrowseNode node : mChildren) {
+ serialized += node.toString();
+ }
+ return serialized;
} else {
return "ID: " + getID();
}
}
- }
- synchronized <E> void refreshChildren(String parentID, List<E> children) {
- BrowseNode parent = findFolderByIDLocked(parentID);
- if (parent == null) {
- Log.w(TAG, "parent not found for parentID " + parentID);
- return;
+ // Returns true if target is a descendant of this.
+ synchronized boolean isDescendant(BrowseNode target) {
+ return getEldestChild(this, target) == null ? false : true;
}
- refreshChildren(parent, children);
- }
-
- synchronized <E> void refreshChildren(BrowseNode parent, List<E> children) {
- if (children == null) {
- Log.e(TAG, "children cannot be null ");
- return;
- }
-
- List<BrowseNode> bnList = new ArrayList<BrowseNode>();
- for (E child : children) {
- if (child instanceof MediaItem) {
- bnList.add(new BrowseNode((MediaItem) child));
- } else if (child instanceof AvrcpPlayer) {
- bnList.add(new BrowseNode((AvrcpPlayer) child));
- }
- }
-
- String parentID = parent.getID();
- // Make sure that the child list is clean.
- if (VDBG) {
- Log.d(TAG, "parent " + parentID + " child list " + parent.getChildren());
- }
-
- addChildrenLocked(parent, bnList);
- List<MediaItem> childrenList = new ArrayList<MediaItem>();
- for (BrowseNode bn : parent.getChildren()) {
- childrenList.add(bn.getMediaItem());
- }
-
- parent.setCached(true);
}
synchronized BrowseNode findBrowseNodeByID(String parentID) {
@@ -233,50 +330,13 @@
return null;
}
if (VDBG) {
- Log.d(TAG, "Browse map: " + mBrowseMap);
+ Log.d(TAG, "Size" + mBrowseMap.size());
}
return bn;
}
- BrowseNode findFolderByIDLocked(String parentID) {
- return mBrowseMap.get(parentID);
- }
-
- void addChildrenLocked(BrowseNode parent, List<BrowseNode> items) {
- // Remove existing children and then add the new children.
- for (BrowseNode c : parent.getChildren()) {
- mBrowseMap.remove(c.getID());
- }
- parent.getChildren().clear();
-
- for (BrowseNode bn : items) {
- parent.getChildren().add(bn);
- mBrowseMap.put(bn.getID(), bn);
- }
- }
-
- synchronized int getDirection(String toUID) {
- BrowseNode fromFolder = mCurrentBrowseNode;
- BrowseNode toFolder = findFolderByIDLocked(toUID);
- if (fromFolder == null || toFolder == null) {
- Log.e(TAG, "from folder " + mCurrentBrowseNode + " or to folder " + toUID + " null!");
- }
-
- // Check the relationship.
- if (fromFolder.isChild(toFolder)) {
- return DIRECTION_DOWN;
- } else if (toFolder.isChild(fromFolder)) {
- return DIRECTION_UP;
- } else if (fromFolder.equals(toFolder)) {
- return DIRECTION_SAME;
- } else {
- Log.w(TAG, "from folder " + mCurrentBrowseNode + "to folder " + toUID);
- return DIRECTION_UNKNOWN;
- }
- }
-
synchronized boolean setCurrentBrowsedFolder(String uid) {
- BrowseNode bn = findFolderByIDLocked(uid);
+ BrowseNode bn = mBrowseMap.get(uid);
if (bn == null) {
Log.e(TAG, "Setting an unknown browsed folder, ignoring bn " + uid);
return false;
@@ -284,10 +344,8 @@
// Set the previous folder as not cached so that we fetch the contents again.
if (!bn.equals(mCurrentBrowseNode)) {
- Log.d(TAG, "Set cache false " + bn + " curr " + mCurrentBrowseNode);
- mCurrentBrowseNode.setCached(false);
+ Log.d(TAG, "Set cache " + bn + " curr " + mCurrentBrowseNode);
}
-
mCurrentBrowseNode = bn;
return true;
}
@@ -296,13 +354,22 @@
return mCurrentBrowseNode;
}
- synchronized boolean setCurrentBrowsedPlayer(String uid) {
- BrowseNode bn = findFolderByIDLocked(uid);
+ synchronized boolean setCurrentBrowsedPlayer(String uid, int items, int depth) {
+ BrowseNode bn = mBrowseMap.get(uid);
if (bn == null) {
Log.e(TAG, "Setting an unknown browsed player, ignoring bn " + uid);
return false;
}
mCurrentBrowsedPlayer = bn;
+ mCurrentBrowseNode = mCurrentBrowsedPlayer;
+ for (Integer level = 0; level < depth; level++) {
+ BrowseNode dummyNode = new BrowseNode(level.toString());
+ dummyNode.mParent = mCurrentBrowseNode;
+ dummyNode.mBrowseScope = AvrcpControllerService.BROWSE_SCOPE_VFS;
+ mCurrentBrowseNode = dummyNode;
+ }
+ mCurrentBrowseNode.setExpectedChildren(items);
+ mDepth = depth;
return true;
}
@@ -311,9 +378,12 @@
}
synchronized boolean setCurrentAddressedPlayer(String uid) {
- BrowseNode bn = findFolderByIDLocked(uid);
+ BrowseNode bn = mBrowseMap.get(uid);
if (bn == null) {
- Log.e(TAG, "Setting an unknown addressed player, ignoring bn " + uid);
+ if (DBG) Log.d(TAG, "Setting an unknown addressed player, ignoring bn " + uid);
+ mRootNode.setCached(false);
+ mRootNode.mChildren.add(mNowPlayingNode);
+ mBrowseMap.put(NOW_PLAYING_PREFIX, mNowPlayingNode);
return false;
}
mCurrentAddressedPlayer = bn;
@@ -326,6 +396,58 @@
@Override
public String toString() {
- return mBrowseMap.toString();
+ String serialized = "Size: " + mBrowseMap.size();
+ if (VDBG) {
+ serialized += mRootNode.toString();
+ }
+ return serialized;
+ }
+
+ // Calculates the path to target node.
+ // Returns: UP node to go up
+ // Returns: target node if there
+ // Returns: named node to go down
+ // Returns: null node if unknown
+ BrowseNode getNextStepToFolder(BrowseNode target) {
+ if (target == null) {
+ return null;
+ } else if (target.equals(mCurrentBrowseNode)
+ || target.equals(mNowPlayingNode)
+ || target.equals(mRootNode)) {
+ return target;
+ } else if (target.isPlayer()) {
+ if (mDepth > 0) {
+ mDepth--;
+ return mNavigateUpNode;
+ } else {
+ return target;
+ }
+ } else if (mBrowseMap.get(target.getID()) == null) {
+ return null;
+ } else {
+ BrowseNode nextChild = getEldestChild(mCurrentBrowseNode, target);
+ if (nextChild == null) {
+ return mNavigateUpNode;
+ } else {
+ return nextChild;
+ }
+ }
+ }
+
+ static BrowseNode getEldestChild(BrowseNode ancestor, BrowseNode target) {
+ // ancestor is an ancestor of target
+ BrowseNode descendant = target;
+ if (DBG) {
+ Log.d(TAG, "NAVIGATING ancestor" + ancestor.toString() + "Target"
+ + target.toString());
+ }
+ while (!ancestor.equals(descendant.mParent)) {
+ descendant = descendant.mParent;
+ if (descendant == null) {
+ return null;
+ }
+ }
+ if (DBG) Log.d(TAG, "NAVIGATING Descendant" + descendant.toString());
+ return descendant;
}
}
diff --git a/src/com/android/bluetooth/avrcpcontroller/StackEvent.java b/src/com/android/bluetooth/avrcpcontroller/StackEvent.java
new file mode 100644
index 0000000..1f96bd2
--- /dev/null
+++ b/src/com/android/bluetooth/avrcpcontroller/StackEvent.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2018 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.avrcpcontroller;
+
+final class StackEvent {
+ // Event types for STACK_EVENT message
+ static final int EVENT_TYPE_NONE = 0;
+ static final int EVENT_TYPE_CONNECTION_STATE_CHANGED = 1;
+ static final int EVENT_TYPE_RC_FEATURES = 2;
+
+ int mType = EVENT_TYPE_NONE;
+ boolean mRemoteControlConnected;
+ boolean mBrowsingConnected;
+ int mFeatures;
+
+ private StackEvent(int type) {
+ this.mType = type;
+ }
+
+ @Override
+ public String toString() {
+ switch (mType) {
+ case EVENT_TYPE_CONNECTION_STATE_CHANGED:
+ return "EVENT_TYPE_CONNECTION_STATE_CHANGED " + mRemoteControlConnected;
+ case EVENT_TYPE_RC_FEATURES:
+ return "EVENT_TYPE_RC_FEATURES";
+ default:
+ return "Unknown";
+ }
+ }
+
+ static StackEvent connectionStateChanged(boolean remoteControlConnected,
+ boolean browsingConnected) {
+ StackEvent event = new StackEvent(EVENT_TYPE_CONNECTION_STATE_CHANGED);
+ event.mRemoteControlConnected = remoteControlConnected;
+ event.mBrowsingConnected = browsingConnected;
+ return event;
+ }
+
+ static StackEvent rcFeatures(int features) {
+ StackEvent event = new StackEvent(EVENT_TYPE_RC_FEATURES);
+ event.mFeatures = features;
+ return event;
+ }
+}
diff --git a/src/com/android/bluetooth/avrcpcontroller/TrackInfo.java b/src/com/android/bluetooth/avrcpcontroller/TrackInfo.java
index f474495..0240610 100644
--- a/src/com/android/bluetooth/avrcpcontroller/TrackInfo.java
+++ b/src/com/android/bluetooth/avrcpcontroller/TrackInfo.java
@@ -14,37 +14,12 @@
* limitations under the License.
*/
-
package com.android.bluetooth.avrcpcontroller;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
import android.media.MediaMetadata;
-import android.net.Uri;
-import android.util.Log;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-/*
- * Contains information about tracks that either currently playing or maintained in playlist
- * This is used as a local repository for information that will be passed on as MediaMetadata to the
- * MediaSessionServicve
- */
-class TrackInfo {
- private static final String TAG = "AvrcpTrackInfo";
- private static final boolean VDBG = AvrcpControllerService.VDBG;
-
- /*
- * Default values for each of the items from JNI
- */
- private static final int TRACK_NUM_INVALID = -1;
- private static final int TOTAL_TRACKS_INVALID = -1;
- private static final int TOTAL_TRACK_TIME_INVALID = -1;
- private static final String UNPOPULATED_ATTRIBUTE = "";
-
+final class TrackInfo {
/*
*Element Id Values for GetMetaData from JNI
*/
@@ -55,143 +30,51 @@
private static final int MEDIA_ATTRIBUTE_TOTAL_TRACK_NUMBER = 0x05;
private static final int MEDIA_ATTRIBUTE_GENRE = 0x06;
private static final int MEDIA_ATTRIBUTE_PLAYING_TIME = 0x07;
- private static final int MEDIA_ATTRIBUTE_COVER_ART_HANDLE = 0x08;
-
- private final String mArtistName;
- private final String mTrackTitle;
- private final String mAlbumTitle;
- private final String mGenre;
- private final long mTrackNum; // number of audio file on original recording.
- private final long mTotalTracks; // total number of tracks on original recording
- private final long mTrackLen; // full length of AudioFile.
- private String mCoverArtHandle;
- private String mImageLocation;
- private String mThumbNailLocation;
-
- TrackInfo() {
- this(new ArrayList<Integer>(), new ArrayList<String>());
+ static MediaMetadata getMetadata(int[] attrIds, String[] attrMap) {
+ MediaMetadata.Builder metaDataBuilder = new MediaMetadata.Builder();
+ int attributeCount = Math.max(attrIds.length, attrMap.length);
+ for (int i = 0; i < attributeCount; i++) {
+ switch (attrIds[i]) {
+ case MEDIA_ATTRIBUTE_TITLE:
+ metaDataBuilder.putString(MediaMetadata.METADATA_KEY_TITLE, attrMap[i]);
+ break;
+ case MEDIA_ATTRIBUTE_ARTIST_NAME:
+ metaDataBuilder.putString(MediaMetadata.METADATA_KEY_ARTIST, attrMap[i]);
+ break;
+ case MEDIA_ATTRIBUTE_ALBUM_NAME:
+ metaDataBuilder.putString(MediaMetadata.METADATA_KEY_ALBUM, attrMap[i]);
+ break;
+ case MEDIA_ATTRIBUTE_TRACK_NUMBER:
+ try {
+ metaDataBuilder.putLong(MediaMetadata.METADATA_KEY_TRACK_NUMBER,
+ Long.valueOf(attrMap[i]));
+ } catch (java.lang.NumberFormatException e) {
+ // If Track Number doesn't parse, leave it unset
+ }
+ break;
+ case MEDIA_ATTRIBUTE_TOTAL_TRACK_NUMBER:
+ try {
+ metaDataBuilder.putLong(MediaMetadata.METADATA_KEY_NUM_TRACKS,
+ Long.valueOf(attrMap[i]));
+ } catch (java.lang.NumberFormatException e) {
+ // If Total Track Number doesn't parse, leave it unset
+ }
+ break;
+ case MEDIA_ATTRIBUTE_GENRE:
+ metaDataBuilder.putString(MediaMetadata.METADATA_KEY_GENRE, attrMap[i]);
+ break;
+ case MEDIA_ATTRIBUTE_PLAYING_TIME:
+ try {
+ metaDataBuilder.putLong(MediaMetadata.METADATA_KEY_DURATION,
+ Long.valueOf(attrMap[i]));
+ } catch (java.lang.NumberFormatException e) {
+ // If Playing Time doesn't parse, leave it unset
+ }
+ break;
+ }
+ }
+
+ return metaDataBuilder.build();
}
-
- TrackInfo(List<Integer> attrIds, List<String> attrMap) {
- Map<Integer, String> attributeMap = new HashMap<>();
- for (int i = 0; i < attrIds.size(); i++) {
- attributeMap.put(attrIds.get(i), attrMap.get(i));
- }
-
- String attribute;
- mTrackTitle = attributeMap.getOrDefault(MEDIA_ATTRIBUTE_TITLE, UNPOPULATED_ATTRIBUTE);
-
- mArtistName = attributeMap.getOrDefault(MEDIA_ATTRIBUTE_ARTIST_NAME, UNPOPULATED_ATTRIBUTE);
-
- mAlbumTitle = attributeMap.getOrDefault(MEDIA_ATTRIBUTE_ALBUM_NAME, UNPOPULATED_ATTRIBUTE);
-
- attribute = attributeMap.get(MEDIA_ATTRIBUTE_TRACK_NUMBER);
- mTrackNum = (attribute != null && !attribute.isEmpty()) ? Long.valueOf(attribute)
- : TRACK_NUM_INVALID;
-
- attribute = attributeMap.get(MEDIA_ATTRIBUTE_TOTAL_TRACK_NUMBER);
- mTotalTracks = (attribute != null && !attribute.isEmpty()) ? Long.valueOf(attribute)
- : TOTAL_TRACKS_INVALID;
-
- mGenre = attributeMap.getOrDefault(MEDIA_ATTRIBUTE_GENRE, UNPOPULATED_ATTRIBUTE);
-
- attribute = attributeMap.get(MEDIA_ATTRIBUTE_PLAYING_TIME);
- mTrackLen = (attribute != null && !attribute.isEmpty()) ? Long.valueOf(attribute)
- : TOTAL_TRACK_TIME_INVALID;
- mCoverArtHandle = attributeMap.getOrDefault(MEDIA_ATTRIBUTE_COVER_ART_HANDLE,
- UNPOPULATED_ATTRIBUTE);
- mImageLocation = UNPOPULATED_ATTRIBUTE;
- mThumbNailLocation = UNPOPULATED_ATTRIBUTE;
- }
-
- boolean updateImageLocation(String mCAHandle, String mLocation) {
- if (VDBG) Log.d(TAG, " updateImageLocation hndl " + mCAHandle + " location " + mLocation);
- if (!mCAHandle.equals(mCoverArtHandle) || (mLocation == null)) {
- return false;
- }
- mImageLocation = mLocation;
- return true;
- }
-
- boolean updateThumbNailLocation(String mCAHandle, String mLocation) {
- if (VDBG) Log.d(TAG, " mCAHandle " + mCAHandle + " location " + mLocation);
- if (!mCAHandle.equals(mCoverArtHandle) || (mLocation == null)) {
- return false;
- }
- mThumbNailLocation = mLocation;
- return true;
- }
-
- public String toString() {
- return "Metadata [artist=" + mArtistName + " trackTitle= " + mTrackTitle + " albumTitle= "
- + mAlbumTitle + " genre= " + mGenre + " trackNum= " + Long.toString(mTrackNum)
- + " track_len : " + Long.toString(mTrackLen) + " TotalTracks " + Long.toString(
- mTotalTracks) + " mCoverArtHandle=" + mCoverArtHandle +
- " mImageLocation :"+mImageLocation+"]";
- }
-
- public MediaMetadata getMediaMetaData() {
- if (VDBG) {
- Log.d(TAG, " TrackInfo " + toString());
- }
- MediaMetadata.Builder mMetaDataBuilder = new MediaMetadata.Builder();
- mMetaDataBuilder.putString(MediaMetadata.METADATA_KEY_ARTIST, mArtistName);
- mMetaDataBuilder.putString(MediaMetadata.METADATA_KEY_TITLE, mTrackTitle);
- mMetaDataBuilder.putString(MediaMetadata.METADATA_KEY_ALBUM, mAlbumTitle);
- mMetaDataBuilder.putString(MediaMetadata.METADATA_KEY_GENRE, mGenre);
- mMetaDataBuilder.putLong(MediaMetadata.METADATA_KEY_TRACK_NUMBER, mTrackNum);
- mMetaDataBuilder.putLong(MediaMetadata.METADATA_KEY_NUM_TRACKS, mTotalTracks);
- mMetaDataBuilder.putLong(MediaMetadata.METADATA_KEY_DURATION, mTrackLen);
- if (mImageLocation != UNPOPULATED_ATTRIBUTE) {
- Uri imageUri = Uri.parse(mImageLocation);
- if (VDBG) Log.d(TAG," updating image uri = " + imageUri.toString());
- mMetaDataBuilder.putString(MediaMetadata.METADATA_KEY_ALBUM_ART_URI,
- imageUri.toString());
- }
- if (mThumbNailLocation != UNPOPULATED_ATTRIBUTE) {
- Bitmap mThumbNailBitmap = BitmapFactory.decodeFile(mThumbNailLocation);
- mMetaDataBuilder.putBitmap(MediaMetadata.METADATA_KEY_DISPLAY_ICON, mThumbNailBitmap);
- }
- return mMetaDataBuilder.build();
- }
-
-
- public String displayMetaData() {
- MediaMetadata metaData = getMediaMetaData();
- StringBuffer sb = new StringBuffer();
- /* getDescription only contains artist, title and album */
- sb.append(metaData.getDescription().toString() + " ");
- if (metaData.containsKey(MediaMetadata.METADATA_KEY_GENRE)) {
- sb.append(metaData.getString(MediaMetadata.METADATA_KEY_GENRE) + " ");
- }
- if (metaData.containsKey(MediaMetadata.METADATA_KEY_MEDIA_ID)) {
- sb.append(metaData.getString(MediaMetadata.METADATA_KEY_MEDIA_ID) + " ");
- }
- if (metaData.containsKey(MediaMetadata.METADATA_KEY_TRACK_NUMBER)) {
- sb.append(
- Long.toString(metaData.getLong(MediaMetadata.METADATA_KEY_TRACK_NUMBER)) + " ");
- }
- if (metaData.containsKey(MediaMetadata.METADATA_KEY_NUM_TRACKS)) {
- sb.append(Long.toString(metaData.getLong(MediaMetadata.METADATA_KEY_NUM_TRACKS)) + " ");
- }
- if (metaData.containsKey(MediaMetadata.METADATA_KEY_TRACK_NUMBER)) {
- sb.append(Long.toString(metaData.getLong(MediaMetadata.METADATA_KEY_DURATION)) + " ");
- }
- if (metaData.containsKey(MediaMetadata.METADATA_KEY_TRACK_NUMBER)) {
- sb.append(Long.toString(metaData.getLong(MediaMetadata.METADATA_KEY_DURATION)) + " ");
- }
- return sb.toString();
- }
-
- String getCoverArtHandle() {
- return mCoverArtHandle;
- }
-
- void clearCoverArtData() {
- mCoverArtHandle = UNPOPULATED_ATTRIBUTE;
- mImageLocation = UNPOPULATED_ATTRIBUTE;
- mThumbNailLocation = UNPOPULATED_ATTRIBUTE;
- }
-
}
diff --git a/src/com/android/bluetooth/btservice/AbstractionLayer.java b/src/com/android/bluetooth/btservice/AbstractionLayer.java
index 597dc45..6ee0d6e 100644
--- a/src/com/android/bluetooth/btservice/AbstractionLayer.java
+++ b/src/com/android/bluetooth/btservice/AbstractionLayer.java
@@ -87,6 +87,9 @@
public static final int BT_DEVICE_TYPE_BLE = 0x02;
public static final int BT_DEVICE_TYPE_DUAL = 0x03;
+ static final int BT_PROPERTY_LOCAL_IO_CAPS = 0x0e;
+ static final int BT_PROPERTY_LOCAL_IO_CAPS_BLE = 0x0f;
+
static final int BT_BOND_STATE_NONE = 0x00;
static final int BT_BOND_STATE_BONDING = 0x01;
static final int BT_BOND_STATE_BONDED = 0x02;
@@ -122,6 +125,7 @@
public static final int AVRCP = 1;
public static final int PBAP = 2;
public static final int MAP = 3;
+ public static final int MAX_POW = 4;
// Profile features supported in profile_conf
public static final int PROFILE_VERSION =1;
@@ -130,6 +134,10 @@
public static final int USE_SIM_SUPPORT = 4;
public static final int MAP_EMAIL_SUPPORT = 5;
public static final int PBAP_0102_SUPPORT = 6;
+ public static final int BR_MAX_POW_SUPPORT = 7;
+ public static final int EDR_MAX_POW_SUPPORT = 8;
+ public static final int BLE_MAX_POW_SUPPORT = 9;
+
static final int BT_VENDOR_PROPERTY_TWS_PLUS_DEVICE_TYPE = 0x01;
static final int BT_VENDOR_PROPERTY_TWS_PLUS_PEER_ADDR = 0x02;
diff --git a/src/com/android/bluetooth/btservice/ActiveDeviceManager.java b/src/com/android/bluetooth/btservice/ActiveDeviceManager.java
index ea414c5..1a356a8 100644
--- a/src/com/android/bluetooth/btservice/ActiveDeviceManager.java
+++ b/src/com/android/bluetooth/btservice/ActiveDeviceManager.java
@@ -33,13 +33,13 @@
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
-import android.support.annotation.VisibleForTesting;
import android.util.Log;
import com.android.bluetooth.a2dp.A2dpService;
import com.android.bluetooth.hearingaid.HearingAidService;
import com.android.bluetooth.hfp.HeadsetService;
import com.android.bluetooth.ba.BATService;
+import com.android.internal.annotations.VisibleForTesting;
import java.util.LinkedList;
import java.util.List;
@@ -227,18 +227,26 @@
+ "device " + device + " disconnected");
}
mA2dpConnectedDevices.remove(device);
- final A2dpService a2dpService = mFactory.getA2dpService();
if (Objects.equals(mA2dpActiveDevice, device)) {
- if (!mA2dpConnectedDevices.isEmpty() &&
- mAdapterService.isTwsPlusDevice(mA2dpConnectedDevices.get(0)) &&
- (a2dpService != null) &&
- (a2dpService.getConnectionState(mA2dpConnectedDevices.get(0)) ==
- BluetoothProfile.STATE_CONNECTED)) {
- Log.d(TAG, "calling set a2dp Active dev: " + mA2dpConnectedDevices.get(0));
- setA2dpActiveDevice(mA2dpConnectedDevices.get(0));
- } else
+ final A2dpService mA2dpService = mFactory.getA2dpService();
+ BluetoothDevice mDevice = null;
+ if (mAdapterService.isTwsPlusDevice(device) &&
+ !mA2dpConnectedDevices.isEmpty()) {
+ for (BluetoothDevice connected_device: mA2dpConnectedDevices) {
+ if (mAdapterService.isTwsPlusDevice(connected_device) &&
+ mA2dpService.getConnectionState(connected_device) ==
+ BluetoothProfile.STATE_CONNECTED) {
+ mDevice = connected_device;
+ break;
+ }
+ }
+ }
+ if (!setA2dpActiveDevice(mDevice) && (mDevice != null) &&
+ mAdapterService.isTwsPlusDevice(mDevice)) {
+ Log.w(TAG, "Switch A2dp active device to peer earbud failed");
setA2dpActiveDevice(null);
+ }
}
}
}
@@ -299,13 +307,28 @@
mHfpConnectedDevices.remove(device);
if (Objects.equals(mHfpActiveDevice, device)) {
- if (!mHfpConnectedDevices.isEmpty() &&
- mAdapterService.isTwsPlusDevice(mHfpConnectedDevices.get(0)) &&
- (hfpService != null) &&
- (hfpService.getConnectionState(mHfpConnectedDevices.get(0)) ==
- BluetoothProfile.STATE_CONNECTED)) {
- setHfpActiveDevice(mHfpConnectedDevices.get(0));
- Log.d(TAG, "calling set Active dev: " + mHfpConnectedDevices.get(0));
+ if (mAdapterService.isTwsPlusDevice(device) &&
+ !mHfpConnectedDevices.isEmpty()) {
+ if (hfpService == null) {
+ Log.e(TAG, "no headsetService, FATAL");
+ return;
+ }
+ BluetoothDevice peerTwsDevice =
+ hfpService.getTwsPlusConnectedPeer(device);
+ if (peerTwsDevice != null &&
+ hfpService.getConnectionState(peerTwsDevice)
+ == BluetoothProfile.STATE_CONNECTED) {
+ Log.d(TAG, "calling set Active dev: "
+ + peerTwsDevice);
+ if (!setHfpActiveDevice(peerTwsDevice)) {
+ Log.w(TAG, "Set hfp active device failed");
+ setHfpActiveDevice(null);
+ }
+ } else {
+ Log.d(TAG, "No Active device Switch" +
+ "as there is no Connected TWS+ peer");
+ setHfpActiveDevice(null);
+ }
} else {
setHfpActiveDevice(null);
}
@@ -446,32 +469,34 @@
return mHandlerThread.getLooper();
}
- private void setA2dpActiveDevice(BluetoothDevice device) {
+ private boolean setA2dpActiveDevice(BluetoothDevice device) {
if (DBG) {
Log.d(TAG, "setA2dpActiveDevice(" + device + ")");
}
final A2dpService a2dpService = mFactory.getA2dpService();
if (a2dpService == null) {
- return;
+ return false;
}
if (!a2dpService.setActiveDevice(device)) {
- return;
+ return false;
}
mA2dpActiveDevice = device;
+ return true;
}
- private void setHfpActiveDevice(BluetoothDevice device) {
+ private boolean setHfpActiveDevice(BluetoothDevice device) {
if (DBG) {
Log.d(TAG, "setHfpActiveDevice(" + device + ")");
}
final HeadsetService headsetService = mFactory.getHeadsetService();
if (headsetService == null) {
- return;
+ return false;
}
if (!headsetService.setActiveDevice(device)) {
- return;
+ return false;
}
mHfpActiveDevice = device;
+ return true;
}
private void setHearingAidActiveDevice(BluetoothDevice device) {
diff --git a/src/com/android/bluetooth/btservice/AdapterProperties.java b/src/com/android/bluetooth/btservice/AdapterProperties.java
index 32fc98e..beff7c8 100644
--- a/src/com/android/bluetooth/btservice/AdapterProperties.java
+++ b/src/com/android/bluetooth/btservice/AdapterProperties.java
@@ -24,6 +24,7 @@
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHeadset;
import android.bluetooth.BluetoothHeadsetClient;
+import android.bluetooth.BluetoothHearingAid;
import android.bluetooth.BluetoothHidDevice;
import android.bluetooth.BluetoothHidHost;
import android.bluetooth.BluetoothMap;
@@ -40,8 +41,6 @@
import android.os.ParcelUuid;
import android.os.SystemProperties;
import android.os.UserHandle;
-import android.provider.Settings.Secure;
-import android.support.annotation.VisibleForTesting;
import android.util.Log;
import android.util.Pair;
import android.util.StatsLog;
@@ -81,6 +80,9 @@
private volatile int mScanMode;
private volatile int mDiscoverableTimeout;
private volatile ParcelUuid[] mUuids;
+ private volatile int mLocalIOCapability = BluetoothAdapter.IO_CAPABILITY_UNKNOWN;
+ private volatile int mLocalIOCapabilityBLE = BluetoothAdapter.IO_CAPABILITY_UNKNOWN;
+
private CopyOnWriteArrayList<BluetoothDevice> mBondedDevices =
new CopyOnWriteArrayList<BluetoothDevice>();
@@ -164,6 +166,9 @@
case BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED:
sendConnectionStateChange(BluetoothProfile.HEADSET_CLIENT, intent);
break;
+ case BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED:
+ sendConnectionStateChange(BluetoothProfile.HEARING_AID, intent);
+ break;
case BluetoothA2dpSink.ACTION_CONNECTION_STATE_CHANGED:
sendConnectionStateChange(BluetoothProfile.A2DP_SINK, intent);
break;
@@ -226,9 +231,9 @@
mMaxConnectedAudioDevices = Math.min(Math.max(propertyOverlayedMaxConnectedAudioDevices,
MAX_CONNECTED_AUDIO_DEVICES_LOWER_BOND), MAX_CONNECTED_AUDIO_DEVICES_UPPER_BOUND);
// if QTI stack, overwrite max audio connections to 2
- if(mService.isVendorIntfEnabled() && mMaxConnectedAudioDevices > 2) {
- Log.i(TAG, "overwriting mMaxConnectedAudioDevices to 2 for vendor stack");
- mMaxConnectedAudioDevices = 2;
+ if(mService.isVendorIntfEnabled() && mMaxConnectedAudioDevices > 5) {
+ Log.i(TAG, "overwriting mMaxConnectedAudioDevices to 5 for vendor stack");
+ mMaxConnectedAudioDevices = 5;
}
Log.i(TAG, "init(), maxConnectedAudioDevices, default="
@@ -243,6 +248,7 @@
IntentFilter filter = new IntentFilter();
filter.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
filter.addAction(BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED);
+ filter.addAction(BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED);
filter.addAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
filter.addAction(BluetoothA2dpSink.ACTION_CONNECTION_STATE_CHANGED);
filter.addAction(BluetoothHidDevice.ACTION_CONNECTION_STATE_CHANGED);
@@ -332,6 +338,45 @@
}
}
+ boolean setIoCapability(int capability) {
+ synchronized (mObject) {
+ boolean result = mService.setAdapterPropertyNative(
+ AbstractionLayer.BT_PROPERTY_LOCAL_IO_CAPS, Utils.intToByteArray(capability));
+
+ if (result) {
+ mLocalIOCapability = capability;
+ }
+
+ return result;
+ }
+ }
+
+ int getIoCapability() {
+ synchronized (mObject) {
+ return mLocalIOCapability;
+ }
+ }
+
+ boolean setLeIoCapability(int capability) {
+ synchronized (mObject) {
+ boolean result = mService.setAdapterPropertyNative(
+ AbstractionLayer.BT_PROPERTY_LOCAL_IO_CAPS_BLE,
+ Utils.intToByteArray(capability));
+
+ if (result) {
+ mLocalIOCapabilityBLE = capability;
+ }
+
+ return result;
+ }
+ }
+
+ int getLeIoCapability() {
+ synchronized (mObject) {
+ return mLocalIOCapabilityBLE;
+ }
+ }
+
/**
* @return the mScanMode
*/
@@ -479,6 +524,12 @@
}
/**
+ * @param set the maximum number of connected audio devices
+ */
+ void setMaxConnectedAudioDevices(int maxConnectedAudioDevices) {
+ mMaxConnectedAudioDevices = maxConnectedAudioDevices;
+ }
+ /**
* @return the maximum number of connected audio devices
*/
int getMaxConnectedAudioDevices() {
@@ -717,7 +768,6 @@
// This function shall be invoked from BondStateMachine whenever the bond
// state changes.
- @VisibleForTesting
void onBondStateChanged(BluetoothDevice device, int state) {
if (device == null) {
Log.w(TAG, "onBondStateChanged, device is null");
@@ -787,11 +837,9 @@
Log.d(TAG,
"PROFILE_CONNECTION_STATE_CHANGE: profile=" + profile + ", device=" + device + ", "
+ prevState + " -> " + state);
- String ssaid = Secure.getString(mService.getContentResolver(), Secure.ANDROID_ID);
- String combined = ssaid + device.getAddress();
- int obfuscated_id = combined.hashCode() & 0xFFFF; // Last two bytes only
- StatsLog.write(StatsLog.BLUETOOTH_CONNECTION_STATE_CHANGED,
- state, obfuscated_id, profile);
+ StatsLog.write(StatsLog.BLUETOOTH_CONNECTION_STATE_CHANGED, state, 0 /* deprecated */,
+ profile, mService.obfuscateAddress(device));
+
if (!isNormalStateTransition(prevState, state)) {
Log.w(TAG,
"PROFILE_CONNECTION_STATE_CHANGE: unexpected transition for profile=" + profile
@@ -1060,6 +1108,16 @@
updateFeatureSupport(val);
break;
+ case AbstractionLayer.BT_PROPERTY_LOCAL_IO_CAPS:
+ mLocalIOCapability = Utils.byteArrayToInt(val);
+ debugLog("mLocalIOCapability set to " + mLocalIOCapability);
+ break;
+
+ case AbstractionLayer.BT_PROPERTY_LOCAL_IO_CAPS_BLE:
+ mLocalIOCapabilityBLE = Utils.byteArrayToInt(val);
+ debugLog("mLocalIOCapabilityBLE set to " + mLocalIOCapabilityBLE);
+ break;
+
default:
errorLog("Property change not handled in Java land:" + type);
}
@@ -1214,6 +1272,7 @@
Intent intent;
if ((state == AbstractionLayer.BT_DISCOVERY_STOPPED) && mDiscovering) {
mDiscovering = false;
+ mService.clearDiscoveringPackages();
mDiscoveryEndMs = System.currentTimeMillis();
intent = new Intent(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
mService.sendBroadcast(intent, AdapterService.BLUETOOTH_PERM);
diff --git a/src/com/android/bluetooth/btservice/AdapterService.java b/src/com/android/bluetooth/btservice/AdapterService.java
index 25b22ec..277eb8c 100644
--- a/src/com/android/bluetooth/btservice/AdapterService.java
+++ b/src/com/android/bluetooth/btservice/AdapterService.java
@@ -53,6 +53,7 @@
import android.app.ActivityManager;
import android.app.AlarmManager;
+import android.app.AppOpsManager;
import android.app.PendingIntent;
import android.app.Service;
import android.bluetooth.BluetoothActivityEnergyInfo;
@@ -60,8 +61,10 @@
import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothProtoEnums;
import android.bluetooth.IBluetooth;
import android.bluetooth.IBluetoothCallback;
+import android.bluetooth.IBluetoothMetadataListener;
import android.bluetooth.IBluetoothSocketManager;
import android.bluetooth.OobData;
import android.bluetooth.UidTraffic;
@@ -101,15 +104,19 @@
import com.android.bluetooth.BluetoothMetricsProto;
import com.android.bluetooth.Utils;
import com.android.bluetooth.btservice.RemoteDevices.DeviceProperties;
+import com.android.bluetooth.btservice.storage.DatabaseManager;
+import com.android.bluetooth.btservice.storage.MetadataDatabase;
import com.android.bluetooth.gatt.GattService;
import com.android.bluetooth.sdp.SdpManager;
import com.android.bluetooth.ba.BATService;
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.IBatteryStats;
+
+import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
-import android.net.ConnectivityManager;
import android.net.NetworkInfo;
+import android.net.wifi.WifiConfiguration;
import com.google.protobuf.InvalidProtocolBufferException;
@@ -151,8 +158,11 @@
private static final String ACTION_ALARM_WAKEUP =
"com.android.bluetooth.btservice.action.ALARM_WAKEUP";
- static final String BLUETOOTH_BTSNOOP_ENABLE_PROPERTY = "persist.bluetooth.btsnoopenable";
- private boolean mSnoopLogSettingAtEnable = false;
+ static final String BLUETOOTH_BTSNOOP_LOG_MODE_PROPERTY = "persist.bluetooth.btsnooplogmode";
+ static final String BLUETOOTH_BTSNOOP_DEFAULT_MODE_PROPERTY =
+ "persist.bluetooth.btsnoopdefaultmode";
+ private String mSnoopLogSettingAtEnable = "empty";
+ private String mDefaultSnoopLogSettingAtEnable = "empty";
public static final String BLUETOOTH_ADMIN_PERM = android.Manifest.permission.BLUETOOTH_ADMIN;
public static final String BLUETOOTH_PRIVILEGED =
@@ -169,6 +179,8 @@
private static final int CONTROLLER_ENERGY_UPDATE_TIMEOUT_MILLIS = 30;
+ private final ArrayList<DiscoveringPackage> mDiscoveringPackages = new ArrayList<>();
+
static {
System.loadLibrary("bluetooth_jni");
classInitNative();
@@ -208,6 +220,8 @@
private boolean mNativeAvailable;
private boolean mCleaningUp;
+ private final HashMap<BluetoothDevice, ArrayList<IBluetoothMetadataListener>>
+ mMetadataListeners = new HashMap<>();
private final HashMap<String, Integer> mProfileServicesState = new HashMap<String, Integer>();
//Only BluetoothManagerService should be registered
private RemoteCallbackList<IBluetoothCallback> mCallbacks;
@@ -226,6 +240,9 @@
private ProfileObserver mProfileObserver;
private PhonePolicy mPhonePolicy;
private ActiveDeviceManager mActiveDeviceManager;
+ private DatabaseManager mDatabaseManager;
+ private SilenceDeviceManager mSilenceDeviceManager;
+ private AppOpsManager mAppOps;
private VendorSocket mVendorSocket;
/**
@@ -274,14 +291,15 @@
if (!isVendorIntfEnabled()) {
return;
}
- ConnectivityManager connMgr =
- (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE);
- NetworkInfo networkInfo = connMgr.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
- if (networkInfo.isConnected()) {
- mVendor.setWifiState(true);
- } else {
- mVendor.setWifiState(false);
+ boolean isWifiConnected = false;
+ WifiManager wifiMgr = (WifiManager) getSystemService(Context.WIFI_SERVICE);
+ if ((wifiMgr != null) && (wifiMgr.isWifiEnabled())) {
+ WifiInfo wifiInfo = wifiMgr.getConnectionInfo();
+ if((wifiInfo != null) && (wifiInfo.getNetworkId() != -1)) {
+ isWifiConnected = true;
+ }
}
+ mVendor.setWifiState(isWifiConnected);
}
public void StartHCIClose() {
@@ -296,6 +314,25 @@
mVendor.voipNetworkWifiInformation(isVoipStarted, isNetworkWifi);
}
+ public String getSocName() {
+ return mVendor.getSocName();
+ }
+
+ public String getA2apOffloadCapability() {
+ return mVendor.getA2apOffloadCapability();
+ }
+
+ public boolean isSplitA2dpEnabled() {
+ return mVendor.isSplitA2dpEnabled();
+ }
+
+ public boolean isSwbEnabled() {
+ return mVendor.isSwbEnabled();
+ }
+ public boolean isSwbPmEnabled() {
+ return mVendor.isSwbPmEnabled();
+ }
+
private static final int MESSAGE_PROFILE_SERVICE_STATE_CHANGED = 1;
private static final int MESSAGE_PROFILE_SERVICE_REGISTERED = 2;
private static final int MESSAGE_PROFILE_SERVICE_UNREGISTERED = 3;
@@ -303,19 +340,19 @@
class AdapterServiceHandler extends Handler {
@Override
public void handleMessage(Message msg) {
- debugLog("handleMessage() - Message: " + msg.what);
+ verboseLog("handleMessage() - Message: " + msg.what);
switch (msg.what) {
case MESSAGE_PROFILE_SERVICE_STATE_CHANGED:
- debugLog("handleMessage() - MESSAGE_PROFILE_SERVICE_STATE_CHANGED");
+ verboseLog("handleMessage() - MESSAGE_PROFILE_SERVICE_STATE_CHANGED");
processProfileServiceStateChanged((ProfileService) msg.obj, msg.arg1);
break;
case MESSAGE_PROFILE_SERVICE_REGISTERED:
- debugLog("handleMessage() - MESSAGE_PROFILE_SERVICE_REGISTERED");
+ verboseLog("handleMessage() - MESSAGE_PROFILE_SERVICE_REGISTERED");
registerProfileService((ProfileService) msg.obj);
break;
case MESSAGE_PROFILE_SERVICE_UNREGISTERED:
- debugLog("handleMessage() - MESSAGE_PROFILE_SERVICE_UNREGISTERED");
+ verboseLog("handleMessage() - MESSAGE_PROFILE_SERVICE_UNREGISTERED");
unregisterProfileService((ProfileService) msg.obj);
break;
}
@@ -331,7 +368,7 @@
private void unregisterProfileService(ProfileService profile) {
if (!mRegisteredProfiles.contains(profile)) {
- Log.e(TAG, profile.getName() + " not registered (UNREGISTERED).");
+ Log.e(TAG, profile.getName() + " not registered (UNREGISTER).");
return;
}
mRegisteredProfiles.remove(profile);
@@ -351,16 +388,25 @@
mRunningProfiles.add(profile);
if (GattService.class.getSimpleName().equals(profile.getName())) {
Log.w(TAG,"onProfileServiceStateChange() - Gatt profile service started..");
- enableNativeWithGuestFlag();
+ enableNative();
} else if (mRegisteredProfiles.size() == Config.getSupportedProfiles().length
&& mRegisteredProfiles.size() == mRunningProfiles.size()) {
Log.w(TAG,"onProfileServiceStateChange() - All profile services started..");
mAdapterProperties.onBluetoothReady();
updateUuids();
setBluetoothClassFromConfig();
+ getAdapterPropertyNative(AbstractionLayer.BT_PROPERTY_LOCAL_IO_CAPS);
+ getAdapterPropertyNative(AbstractionLayer.BT_PROPERTY_LOCAL_IO_CAPS_BLE);
mAdapterStateMachine.sendMessage(AdapterState.BREDR_STARTED);
//update wifi state to lower layers
fetchWifiState();
+ if (isVendorIntfEnabled()) {
+ if (isPowerbackRequired()) {
+ mVendor.setPowerBackoff(true);
+ } else {
+ mVendor.setPowerBackoff(false);
+ }
+ }
}
break;
case BluetoothAdapter.STATE_OFF:
@@ -381,6 +427,7 @@
} else if (mRunningProfiles.size() == 0) {
Log.w(TAG,"onProfileServiceStateChange() - All profile services stopped..");
mAdapterStateMachine.sendMessage(AdapterState.BLE_STOPPED);
+ disableNative();
}
break;
default:
@@ -457,15 +504,17 @@
mAdapter = BluetoothAdapter.getDefaultAdapter();
mRemoteDevices = new RemoteDevices(this, Looper.getMainLooper());
mRemoteDevices.init();
+ clearDiscoveringPackages();
mBinder = new AdapterServiceBinder(this);
mAdapterProperties = new AdapterProperties(this);
mVendor = new Vendor(this);
mAdapterStateMachine = AdapterState.make(this);
mJniCallbacks = new JniCallbacks(this, mAdapterProperties);
mVendorSocket = new VendorSocket(this);
- initNative();
+ initNative(isGuest(), isSingleUserMode());
mNativeAvailable = true;
mCallbacks = new RemoteCallbackList<IBluetoothCallback>();
+ mAppOps = getSystemService(AppOpsManager.class);
//Load the name and address
getAdapterPropertyNative(AbstractionLayer.BT_PROPERTY_BDADDR);
getAdapterPropertyNative(AbstractionLayer.BT_PROPERTY_BDNAME);
@@ -478,6 +527,11 @@
mSdpManager = SdpManager.init(this);
registerReceiver(mAlarmBroadcastReceiver, new IntentFilter(ACTION_ALARM_WAKEUP));
+ IntentFilter wifiFilter = new IntentFilter();
+ wifiFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
+ wifiFilter.addAction(WifiManager.WIFI_AP_STATE_CHANGED_ACTION);
+ wifiFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
+ registerReceiver(mWifiStateBroadcastReceiver, wifiFilter);
mProfileObserver = new ProfileObserver(getApplicationContext(), this, new Handler());
mProfileObserver.start();
@@ -495,6 +549,13 @@
mActiveDeviceManager = new ActiveDeviceManager(this, new ServiceFactory());
mActiveDeviceManager.start();
+ mDatabaseManager = new DatabaseManager(this);
+ mDatabaseManager.start(MetadataDatabase.createDatabase(this));
+
+ mSilenceDeviceManager = new SilenceDeviceManager(this, new ServiceFactory(),
+ Looper.getMainLooper());
+ mSilenceDeviceManager.start();
+
setAdapterService(this);
// First call to getSharedPreferences will result in a file read into
@@ -617,6 +678,12 @@
debugLog("stateChangeCallback: disableNative() completed");
mAdapterStateMachine.sendMessage(AdapterState.STACK_DISABLED);
} else if (status == AbstractionLayer.BT_STATE_ON) {
+ String BT_SOC = getSocName();
+
+ if (BT_SOC.equals("pronto")) {
+ Log.i(TAG, "setting max audio connection to 2");
+ mAdapterProperties.setMaxConnectedAudioDevices(2);
+ }
mAdapterStateMachine.sendMessage(AdapterState.BLE_STARTED);
} else {
Log.e(TAG, "Incorrect status " + status + " in stateChangeCallback");
@@ -673,6 +740,12 @@
}
}
+ void startBrEdrStartup(){
+ if (isVendorIntfEnabled()) {
+ mVendor.bredrStartup();
+ }
+ }
+
void startBrEdrCleanup(){
mAdapterProperties.onBluetoothDisable();
if (isVendorIntfEnabled()) {
@@ -739,15 +812,27 @@
}
mCallbacks.finishBroadcast();
}
+
// Turn the Adapter all the way off if we are disabling and the snoop log setting changed.
if (newState == BluetoothAdapter.STATE_BLE_TURNING_ON) {
mSnoopLogSettingAtEnable =
- SystemProperties.getBoolean(BLUETOOTH_BTSNOOP_ENABLE_PROPERTY, false);
+ SystemProperties.get(BLUETOOTH_BTSNOOP_LOG_MODE_PROPERTY, "empty");
+ mDefaultSnoopLogSettingAtEnable =
+ Settings.Global.getString(getContentResolver(),
+ Settings.Global.BLUETOOTH_BTSNOOP_DEFAULT_MODE);
+ SystemProperties.set(BLUETOOTH_BTSNOOP_DEFAULT_MODE_PROPERTY,
+ mDefaultSnoopLogSettingAtEnable);
} else if (newState == BluetoothAdapter.STATE_BLE_ON
&& prevState != BluetoothAdapter.STATE_OFF) {
- boolean snoopLogSetting =
- SystemProperties.getBoolean(BLUETOOTH_BTSNOOP_ENABLE_PROPERTY, false);
- if (mSnoopLogSettingAtEnable != snoopLogSetting) {
+ String snoopLogSetting =
+ SystemProperties.get(BLUETOOTH_BTSNOOP_LOG_MODE_PROPERTY, "empty");
+ String snoopDefaultModeSetting =
+ Settings.Global.getString(getContentResolver(),
+ Settings.Global.BLUETOOTH_BTSNOOP_DEFAULT_MODE);
+
+ if (!TextUtils.equals(mSnoopLogSettingAtEnable, snoopLogSetting)
+ || !TextUtils.equals(mDefaultSnoopLogSettingAtEnable,
+ snoopDefaultModeSetting)) {
mAdapterStateMachine.sendMessage(AdapterState.BLE_TURN_OFF);
}
}
@@ -771,6 +856,7 @@
mCleaningUp = true;
unregisterReceiver(mAlarmBroadcastReceiver);
+ unregisterReceiver(mWifiStateBroadcastReceiver);
if (mPendingAlarm != null) {
mAlarmManager.cancel(mPendingAlarm);
@@ -788,6 +874,10 @@
}
}
+ if (mDatabaseManager != null) {
+ mDatabaseManager.cleanup();
+ }
+
if (mAdapterStateMachine != null) {
mAdapterStateMachine.doQuit();
}
@@ -831,6 +921,10 @@
mPhonePolicy.cleanup();
}
+ if (mSilenceDeviceManager != null) {
+ mSilenceDeviceManager.cleanup();
+ }
+
if (mActiveDeviceManager != null) {
mActiveDeviceManager.cleanup();
}
@@ -1022,6 +1116,7 @@
return service.setName(name);
}
+ @Override
public BluetoothClass getBluetoothClass() {
if (!Utils.checkCaller()) {
Log.w(TAG, "getBluetoothClass() - Not allowed for non-active user");
@@ -1033,6 +1128,7 @@
return service.getBluetoothClass();
}
+ @Override
public boolean setBluetoothClass(BluetoothClass bluetoothClass) {
if (!Utils.checkCaller()) {
Log.w(TAG, "setBluetoothClass() - Not allowed for non-active user");
@@ -1047,6 +1143,54 @@
}
@Override
+ public int getIoCapability() {
+ if (!Utils.checkCaller()) {
+ Log.w(TAG, "setBluetoothClass() - Not allowed for non-active user");
+ return BluetoothAdapter.IO_CAPABILITY_UNKNOWN;
+ }
+
+ AdapterService service = getService();
+ if (service == null) return BluetoothAdapter.IO_CAPABILITY_UNKNOWN;
+ return service.getIoCapability();
+ }
+
+ @Override
+ public boolean setIoCapability(int capability) {
+ if (!Utils.checkCaller()) {
+ Log.w(TAG, "setBluetoothClass() - Not allowed for non-active user");
+ return false;
+ }
+
+ AdapterService service = getService();
+ if (service == null) return false;
+ return service.setIoCapability(capability);
+ }
+
+ @Override
+ public int getLeIoCapability() {
+ if (!Utils.checkCaller()) {
+ Log.w(TAG, "setBluetoothClass() - Not allowed for non-active user");
+ return BluetoothAdapter.IO_CAPABILITY_UNKNOWN;
+ }
+
+ AdapterService service = getService();
+ if (service == null) return BluetoothAdapter.IO_CAPABILITY_UNKNOWN;
+ return service.getLeIoCapability();
+ }
+
+ @Override
+ public boolean setLeIoCapability(int capability) {
+ if (!Utils.checkCaller()) {
+ Log.w(TAG, "setBluetoothClass() - Not allowed for non-active user");
+ return false;
+ }
+
+ AdapterService service = getService();
+ if (service == null) return false;
+ return service.setLeIoCapability(capability);
+ }
+
+ @Override
public int getScanMode() {
if (!Utils.checkCallerAllowManagedProfiles(mService)) {
Log.w(TAG, "getScanMode() - Not allowed for non-active user");
@@ -1103,7 +1247,7 @@
}
@Override
- public boolean startDiscovery() {
+ public boolean startDiscovery(String callingPackage) {
if (!Utils.checkCaller()) {
Log.w(TAG, "startDiscovery() - Not allowed for non-active user");
return false;
@@ -1113,7 +1257,7 @@
if (service == null) {
return false;
}
- return service.startDiscovery();
+ return service.startDiscovery(callingPackage);
}
@Override
@@ -1269,6 +1413,17 @@
}
@Override
+ public void setBondingInitiatedLocally(BluetoothDevice device, boolean localInitiated) {
+ // don't check caller, may be called from system UI
+ AdapterService service = getService();
+ if (service == null) {
+ return;
+ }
+ service.setBondingInitiatedLocally(device,localInitiated);
+ return;
+ }
+
+ @Override
public long getSupportedProfiles() {
AdapterService service = getService();
if (service == null) {
@@ -1442,6 +1597,34 @@
}
@Override
+ public boolean setSilenceMode(BluetoothDevice device, boolean silence) {
+ if (!Utils.checkCaller()) {
+ Log.w(TAG, "setSilenceMode() - Not allowed for non-active user");
+ return false;
+ }
+
+ AdapterService service = getService();
+ if (service == null) {
+ return false;
+ }
+ return service.setSilenceMode(device, silence);
+ }
+
+ @Override
+ public boolean getSilenceMode(BluetoothDevice device) {
+ if (!Utils.checkCaller()) {
+ Log.w(TAG, "getSilenceMode() - Not allowed for non-active user");
+ return false;
+ }
+
+ AdapterService service = getService();
+ if (service == null) {
+ return false;
+ }
+ return service.getSilenceMode(device);
+ }
+
+ @Override
public boolean setPhonebookAccessPermission(BluetoothDevice device, int value) {
if (!Utils.checkCaller()) {
Log.w(TAG, "setPhonebookAccessPermission() - Not allowed for non-active user");
@@ -1726,6 +1909,43 @@
}
@Override
+ public boolean registerMetadataListener(IBluetoothMetadataListener listener,
+ BluetoothDevice device) {
+ AdapterService service = getService();
+ if (service == null) {
+ return false;
+ }
+ return service.registerMetadataListener(listener, device);
+ }
+
+ @Override
+ public boolean unregisterMetadataListener(BluetoothDevice device) {
+ AdapterService service = getService();
+ if (service == null) {
+ return false;
+ }
+ return service.unregisterMetadataListener(device);
+ }
+
+ @Override
+ public boolean setMetadata(BluetoothDevice device, int key, byte[] value) {
+ AdapterService service = getService();
+ if (service == null) {
+ return false;
+ }
+ return service.setMetadata(device, key, value);
+ }
+
+ @Override
+ public byte[] getMetadata(BluetoothDevice device, int key) {
+ AdapterService service = getService();
+ if (service == null) {
+ return null;
+ }
+ return service.getMetadata(device, key);
+ }
+
+ @Override
public void requestActivityInfo(ResultReceiver result) {
Bundle bundle = new Bundle();
bundle.putParcelable(BatteryStats.RESULT_RECEIVER_CONTROLLER_KEY, reportActivityInfo());
@@ -1902,6 +2122,47 @@
return result && storeBluetoothClassConfig(bluetoothClass.getClassOfDevice());
}
+ private boolean validateInputOutputCapability(int capability) {
+ if (capability < 0 || capability >= BluetoothAdapter.IO_CAPABILITY_MAX) {
+ Log.e(TAG, "Invalid IO capability value - " + capability);
+ return false;
+ }
+
+ 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);
+ }
+
boolean setTwsPlusDevType(byte[] address, short twsPlusDevType) {
enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
BluetoothDevice device = mRemoteDevices.getDevice(address);
@@ -1997,14 +2258,46 @@
return mAdapterProperties.setDiscoverableTimeout(timeout);
}
- boolean startDiscovery() {
+ ArrayList<DiscoveringPackage> getDiscoveringPackages() {
+ return mDiscoveringPackages;
+ }
+
+ void clearDiscoveringPackages() {
+ synchronized (mDiscoveringPackages) {
+ mDiscoveringPackages.clear();
+ }
+ }
+
+ boolean startDiscovery(String callingPackage) {
+ 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;
+ if (Utils.checkCallerHasNetworkSettingsPermission(this)) {
+ permission = android.Manifest.permission.NETWORK_SETTINGS;
+ } else if (Utils.checkCallerHasNetworkSetupWizardPermission(this)) {
+ permission = android.Manifest.permission.NETWORK_SETUP_WIZARD;
+ } else if (isQApp) {
+ if (!Utils.checkCallerHasFineLocation(this, mAppOps, callingPackage, callingUser)) {
+ return false;
+ }
+ permission = android.Manifest.permission.ACCESS_FINE_LOCATION;
+ } else {
+ if (!Utils.checkCallerHasCoarseLocation(this, mAppOps, callingPackage, callingUser)) {
+ return false;
+ }
+ permission = android.Manifest.permission.ACCESS_COARSE_LOCATION;
+ }
if (mAdapterProperties.isDiscovering()) {
Log.i(TAG,"discovery already active, ignore startDiscovery");
return false;
}
+ synchronized (mDiscoveringPackages) {
+ mDiscoveringPackages.add(new DiscoveringPackage(callingPackage, permission));
+ }
return startDiscoveryNative();
}
@@ -2041,6 +2334,16 @@
return mAdapterProperties.getBondedDevices();
}
+ /**
+ * Get the database manager to access Bluetooth storage
+ *
+ * @return {@link DatabaseManager} or null on error
+ */
+ @VisibleForTesting
+ public DatabaseManager getDatabase() {
+ return mDatabaseManager;
+ }
+
int getAdapterConnectionState() {
enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
return mAdapterProperties.getConnectionState();
@@ -2138,18 +2441,6 @@
}
}
- /**
- * Update device UUID changed to {@link BondStateMachine}
- *
- * @param device remote device of interest
- */
- public void deviceUuidUpdated(BluetoothDevice device) {
- // Notify BondStateMachine for SDP complete / UUID changed.
- Message msg = mBondStateMachine.obtainMessage(BondStateMachine.UUID_UPDATE);
- msg.obj = device;
- mBondStateMachine.sendMessage(msg);
- }
-
boolean cancelBondProcess(BluetoothDevice device) {
enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
byte[] addr = Utils.getBytesFromAddress(device.getAddress());
@@ -2204,6 +2495,17 @@
return deviceProp.isBondingInitiatedLocally();
}
+ void setBondingInitiatedLocally(BluetoothDevice device, boolean localInitiated) {
+ enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+ DeviceProperties deviceProp = mRemoteDevices.getDeviceProperties(device);
+ if (deviceProp == null) {
+ return;
+ }
+ Log.w(TAG," localInitiated " + localInitiated);
+ deviceProp.setBondingInitiatedLocally(localInitiated);
+ return;
+ }
+
long getSupportedProfiles() {
return Config.getSupportedProfilesBitMask();
}
@@ -2318,6 +2620,11 @@
return false;
}
+ StatsLog.write(StatsLog.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);
}
@@ -2329,6 +2636,11 @@
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));
@@ -2342,6 +2654,11 @@
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);
@@ -2358,9 +2675,20 @@
: BluetoothDevice.ACCESS_REJECTED;
}
- boolean setPhonebookAccessPermission(BluetoothDevice device, int value) {
+ 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);
SharedPreferences.Editor editor = pref.edit();
@@ -2447,6 +2775,9 @@
boolean factoryReset() {
enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED, "Need BLUETOOTH permission");
+ if (mDatabaseManager != null) {
+ mDatabaseManager.factoryReset();
+ }
return factoryResetNative();
}
@@ -2634,8 +2965,10 @@
* @return true if Split A2DP Source LDAC is enabled
*/
public boolean isSplitA2DPSourceLDAC() {
+ String BT_SOC = getSocName();
enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- return mAdapterProperties.isSplitA2DPSourceLDAC();
+ return (!mAdapterProperties.isAddonFeaturesCmdSupported() && BT_SOC.equals("cherokee")) ||
+ mAdapterProperties.isSplitA2DPSourceLDAC();
}
/**
@@ -2654,8 +2987,10 @@
* @return true if Split A2DP Source APTX HD is enabled
*/
public boolean isSplitA2DPSourceAPTXHD() {
+ String BT_SOC = getSocName();
enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- return mAdapterProperties.isSplitA2DPSourceAPTXHD();
+ return (!mAdapterProperties.isAddonFeaturesCmdSupported() && BT_SOC.equals("cherokee")) ||
+ mAdapterProperties.isSplitA2DPSourceAPTXHD();
}
/**
@@ -2664,8 +2999,10 @@
* @return true if Split A2DP Source APTX ADAPTIVE is enabled
*/
public boolean isSplitA2DPSourceAPTXADAPTIVE() {
+ String BT_SOC = getSocName();
enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- return mAdapterProperties.isSplitA2DPSourceAPTXADAPTIVE();
+ return (!mAdapterProperties.isAddonFeaturesCmdSupported() && BT_SOC.equals("cherokee")) ||
+ mAdapterProperties.isSplitA2DPSourceAPTXADAPTIVE();
}
/**
@@ -3065,6 +3402,64 @@
+ 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
+ */
+ @VisibleForTesting
+ public void metadataChanged(String address, int key, byte[] value) {
+ BluetoothDevice device = mRemoteDevices.getDevice(Utils.getBytesFromAddress(address));
+ if (mMetadataListeners.containsKey(device)) {
+ ArrayList<IBluetoothMetadataListener> list = mMetadataListeners.get(device);
+ for (IBluetoothMetadataListener listener : list) {
+ try {
+ listener.onMetadataChanged(device, key, value);
+ } catch (RemoteException e) {
+ Log.w(TAG, "RemoteException when onMetadataChanged");
+ }
+ }
+ }
+ }
+
private int getIdleCurrentMa() {
return getResources().getInteger(R.integer.config_bluetooth_idle_cur_ma);
}
@@ -3100,6 +3495,7 @@
writer.println();
mAdapterProperties.dump(fd, writer, args);
writer.println("mSnoopLogSettingAtEnable = " + mSnoopLogSettingAtEnable);
+ writer.println("mDefaultSnoopLogSettingAtEnable = " + mDefaultSnoopLogSettingAtEnable);
writer.println();
mAdapterStateMachine.dump(fd, writer, args);
@@ -3108,6 +3504,7 @@
for (ProfileService profile : mRegisteredProfiles) {
profile.dump(sb);
}
+ mSilenceDeviceManager.dump(fd, writer, args);
writer.write(sb.toString());
writer.flush();
@@ -3171,36 +3568,78 @@
private final BroadcastReceiver mWifiStateBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- if (intent.getAction().equals(WifiManager.NETWORK_STATE_CHANGED_ACTION) && isEnabled()) {
- NetworkInfo networkInfo = intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);
- NetworkInfo.DetailedState ds = networkInfo.getDetailedState();
- if (ds == NetworkInfo.DetailedState.CONNECTED) {
- if(isVendorIntfEnabled())
- mVendor.setWifiState(true);
- }
- else if (ds == NetworkInfo.DetailedState.DISCONNECTED) {
- if(isVendorIntfEnabled())
- mVendor.setWifiState(false);
- }
- }
- }
+ String action = (intent != null) ? intent.getAction():null;
+ debugLog(action);
+ if (action == null) return;
+ if (isEnabled() && (action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION))) {
+ WifiManager wifiMgr = (WifiManager) getSystemService(Context.WIFI_SERVICE);
+ if ((wifiMgr != null) && (wifiMgr.isWifiEnabled())) {
+ WifiInfo wifiInfo = wifiMgr.getConnectionInfo();
+ if ((wifiInfo != null) && (wifiInfo.getNetworkId() != -1)) {
+ mVendor.setWifiState(true);
+ } else {
+ mVendor.setWifiState(false);
+ }
+ }
+ } else if (isEnabled() &&
+ (action.equals(WifiManager.WIFI_STATE_CHANGED_ACTION) ||
+ (action.equals(WifiManager.WIFI_AP_STATE_CHANGED_ACTION)))){
+ if (isPowerbackRequired()) {
+ mVendor.setPowerBackoff(true);
+ } else {
+ mVendor.setPowerBackoff(false);
+ }
+ }
+ }
};
- private void enableNativeWithGuestFlag() {
- boolean isGuest = UserManager.get(this).isGuestUser();
- if (!enableNative(isGuest)) {
- Log.e(TAG, "enableNative() returned false");
- }
+ private boolean isGuest() {
+ return UserManager.get(this).isGuestUser();
}
+ private boolean isSingleUserMode() {
+ return UserManager.get(this).hasUserRestriction(UserManager.DISALLOW_ADD_USER);
+ }
+
+ /**
+ * Obfuscate Bluetooth MAC address into a PII free ID string
+ *
+ * @param device Bluetooth device whose MAC address will be obfuscated
+ * @return a byte array that is unique to this MAC address on this device,
+ * or empty byte array when either device is null or obfuscateAddressNative fails
+ */
+ public byte[] obfuscateAddress(BluetoothDevice device) {
+ if (device == null) {
+ return new byte[0];
+ }
+ return obfuscateAddressNative(Utils.getByteAddress(device));
+ }
+
+ private boolean isPowerbackRequired() {
+ try {
+
+ WifiManager mWifiManager = (WifiManager)getSystemService(Context.WIFI_SERVICE);
+ final WifiConfiguration config = mWifiManager.getWifiApConfiguration();
+ if ((mWifiManager != null) && ((mWifiManager.isWifiEnabled() ||
+ ((mWifiManager.getWifiApState() == WifiManager.WIFI_AP_STATE_ENABLED) &&
+ (config.apBand == WifiConfiguration.AP_BAND_5GHZ))))) {
+ return true;
+ }
+ return false;
+ } catch(SecurityException e) {
+ debugLog(e.toString());
+ }
+ return false;
+ }
+
static native void classInitNative();
- native boolean initNative();
+ native boolean initNative(boolean startRestricted, boolean isSingleUserMode);
native void cleanupNative();
/*package*/
- native boolean enableNative(boolean startRestricted);
+ native boolean enableNative();
/*package*/
native boolean disableNative();
@@ -3276,6 +3715,8 @@
private native void interopDatabaseAddNative(int feature, byte[] address, int length);
+ private native byte[] obfuscateAddressNative(byte[] address);
+
// Returns if this is a mock object. This is currently used in testing so that we may not call
// System.exit() while finalizing the object. Otherwise GC of mock objects unfortunately ends up
// calling finalize() which in turn calls System.exit() and the process crashes.
diff --git a/src/com/android/bluetooth/btservice/AdapterState.java b/src/com/android/bluetooth/btservice/AdapterState.java
index 16e7633..d36f9e6 100644
--- a/src/com/android/bluetooth/btservice/AdapterState.java
+++ b/src/com/android/bluetooth/btservice/AdapterState.java
@@ -191,6 +191,11 @@
transitionTo(mTurningBleOnState);
break;
+ case BT_FORCEKILL_TIMEOUT:
+ errorLog("Killing the process to force a restart as part of cleanup");
+ android.os.Process.killProcess(android.os.Process.myPid());
+ break;
+
default:
infoLog("Unhandled message - " + messageString(msg.what));
return false;
@@ -210,6 +215,7 @@
public boolean processMessage(Message msg) {
switch (msg.what) {
case USER_TURN_ON:
+ mAdapterService.startBrEdrStartup();
transitionTo(mTurningOnState);
break;
@@ -217,6 +223,12 @@
transitionTo(mTurningBleOffState);
break;
+ case BT_FORCEKILL_TIMEOUT:
+ transitionTo(mOffState);
+ errorLog("Killing the process to force a restart as part of cleanup");
+ android.os.Process.killProcess(android.os.Process.myPid());
+ break;
+
default:
infoLog("Unhandled message - " + messageString(msg.what));
return false;
@@ -239,6 +251,12 @@
transitionTo(mTurningOffState);
break;
+ case BT_FORCEKILL_TIMEOUT:
+ transitionTo(mOffState);
+ errorLog("Killing the process to force a restart as part of cleanup");
+ android.os.Process.killProcess(android.os.Process.myPid());
+ break;
+
default:
infoLog("Unhandled message - " + messageString(msg.what));
return false;
diff --git a/src/com/android/bluetooth/btservice/BondStateMachine.java b/src/com/android/bluetooth/btservice/BondStateMachine.java
index 13da892..5985328 100644
--- a/src/com/android/bluetooth/btservice/BondStateMachine.java
+++ b/src/com/android/bluetooth/btservice/BondStateMachine.java
@@ -22,12 +22,14 @@
import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothProtoEnums;
import android.bluetooth.OobData;
import android.content.Intent;
import android.os.Message;
import android.os.UserHandle;
import android.util.Log;
import android.os.PowerManager;
+import android.util.StatsLog;
import com.android.bluetooth.Utils;
import com.android.bluetooth.a2dp.A2dpService;
@@ -37,12 +39,12 @@
import com.android.bluetooth.hfpclient.HeadsetClientService;
import com.android.bluetooth.hid.HidHostService;
import com.android.bluetooth.pbapclient.PbapClientService;
-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;
+import java.util.Objects;
import java.util.Set;
/**
@@ -63,7 +65,6 @@
static final int BONDING_STATE_CHANGE = 4;
static final int SSP_REQUEST = 5;
static final int PIN_REQUEST = 6;
- static final int UUID_UPDATE = 10;
static final int BOND_STATE_NONE = 0;
static final int BOND_STATE_BONDING = 1;
static final int BOND_STATE_BONDED = 2;
@@ -82,8 +83,6 @@
public static final String OOBDATA = "oobdata";
- @VisibleForTesting Set<BluetoothDevice> mPendingBondedDevices = new HashSet<>();
-
private final ArrayList<BluetoothDevice> mDevices =
new ArrayList<BluetoothDevice>();
@@ -99,8 +98,8 @@
setInitialState(mStableState);
//WakeLock instantiation in RemoteDevices class
- mWakeLock = pm.newWakeLock(PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP
- | PowerManager.ON_AFTER_RELEASE, TAG);
+ mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK
+ | PowerManager.ACQUIRE_CAUSES_WAKEUP | PowerManager.ON_AFTER_RELEASE, TAG);
mWakeLock.setReferenceCounted(false);
}
@@ -168,11 +167,7 @@
+ state2str(newState));
}
break;
- case UUID_UPDATE:
- if (mPendingBondedDevices.contains(dev)) {
- sendIntent(dev, BluetoothDevice.BOND_BONDED, 0);
- }
- break;
+
case CANCEL_BOND:
default:
Log.e(TAG, "Received unhandled state: " + msg.what);
@@ -221,6 +216,11 @@
case BONDING_STATE_CHANGE:
int newState = msg.arg1;
int reason = getUnbondReasonFromHALCode(msg.arg2);
+ // Bond is explicitly removed if we are in pending command state
+ if (newState == BluetoothDevice.BOND_NONE
+ && reason == BluetoothDevice.BOND_SUCCESS) {
+ reason = BluetoothDevice.UNBOND_REASON_REMOVED;
+ }
sendIntent(dev, newState, reason);
if (newState != BluetoothDevice.BOND_BONDING) {
// check if bond none is received from device which
@@ -354,8 +354,18 @@
} else {
result = mAdapterService.createBondNative(addr, transport);
}
-
+ StatsLog.write(StatsLog.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,
+ mAdapterService.obfuscateAddress(dev), transport, dev.getType(),
+ BluetoothDevice.BOND_NONE, BluetoothProtoEnums.BOND_SUB_STATE_UNKNOWN,
+ BluetoothDevice.UNBOND_REASON_REPEATED_ATTEMPTS);
+ // Using UNBOND_REASON_REMOVED for legacy reason
sendIntent(dev, BluetoothDevice.BOND_NONE, BluetoothDevice.UNBOND_REASON_REMOVED);
return false;
} else if (transition) {
@@ -379,57 +389,29 @@
intent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
// Workaround for Android Auto until pre-accepting pairing requests is added.
intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
- mAdapterService.sendOrderedBroadcast(intent, mAdapterService.BLUETOOTH_ADMIN_PERM);
+ mAdapterService.sendOrderedBroadcast(intent, AdapterService.BLUETOOTH_ADMIN_PERM);
// Release wakelock to allow the LCD to go off after the PIN popup notification.
mWakeLock.release();
}
- @VisibleForTesting
- void sendIntent(BluetoothDevice device, int newState, int reason) {
+ private void sendIntent(BluetoothDevice device, int newState, int reason) {
DeviceProperties devProp = mRemoteDevices.getDeviceProperties(device);
int oldState = BluetoothDevice.BOND_NONE;
- if (newState != BluetoothDevice.BOND_NONE
- && newState != BluetoothDevice.BOND_BONDING
- && newState != BluetoothDevice.BOND_BONDED) {
- infoLog("Invalid bond state " + newState);
- return;
- }
if (devProp != null) {
oldState = devProp.getBondState();
}
- if (mPendingBondedDevices.contains(device)) {
- mPendingBondedDevices.remove(device);
- if (oldState == BluetoothDevice.BOND_BONDED) {
- if (newState == BluetoothDevice.BOND_BONDING) {
- mAdapterProperties.onBondStateChanged(device, newState);
- }
- oldState = BluetoothDevice.BOND_BONDING;
- } else {
- // Should not enter here.
- throw new IllegalArgumentException("Invalid old state " + oldState);
- }
- }
if (oldState == newState) {
return;
}
-
+ StatsLog.write(StatsLog.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,
+ mAdapterService.obfuscateAddress(device), classOfDevice);
mAdapterProperties.onBondStateChanged(device, newState);
- if ((devProp.getDeviceType() == BluetoothDevice.DEVICE_TYPE_CLASSIC
- || devProp.getDeviceType() == BluetoothDevice.DEVICE_TYPE_DUAL)
- && newState == BluetoothDevice.BOND_BONDED && devProp.getUuids() == null) {
- infoLog(device + " is bonded, wait for SDP complete to broadcast bonded intent");
- if (!mPendingBondedDevices.contains(device)) {
- mPendingBondedDevices.add(device);
- }
- if (oldState == BluetoothDevice.BOND_NONE) {
- // Broadcast NONE->BONDING for NONE->BONDED case.
- newState = BluetoothDevice.BOND_BONDING;
- } else {
- return;
- }
- }
-
Intent intent = new Intent(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
intent.putExtra(BluetoothDevice.EXTRA_BOND_STATE, newState);
@@ -508,9 +490,14 @@
if (device == null) {
warnLog("Device is not known for:" + Utils.getAddressStringFromByte(address));
mRemoteDevices.addDeviceProperties(address);
- device = mRemoteDevices.getDevice(address);
+ device = Objects.requireNonNull(mRemoteDevices.getDevice(address));
}
+ StatsLog.write(StatsLog.BLUETOOTH_BOND_STATE_CHANGED,
+ mAdapterService.obfuscateAddress(device), 0, device.getType(),
+ BluetoothDevice.BOND_BONDING,
+ BluetoothProtoEnums.BOND_SUB_STATE_LOCAL_SSP_REQUESTED, 0);
+
Message msg = obtainMessage(SSP_REQUEST);
msg.obj = device;
if (displayPasskey) {
@@ -526,7 +513,14 @@
BluetoothDevice bdDevice = mRemoteDevices.getDevice(address);
if (bdDevice == null) {
mRemoteDevices.addDeviceProperties(address);
+ bdDevice = Objects.requireNonNull(mRemoteDevices.getDevice(address));
}
+
+ StatsLog.write(StatsLog.BLUETOOTH_BOND_STATE_CHANGED,
+ mAdapterService.obfuscateAddress(bdDevice), 0, bdDevice.getType(),
+ BluetoothDevice.BOND_BONDING,
+ BluetoothProtoEnums.BOND_SUB_STATE_LOCAL_PIN_REQUESTED, 0);
+
infoLog("pinRequestCallback: " + address + " name:" + name + " cod:" + cod);
Message msg = obtainMessage(PIN_REQUEST);
diff --git a/src/com/android/bluetooth/btservice/Config.java b/src/com/android/bluetooth/btservice/Config.java
index 2355b88..0e170b1 100644
--- a/src/com/android/bluetooth/btservice/Config.java
+++ b/src/com/android/bluetooth/btservice/Config.java
@@ -31,7 +31,6 @@
import com.android.bluetooth.avrcp.AvrcpTargetService;
import com.android.bluetooth.avrcpcontroller.AvrcpControllerService;
import com.android.bluetooth.gatt.GattService;
-import com.android.bluetooth.hdp.HealthService;
import com.android.bluetooth.hearingaid.HearingAidService;
import com.android.bluetooth.hfp.HeadsetService;
import com.android.bluetooth.hfpclient.HeadsetClientService;
@@ -75,8 +74,6 @@
(1 << BluetoothProfile.A2DP_SINK)),
new ProfileConfig(HidHostService.class, R.bool.profile_supported_hid_host,
(1 << BluetoothProfile.HID_HOST)),
- new ProfileConfig(HealthService.class, R.bool.profile_supported_hdp,
- (1 << BluetoothProfile.HEALTH)),
new ProfileConfig(PanService.class, R.bool.profile_supported_pan,
(1 << BluetoothProfile.PAN)),
new ProfileConfig(GattService.class, R.bool.profile_supported_gatt,
@@ -102,10 +99,11 @@
(1 << BluetoothProfile.OPP)),
new ProfileConfig(BluetoothPbapService.class, R.bool.profile_supported_pbap,
(1 << BluetoothProfile.PBAP)),
- new ProfileConfig(HearingAidService.class, R.bool.profile_supported_hearing_aid,
+ new ProfileConfig(HearingAidService.class,
+ com.android.internal.R.bool.config_hearing_aid_profile_supported,
(1 << BluetoothProfile.HEARING_AID)),
new ProfileConfig(BATService.class, R.bool.profile_supported_ba,
- (1 << BluetoothProfile.BA_TRANSMITTER))
+ (1 << BATService.BA_TRANSMITTER))
};
private static Class[] sSupportedProfiles = new Class[0];
@@ -183,13 +181,22 @@
return false;
boolean isBAEnabled = SystemProperties.getBoolean("persist.vendor.service.bt.bca", false);
- boolean isSplitA2dpSupported = SystemProperties.
- getBoolean("persist.vendor.btstack.enable.splita2dp", true);
+
+ // Split A2dp will be enabled by default
+ boolean isSplitA2dpEnabled = true;
+ AdapterService adapterService = AdapterService.getAdapterService();
+
+ if (adapterService != null){
+ isSplitA2dpEnabled = adapterService.isSplitA2dpEnabled();
+ Log.v(TAG,"isSplitA2dpEnabled: " + isSplitA2dpEnabled);
+ } else {
+ Log.e(TAG,"adapterService is null");
+ }
if(serviceName.equals("BATService")) {
Log.d(TAG," isBAEnabled = " + isBAEnabled
- + " isSplitEnabled " + isSplitA2dpSupported);
- return isBAEnabled && isSplitA2dpSupported;
+ + " isSplitEnabled " + isSplitA2dpEnabled);
+ return isBAEnabled && isSplitA2dpEnabled;
}
// always return true for other profiles
return true;
diff --git a/src/com/android/bluetooth/btservice/DiscoveringPackage.java b/src/com/android/bluetooth/btservice/DiscoveringPackage.java
new file mode 100644
index 0000000..baa2b24
--- /dev/null
+++ b/src/com/android/bluetooth/btservice/DiscoveringPackage.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 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.btservice;
+
+final class DiscoveringPackage {
+ private String mPackageName;
+ private String mPermission;
+
+ DiscoveringPackage(String packageName, String permission) {
+ mPackageName = packageName;
+ mPermission = permission;
+ }
+
+ public String getPackageName() {
+ return mPackageName;
+ }
+
+ public String getPermission() {
+ return mPermission;
+ }
+}
diff --git a/src/com/android/bluetooth/btservice/PhonePolicy.java b/src/com/android/bluetooth/btservice/PhonePolicy.java
index 7cd45f9..f3097fe 100644
--- a/src/com/android/bluetooth/btservice/PhonePolicy.java
+++ b/src/com/android/bluetooth/btservice/PhonePolicy.java
@@ -33,7 +33,6 @@
import android.os.Message;
import android.os.ParcelUuid;
import android.os.Parcelable;
-import android.support.annotation.VisibleForTesting;
import android.util.Log;
import com.android.bluetooth.a2dp.A2dpService;
@@ -44,6 +43,7 @@
import com.android.bluetooth.pan.PanService;
import com.android.bluetooth.ba.BATService;
import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
import java.util.HashSet;
import java.util.List;
@@ -347,23 +347,26 @@
}
connectOtherProfile(device);
}
- if (prevState == BluetoothProfile.STATE_CONNECTING
- && nextState == BluetoothProfile.STATE_DISCONNECTED) {
- 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) {
- //remove a2dp and headset retry set.
- mA2dpRetrySet.remove(device);
- mHeadsetRetrySet.remove(device);
- removeAutoConnectFromA2dpSink(device);
- removeAutoConnectFromHeadset(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) {
+ //remove a2dp and headset retry set.
+ mA2dpRetrySet.remove(device);
+ mHeadsetRetrySet.remove(device);
+ removeAutoConnectFromA2dpSink(device);
+ removeAutoConnectFromHeadset(device);
+ }
}
}
}
@@ -385,15 +388,14 @@
return;
}
for (BluetoothDevice device : mAdapterService.getBondedDevices()) {
- removeAutoConnectFromA2dpSink(device);
- removeAutoConnectFromHeadset(device);
+ removeAutoConnectFromA2dpSink(device);
+ removeAutoConnectFromHeadset(device);
}
+
setAutoConnectForA2dpSink(activeDevice);
setAutoConnectForHeadset(activeDevice);
- if ((mAdapterService != null) &&
- (mAdapterService.isTwsPlusDevice(activeDevice))) {
- BluetoothDevice peerTwsDevice =
- mAdapterService.getTwsPlusPeerDevice(activeDevice);
+ if (mAdapterService.isTwsPlusDevice(activeDevice)) {
+ BluetoothDevice peerTwsDevice = hsService.getTwsPlusConnectedPeer(activeDevice);
if (peerTwsDevice != null) {
if (a2dpService != null &&
a2dpService.getConnectionState(peerTwsDevice) !=
@@ -431,6 +433,51 @@
}
}
+ private boolean handleAllProfilesDisconnected(BluetoothDevice device) {
+ boolean atLeastOneProfileConnectedForDevice = false;
+ boolean allProfilesEmpty = true;
+ HeadsetService hsService = mFactory.getHeadsetService();
+ A2dpService a2dpService = mFactory.getA2dpService();
+ PanService panService = mFactory.getPanService();
+ A2dpSinkService a2dpSinkService = mFactory.getA2dpSinkService();
+
+ if (hsService != null) {
+ List<BluetoothDevice> hsConnDevList = hsService.getConnectedDevices();
+ allProfilesEmpty &= hsConnDevList.isEmpty();
+ atLeastOneProfileConnectedForDevice |= hsConnDevList.contains(device);
+ }
+ if (a2dpService != null) {
+ List<BluetoothDevice> a2dpConnDevList = a2dpService.getConnectedDevices();
+ allProfilesEmpty &= a2dpConnDevList.isEmpty();
+ atLeastOneProfileConnectedForDevice |= a2dpConnDevList.contains(device);
+ }
+ if (a2dpSinkService != null) {
+ List<BluetoothDevice> a2dpSinkConnDevList = a2dpSinkService.getConnectedDevices();
+ allProfilesEmpty &= a2dpSinkConnDevList.isEmpty();
+ atLeastOneProfileConnectedForDevice |= a2dpSinkConnDevList.contains(device);
+ }
+ if (panService != null) {
+ List<BluetoothDevice> panConnDevList = panService.getConnectedDevices();
+ allProfilesEmpty &= panConnDevList.isEmpty();
+ atLeastOneProfileConnectedForDevice |= panConnDevList.contains(device);
+ }
+
+ if (!atLeastOneProfileConnectedForDevice) {
+ // Consider this device as fully disconnected, don't bother connecting others
+ debugLog("handleAllProfilesDisconnected: all profiles disconnected for " + device);
+ mHeadsetRetrySet.remove(device);
+ mA2dpRetrySet.remove(device);
+ if (allProfilesEmpty) {
+ debugLog("handleAllProfilesDisconnected: all profiles disconnected for all"
+ + " devices");
+ // reset retry status so that in the next round we can start retrying connections
+ resetStates();
+ }
+ return true;
+ }
+ return false;
+ }
+
private void resetStates() {
mHeadsetRetrySet.clear();
mA2dpRetrySet.clear();
@@ -578,55 +625,31 @@
warnLog("processConnectOtherProfiles, adapter is not ON " + mAdapterService.getState());
return;
}
+ if (handleAllProfilesDisconnected(device)) {
+ debugLog("processConnectOtherProfiles: all profiles disconnected for " + device);
+ return;
+ }
+
HeadsetService hsService = mFactory.getHeadsetService();
A2dpService a2dpService = mFactory.getA2dpService();
PanService panService = mFactory.getPanService();
A2dpSinkService a2dpSinkService = mFactory.getA2dpSinkService();
- boolean a2dpConnected = false;
- boolean hsConnected = false;
-
- boolean atLeastOneProfileConnectedForDevice = false;
- boolean allProfilesEmpty = true;
+ List<BluetoothDevice> hsConnDevList = null;
List<BluetoothDevice> a2dpConnDevList = null;
List<BluetoothDevice> a2dpSinkConnDevList = null;
- List<BluetoothDevice> hsConnDevList = null;
- List<BluetoothDevice> panConnDevList = null;
-
if (hsService != null) {
hsConnDevList = hsService.getConnectedDevices();
- allProfilesEmpty &= hsConnDevList.isEmpty();
- atLeastOneProfileConnectedForDevice |= hsConnDevList.contains(device);
}
if (a2dpService != null) {
a2dpConnDevList = a2dpService.getConnectedDevices();
- allProfilesEmpty &= a2dpConnDevList.isEmpty();
- atLeastOneProfileConnectedForDevice |= a2dpConnDevList.contains(device);
}
if (a2dpSinkService != null) {
a2dpSinkConnDevList = a2dpSinkService.getConnectedDevices();
- allProfilesEmpty &= a2dpSinkConnDevList.isEmpty();
- atLeastOneProfileConnectedForDevice |= a2dpSinkConnDevList.contains(device);
- }
- if (panService != null) {
- panConnDevList = panService.getConnectedDevices();
- allProfilesEmpty &= panConnDevList.isEmpty();
- atLeastOneProfileConnectedForDevice |= panConnDevList.contains(device);
}
- if (!atLeastOneProfileConnectedForDevice) {
- // Consider this device as fully disconnected, don't bother connecting others
- debugLog("processConnectOtherProfiles, all profiles disconnected for " + device);
- mHeadsetRetrySet.remove(device);
- mA2dpRetrySet.remove(device);
- if (allProfilesEmpty) {
- debugLog("processConnectOtherProfiles, all profiles disconnected for all devices");
- // reset retry status so that in the next round we can start retrying connections
- resetStates();
- }
- return;
- }
-
+ boolean a2dpConnected = false;
+ boolean hsConnected = false;
if(a2dpConnDevList != null && !a2dpConnDevList.isEmpty()) {
for (BluetoothDevice a2dpDevice : a2dpConnDevList)
{
@@ -636,7 +659,6 @@
}
}
}
-
if(hsConnDevList != null && !hsConnDevList.isEmpty()) {
for (BluetoothDevice hsDevice : hsConnDevList)
{
@@ -717,6 +739,7 @@
}
}
if (panService != null) {
+ 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)
diff --git a/src/com/android/bluetooth/btservice/RemoteDevices.java b/src/com/android/bluetooth/btservice/RemoteDevices.java
index d4e7fec..d585cb4 100644
--- a/src/com/android/bluetooth/btservice/RemoteDevices.java
+++ b/src/com/android/bluetooth/btservice/RemoteDevices.java
@@ -65,12 +65,13 @@
import android.os.Looper;
import android.os.Message;
import android.os.ParcelUuid;
-import android.support.annotation.VisibleForTesting;
import android.util.Log;
+import android.util.StatsLog;
import com.android.bluetooth.R;
import com.android.bluetooth.Utils;
import com.android.bluetooth.hfp.HeadsetHalConstants;
+import com.android.internal.annotations.VisibleForTesting;
import java.util.ArrayList;
import java.util.HashMap;
@@ -114,7 +115,8 @@
case MESSAGE_UUID_INTENT:
BluetoothDevice device = (BluetoothDevice) msg.obj;
if (device != null) {
- sendUuidIntent(device);
+ DeviceProperties prop = getDeviceProperties(device);
+ sendUuidIntent(device, prop);
}
break;
}
@@ -221,7 +223,6 @@
return null;
}
- @VisibleForTesting
DeviceProperties addDeviceProperties(byte[] address) {
synchronized (mDevices) {
DeviceProperties prop = new DeviceProperties();
@@ -252,13 +253,13 @@
private byte[] mAddress;
private int mBluetoothClass = BluetoothClass.Device.Major.UNCATEGORIZED;
private short mRssi;
+ private ParcelUuid[] mUuids;
+ private int mDeviceType;
private String mAlias;
+ private int mBondState;
private BluetoothDevice mDevice;
private boolean mIsBondingInitiatedLocally;
private int mBatteryLevel = BluetoothDevice.BATTERY_LEVEL_UNKNOWN;
- @VisibleForTesting int mBondState;
- @VisibleForTesting int mDeviceType;
- @VisibleForTesting ParcelUuid[] mUuids;
private short mTwsPlusDevType;
private byte[] peerEbAddress;
private boolean autoConnect;
@@ -323,6 +324,7 @@
return mRssi;
}
}
+
/**
* @return mDeviceType
*/
@@ -483,8 +485,7 @@
}
}
- private void sendUuidIntent(BluetoothDevice device) {
- DeviceProperties prop = getDeviceProperties(device);
+ private void sendUuidIntent(BluetoothDevice device, DeviceProperties prop) {
Intent intent = new Intent(BluetoothDevice.ACTION_UUID);
intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
intent.putExtra(BluetoothDevice.EXTRA_UUID, prop == null ? null : prop.mUuids);
@@ -574,6 +575,7 @@
intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
intent.putExtra(BluetoothDevice.EXTRA_BATTERY_LEVEL, batteryLevel);
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+ intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
sAdapterService.sendBroadcast(intent, AdapterService.BLUETOOTH_PERM);
}
@@ -627,7 +629,7 @@
case AbstractionLayer.BT_PROPERTY_BDNAME:
final String newName = new String(val);
if (newName.equals(device.mName)) {
- Log.w(TAG, "Skip name update for " + bdDevice);
+ debugLog("Skip name update for " + bdDevice);
break;
}
device.mName = newName;
@@ -635,6 +637,7 @@
intent.putExtra(BluetoothDevice.EXTRA_DEVICE, bdDevice);
intent.putExtra(BluetoothDevice.EXTRA_NAME, device.mName);
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+ intent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
sAdapterService.sendBroadcast(intent, sAdapterService.BLUETOOTH_PERM);
debugLog("Remote Device name is: " + device.mName);
break;
@@ -649,7 +652,7 @@
case AbstractionLayer.BT_PROPERTY_CLASS_OF_DEVICE:
final int newClass = Utils.byteArrayToInt(val);
if (newClass == device.mBluetoothClass) {
- Log.w(TAG, "Skip class update for " + bdDevice);
+ debugLog("Skip class update for " + bdDevice);
break;
}
device.mBluetoothClass = Utils.byteArrayToInt(val);
@@ -665,15 +668,14 @@
int numUuids = val.length / AbstractionLayer.BT_UUID_SIZE;
final ParcelUuid[] newUuids = Utils.byteArrayToUuid(val);
if (areUuidsEqual(newUuids, device.mUuids)) {
- Log.w(TAG, "Skip uuids update for " + bdDevice.getAddress());
+ debugLog( "Skip uuids update for " + bdDevice.getAddress());
break;
}
device.mUuids = newUuids;
if ((sAdapterService.getState() == BluetoothAdapter.STATE_ON) &&
device.autoConnect ) {
- sAdapterService.deviceUuidUpdated(bdDevice);
debugLog("sendUuidIntent as Auto connect is set ");
- sendUuidIntent(bdDevice);
+ sendUuidIntent(bdDevice, device);
}
break;
case AbstractionLayer.BT_PROPERTY_TYPE_OF_DEVICE:
@@ -709,9 +711,15 @@
intent.putExtra(BluetoothDevice.EXTRA_RSSI, deviceProp.mRssi);
intent.putExtra(BluetoothDevice.EXTRA_NAME, deviceProp.mName);
- sAdapterService.sendBroadcastMultiplePermissions(intent, new String[]{
- AdapterService.BLUETOOTH_PERM, android.Manifest.permission.ACCESS_COARSE_LOCATION
- });
+ final ArrayList<DiscoveringPackage> packages = sAdapterService.getDiscoveringPackages();
+ synchronized (packages) {
+ for (DiscoveringPackage pkg : packages) {
+ intent.setPackage(pkg.getPackageName());
+ sAdapterService.sendBroadcastMultiplePermissions(intent, new String[]{
+ AdapterService.BLUETOOTH_PERM, pkg.getPermission()
+ });
+ }
+ }
}
void aclStateChangeCallback(int status, byte[] address, int newState) {
@@ -751,6 +759,15 @@
+ " Disconnected: " + device);
}
+ int connectionState = newState == AbstractionLayer.BT_ACL_STATE_CONNECTED
+ ? BluetoothAdapter.STATE_CONNECTED : BluetoothAdapter.STATE_DISCONNECTED;
+ StatsLog.write(StatsLog.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,
+ sAdapterService.obfuscateAddress(device), classOfDevice);
+
if (intent != null) {
intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
@@ -950,13 +967,13 @@
}
int batteryLevel = (Integer) args[1];
int numberOfLevels = (Integer) args[2];
- if (batteryLevel < 0 || numberOfLevels < 0 || batteryLevel > numberOfLevels) {
+ if (batteryLevel < 0 || numberOfLevels <= 1 || batteryLevel > numberOfLevels) {
Log.w(TAG, "getBatteryLevelFromXEventVsc() wrong event value, batteryLevel="
+ String.valueOf(batteryLevel) + ", numberOfLevels=" + String.valueOf(
numberOfLevels));
return BluetoothDevice.BATTERY_LEVEL_UNKNOWN;
}
- return batteryLevel * 100 / numberOfLevels;
+ return batteryLevel * 100 / (numberOfLevels - 1);
}
private static void errorLog(String msg) {
diff --git a/src/com/android/bluetooth/btservice/ServiceFactory.java b/src/com/android/bluetooth/btservice/ServiceFactory.java
index 580de3b..c520b9a 100644
--- a/src/com/android/bluetooth/btservice/ServiceFactory.java
+++ b/src/com/android/bluetooth/btservice/ServiceFactory.java
@@ -18,6 +18,7 @@
import com.android.bluetooth.a2dp.A2dpService;
import com.android.bluetooth.a2dpsink.A2dpSinkService;
+import com.android.bluetooth.avrcp.AvrcpTargetService;
import com.android.bluetooth.hearingaid.HearingAidService;
import com.android.bluetooth.hfp.HeadsetService;
import com.android.bluetooth.hid.HidDeviceService;
@@ -53,4 +54,8 @@
public A2dpSinkService getA2dpSinkService() {
return A2dpSinkService.getA2dpSinkService();
}
+
+ public AvrcpTargetService getAvrcpTargetService() {
+ return AvrcpTargetService.get();
+ }
}
diff --git a/src/com/android/bluetooth/btservice/SilenceDeviceManager.java b/src/com/android/bluetooth/btservice/SilenceDeviceManager.java
new file mode 100644
index 0000000..e8ec235
--- /dev/null
+++ b/src/com/android/bluetooth/btservice/SilenceDeviceManager.java
@@ -0,0 +1,346 @@
+/*
+ * Copyright 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.btservice;
+
+import android.bluetooth.BluetoothA2dp;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHeadset;
+import android.bluetooth.BluetoothProfile;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.UserHandle;
+import android.util.Log;
+
+import com.android.bluetooth.a2dp.A2dpService;
+import com.android.bluetooth.hfp.HeadsetService;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * The silence device manager controls silence mode for A2DP, HFP, and AVRCP.
+ *
+ * 1) If an active device (for A2DP or HFP) enters silence mode, the active device
+ * for that profile will be set to null.
+ * 2) If a device exits silence mode while the A2DP or HFP active device is null,
+ * the device will be set as the active device for that profile.
+ * 3) If a device is disconnected, it exits silence mode.
+ * 4) If a device is set as the active device for A2DP or HFP, while silence mode
+ * is enabled, then the device will exit silence mode.
+ * 5) If a device is in silence mode, AVRCP position change event and HFP AG indicators
+ * will be disabled.
+ * 6) If a device is not connected with A2DP or HFP, it cannot enter silence mode.
+ */
+public class SilenceDeviceManager {
+ private static final boolean DBG = true;
+ private static final boolean VERBOSE = false;
+ private static final String TAG = "SilenceDeviceManager";
+
+ private final AdapterService mAdapterService;
+ private final ServiceFactory mFactory;
+ private Handler mHandler = null;
+ private Looper mLooper = null;
+
+ private final Map<BluetoothDevice, Boolean> mSilenceDevices = new HashMap<>();
+ private final List<BluetoothDevice> mA2dpConnectedDevices = new ArrayList<>();
+ private final List<BluetoothDevice> mHfpConnectedDevices = new ArrayList<>();
+
+ private static final int MSG_SILENCE_DEVICE_STATE_CHANGED = 1;
+ private static final int MSG_A2DP_CONNECTION_STATE_CHANGED = 10;
+ private static final int MSG_HFP_CONNECTION_STATE_CHANGED = 11;
+ private static final int MSG_A2DP_ACTIVE_DEIVCE_CHANGED = 20;
+ private static final int MSG_HFP_ACTIVE_DEVICE_CHANGED = 21;
+ private static final int ENABLE_SILENCE = 0;
+ private static final int DISABLE_SILENCE = 1;
+
+ // Broadcast receiver for all changes
+ private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (action == null) {
+ Log.e(TAG, "Received intent with null action");
+ return;
+ }
+ switch (action) {
+ case BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED:
+ mHandler.obtainMessage(MSG_A2DP_CONNECTION_STATE_CHANGED,
+ intent).sendToTarget();
+ break;
+ case BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED:
+ mHandler.obtainMessage(MSG_HFP_CONNECTION_STATE_CHANGED,
+ intent).sendToTarget();
+ break;
+ case BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED:
+ mHandler.obtainMessage(MSG_A2DP_ACTIVE_DEIVCE_CHANGED,
+ intent).sendToTarget();
+ break;
+ case BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED:
+ mHandler.obtainMessage(MSG_HFP_ACTIVE_DEVICE_CHANGED,
+ intent).sendToTarget();
+ break;
+ default:
+ Log.e(TAG, "Received unexpected intent, action=" + action);
+ break;
+ }
+ }
+ };
+
+ class SilenceDeviceManagerHandler extends Handler {
+ SilenceDeviceManagerHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ if (VERBOSE) {
+ Log.d(TAG, "handleMessage: " + msg.what);
+ }
+ switch (msg.what) {
+ case MSG_SILENCE_DEVICE_STATE_CHANGED: {
+ BluetoothDevice device = (BluetoothDevice) msg.obj;
+ boolean state = (msg.arg1 == ENABLE_SILENCE);
+ handleSilenceDeviceStateChanged(device, state);
+ }
+ break;
+
+ case MSG_A2DP_CONNECTION_STATE_CHANGED: {
+ Intent intent = (Intent) msg.obj;
+ BluetoothDevice device =
+ intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+ int prevState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, -1);
+ int nextState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1);
+
+ if (nextState == BluetoothProfile.STATE_CONNECTED) {
+ // enter connected state
+ addConnectedDevice(device, BluetoothProfile.A2DP);
+ if (!mSilenceDevices.containsKey(device)) {
+ mSilenceDevices.put(device, false);
+ }
+ } else if (prevState == BluetoothProfile.STATE_CONNECTED) {
+ // exiting from connected state
+ removeConnectedDevice(device, BluetoothProfile.A2DP);
+ if (!isBluetoothAudioConnected(device)) {
+ handleSilenceDeviceStateChanged(device, false);
+ mSilenceDevices.remove(device);
+ }
+ }
+ }
+ break;
+
+ case MSG_HFP_CONNECTION_STATE_CHANGED: {
+ Intent intent = (Intent) msg.obj;
+ BluetoothDevice device =
+ intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+ int prevState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, -1);
+ int nextState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1);
+
+ if (nextState == BluetoothProfile.STATE_CONNECTED) {
+ // enter connected state
+ addConnectedDevice(device, BluetoothProfile.HEADSET);
+ if (!mSilenceDevices.containsKey(device)) {
+ mSilenceDevices.put(device, false);
+ }
+ } else if (prevState == BluetoothProfile.STATE_CONNECTED) {
+ // exiting from connected state
+ removeConnectedDevice(device, BluetoothProfile.HEADSET);
+ if (!isBluetoothAudioConnected(device)) {
+ handleSilenceDeviceStateChanged(device, false);
+ mSilenceDevices.remove(device);
+ }
+ }
+ }
+ break;
+
+ case MSG_A2DP_ACTIVE_DEIVCE_CHANGED: {
+ Intent intent = (Intent) msg.obj;
+ BluetoothDevice a2dpActiveDevice =
+ intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+ if (getSilenceMode(a2dpActiveDevice)) {
+ // Resume the device from silence mode.
+ setSilenceMode(a2dpActiveDevice, false);
+ }
+ }
+ break;
+
+ case MSG_HFP_ACTIVE_DEVICE_CHANGED: {
+ Intent intent = (Intent) msg.obj;
+ BluetoothDevice hfpActiveDevice =
+ intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+ if (getSilenceMode(hfpActiveDevice)) {
+ // Resume the device from silence mode.
+ setSilenceMode(hfpActiveDevice, false);
+ }
+ }
+ break;
+
+ default: {
+ Log.e(TAG, "Unknown message: " + msg.what);
+ }
+ break;
+ }
+ }
+ };
+
+ SilenceDeviceManager(AdapterService service, ServiceFactory factory, Looper looper) {
+ mAdapterService = service;
+ mFactory = factory;
+ mLooper = looper;
+ }
+
+ void start() {
+ if (VERBOSE) {
+ Log.v(TAG, "start()");
+ }
+ mHandler = new SilenceDeviceManagerHandler(mLooper);
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
+ filter.addAction(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED);
+ filter.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
+ filter.addAction(BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED);
+ mAdapterService.registerReceiver(mReceiver, filter);
+ }
+
+ void cleanup() {
+ if (VERBOSE) {
+ Log.v(TAG, "cleanup()");
+ }
+ mSilenceDevices.clear();
+ mAdapterService.unregisterReceiver(mReceiver);
+ }
+
+ @VisibleForTesting
+ boolean setSilenceMode(BluetoothDevice device, boolean silence) {
+ if (mHandler == null) {
+ Log.e(TAG, "setSilenceMode() mHandler is null!");
+ return false;
+ }
+ Log.d(TAG, "setSilenceMode: " + device.getAddress() + ", " + silence);
+ Message message = mHandler.obtainMessage(MSG_SILENCE_DEVICE_STATE_CHANGED,
+ silence ? ENABLE_SILENCE : DISABLE_SILENCE, 0, device);
+ mHandler.sendMessage(message);
+ return true;
+ }
+
+ void handleSilenceDeviceStateChanged(BluetoothDevice device, boolean state) {
+ boolean oldState = getSilenceMode(device);
+ if (oldState == state) {
+ return;
+ }
+ if (!isBluetoothAudioConnected(device)) {
+ if (oldState) {
+ // Device is disconnected, resume all silenced profiles.
+ state = false;
+ } else {
+ Log.d(TAG, "Deivce is not connected to any Bluetooth audio.");
+ return;
+ }
+ }
+ mSilenceDevices.replace(device, state);
+
+ A2dpService a2dpService = mFactory.getA2dpService();
+ if (a2dpService != null) {
+ a2dpService.setSilenceMode(device, state);
+ }
+ HeadsetService headsetService = mFactory.getHeadsetService();
+ if (headsetService != null) {
+ headsetService.setSilenceMode(device, state);
+ }
+ Log.i(TAG, "Silence mode change " + device.getAddress() + ": " + oldState + " -> "
+ + state);
+ broadcastSilenceStateChange(device, state);
+ }
+
+ void broadcastSilenceStateChange(BluetoothDevice device, boolean state) {
+ Intent intent = new Intent(BluetoothDevice.ACTION_SILENCE_MODE_CHANGED);
+ intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
+ mAdapterService.sendBroadcastAsUser(intent, UserHandle.ALL, AdapterService.BLUETOOTH_PERM);
+
+ }
+
+ @VisibleForTesting
+ boolean getSilenceMode(BluetoothDevice device) {
+ boolean state = false;
+ if (mSilenceDevices.containsKey(device)) {
+ state = mSilenceDevices.get(device);
+ }
+ return state;
+ }
+
+ void addConnectedDevice(BluetoothDevice device, int profile) {
+ if (VERBOSE) {
+ Log.d(TAG, "addConnectedDevice: " + device.getAddress() + ", profile:" + profile);
+ }
+ switch (profile) {
+ case BluetoothProfile.A2DP:
+ if (!mA2dpConnectedDevices.contains(device)) {
+ mA2dpConnectedDevices.add(device);
+ }
+ break;
+ case BluetoothProfile.HEADSET:
+ if (!mHfpConnectedDevices.contains(device)) {
+ mHfpConnectedDevices.add(device);
+ }
+ break;
+ }
+ }
+
+ void removeConnectedDevice(BluetoothDevice device, int profile) {
+ if (VERBOSE) {
+ Log.d(TAG, "removeConnectedDevice: " + device.getAddress() + ", profile:" + profile);
+ }
+ switch (profile) {
+ case BluetoothProfile.A2DP:
+ if (mA2dpConnectedDevices.contains(device)) {
+ mA2dpConnectedDevices.remove(device);
+ }
+ break;
+ case BluetoothProfile.HEADSET:
+ if (mHfpConnectedDevices.contains(device)) {
+ mHfpConnectedDevices.remove(device);
+ }
+ break;
+ }
+ }
+
+ boolean isBluetoothAudioConnected(BluetoothDevice device) {
+ return (mA2dpConnectedDevices.contains(device) || mHfpConnectedDevices.contains(device));
+ }
+
+ protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
+ writer.println("\nSilenceDeviceManager:");
+ writer.println(" Address | Is silenced?");
+ for (BluetoothDevice device : mSilenceDevices.keySet()) {
+ writer.println(" " + device.getAddress() + " | " + getSilenceMode(device));
+ }
+ }
+
+ @VisibleForTesting
+ BroadcastReceiver getBroadcastReceiver() {
+ return mReceiver;
+ }
+}
diff --git a/src/com/android/bluetooth/btservice/storage/CustomizedMetadataEntity.java b/src/com/android/bluetooth/btservice/storage/CustomizedMetadataEntity.java
new file mode 100644
index 0000000..d0bddf1
--- /dev/null
+++ b/src/com/android/bluetooth/btservice/storage/CustomizedMetadataEntity.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 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.btservice.storage;
+
+import androidx.room.Entity;
+
+@Entity
+class CustomizedMetadataEntity {
+ public byte[] manufacturer_name;
+ public byte[] model_name;
+ public byte[] software_version;
+ public byte[] hardware_version;
+ public byte[] companion_app;
+ public byte[] main_icon;
+ public byte[] is_untethered_headset;
+ public byte[] untethered_left_icon;
+ public byte[] untethered_right_icon;
+ public byte[] untethered_case_icon;
+ public byte[] untethered_left_battery;
+ public byte[] untethered_right_battery;
+ public byte[] untethered_case_battery;
+ public byte[] untethered_left_charging;
+ public byte[] untethered_right_charging;
+ public byte[] untethered_case_charging;
+ public byte[] enhanced_settings_ui_uri;
+}
diff --git a/src/com/android/bluetooth/btservice/storage/DatabaseManager.java b/src/com/android/bluetooth/btservice/storage/DatabaseManager.java
new file mode 100644
index 0000000..a0e3c5a
--- /dev/null
+++ b/src/com/android/bluetooth/btservice/storage/DatabaseManager.java
@@ -0,0 +1,796 @@
+/*
+ * Copyright 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.btservice.storage;
+
+import android.bluetooth.BluetoothA2dp;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothProtoEnums;
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+import android.provider.Settings;
+import android.util.Log;
+import android.util.StatsLog;
+
+import com.android.bluetooth.Utils;
+import com.android.bluetooth.btservice.AdapterService;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * The active device manager is responsible to handle a Room database
+ * for Bluetooth persistent data.
+ */
+public class DatabaseManager {
+ private static final boolean DBG = true;
+ private static final boolean VERBOSE = true;
+ private static final String TAG = "BluetoothDatabase";
+
+ private AdapterService mAdapterService = null;
+ private HandlerThread mHandlerThread = null;
+ private Handler mHandler = null;
+ private MetadataDatabase mDatabase = null;
+ private boolean mMigratedFromSettingsGlobal = false;
+
+ @VisibleForTesting
+ final Map<String, Metadata> mMetadataCache = new HashMap<>();
+ private final Semaphore mSemaphore = new Semaphore(1);
+
+ private static final int LOAD_DATABASE_TIMEOUT = 500; // milliseconds
+ private static final int MSG_LOAD_DATABASE = 0;
+ private static final int MSG_UPDATE_DATABASE = 1;
+ private static final int MSG_DELETE_DATABASE = 2;
+ private static final int MSG_CLEAR_DATABASE = 100;
+ private static final String LOCAL_STORAGE = "LocalStorage";
+
+ /**
+ * Constructor of the DatabaseManager
+ */
+ public DatabaseManager(AdapterService service) {
+ mAdapterService = service;
+ }
+
+ class DatabaseHandler extends Handler {
+ DatabaseHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_LOAD_DATABASE: {
+ synchronized (mDatabase) {
+ List<Metadata> list;
+ try {
+ list = mDatabase.load();
+ } catch (IllegalStateException e) {
+ Log.e(TAG, "Unable to open database: " + e);
+ mDatabase = MetadataDatabase
+ .createDatabaseWithoutMigration(mAdapterService);
+ list = mDatabase.load();
+ }
+ cacheMetadata(list);
+ }
+ break;
+ }
+ case MSG_UPDATE_DATABASE: {
+ Metadata data = (Metadata) msg.obj;
+ synchronized (mDatabase) {
+ mDatabase.insert(data);
+ }
+ break;
+ }
+ case MSG_DELETE_DATABASE: {
+ String address = (String) msg.obj;
+ synchronized (mDatabase) {
+ mDatabase.delete(address);
+ }
+ break;
+ }
+ case MSG_CLEAR_DATABASE: {
+ synchronized (mDatabase) {
+ mDatabase.deleteAll();
+ }
+ break;
+ }
+ }
+ }
+ }
+
+ private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (action == null) {
+ Log.e(TAG, "Received intent with null action");
+ return;
+ }
+ switch (action) {
+ case BluetoothDevice.ACTION_BOND_STATE_CHANGED: {
+ int state = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE,
+ BluetoothDevice.ERROR);
+ BluetoothDevice device =
+ intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+ Objects.requireNonNull(device,
+ "ACTION_BOND_STATE_CHANGED with no EXTRA_DEVICE");
+ bondStateChanged(device, state);
+ break;
+ }
+ case BluetoothAdapter.ACTION_STATE_CHANGED: {
+ int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE,
+ BluetoothAdapter.STATE_OFF);
+ if (!mMigratedFromSettingsGlobal
+ && state == BluetoothAdapter.STATE_TURNING_ON) {
+ migrateSettingsGlobal();
+ }
+ break;
+ }
+ }
+ }
+ };
+
+ void bondStateChanged(BluetoothDevice device, int state) {
+ synchronized (mMetadataCache) {
+ String address = device.getAddress();
+ if (state != BluetoothDevice.BOND_NONE) {
+ if (mMetadataCache.containsKey(address)) {
+ return;
+ }
+ createMetadata(address);
+ } else {
+ Metadata metadata = mMetadataCache.get(address);
+ if (metadata != null) {
+ mMetadataCache.remove(address);
+ deleteDatabase(metadata);
+ }
+ }
+ }
+ }
+
+ boolean isValidMetaKey(int key) {
+ switch (key) {
+ case BluetoothDevice.METADATA_MANUFACTURER_NAME:
+ case BluetoothDevice.METADATA_MODEL_NAME:
+ case BluetoothDevice.METADATA_SOFTWARE_VERSION:
+ case BluetoothDevice.METADATA_HARDWARE_VERSION:
+ case BluetoothDevice.METADATA_COMPANION_APP:
+ case BluetoothDevice.METADATA_MAIN_ICON:
+ case BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET:
+ case BluetoothDevice.METADATA_UNTETHERED_LEFT_ICON:
+ case BluetoothDevice.METADATA_UNTETHERED_RIGHT_ICON:
+ case BluetoothDevice.METADATA_UNTETHERED_CASE_ICON:
+ case BluetoothDevice.METADATA_UNTETHERED_LEFT_BATTERY:
+ case BluetoothDevice.METADATA_UNTETHERED_RIGHT_BATTERY:
+ case BluetoothDevice.METADATA_UNTETHERED_CASE_BATTERY:
+ case BluetoothDevice.METADATA_UNTETHERED_LEFT_CHARGING:
+ case BluetoothDevice.METADATA_UNTETHERED_RIGHT_CHARGING:
+ case BluetoothDevice.METADATA_UNTETHERED_CASE_CHARGING:
+ case BluetoothDevice.METADATA_ENHANCED_SETTINGS_UI_URI:
+ return true;
+ }
+ Log.w(TAG, "Invalid metadata key " + key);
+ return false;
+ }
+
+ /**
+ * Set customized metadata to database with requested key
+ */
+ @VisibleForTesting
+ public boolean setCustomMeta(BluetoothDevice device, int key, byte[] newValue) {
+ synchronized (mMetadataCache) {
+ if (device == null) {
+ Log.e(TAG, "setCustomMeta: device is null");
+ return false;
+ }
+ if (!isValidMetaKey(key)) {
+ Log.e(TAG, "setCustomMeta: meta key invalid " + key);
+ return false;
+ }
+
+ String address = device.getAddress();
+ if (VERBOSE) {
+ Log.d(TAG, "setCustomMeta: " + address + ", key=" + key);
+ }
+ if (!mMetadataCache.containsKey(address)) {
+ createMetadata(address);
+ }
+ Metadata data = mMetadataCache.get(address);
+ byte[] oldValue = data.getCustomizedMeta(key);
+ if (oldValue != null && Arrays.equals(oldValue, newValue)) {
+ if (VERBOSE) {
+ Log.d(TAG, "setCustomMeta: metadata not changed.");
+ }
+ return true;
+ }
+ logManufacturerInfo(device, key, newValue);
+ data.setCustomizedMeta(key, newValue);
+
+ updateDatabase(data);
+ mAdapterService.metadataChanged(address, key, newValue);
+ return true;
+ }
+ }
+
+ /**
+ * Get customized metadata from database with requested key
+ */
+ @VisibleForTesting
+ public byte[] getCustomMeta(BluetoothDevice device, int key) {
+ synchronized (mMetadataCache) {
+ if (device == null) {
+ Log.e(TAG, "getCustomMeta: device is null");
+ return null;
+ }
+ if (!isValidMetaKey(key)) {
+ Log.e(TAG, "getCustomMeta: meta key invalid " + key);
+ return null;
+ }
+
+ String address = device.getAddress();
+
+ if (!mMetadataCache.containsKey(address)) {
+ Log.e(TAG, "getCustomMeta: device " + address + " is not in cache");
+ return null;
+ }
+
+ Metadata data = mMetadataCache.get(address);
+ return data.getCustomizedMeta(key);
+ }
+ }
+
+ /**
+ * Set the device profile prioirty
+ *
+ * @param device {@link BluetoothDevice} wish to set
+ * @param profile The Bluetooth profile; one of {@link BluetoothProfile#HEADSET},
+ * {@link BluetoothProfile#HEADSET_CLIENT}, {@link BluetoothProfile#A2DP},
+ * {@link BluetoothProfile#A2DP_SINK}, {@link BluetoothProfile#HID_HOST},
+ * {@link BluetoothProfile#PAN}, {@link BluetoothProfile#PBAP},
+ * {@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}
+ */
+ @VisibleForTesting
+ public boolean setProfilePriority(BluetoothDevice device, int profile, int newPriority) {
+ synchronized (mMetadataCache) {
+ if (device == null) {
+ Log.e(TAG, "setProfilePriority: 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);
+ return false;
+ }
+
+ String address = device.getAddress();
+ if (VERBOSE) {
+ Log.v(TAG, "setProfilePriority: " + address + ", profile=" + profile
+ + ", priority = " + newPriority);
+ }
+ if (!mMetadataCache.containsKey(address)) {
+ if (newPriority == BluetoothProfile.PRIORITY_UNDEFINED) {
+ return true;
+ }
+ createMetadata(address);
+ }
+ Metadata data = mMetadataCache.get(address);
+ int oldPriority = data.getProfilePriority(profile);
+ if (oldPriority == newPriority) {
+ if (VERBOSE) {
+ Log.v(TAG, "setProfilePriority priority not changed.");
+ }
+ return true;
+ }
+
+ data.setProfilePriority(profile, newPriority);
+ updateDatabase(data);
+ return true;
+ }
+ }
+
+ /**
+ * Get the device profile prioirty
+ *
+ * @param device {@link BluetoothDevice} wish to get
+ * @param profile The Bluetooth profile; one of {@link BluetoothProfile#HEADSET},
+ * {@link BluetoothProfile#HEADSET_CLIENT}, {@link BluetoothProfile#A2DP},
+ * {@link BluetoothProfile#A2DP_SINK}, {@link BluetoothProfile#HID_HOST},
+ * {@link BluetoothProfile#PAN}, {@link BluetoothProfile#PBAP},
+ * {@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}
+ */
+ @VisibleForTesting
+ public int getProfilePriority(BluetoothDevice device, int profile) {
+ synchronized (mMetadataCache) {
+ if (device == null) {
+ Log.e(TAG, "getProfilePriority: device is null");
+ return BluetoothProfile.PRIORITY_UNDEFINED;
+ }
+
+ String address = device.getAddress();
+
+ if (!mMetadataCache.containsKey(address)) {
+ Log.e(TAG, "getProfilePriority: device " + address + " is not in cache");
+ return BluetoothProfile.PRIORITY_UNDEFINED;
+ }
+
+ Metadata data = mMetadataCache.get(address);
+ int priority = data.getProfilePriority(profile);
+ if (VERBOSE) {
+ Log.v(TAG, "getProfilePriority: " + address + ", profile=" + profile
+ + ", priority = " + priority);
+ }
+ return priority;
+ }
+ }
+
+ /**
+ * Set the A2DP optional coedc support value
+ *
+ * @param device {@link BluetoothDevice} wish to set
+ * @param newValue the new A2DP optional coedc support value, one of
+ * {@link BluetoothA2dp#OPTIONAL_CODECS_SUPPORT_UNKNOWN},
+ * {@link BluetoothA2dp#OPTIONAL_CODECS_NOT_SUPPORTED},
+ * {@link BluetoothA2dp#OPTIONAL_CODECS_SUPPORTED}
+ */
+ @VisibleForTesting
+ public void setA2dpSupportsOptionalCodecs(BluetoothDevice device, int newValue) {
+ synchronized (mMetadataCache) {
+ if (device == null) {
+ Log.e(TAG, "setA2dpOptionalCodec: device is null");
+ return;
+ }
+ if (newValue != BluetoothA2dp.OPTIONAL_CODECS_SUPPORT_UNKNOWN
+ && newValue != BluetoothA2dp.OPTIONAL_CODECS_NOT_SUPPORTED
+ && newValue != BluetoothA2dp.OPTIONAL_CODECS_SUPPORTED) {
+ Log.e(TAG, "setA2dpSupportsOptionalCodecs: invalid value " + newValue);
+ return;
+ }
+
+ String address = device.getAddress();
+
+ if (!mMetadataCache.containsKey(address)) {
+ return;
+ }
+ Metadata data = mMetadataCache.get(address);
+ int oldValue = data.a2dpSupportsOptionalCodecs;
+ if (oldValue == newValue) {
+ return;
+ }
+
+ data.a2dpSupportsOptionalCodecs = newValue;
+ updateDatabase(data);
+ }
+ }
+
+ /**
+ * Get the A2DP optional coedc support value
+ *
+ * @param device {@link BluetoothDevice} wish to get
+ * @return the A2DP optional coedc support value, one of
+ * {@link BluetoothA2dp#OPTIONAL_CODECS_SUPPORT_UNKNOWN},
+ * {@link BluetoothA2dp#OPTIONAL_CODECS_NOT_SUPPORTED},
+ * {@link BluetoothA2dp#OPTIONAL_CODECS_SUPPORTED},
+ */
+ @VisibleForTesting
+ public int getA2dpSupportsOptionalCodecs(BluetoothDevice device) {
+ synchronized (mMetadataCache) {
+ if (device == null) {
+ Log.e(TAG, "setA2dpOptionalCodec: device is null");
+ return BluetoothA2dp.OPTIONAL_CODECS_SUPPORT_UNKNOWN;
+ }
+
+ String address = device.getAddress();
+
+ if (!mMetadataCache.containsKey(address)) {
+ Log.e(TAG, "getA2dpOptionalCodec: device " + address + " is not in cache");
+ return BluetoothA2dp.OPTIONAL_CODECS_SUPPORT_UNKNOWN;
+ }
+
+ Metadata data = mMetadataCache.get(address);
+ return data.a2dpSupportsOptionalCodecs;
+ }
+ }
+
+ /**
+ * Set the A2DP optional coedc enabled value
+ *
+ * @param device {@link BluetoothDevice} wish to set
+ * @param newValue the new A2DP optional coedc enabled value, one of
+ * {@link BluetoothA2dp#OPTIONAL_CODECS_PREF_UNKNOWN},
+ * {@link BluetoothA2dp#OPTIONAL_CODECS_PREF_DISABLED},
+ * {@link BluetoothA2dp#OPTIONAL_CODECS_PREF_ENABLED}
+ */
+ @VisibleForTesting
+ public void setA2dpOptionalCodecsEnabled(BluetoothDevice device, int newValue) {
+ synchronized (mMetadataCache) {
+ if (device == null) {
+ Log.e(TAG, "setA2dpOptionalCodecEnabled: device is null");
+ return;
+ }
+ if (newValue != BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN
+ && newValue != BluetoothA2dp.OPTIONAL_CODECS_PREF_DISABLED
+ && newValue != BluetoothA2dp.OPTIONAL_CODECS_PREF_ENABLED) {
+ Log.e(TAG, "setA2dpOptionalCodecsEnabled: invalid value " + newValue);
+ return;
+ }
+
+ String address = device.getAddress();
+
+ if (!mMetadataCache.containsKey(address)) {
+ return;
+ }
+ Metadata data = mMetadataCache.get(address);
+ int oldValue = data.a2dpOptionalCodecsEnabled;
+ if (oldValue == newValue) {
+ return;
+ }
+
+ data.a2dpOptionalCodecsEnabled = newValue;
+ updateDatabase(data);
+ }
+ }
+
+ /**
+ * Get the A2DP optional coedc enabled value
+ *
+ * @param device {@link BluetoothDevice} wish to get
+ * @return the A2DP optional coedc enabled value, one of
+ * {@link BluetoothA2dp#OPTIONAL_CODECS_PREF_UNKNOWN},
+ * {@link BluetoothA2dp#OPTIONAL_CODECS_PREF_DISABLED},
+ * {@link BluetoothA2dp#OPTIONAL_CODECS_PREF_ENABLED}
+ */
+ @VisibleForTesting
+ public int getA2dpOptionalCodecsEnabled(BluetoothDevice device) {
+ synchronized (mMetadataCache) {
+ if (device == null) {
+ Log.e(TAG, "getA2dpOptionalCodecEnabled: device is null");
+ return BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN;
+ }
+
+ String address = device.getAddress();
+
+ if (!mMetadataCache.containsKey(address)) {
+ Log.e(TAG, "getA2dpOptionalCodecEnabled: device " + address + " is not in cache");
+ return BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN;
+ }
+
+ Metadata data = mMetadataCache.get(address);
+ return data.a2dpOptionalCodecsEnabled;
+ }
+ }
+
+ /**
+ * Get the {@link Looper} for the handler thread. This is used in testing and helper
+ * objects
+ *
+ * @return {@link Looper} for the handler thread
+ */
+ @VisibleForTesting
+ public Looper getHandlerLooper() {
+ if (mHandlerThread == null) {
+ return null;
+ }
+ return mHandlerThread.getLooper();
+ }
+
+ /**
+ * Start and initialize the DatabaseManager
+ *
+ * @param database the Bluetooth storage {@link MetadataDatabase}
+ */
+ public void start(MetadataDatabase database) {
+ if (DBG) {
+ Log.d(TAG, "start()");
+ }
+
+ if (mAdapterService == null) {
+ Log.e(TAG, "stat failed, mAdapterService is null.");
+ return;
+ }
+
+ if (database == null) {
+ Log.e(TAG, "stat failed, database is null.");
+ return;
+ }
+
+ mDatabase = database;
+
+ mHandlerThread = new HandlerThread("BluetoothDatabaseManager");
+ mHandlerThread.start();
+ mHandler = new DatabaseHandler(mHandlerThread.getLooper());
+
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
+ filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
+ mAdapterService.registerReceiver(mReceiver, filter);
+
+ loadDatabase();
+ }
+
+ String getDatabaseAbsolutePath() {
+ //TODO backup database when Bluetooth turn off and FOTA?
+ return mAdapterService.getDatabasePath(MetadataDatabase.DATABASE_NAME)
+ .getAbsolutePath();
+ }
+
+ /**
+ * Clear all persistence data in database
+ */
+ public void factoryReset() {
+ Log.w(TAG, "factoryReset");
+ Message message = mHandler.obtainMessage(MSG_CLEAR_DATABASE);
+ mHandler.sendMessage(message);
+ }
+
+ /**
+ * Close and de-init the DatabaseManager
+ */
+ public void cleanup() {
+ removeUnusedMetadata();
+ mAdapterService.unregisterReceiver(mReceiver);
+ if (mHandlerThread != null) {
+ mHandlerThread.quit();
+ mHandlerThread = null;
+ }
+ mMetadataCache.clear();
+ }
+
+ void createMetadata(String address) {
+ if (VERBOSE) {
+ Log.v(TAG, "createMetadata " + address);
+ }
+ Metadata data = new Metadata(address);
+ mMetadataCache.put(address, data);
+ updateDatabase(data);
+ }
+
+ @VisibleForTesting
+ void removeUnusedMetadata() {
+ BluetoothDevice[] bondedDevices = mAdapterService.getBondedDevices();
+ synchronized (mMetadataCache) {
+ mMetadataCache.forEach((address, metadata) -> {
+ if (!address.equals(LOCAL_STORAGE)
+ && !Arrays.asList(bondedDevices).stream().anyMatch(device ->
+ address.equals(device.getAddress()))) {
+ List<Integer> list = metadata.getChangedCustomizedMeta();
+ for (int key : list) {
+ mAdapterService.metadataChanged(address, key, null);
+ }
+ Log.i(TAG, "remove unpaired device from database " + address);
+ deleteDatabase(mMetadataCache.get(address));
+ }
+ });
+ }
+ }
+
+ void cacheMetadata(List<Metadata> list) {
+ synchronized (mMetadataCache) {
+ Log.i(TAG, "cacheMetadata");
+ // Unlock the main thread.
+ mSemaphore.release();
+
+ if (!isMigrated(list)) {
+ // Wait for data migrate from Settings Global
+ mMigratedFromSettingsGlobal = false;
+ return;
+ }
+ mMigratedFromSettingsGlobal = true;
+ for (Metadata data : list) {
+ String address = data.getAddress();
+ if (VERBOSE) {
+ Log.v(TAG, "cacheMetadata: found device " + address);
+ }
+ mMetadataCache.put(address, data);
+ }
+ if (VERBOSE) {
+ Log.v(TAG, "cacheMetadata: Database is ready");
+ }
+ }
+ }
+
+ boolean isMigrated(List<Metadata> list) {
+ for (Metadata data : list) {
+ String address = data.getAddress();
+ if (address.equals(LOCAL_STORAGE) && data.migrated) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ void migrateSettingsGlobal() {
+ Log.i(TAG, "migrateSettingGlobal");
+
+ BluetoothDevice[] bondedDevices = mAdapterService.getBondedDevices();
+ 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 a2dpSupportsOptionalCodec = Settings.Global.getInt(contentResolver,
+ Settings.Global.getBluetoothA2dpSupportsOptionalCodecsKey(device.getAddress()),
+ BluetoothA2dp.OPTIONAL_CODECS_SUPPORT_UNKNOWN);
+ int a2dpOptionalCodecEnabled = Settings.Global.getInt(contentResolver,
+ Settings.Global.getBluetoothA2dpOptionalCodecsEnabledKey(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.a2dpSupportsOptionalCodecs = a2dpSupportsOptionalCodec;
+ data.a2dpOptionalCodecsEnabled = a2dpOptionalCodecEnabled;
+ mMetadataCache.put(address, data);
+ updateDatabase(data);
+ }
+
+ // Mark database migrated from Settings Global
+ Metadata localData = new Metadata(LOCAL_STORAGE);
+ localData.migrated = true;
+ mMetadataCache.put(LOCAL_STORAGE, localData);
+ updateDatabase(localData);
+
+ // Reload database after migration is completed
+ loadDatabase();
+
+ }
+
+ private void loadDatabase() {
+ if (DBG) {
+ Log.d(TAG, "Load Database");
+ }
+ Message message = mHandler.obtainMessage(MSG_LOAD_DATABASE);
+ mHandler.sendMessage(message);
+ try {
+ // Lock the thread until handler thread finish loading database.
+ mSemaphore.tryAcquire(LOAD_DATABASE_TIMEOUT, TimeUnit.MILLISECONDS);
+ } catch (InterruptedException e) {
+ Log.e(TAG, "loadDatabase: semaphore acquire failed");
+ }
+ }
+
+ private void updateDatabase(Metadata data) {
+ if (data.getAddress() == null) {
+ Log.e(TAG, "updateDatabase: address is null");
+ return;
+ }
+ if (DBG) {
+ Log.d(TAG, "updateDatabase " + data.getAddress());
+ }
+ Message message = mHandler.obtainMessage(MSG_UPDATE_DATABASE);
+ message.obj = data;
+ mHandler.sendMessage(message);
+ }
+
+ private void deleteDatabase(Metadata data) {
+ if (data.getAddress() == null) {
+ Log.e(TAG, "deleteDatabase: address is null");
+ return;
+ }
+ Log.d(TAG, "deleteDatabase: " + data.getAddress());
+ Message message = mHandler.obtainMessage(MSG_DELETE_DATABASE);
+ message.obj = data.getAddress();
+ mHandler.sendMessage(message);
+ }
+
+ private void logManufacturerInfo(BluetoothDevice device, int key, byte[] bytesValue) {
+ String callingApp = mAdapterService.getPackageManager().getNameForUid(
+ Binder.getCallingUid());
+ String manufacturerName = "";
+ String modelName = "";
+ String hardwareVersion = "";
+ String softwareVersion = "";
+ String value = Utils.byteArrayToUtf8String(bytesValue);
+ switch (key) {
+ case BluetoothDevice.METADATA_MANUFACTURER_NAME:
+ manufacturerName = value;
+ break;
+ case BluetoothDevice.METADATA_MODEL_NAME:
+ modelName = value;
+ break;
+ case BluetoothDevice.METADATA_HARDWARE_VERSION:
+ hardwareVersion = value;
+ break;
+ case BluetoothDevice.METADATA_SOFTWARE_VERSION:
+ softwareVersion = value;
+ break;
+ default:
+ // Do not log anything if metadata doesn't fall into above categories
+ return;
+ }
+ StatsLog.write(StatsLog.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
new file mode 100644
index 0000000..b39333f
--- /dev/null
+++ b/src/com/android/bluetooth/btservice/storage/Metadata.java
@@ -0,0 +1,304 @@
+/*
+ * Copyright 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.btservice.storage;
+
+import android.bluetooth.BluetoothA2dp;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
+
+import androidx.annotation.NonNull;
+import androidx.room.Embedded;
+import androidx.room.Entity;
+import androidx.room.PrimaryKey;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Entity(tableName = "metadata")
+class Metadata {
+ @PrimaryKey
+ @NonNull
+ private String address;
+
+ public boolean migrated;
+
+ @Embedded
+ public ProfilePrioritiesEntity profilePriorities;
+
+ @Embedded
+ @NonNull
+ public CustomizedMetadataEntity publicMetadata;
+
+ public int a2dpSupportsOptionalCodecs;
+ public int a2dpOptionalCodecsEnabled;
+
+ Metadata(String address) {
+ this.address = address;
+ migrated = false;
+ profilePriorities = new ProfilePrioritiesEntity();
+ publicMetadata = new CustomizedMetadataEntity();
+ a2dpSupportsOptionalCodecs = BluetoothA2dp.OPTIONAL_CODECS_SUPPORT_UNKNOWN;
+ a2dpOptionalCodecsEnabled = BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN;
+ }
+
+ String getAddress() {
+ return address;
+ }
+
+ void setProfilePriority(int profile, int priority) {
+ switch (profile) {
+ case BluetoothProfile.A2DP:
+ profilePriorities.a2dp_priority = priority;
+ break;
+ case BluetoothProfile.A2DP_SINK:
+ profilePriorities.a2dp_sink_priority = priority;
+ break;
+ case BluetoothProfile.HEADSET:
+ profilePriorities.hfp_priority = priority;
+ break;
+ case BluetoothProfile.HEADSET_CLIENT:
+ profilePriorities.hfp_client_priority = priority;
+ break;
+ case BluetoothProfile.HID_HOST:
+ profilePriorities.hid_host_priority = priority;
+ break;
+ case BluetoothProfile.PAN:
+ profilePriorities.pan_priority = priority;
+ break;
+ case BluetoothProfile.PBAP:
+ profilePriorities.pbap_priority = priority;
+ break;
+ case BluetoothProfile.PBAP_CLIENT:
+ profilePriorities.pbap_client_priority = priority;
+ break;
+ case BluetoothProfile.MAP:
+ profilePriorities.map_priority = priority;
+ break;
+ case BluetoothProfile.MAP_CLIENT:
+ profilePriorities.map_client_priority = priority;
+ break;
+ case BluetoothProfile.SAP:
+ profilePriorities.sap_priority = priority;
+ break;
+ case BluetoothProfile.HEARING_AID:
+ profilePriorities.hearing_aid_priority = priority;
+ break;
+ default:
+ throw new IllegalArgumentException("invalid profile " + profile);
+ }
+ }
+
+ int getProfilePriority(int profile) {
+ switch (profile) {
+ case BluetoothProfile.A2DP:
+ return profilePriorities.a2dp_priority;
+ case BluetoothProfile.A2DP_SINK:
+ return profilePriorities.a2dp_sink_priority;
+ case BluetoothProfile.HEADSET:
+ return profilePriorities.hfp_priority;
+ case BluetoothProfile.HEADSET_CLIENT:
+ return profilePriorities.hfp_client_priority;
+ case BluetoothProfile.HID_HOST:
+ return profilePriorities.hid_host_priority;
+ case BluetoothProfile.PAN:
+ return profilePriorities.pan_priority;
+ case BluetoothProfile.PBAP:
+ return profilePriorities.pbap_priority;
+ case BluetoothProfile.PBAP_CLIENT:
+ return profilePriorities.pbap_client_priority;
+ case BluetoothProfile.MAP:
+ return profilePriorities.map_priority;
+ case BluetoothProfile.MAP_CLIENT:
+ return profilePriorities.map_client_priority;
+ case BluetoothProfile.SAP:
+ return profilePriorities.sap_priority;
+ case BluetoothProfile.HEARING_AID:
+ return profilePriorities.hearing_aid_priority;
+ }
+ return BluetoothProfile.PRIORITY_UNDEFINED;
+ }
+
+ void setCustomizedMeta(int key, byte[] value) {
+ switch (key) {
+ case BluetoothDevice.METADATA_MANUFACTURER_NAME:
+ publicMetadata.manufacturer_name = value;
+ break;
+ case BluetoothDevice.METADATA_MODEL_NAME:
+ publicMetadata.model_name = value;
+ break;
+ case BluetoothDevice.METADATA_SOFTWARE_VERSION:
+ publicMetadata.software_version = value;
+ break;
+ case BluetoothDevice.METADATA_HARDWARE_VERSION:
+ publicMetadata.hardware_version = value;
+ break;
+ case BluetoothDevice.METADATA_COMPANION_APP:
+ publicMetadata.companion_app = value;
+ break;
+ case BluetoothDevice.METADATA_MAIN_ICON:
+ publicMetadata.main_icon = value;
+ break;
+ case BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET:
+ publicMetadata.is_untethered_headset = value;
+ break;
+ case BluetoothDevice.METADATA_UNTETHERED_LEFT_ICON:
+ publicMetadata.untethered_left_icon = value;
+ break;
+ case BluetoothDevice.METADATA_UNTETHERED_RIGHT_ICON:
+ publicMetadata.untethered_right_icon = value;
+ break;
+ case BluetoothDevice.METADATA_UNTETHERED_CASE_ICON:
+ publicMetadata.untethered_case_icon = value;
+ break;
+ case BluetoothDevice.METADATA_UNTETHERED_LEFT_BATTERY:
+ publicMetadata.untethered_left_battery = value;
+ break;
+ case BluetoothDevice.METADATA_UNTETHERED_RIGHT_BATTERY:
+ publicMetadata.untethered_right_battery = value;
+ break;
+ case BluetoothDevice.METADATA_UNTETHERED_CASE_BATTERY:
+ publicMetadata.untethered_case_battery = value;
+ break;
+ case BluetoothDevice.METADATA_UNTETHERED_LEFT_CHARGING:
+ publicMetadata.untethered_left_charging = value;
+ break;
+ case BluetoothDevice.METADATA_UNTETHERED_RIGHT_CHARGING:
+ publicMetadata.untethered_right_charging = value;
+ break;
+ case BluetoothDevice.METADATA_UNTETHERED_CASE_CHARGING:
+ publicMetadata.untethered_case_charging = value;
+ break;
+ case BluetoothDevice.METADATA_ENHANCED_SETTINGS_UI_URI:
+ publicMetadata.enhanced_settings_ui_uri = value;
+ break;
+ }
+ }
+
+ byte[] getCustomizedMeta(int key) {
+ byte[] value = null;
+ switch (key) {
+ case BluetoothDevice.METADATA_MANUFACTURER_NAME:
+ value = publicMetadata.manufacturer_name;
+ break;
+ case BluetoothDevice.METADATA_MODEL_NAME:
+ value = publicMetadata.model_name;
+ break;
+ case BluetoothDevice.METADATA_SOFTWARE_VERSION:
+ value = publicMetadata.software_version;
+ break;
+ case BluetoothDevice.METADATA_HARDWARE_VERSION:
+ value = publicMetadata.hardware_version;
+ break;
+ case BluetoothDevice.METADATA_COMPANION_APP:
+ value = publicMetadata.companion_app;
+ break;
+ case BluetoothDevice.METADATA_MAIN_ICON:
+ value = publicMetadata.main_icon;
+ break;
+ case BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET:
+ value = publicMetadata.is_untethered_headset;
+ break;
+ case BluetoothDevice.METADATA_UNTETHERED_LEFT_ICON:
+ value = publicMetadata.untethered_left_icon;
+ break;
+ case BluetoothDevice.METADATA_UNTETHERED_RIGHT_ICON:
+ value = publicMetadata.untethered_right_icon;
+ break;
+ case BluetoothDevice.METADATA_UNTETHERED_CASE_ICON:
+ value = publicMetadata.untethered_case_icon;
+ break;
+ case BluetoothDevice.METADATA_UNTETHERED_LEFT_BATTERY:
+ value = publicMetadata.untethered_left_battery;
+ break;
+ case BluetoothDevice.METADATA_UNTETHERED_RIGHT_BATTERY:
+ value = publicMetadata.untethered_right_battery;
+ break;
+ case BluetoothDevice.METADATA_UNTETHERED_CASE_BATTERY:
+ value = publicMetadata.untethered_case_battery;
+ break;
+ case BluetoothDevice.METADATA_UNTETHERED_LEFT_CHARGING:
+ value = publicMetadata.untethered_left_charging;
+ break;
+ case BluetoothDevice.METADATA_UNTETHERED_RIGHT_CHARGING:
+ value = publicMetadata.untethered_right_charging;
+ break;
+ case BluetoothDevice.METADATA_UNTETHERED_CASE_CHARGING:
+ value = publicMetadata.untethered_case_charging;
+ break;
+ case BluetoothDevice.METADATA_ENHANCED_SETTINGS_UI_URI:
+ value = publicMetadata.enhanced_settings_ui_uri;
+ break;
+ }
+ return value;
+ }
+
+ List<Integer> getChangedCustomizedMeta() {
+ List<Integer> list = new ArrayList<>();
+ if (publicMetadata.manufacturer_name != null) {
+ list.add(BluetoothDevice.METADATA_MANUFACTURER_NAME);
+ }
+ if (publicMetadata.model_name != null) {
+ list.add(BluetoothDevice.METADATA_MODEL_NAME);
+ }
+ if (publicMetadata.software_version != null) {
+ list.add(BluetoothDevice.METADATA_SOFTWARE_VERSION);
+ }
+ if (publicMetadata.hardware_version != null) {
+ list.add(BluetoothDevice.METADATA_HARDWARE_VERSION);
+ }
+ if (publicMetadata.companion_app != null) {
+ list.add(BluetoothDevice.METADATA_COMPANION_APP);
+ }
+ if (publicMetadata.main_icon != null) {
+ list.add(BluetoothDevice.METADATA_MAIN_ICON);
+ }
+ if (publicMetadata.is_untethered_headset != null) {
+ list.add(BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET);
+ }
+ if (publicMetadata.untethered_left_icon != null) {
+ list.add(BluetoothDevice.METADATA_UNTETHERED_LEFT_ICON);
+ }
+ if (publicMetadata.untethered_right_icon != null) {
+ list.add(BluetoothDevice.METADATA_UNTETHERED_RIGHT_ICON);
+ }
+ if (publicMetadata.untethered_case_icon != null) {
+ list.add(BluetoothDevice.METADATA_UNTETHERED_CASE_ICON);
+ }
+ if (publicMetadata.untethered_left_battery != null) {
+ list.add(BluetoothDevice.METADATA_UNTETHERED_LEFT_BATTERY);
+ }
+ if (publicMetadata.untethered_right_battery != null) {
+ list.add(BluetoothDevice.METADATA_UNTETHERED_RIGHT_BATTERY);
+ }
+ if (publicMetadata.untethered_case_battery != null) {
+ list.add(BluetoothDevice.METADATA_UNTETHERED_CASE_BATTERY);
+ }
+ if (publicMetadata.untethered_left_charging != null) {
+ list.add(BluetoothDevice.METADATA_UNTETHERED_LEFT_CHARGING);
+ }
+ if (publicMetadata.untethered_right_charging != null) {
+ list.add(BluetoothDevice.METADATA_UNTETHERED_RIGHT_CHARGING);
+ }
+ if (publicMetadata.untethered_case_charging != null) {
+ list.add(BluetoothDevice.METADATA_UNTETHERED_CASE_CHARGING);
+ }
+ if (publicMetadata.enhanced_settings_ui_uri != null) {
+ list.add(BluetoothDevice.METADATA_ENHANCED_SETTINGS_UI_URI);
+ }
+ return list;
+ }
+}
diff --git a/src/com/android/bluetooth/btservice/storage/MetadataDao.java b/src/com/android/bluetooth/btservice/storage/MetadataDao.java
new file mode 100644
index 0000000..7c5f440
--- /dev/null
+++ b/src/com/android/bluetooth/btservice/storage/MetadataDao.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 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.btservice.storage;
+
+import androidx.room.Dao;
+import androidx.room.Insert;
+import androidx.room.OnConflictStrategy;
+import androidx.room.Query;
+
+import java.util.List;
+
+@Dao
+interface MetadataDao {
+ /**
+ * Load all items in the database
+ */
+ @Query("SELECT * FROM metadata")
+ List<Metadata> load();
+
+ /**
+ * Create or update a Metadata in the database
+ */
+ @Insert(onConflict = OnConflictStrategy.REPLACE)
+ void insert(Metadata... metadata);
+
+ /**
+ * Delete a Metadata in the database
+ */
+ @Query("DELETE FROM metadata WHERE address = :address")
+ void delete(String address);
+
+ /**
+ * Delete all Metadatas in the database
+ */
+ @Query("DELETE FROM metadata")
+ void deleteAll();
+}
diff --git a/src/com/android/bluetooth/btservice/storage/MetadataDatabase.java b/src/com/android/bluetooth/btservice/storage/MetadataDatabase.java
new file mode 100644
index 0000000..48d1ec3
--- /dev/null
+++ b/src/com/android/bluetooth/btservice/storage/MetadataDatabase.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright 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.btservice.storage;
+
+import android.content.Context;
+
+import androidx.room.Database;
+import androidx.room.Room;
+import androidx.room.RoomDatabase;
+import androidx.room.migration.Migration;
+import androidx.sqlite.db.SupportSQLiteDatabase;
+
+import java.util.List;
+
+/**
+ * MetadataDatabase is a Room database stores Bluetooth persistence data
+ */
+@Database(entities = {Metadata.class}, exportSchema = false, version = 102)
+public abstract class MetadataDatabase extends RoomDatabase {
+ /**
+ * The database file name
+ */
+ public static final String DATABASE_NAME = "bluetooth_db";
+
+ protected abstract MetadataDao mMetadataDao();
+
+ /**
+ * Create a {@link MetadataDatabase} database with migrations
+ *
+ * @param context the Context to create database
+ * @return the created {@link MetadataDatabase}
+ */
+ public static MetadataDatabase createDatabase(Context context) {
+ return Room.databaseBuilder(context,
+ MetadataDatabase.class, DATABASE_NAME)
+ .addMigrations(MIGRATION_100_101)
+ .addMigrations(MIGRATION_101_102)
+ .build();
+ }
+
+ /**
+ * Create a {@link MetadataDatabase} database without migration, database
+ * would be reset if any load failure happens
+ *
+ * @param context the Context to create database
+ * @return the created {@link MetadataDatabase}
+ */
+ public static MetadataDatabase createDatabaseWithoutMigration(Context context) {
+ return Room.databaseBuilder(context,
+ MetadataDatabase.class, DATABASE_NAME)
+ .fallbackToDestructiveMigration()
+ .build();
+ }
+
+ /**
+ * Insert a {@link Metadata} to database
+ *
+ * @param metadata the data wish to put into storage
+ */
+ public void insert(Metadata... metadata) {
+ mMetadataDao().insert(metadata);
+ }
+
+ /**
+ * Load all data from database as a {@link List} of {@link Metadata}
+ *
+ * @return a {@link List} of {@link Metadata}
+ */
+ public List<Metadata> load() {
+ return mMetadataDao().load();
+ }
+
+ /**
+ * Delete one of the {@link Metadata} contains in database
+ *
+ * @param address the address of Metadata to delete
+ */
+ public void delete(String address) {
+ mMetadataDao().delete(address);
+ }
+
+ /**
+ * Clear database.
+ */
+ public void deleteAll() {
+ mMetadataDao().deleteAll();
+ }
+
+ private static final Migration MIGRATION_100_101 = new Migration(100, 101) {
+ @Override
+ public void migrate(SupportSQLiteDatabase database) {
+ database.execSQL("ALTER TABLE metadata ADD COLUMN `pbap_client_priority` INTEGER");
+ }
+ };
+
+ private static final Migration MIGRATION_101_102 = new Migration(101, 102) {
+ @Override
+ public void migrate(SupportSQLiteDatabase database) {
+ 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_priority` INTEGER, `a2dp_sink_priority` INTEGER, "
+ + "`hfp_priority` INTEGER, `hfp_client_priority` INTEGER, "
+ + "`hid_host_priority` INTEGER, `pan_priority` INTEGER, "
+ + "`pbap_priority` INTEGER, `pbap_client_priority` INTEGER, "
+ + "`map_priority` INTEGER, `sap_priority` INTEGER, "
+ + "`hearing_aid_priority` INTEGER, `map_client_priority` 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_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, "
+ + "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_unthethered_headset AS BLOB), "
+ + "CAST (unthethered_left_icon AS BLOB), "
+ + "CAST (unthethered_right_icon AS BLOB), "
+ + "CAST (unthethered_case_icon AS BLOB), "
+ + "CAST (unthethered_left_battery AS BLOB), "
+ + "CAST (unthethered_right_battery AS BLOB), "
+ + "CAST (unthethered_case_battery AS BLOB), "
+ + "CAST (unthethered_left_charging AS BLOB), "
+ + "CAST (unthethered_right_charging AS BLOB), "
+ + "CAST (unthethered_case_charging AS BLOB), "
+ + "CAST (enhanced_settings_ui_uri AS BLOB)"
+ + "FROM metadata");
+
+ database.execSQL("DROP TABLE `metadata`");
+ database.execSQL("ALTER TABLE `metadata_tmp` RENAME TO `metadata`");
+ }
+ };
+}
diff --git a/src/com/android/bluetooth/btservice/storage/ProfilePrioritiesEntity.java b/src/com/android/bluetooth/btservice/storage/ProfilePrioritiesEntity.java
new file mode 100644
index 0000000..f4ea719
--- /dev/null
+++ b/src/com/android/bluetooth/btservice/storage/ProfilePrioritiesEntity.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 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.btservice.storage;
+
+import android.bluetooth.BluetoothProfile;
+
+import androidx.room.Entity;
+
+@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;
+
+ 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;
+ }
+}
diff --git a/src/com/android/bluetooth/gatt/AppScanStats.java b/src/com/android/bluetooth/gatt/AppScanStats.java
index dd499f2..ec8b1f8 100644
--- a/src/com/android/bluetooth/gatt/AppScanStats.java
+++ b/src/com/android/bluetooth/gatt/AppScanStats.java
@@ -122,16 +122,17 @@
synchronized void addResult(int scannerId) {
LastScan scan = getScanFromScannerId(scannerId);
if (scan != null) {
- int batteryStatsResults = ++scan.results;
+ scan.results++;
// Only update battery stats after receiving 100 new results in order
// to lower the cost of the binder transaction
- if (batteryStatsResults % 100 == 0) {
+ if (scan.results % 100 == 0) {
try {
mBatteryStats.noteBleScanResults(mWorkSource, 100);
} catch (RemoteException e) {
/* ignore */
}
+ StatsLog.write(StatsLog.BLE_SCAN_RESULT_RECEIVED, mWorkSource, 100);
}
}
@@ -178,7 +179,9 @@
} catch (RemoteException e) {
/* ignore */
}
- writeToStatsLog(scan, StatsLog.BLE_SCAN_STATE_CHANGED__STATE__ON);
+ StatsLog.write(StatsLog.BLE_SCAN_STATE_CHANGED, mWorkSource,
+ StatsLog.BLE_SCAN_STATE_CHANGED__STATE__ON,
+ scan.filtered, scan.background, scan.opportunistic);
mOngoingScans.put(scannerId, scan);
}
@@ -221,15 +224,17 @@
}
try {
- // Inform battery stats of any results it might be missing on
- // scan stop
+ // Inform battery stats of any results it might be missing on scan stop
boolean isUnoptimized = !(scan.filtered || scan.background || scan.opportunistic);
mBatteryStats.noteBleScanResults(mWorkSource, scan.results % 100);
mBatteryStats.noteBleScanStopped(mWorkSource, isUnoptimized);
} catch (RemoteException e) {
/* ignore */
}
- writeToStatsLog(scan, StatsLog.BLE_SCAN_STATE_CHANGED__STATE__OFF);
+ 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,
+ scan.filtered, scan.background, scan.opportunistic);
}
synchronized void recordScanSuspend(int scannerId) {
@@ -254,24 +259,6 @@
mTotalSuspendTime += suspendDuration;
}
- private void writeToStatsLog(LastScan scan, int statsLogState) {
- for (int i = 0; i < mWorkSource.size(); i++) {
- StatsLog.write_non_chained(StatsLog.BLE_SCAN_STATE_CHANGED,
- mWorkSource.get(i), null,
- statsLogState, scan.filtered, scan.background, scan.opportunistic);
- }
-
- final List<WorkSource.WorkChain> workChains = mWorkSource.getWorkChains();
- if (workChains != null) {
- for (int i = 0; i < workChains.size(); ++i) {
- WorkSource.WorkChain workChain = workChains.get(i);
- StatsLog.write(StatsLog.BLE_SCAN_STATE_CHANGED,
- workChain.getUids(), workChain.getTags(),
- statsLogState, scan.filtered, scan.background, scan.opportunistic);
- }
- }
- }
-
synchronized void setScanTimeout(int scannerId) {
if (!isScanning()) {
return;
diff --git a/src/com/android/bluetooth/gatt/ContextMap.java b/src/com/android/bluetooth/gatt/ContextMap.java
index 262e76e..9267f78 100644
--- a/src/com/android/bluetooth/gatt/ContextMap.java
+++ b/src/com/android/bluetooth/gatt/ContextMap.java
@@ -20,6 +20,7 @@
import android.os.IInterface;
import android.os.RemoteException;
import android.os.SystemClock;
+import android.os.UserHandle;
import android.os.WorkSource;
import android.util.Log;
@@ -88,10 +89,22 @@
public Boolean isCongested = false;
/** Whether the calling app has location permission */
- boolean hasLocationPermisson;
+ boolean hasLocationPermission;
- /** Whether the calling app has peers mac address permission */
- boolean hasPeersMacAddressPermission;
+ /** Whether the calling app has bluetooth privileged permission */
+ boolean hasBluetoothPrivilegedPermission;
+
+ /** The user handle of the app that started the scan */
+ UserHandle mUserHandle;
+
+ /** Whether the calling app is targeting Q or better */
+ boolean mIsQApp;
+
+ /** Whether the calling app has the network settings permission */
+ boolean mHasNetworkSettingsPermission;
+
+ /** Whether the calling app has the network setup wizard permission */
+ boolean mHasNetworkSetupWizardPermission;
/** Internal callback info queue, waiting to be send on congestion clear */
private List<CallbackInfo> mCongestionQueue = new ArrayList<CallbackInfo>();
diff --git a/src/com/android/bluetooth/gatt/GattService.java b/src/com/android/bluetooth/gatt/GattService.java
index 7040307..f91e6c4 100644
--- a/src/com/android/bluetooth/gatt/GattService.java
+++ b/src/com/android/bluetooth/gatt/GattService.java
@@ -50,8 +50,8 @@
import android.os.ParcelUuid;
import android.os.RemoteException;
import android.os.SystemClock;
+import android.os.UserHandle;
import android.os.WorkSource;
-import android.provider.Settings;
import android.util.Log;
import com.android.bluetooth.BluetoothMetricsProto;
@@ -63,6 +63,7 @@
import com.android.bluetooth.util.NumberUtils;
import com.android.internal.annotations.VisibleForTesting;
+import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -97,6 +98,9 @@
private static final int ADVT_STATE_ONLOST = 1;
private static final int ET_LEGACY_MASK = 0x10;
+ private static final int ET_CONNECTABLE_MASK = 0x01;
+ private static final UUID HID_SERVICE_UUID =
+ UUID.fromString("00001812-0000-1000-8000-00805F9B34FB");
private static final UUID[] HID_UUIDS = {
UUID.fromString("00002A4A-0000-1000-8000-00805F9B34FB"),
@@ -105,9 +109,11 @@
UUID.fromString("00002A4D-0000-1000-8000-00805F9B34FB")
};
- private static final UUID[] FIDO_UUIDS = {
- UUID.fromString("0000FFFD-0000-1000-8000-00805F9B34FB") // U2F
- };
+ private static final UUID ANDROID_TV_REMOTE_SERVICE_UUID =
+ UUID.fromString("AB5E0001-5A21-4F05-BC7D-AF01F617B664");
+
+ private static final UUID FIDO_SERVICE_UUID =
+ UUID.fromString("0000FFFD-0000-1000-8000-00805F9B34FB"); // U2F
/**
* Keep the arguments passed in for the PendingIntent.
@@ -157,13 +163,17 @@
private int mMaxScanFilters;
private static final int NUM_SCAN_EVENTS_KEPT = 20;
+
/**
* Internal list of scan events to use with the proto
*/
- private final ArrayList<BluetoothMetricsProto.ScanEvent> mScanEvents =
- new ArrayList<>(NUM_SCAN_EVENTS_KEPT);
+ private final ArrayDeque<BluetoothMetricsProto.ScanEvent> mScanEvents =
+ new ArrayDeque<>(NUM_SCAN_EVENTS_KEPT);
- private final Map<Integer, List<BluetoothGattService>> mGattClientDatabases = new HashMap<>();
+ /**
+ * Set of restricted (which require a BLUETOOTH_PRIVILEGED permission) handles per connectionId.
+ */
+ private final Map<Integer, Set<Integer>> mRestrictedHandles = new HashMap<>();
private BluetoothAdapter mAdapter;
private AdvertiseManager mAdvertiseManager;
@@ -286,36 +296,34 @@
sGattService = instance;
}
- boolean permissionCheck(UUID uuid) {
- return !(isRestrictedCharUuid(uuid) && (0 != checkCallingOrSelfPermission(
- BLUETOOTH_PRIVILEGED)));
+ private boolean permissionCheck(UUID characteristicUuid) {
+ return !isHidCharUuid(characteristicUuid)
+ || (checkCallingOrSelfPermission(BLUETOOTH_PRIVILEGED)
+ == PERMISSION_GRANTED);
}
- boolean permissionCheck(int connId, int handle) {
- List<BluetoothGattService> db = mGattClientDatabases.get(connId);
- if (db == null) {
+ private boolean permissionCheck(int connId, int handle) {
+ Set<Integer> restrictedHandles = mRestrictedHandles.get(connId);
+ if (restrictedHandles == null || !restrictedHandles.contains(handle)) {
return true;
}
- for (BluetoothGattService service : db) {
- for (BluetoothGattCharacteristic characteristic : service.getCharacteristics()) {
- if (handle == characteristic.getInstanceId()) {
- return !((isRestrictedCharUuid(characteristic.getUuid())
- || isRestrictedSrvcUuid(service.getUuid()))
- && (0 != checkCallingOrSelfPermission(BLUETOOTH_PRIVILEGED)));
- }
+ return (checkCallingOrSelfPermission(BLUETOOTH_PRIVILEGED)
+ == PERMISSION_GRANTED);
+ }
- for (BluetoothGattDescriptor descriptor : characteristic.getDescriptors()) {
- if (handle == descriptor.getInstanceId()) {
- return !((isRestrictedCharUuid(characteristic.getUuid())
- || isRestrictedSrvcUuid(service.getUuid())) && (0
- != checkCallingOrSelfPermission(BLUETOOTH_PRIVILEGED)));
- }
- }
- }
+ private boolean permissionCheck(ClientMap.App app, int connId, int handle) {
+ Set<Integer> restrictedHandles = mRestrictedHandles.get(connId);
+ if (restrictedHandles == null || !restrictedHandles.contains(handle)) {
+ return true;
}
- return true;
+ if (!app.hasBluetoothPrivilegedPermission
+ && checkCallingOrSelfPermission(BLUETOOTH_PRIVILEGED)== PERMISSION_GRANTED) {
+ app.hasBluetoothPrivilegedPermission = true;
+ }
+
+ return app.hasBluetoothPrivilegedPermission;
}
@Override
@@ -1012,8 +1020,7 @@
txPower, rssi, periodicAdvInt,
ScanRecord.parseFromBytes(scanRecordData),
SystemClock.elapsedRealtimeNanos());
- // Do no report if location mode is OFF or the client has no location permission
- // PEERS_MAC_ADDRESS permission holders always get results
+ // Do not report if location mode is OFF or the client has no location permission
if (!hasScanResultPermission(client) || !matchesFilters(client, result)) {
continue;
}
@@ -1100,15 +1107,10 @@
/** Determines if the given scan client has the appropriate permissions to receive callbacks. */
private boolean hasScanResultPermission(final ScanClient client) {
- final boolean requiresLocationEnabled =
- getResources().getBoolean(R.bool.strict_location_check);
- final boolean locationEnabledSetting =
- Settings.Secure.getInt(getContentResolver(), Settings.Secure.LOCATION_MODE,
- Settings.Secure.LOCATION_MODE_OFF) != Settings.Secure.LOCATION_MODE_OFF;
- final boolean locationEnabled =
- !requiresLocationEnabled || locationEnabledSetting || client.legacyForegroundApp;
- return (client.hasPeersMacAddressPermission || (client.hasLocationPermission
- && locationEnabled));
+ if (client.hasNetworkSettingsPermission || client.hasNetworkSetupWizardPermission) {
+ return true;
+ }
+ return client.hasLocationPermission && !Utils.blockedByLocationOff(this, client.userHandle);
}
// Check if a scan record matches a specific filters.
@@ -1324,9 +1326,13 @@
}
List<BluetoothGattService> dbOut = new ArrayList<BluetoothGattService>();
+ Set<Integer> restrictedIds = new HashSet<>();
BluetoothGattService currSrvc = null;
BluetoothGattCharacteristic currChar = null;
+ boolean isRestrictedSrvc = false;
+ boolean isHidSrvc = false;
+ boolean isRestrictedChar = false;
for (GattDbElement el : db) {
switch (el.type) {
@@ -1338,6 +1344,12 @@
currSrvc = new BluetoothGattService(el.uuid, el.id, el.type);
dbOut.add(currSrvc);
+ isRestrictedSrvc =
+ isFidoSrvcUuid(el.uuid) || isAndroidTvRemoteSrvcUuid(el.uuid);
+ isHidSrvc = isHidSrvcUuid(el.uuid);
+ if (isRestrictedSrvc) {
+ restrictedIds.add(el.id);
+ }
break;
case GattDbElement.TYPE_CHARACTERISTIC:
@@ -1347,6 +1359,10 @@
currChar = new BluetoothGattCharacteristic(el.uuid, el.id, el.properties, 0);
currSrvc.addCharacteristic(currChar);
+ isRestrictedChar = isRestrictedSrvc || (isHidSrvc && isHidCharUuid(el.uuid));
+ if (isRestrictedChar) {
+ restrictedIds.add(el.id);
+ }
break;
case GattDbElement.TYPE_DESCRIPTOR:
@@ -1355,6 +1371,9 @@
}
currChar.addDescriptor(new BluetoothGattDescriptor(el.uuid, el.id, 0));
+ if (isRestrictedChar) {
+ restrictedIds.add(el.id);
+ }
break;
case GattDbElement.TYPE_INCLUDED_SERVICE:
@@ -1373,8 +1392,10 @@
}
}
+ if (!restrictedIds.isEmpty()) {
+ mRestrictedHandles.put(connId, restrictedIds);
+ }
// Search is complete when there was error, or nothing more to process
- mGattClientDatabases.put(connId, dbOut);
app.callback.onSearchComplete(address, dbOut, 0 /* status */);
}
@@ -1395,13 +1416,12 @@
+ data.length);
}
- if (!permissionCheck(connId, handle)) {
- Log.w(TAG, "onNotify() - permission check failed!");
- return;
- }
-
ClientMap.App app = mClientMap.getByConnId(connId);
if (app != null) {
+ if (!permissionCheck(app, connId, handle)) {
+ Log.w(TAG, "onNotify() - permission check failed!");
+ return;
+ }
app.callback.onNotify(address, handle, data);
}
}
@@ -1596,6 +1616,7 @@
throws RemoteException {
ScannerMap.App app = mScannerMap.getById(client.scannerId);
if (app == null) {
+ Log.e(TAG, "app not found from id(" + client.scannerId + ") for received callback");
return;
}
if (client.filters == null || client.filters.isEmpty()) {
@@ -1641,7 +1662,7 @@
BluetoothDevice device = mAdapter.getRemoteDevice(address);
int rssi = record[8];
long timestampNanos = now - parseTimestampNanos(extractBytes(record, 9, 2));
- results.add(new ScanResult(device, ScanRecord.parseFromBytes(new byte[0]), rssi,
+ results.add(getScanResultInstance(device, ScanRecord.parseFromBytes(new byte[0]), rssi,
timestampNanos));
}
return results;
@@ -1689,7 +1710,7 @@
if (DBG) {
Log.d(TAG, "ScanRecord : " + Arrays.toString(scanRecord));
}
- results.add(new ScanResult(device, ScanRecord.parseFromBytes(scanRecord), rssi,
+ results.add(getScanResultInstance(device, ScanRecord.parseFromBytes(scanRecord), rssi,
timestampNanos));
}
return results;
@@ -1746,7 +1767,7 @@
BluetoothAdapter.getDefaultAdapter().getRemoteDevice(trackingInfo.getAddress());
int advertiserState = trackingInfo.getAdvState();
ScanResult result =
- new ScanResult(device, ScanRecord.parseFromBytes(trackingInfo.getResult()),
+ getScanResultInstance(device, ScanRecord.parseFromBytes(trackingInfo.getResult()),
trackingInfo.getRSSIValue(), SystemClock.elapsedRealtimeNanos());
for (ScanClient client : mScanManager.getRegularScanQueue()) {
@@ -1886,7 +1907,8 @@
}
}
}
-
+ if (VDBG) Log.v(TAG, "getDevicesMatchingConnectionStates: State = "
+ + Arrays.toString(states) + ", deviceList = " + deviceList);
return deviceList;
}
@@ -1927,18 +1949,30 @@
void startScan(int scannerId, ScanSettings settings, List<ScanFilter> filters,
List<List<ResultStorageDescriptor>> storages, String callingPackage) {
if (DBG) {
- Log.d(TAG, "start scan with filters");
+ Log.d(TAG, "start scan with filters. callingPackage: " + callingPackage);
}
+ UserHandle callingUser = UserHandle.of(UserHandle.getCallingUserId());
enforceAdminPermission();
if (needsPrivilegedPermissionForScan(settings)) {
enforcePrivilegedPermission();
}
final ScanClient scanClient = new ScanClient(scannerId, settings, filters, storages);
- scanClient.hasLocationPermission =
- Utils.checkCallerHasLocationPermission(this, mAppOps, callingPackage);
- scanClient.hasPeersMacAddressPermission =
- Utils.checkCallerHasPeersMacAddressPermission(this);
- scanClient.legacyForegroundApp = Utils.isLegacyForegroundApp(this, callingPackage);
+ scanClient.userHandle = UserHandle.of(UserHandle.getCallingUserId());
+ mAppOps.checkPackage(Binder.getCallingUid(), callingPackage);
+ scanClient.isQApp = Utils.isQApp(this, callingPackage);
+ if (scanClient.isQApp) {
+ scanClient.hasLocationPermission =
+ Utils.checkCallerHasFineLocation(
+ this, mAppOps, callingPackage, scanClient.userHandle);
+ } else {
+ scanClient.hasLocationPermission =
+ Utils.checkCallerHasCoarseOrFineLocation(
+ this, mAppOps, callingPackage, scanClient.userHandle);
+ }
+ scanClient.hasNetworkSettingsPermission =
+ Utils.checkCallerHasNetworkSettingsPermission(this);
+ scanClient.hasNetworkSetupWizardPermission =
+ Utils.checkCallerHasNetworkSetupWizardPermission(this);
AppScanStats app = mScannerMap.getAppScanStatsById(scannerId);
if (app != null) {
@@ -1953,7 +1987,8 @@
void registerPiAndStartScan(PendingIntent pendingIntent, ScanSettings settings,
List<ScanFilter> filters, String callingPackage) {
if (DBG) {
- Log.d(TAG, "start scan with filters, for PendingIntent");
+ Log.d(TAG, "start scan with filters, for PendingIntent. callingPackage: "
+ + callingPackage);
}
enforceAdminPermission();
if (needsPrivilegedPermissionForScan(settings)) {
@@ -1970,19 +2005,25 @@
piInfo.filters = filters;
piInfo.callingPackage = callingPackage;
ScannerMap.App app = mScannerMap.add(uuid, null, null, piInfo, this);
+ app.mUserHandle = UserHandle.of(UserHandle.getCallingUserId());
+ mAppOps.checkPackage(Binder.getCallingUid(), callingPackage);
+ app.mIsQApp = Utils.isQApp(this, callingPackage);
try {
- app.hasLocationPermisson =
- Utils.checkCallerHasLocationPermission(this, mAppOps, callingPackage);
+ if (app.mIsQApp) {
+ app.hasLocationPermission = Utils.checkCallerHasFineLocation(
+ this, mAppOps, callingPackage, app.mUserHandle);
+ } else {
+ app.hasLocationPermission = Utils.checkCallerHasCoarseOrFineLocation(
+ this, mAppOps, callingPackage, app.mUserHandle);
+ }
} catch (SecurityException se) {
// No need to throw here. Just mark as not granted.
- app.hasLocationPermisson = false;
+ app.hasLocationPermission = false;
}
- try {
- app.hasPeersMacAddressPermission = Utils.checkCallerHasPeersMacAddressPermission(this);
- } catch (SecurityException se) {
- // No need to throw here. Just mark as not granted.
- app.hasPeersMacAddressPermission = false;
- }
+ app.mHasNetworkSettingsPermission =
+ Utils.checkCallerHasNetworkSettingsPermission(this);
+ app.mHasNetworkSetupWizardPermission =
+ Utils.checkCallerHasNetworkSetupWizardPermission(this);
mScanManager.registerScanner(uuid);
}
@@ -1990,9 +2031,11 @@
final PendingIntentInfo piInfo = app.info;
final ScanClient scanClient =
new ScanClient(scannerId, piInfo.settings, piInfo.filters, null);
- scanClient.hasLocationPermission = app.hasLocationPermisson;
- scanClient.hasPeersMacAddressPermission = app.hasPeersMacAddressPermission;
- scanClient.legacyForegroundApp = Utils.isLegacyForegroundApp(this, piInfo.callingPackage);
+ scanClient.hasLocationPermission = app.hasLocationPermission;
+ scanClient.userHandle = app.mUserHandle;
+ scanClient.isQApp = app.mIsQApp;
+ scanClient.hasNetworkSettingsPermission = app.mHasNetworkSettingsPermission;
+ scanClient.hasNetworkSetupWizardPermission = app.mHasNetworkSetupWizardPermission;
AppScanStats scanStats = mScannerMap.getAppScanStatsById(scannerId);
if (scanStats != null) {
@@ -2070,6 +2113,7 @@
return true;
}
}
+ if (VDBG) Log.v(TAG, "clientIf: " + clientIf + " is not a ScanClient");
return false;
}
@@ -2503,7 +2547,7 @@
int latency;
// Link supervision timeout is measured in N * 10ms
- int timeout = 2000; // 20s
+ int timeout = 500; // 5s
switch (connectionPriority) {
case BluetoothGatt.CONNECTION_PRIORITY_HIGH:
@@ -2649,6 +2693,7 @@
ServerMap.App app = mServerMap.getById(serverIf);
if (app == null) {
+ Log.e(TAG, "app not found from id(" + serverIf + ") for received callback");
return;
}
@@ -2677,6 +2722,7 @@
ServerMap.App app = mServerMap.getById(entry.serverIf);
if (app == null) {
+ Log.e(TAG, "app not found from id(" + entry.serverIf + ") for received callback");
return;
}
@@ -2699,6 +2745,7 @@
ServerMap.App app = mServerMap.getById(entry.serverIf);
if (app == null) {
+ Log.e(TAG, "app not found from id(" + entry.serverIf + ") for received callback");
return;
}
@@ -2723,6 +2770,7 @@
ServerMap.App app = mServerMap.getById(entry.serverIf);
if (app == null) {
+ Log.e(TAG, "app not found from id(" + entry.serverIf + ") for received callback");
return;
}
@@ -2747,6 +2795,7 @@
ServerMap.App app = mServerMap.getById(entry.serverIf);
if (app == null) {
+ Log.e(TAG, "app not found from id(" + entry.serverIf + ") for received callback");
return;
}
@@ -2763,6 +2812,7 @@
ServerMap.App app = mServerMap.getByConnId(connId);
if (app == null) {
+ Log.e(TAG, "app not found from connId(" + connId + ") for received callback");
return;
}
@@ -2782,11 +2832,13 @@
String address = mServerMap.addressByConnId(connId);
if (address == null) {
+ Log.e(TAG, "address not found for given connId:" + connId);
return;
}
ServerMap.App app = mServerMap.getByConnId(connId);
if (app == null) {
+ Log.e(TAG, "app not found from connId(" + connId + ") for received callback");
return;
}
@@ -2807,6 +2859,7 @@
ServerMap.App app = mServerMap.getByConnId(connId);
if (app == null) {
+ Log.e(TAG, "app not found from connId(" + connId + ") for received callback");
return;
}
@@ -2827,11 +2880,13 @@
String address = mServerMap.addressByConnId(connId);
if (address == null) {
+ Log.e(TAG, "address not found for given connId: " + connId);
return;
}
ServerMap.App app = mServerMap.getByConnId(connId);
if (app == null) {
+ Log.e(TAG, "app not found from connId(" + connId + ") for received callback");
return;
}
@@ -2985,7 +3040,9 @@
enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
if (VDBG) {
- Log.d(TAG, "sendResponse() - address=" + address);
+ Log.d(TAG, "sendResponse() - address=" + address + " serverIf: " + serverIf
+ + "requestId: " + requestId + " status: " + status + " value: "
+ + Arrays.toString(value));
}
int handle = 0;
@@ -3004,11 +3061,13 @@
enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
if (VDBG) {
- Log.d(TAG, "sendNotification() - address=" + address + " handle=" + handle);
+ Log.d(TAG, "sendNotification() - address = " + address + " handle = " + handle
+ + " confirm = " + confirm + " value = " + Arrays.toString(value));
}
Integer connId = mServerMap.connIdByAddress(serverIf, address);
if (connId == null || connId == 0) {
+ Log.e(TAG, "couldn't find connId for given address. Return");
return;
}
@@ -3024,15 +3083,11 @@
* Private functions
*************************************************************************/
- private boolean isRestrictedCharUuid(final UUID charUuid) {
- return isHidUuid(charUuid);
+ private boolean isHidSrvcUuid(final UUID uuid) {
+ return HID_SERVICE_UUID.equals(uuid);
}
- private boolean isRestrictedSrvcUuid(final UUID srvcUuid) {
- return isFidoUUID(srvcUuid);
- }
-
- private boolean isHidUuid(final UUID uuid) {
+ private boolean isHidCharUuid(final UUID uuid) {
for (UUID hidUuid : HID_UUIDS) {
if (hidUuid.equals(uuid)) {
return true;
@@ -3041,13 +3096,12 @@
return false;
}
- private boolean isFidoUUID(final UUID uuid) {
- for (UUID fidoUuid : FIDO_UUIDS) {
- if (fidoUuid.equals(uuid)) {
- return true;
- }
- }
- return false;
+ private boolean isAndroidTvRemoteSrvcUuid(final UUID uuid) {
+ return ANDROID_TV_REMOTE_SERVICE_UUID.equals(uuid);
+ }
+
+ private boolean isFidoSrvcUuid(final UUID uuid) {
+ return FIDO_SERVICE_UUID.equals(uuid);
}
private int getDeviceType(BluetoothDevice device) {
@@ -3135,6 +3189,8 @@
handleList.add(entry.handle);
}
+ if (VDBG) Log.v(TAG, "delete Services handleList : " + handleList);
+
/* Now actually delete the services.... */
for (Integer handle : handleList) {
gattServerDeleteServiceNative(serverIf, handle);
@@ -3199,7 +3255,7 @@
void addScanEvent(BluetoothMetricsProto.ScanEvent event) {
synchronized (mScanEvents) {
if (mScanEvents.size() == NUM_SCAN_EVENTS_KEPT) {
- mScanEvents.remove(0);
+ mScanEvents.remove();
}
mScanEvents.add(event);
}
@@ -3212,7 +3268,17 @@
}
}
- /**************************************************************************
+ private ScanResult getScanResultInstance(BluetoothDevice device, ScanRecord scanRecord,
+ int rssi, long timestampNanos) {
+ int eventType = (ScanResult.DATA_COMPLETE << 5) | ET_LEGACY_MASK | ET_CONNECTABLE_MASK;
+ int txPower = 127;
+ int periodicAdvertisingInterval = 0;
+ return new ScanResult(device, eventType, BluetoothDevice.PHY_LE_1M, ScanResult.PHY_UNUSED,
+ ScanResult.SID_NOT_PRESENT, txPower, rssi, periodicAdvertisingInterval,
+ scanRecord, timestampNanos);
+ }
+
+ /**************************************************************************
* GATT Test functions
*************************************************************************/
diff --git a/src/com/android/bluetooth/gatt/GattServiceConfig.java b/src/com/android/bluetooth/gatt/GattServiceConfig.java
index 290d15d..3ffce38 100644
--- a/src/com/android/bluetooth/gatt/GattServiceConfig.java
+++ b/src/com/android/bluetooth/gatt/GattServiceConfig.java
@@ -16,12 +16,15 @@
package com.android.bluetooth.gatt;
+import android.util.Log;
+
/**
* GattService configuration.
*/
/*package*/ class GattServiceConfig {
- public static final boolean DBG = false;
- public static final boolean VDBG = false;
+ public static final String LOG_TAG = "BluetoothGatt";
+ public static final boolean DBG = true;
+ public static final boolean VDBG = Log.isLoggable(LOG_TAG, Log.VERBOSE);;
public static final String TAG_PREFIX = "BtGatt.";
public static final boolean DEBUG_ADMIN = true;
}
diff --git a/src/com/android/bluetooth/gatt/ScanClient.java b/src/com/android/bluetooth/gatt/ScanClient.java
index f774347..83a034b 100644
--- a/src/com/android/bluetooth/gatt/ScanClient.java
+++ b/src/com/android/bluetooth/gatt/ScanClient.java
@@ -20,6 +20,7 @@
import android.bluetooth.le.ScanFilter;
import android.bluetooth.le.ScanSettings;
import android.os.Binder;
+import android.os.UserHandle;
import java.util.List;
import java.util.Objects;
@@ -41,9 +42,10 @@
// App associated with the scan client died.
public boolean appDied;
public boolean hasLocationPermission;
- public boolean hasPeersMacAddressPermission;
- // Pre-M apps are allowed to get scan results even if location is disabled
- public boolean legacyForegroundApp;
+ public UserHandle userHandle;
+ public boolean isQApp;
+ public boolean hasNetworkSettingsPermission;
+ public boolean hasNetworkSetupWizardPermission;
public AppScanStats stats = null;
diff --git a/src/com/android/bluetooth/gatt/ScanFilterQueue.java b/src/com/android/bluetooth/gatt/ScanFilterQueue.java
index 6c47711..61133e1 100644
--- a/src/com/android/bluetooth/gatt/ScanFilterQueue.java
+++ b/src/com/android/bluetooth/gatt/ScanFilterQueue.java
@@ -39,6 +39,7 @@
public static final int TYPE_LOCAL_NAME = 4;
public static final int TYPE_MANUFACTURER_DATA = 5;
public static final int TYPE_SERVICE_DATA = 6;
+ public static final int TYPE_TRANSPORT_DISCOVERY_DATA = 7;
// Max length is 31 - 3(flags) - 2 (one byte for length and one byte for type).
private static final int MAX_LEN_PER_FIELD = 26;
@@ -57,6 +58,9 @@
public int company_mask;
public byte[] data;
public byte[] data_mask;
+ public int org_id;
+ public int tds_flags;
+ public int tds_flags_mask;
}
private Set<Entry> mEntries = new HashSet<Entry>();
@@ -143,6 +147,16 @@
mEntries.add(entry);
}
+ void addTransportDiscoveryData(int orgId, int TDSFlags, int TDSFlagsMask, byte[] wifiNANHash) {
+ Entry entry = new Entry();
+ entry.type = TYPE_TRANSPORT_DISCOVERY_DATA;
+ entry.org_id = orgId;
+ entry.tds_flags = TDSFlags;
+ entry.tds_flags_mask = TDSFlagsMask;
+ entry.data = wifiNANHash;
+ mEntries.add(entry);
+ }
+
Entry pop() {
if (mEntries.isEmpty()) {
return null;
@@ -218,6 +232,10 @@
addServiceData(serviceData, serviceDataMask);
}
}
+ if (filter.getOrgId() >= 0) {
+ addTransportDiscoveryData(filter.getOrgId(), filter.getTDSFlags(),
+ filter.getTDSFlagsMask(), filter.getWifiNANHash());
+ }
}
private byte[] concate(ParcelUuid serviceDataUuid, byte[] serviceData) {
diff --git a/src/com/android/bluetooth/gatt/ScanManager.java b/src/com/android/bluetooth/gatt/ScanManager.java
index 926837b..79c8251 100644
--- a/src/com/android/bluetooth/gatt/ScanManager.java
+++ b/src/com/android/bluetooth/gatt/ScanManager.java
@@ -351,7 +351,7 @@
}
final boolean locationEnabled = mLocationManager.isLocationEnabled();
- if (!locationEnabled && !isFiltered && !client.legacyForegroundApp) {
+ if (!locationEnabled && !isFiltered) {
Log.i(TAG, "Cannot start unfiltered scan in location-off. This scan will be"
+ " resumed when location is on: " + client.scannerId);
mSuspendedScanClients.add(client);
@@ -477,7 +477,7 @@
void handleSuspendScans() {
for (ScanClient client : mRegularScanClients) {
if (!mScanNative.isOpportunisticScanClient(client) && (client.filters == null
- || client.filters.isEmpty()) && !client.legacyForegroundApp) {
+ || client.filters.isEmpty())) {
/*Suspend unfiltered scans*/
if (client.stats != null) {
client.stats.recordScanSuspend(client.scannerId);
@@ -541,6 +541,8 @@
}
public int getCurrentUsedTrackingAdvertisement() {
+ Log.d(TAG, "mCurUsedTrackableAdvertisements: "
+ + mCurUsedTrackableAdvertisements);
return mCurUsedTrackableAdvertisements;
}
@@ -1117,6 +1119,7 @@
0);
waitForCallback();
} else {
+ Log.d(TAG, "Available Filter size: " + mFilterIndexStack.size());
Deque<Integer> clientFilterIndices = new ArrayDeque<Integer>();
for (ScanFilter filter : client.filters) {
ScanFilterQueue queue = new ScanFilterQueue();
diff --git a/src/com/android/bluetooth/hdp/HealthService.java b/src/com/android/bluetooth/hdp/HealthService.java
deleted file mode 100644
index b60b0d6..0000000
--- a/src/com/android/bluetooth/hdp/HealthService.java
+++ /dev/null
@@ -1,1038 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.bluetooth.hdp;
-
-import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothHealth;
-import android.bluetooth.BluetoothHealthAppConfiguration;
-import android.bluetooth.IBluetoothHealth;
-import android.bluetooth.IBluetoothHealthCallback;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.IBinder;
-import android.os.Looper;
-import android.os.Message;
-import android.os.ParcelFileDescriptor;
-import android.os.RemoteException;
-import android.support.annotation.VisibleForTesting;
-import android.util.Log;
-
-import com.android.bluetooth.BluetoothMetricsProto;
-import com.android.bluetooth.Utils;
-import com.android.bluetooth.btservice.MetricsLogger;
-import com.android.bluetooth.btservice.ProfileService;
-
-import java.io.FileDescriptor;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.NoSuchElementException;
-
-
-/**
- * Provides Bluetooth Health Device profile, as a service in
- * the Bluetooth application.
- * @hide
- */
-public class HealthService extends ProfileService {
- private static final boolean DBG = true;
- private static final boolean VDBG = false;
- private static final String TAG = "HealthService";
-
- private List<HealthChannel> mHealthChannels;
- private Map<BluetoothHealthAppConfiguration, AppInfo> mApps;
- private Map<BluetoothDevice, Integer> mHealthDevices;
- private boolean mNativeAvailable;
- private HealthServiceMessageHandler mHandler;
- private static final int MESSAGE_REGISTER_APPLICATION = 1;
- private static final int MESSAGE_UNREGISTER_APPLICATION = 2;
- private static final int MESSAGE_CONNECT_CHANNEL = 3;
- private static final int MESSAGE_DISCONNECT_CHANNEL = 4;
- private static final int MESSAGE_APP_REGISTRATION_CALLBACK = 11;
- private static final int MESSAGE_CHANNEL_STATE_CALLBACK = 12;
-
- private static HealthService sHealthService;
-
- static {
- classInitNative();
- }
-
- @Override
- protected IProfileServiceBinder initBinder() {
- return new BluetoothHealthBinder(this);
- }
-
- @Override
- protected boolean start() {
- mHealthChannels = Collections.synchronizedList(new ArrayList<HealthChannel>());
- mApps = Collections.synchronizedMap(
- new HashMap<BluetoothHealthAppConfiguration, AppInfo>());
- mHealthDevices = Collections.synchronizedMap(new HashMap<BluetoothDevice, Integer>());
-
- HandlerThread thread = new HandlerThread("BluetoothHdpHandler");
- thread.start();
- Looper looper = thread.getLooper();
- mHandler = new HealthServiceMessageHandler(looper);
- initializeNative();
- mNativeAvailable = true;
- setHealthService(this);
- return true;
- }
-
- @Override
- protected boolean stop() {
- setHealthService(null);
- if (mHandler != null) {
- mHandler.removeCallbacksAndMessages(null);
- Looper looper = mHandler.getLooper();
- if (looper != null) {
- looper.quit();
- }
- }
- cleanupApps();
- return true;
- }
-
- private void cleanupApps() {
- if (mApps != null) {
- Iterator<Map.Entry<BluetoothHealthAppConfiguration, AppInfo>> it =
- mApps.entrySet().iterator();
- while (it.hasNext()) {
- Map.Entry<BluetoothHealthAppConfiguration, AppInfo> entry = it.next();
- AppInfo appInfo = entry.getValue();
- if (appInfo != null) {
- appInfo.cleanup();
- }
- it.remove();
- }
- }
- }
-
- @Override
- protected void cleanup() {
- mHandler = null;
- //Cleanup native
- if (mNativeAvailable) {
- cleanupNative();
- mNativeAvailable = false;
- }
- if (mHealthChannels != null) {
- mHealthChannels.clear();
- }
- if (mHealthDevices != null) {
- mHealthDevices.clear();
- }
- if (mApps != null) {
- mApps.clear();
- }
- }
-
- /**
- * Get a static reference to the current health service instance
- *
- * @return current health service instance
- */
- @VisibleForTesting
- public static synchronized HealthService getHealthService() {
- if (sHealthService == null) {
- Log.w(TAG, "getHealthService(): service is null");
- return null;
- }
- if (!sHealthService.isAvailable()) {
- Log.w(TAG, "getHealthService(): service is not available");
- return null;
- }
- return sHealthService;
- }
-
- private static synchronized void setHealthService(HealthService instance) {
- if (DBG) {
- Log.d(TAG, "setHealthService(): set to: " + instance);
- }
- sHealthService = instance;
- }
-
- private final class HealthServiceMessageHandler extends Handler {
- private HealthServiceMessageHandler(Looper looper) {
- super(looper);
- }
-
- @Override
- public void handleMessage(Message msg) {
- if (DBG) {
- Log.d(TAG, "HealthService Handler msg: " + msg.what);
- }
- switch (msg.what) {
- case MESSAGE_REGISTER_APPLICATION: {
- BluetoothHealthAppConfiguration appConfig =
- (BluetoothHealthAppConfiguration) msg.obj;
- AppInfo appInfo = mApps.get(appConfig);
- if (appInfo == null) {
- break;
- }
- int halRole = convertRoleToHal(appConfig.getRole());
- int halChannelType = convertChannelTypeToHal(appConfig.getChannelType());
- if (VDBG) {
- Log.d(TAG, "register datatype: " + appConfig.getDataType() + " role: "
- + halRole + " name: " + appConfig.getName() + " channeltype: "
- + halChannelType);
- }
- int appId = registerHealthAppNative(appConfig.getDataType(), halRole,
- appConfig.getName(), halChannelType);
- if (appId == -1) {
- callStatusCallback(appConfig,
- BluetoothHealth.APP_CONFIG_REGISTRATION_FAILURE);
- appInfo.cleanup();
- mApps.remove(appConfig);
- } else {
- //link to death with a recipient object to implement binderDead()
- appInfo.mRcpObj =
- new BluetoothHealthDeathRecipient(HealthService.this, appConfig);
- IBinder binder = appInfo.mCallback.asBinder();
- try {
- binder.linkToDeath(appInfo.mRcpObj, 0);
- } catch (RemoteException e) {
- Log.e(TAG, "LinktoDeath Exception:" + e);
- }
- appInfo.mAppId = appId;
- callStatusCallback(appConfig,
- BluetoothHealth.APP_CONFIG_REGISTRATION_SUCCESS);
- }
- }
- break;
- case MESSAGE_UNREGISTER_APPLICATION: {
- BluetoothHealthAppConfiguration appConfig =
- (BluetoothHealthAppConfiguration) msg.obj;
- AppInfo appInfo = mApps.get(appConfig);
- if (appInfo == null) {
- Log.e(TAG, "No AppInfo found for AppConfig: " + appConfig);
- break;
- }
- int appId = appInfo.mAppId;
- if (!unregisterHealthAppNative(appId)) {
- Log.e(TAG, "Failed to unregister application: id: " + appId);
- callStatusCallback(appConfig,
- BluetoothHealth.APP_CONFIG_UNREGISTRATION_FAILURE);
- }
- }
- break;
- case MESSAGE_CONNECT_CHANNEL: {
- HealthChannel chan = (HealthChannel) msg.obj;
- byte[] devAddr = Utils.getByteAddress(chan.mDevice);
- AppInfo appInfo = mApps.get(chan.mConfig);
- if (appInfo == null) {
- Log.e(TAG, "No AppInfo found for AppConfig: " + chan.mConfig);
- break;
- }
- int appId = appInfo.mAppId;
- chan.mChannelId = connectChannelNative(devAddr, appId);
- if (chan.mChannelId == -1) {
- callHealthChannelCallback(chan.mConfig, chan.mDevice,
- BluetoothHealth.STATE_CHANNEL_DISCONNECTING,
- BluetoothHealth.STATE_CHANNEL_DISCONNECTED, chan.mChannelFd,
- chan.mChannelId);
- callHealthChannelCallback(chan.mConfig, chan.mDevice,
- BluetoothHealth.STATE_CHANNEL_DISCONNECTED,
- BluetoothHealth.STATE_CHANNEL_DISCONNECTING, chan.mChannelFd,
- chan.mChannelId);
- }
- }
- break;
- case MESSAGE_DISCONNECT_CHANNEL: {
- HealthChannel chan = (HealthChannel) msg.obj;
- if (!disconnectChannelNative(chan.mChannelId)) {
- callHealthChannelCallback(chan.mConfig, chan.mDevice,
- BluetoothHealth.STATE_CHANNEL_DISCONNECTING,
- BluetoothHealth.STATE_CHANNEL_CONNECTED, chan.mChannelFd,
- chan.mChannelId);
- callHealthChannelCallback(chan.mConfig, chan.mDevice,
- BluetoothHealth.STATE_CHANNEL_CONNECTED,
- BluetoothHealth.STATE_CHANNEL_DISCONNECTING, chan.mChannelFd,
- chan.mChannelId);
- }
- }
- break;
- case MESSAGE_APP_REGISTRATION_CALLBACK: {
- BluetoothHealthAppConfiguration appConfig = findAppConfigByAppId(msg.arg1);
- if (appConfig == null) {
- break;
- }
-
- int regStatus = convertHalRegStatus(msg.arg2);
- callStatusCallback(appConfig, regStatus);
- if (regStatus == BluetoothHealth.APP_CONFIG_REGISTRATION_FAILURE
- || regStatus == BluetoothHealth.APP_CONFIG_UNREGISTRATION_SUCCESS) {
- //unlink to death once app is unregistered
- AppInfo appInfo = mApps.get(appConfig);
- if (appInfo == null){
- Log.e(TAG, "No AppInfo found for AppConfig " + appConfig);
- break;
- }
- appInfo.cleanup();
- mApps.remove(appConfig);
- }
- }
- break;
- case MESSAGE_CHANNEL_STATE_CALLBACK: {
- ChannelStateEvent channelStateEvent = (ChannelStateEvent) msg.obj;
- HealthChannel chan = findChannelById(channelStateEvent.mChannelId);
- BluetoothHealthAppConfiguration appConfig =
- findAppConfigByAppId(channelStateEvent.mAppId);
- int newState;
- newState = convertHalChannelState(channelStateEvent.mState);
- if (newState == BluetoothHealth.STATE_CHANNEL_DISCONNECTED ||
- appConfig == null) {
- Log.e(TAG,"Disconnected for non existing app");
- break;
- }
- if (chan == null) {
- // incoming connection
-
- BluetoothDevice device = getDevice(channelStateEvent.mAddr);
- chan = new HealthChannel(device, appConfig, appConfig.getChannelType());
- chan.mChannelId = channelStateEvent.mChannelId;
- mHealthChannels.add(chan);
- }
- newState = convertHalChannelState(channelStateEvent.mState);
- if (newState == BluetoothHealth.STATE_CHANNEL_CONNECTED) {
- try {
- chan.mChannelFd = ParcelFileDescriptor.dup(channelStateEvent.mFd);
- } catch (IOException e) {
- Log.e(TAG, "failed to dup ParcelFileDescriptor");
- break;
- }
- } else {
- /*set the channel fd to null if channel state isnot equal to connected*/
- chan.mChannelFd = null;
- }
- callHealthChannelCallback(chan.mConfig, chan.mDevice, newState, chan.mState,
- chan.mChannelFd, chan.mChannelId);
- chan.mState = newState;
- if (channelStateEvent.mState == CONN_STATE_DESTROYED) {
- mHealthChannels.remove(chan);
- }
- }
- break;
- }
- }
- }
-
- //Handler for DeathReceipient
- private static class BluetoothHealthDeathRecipient implements IBinder.DeathRecipient {
- private BluetoothHealthAppConfiguration mConfig;
- private HealthService mService;
-
- BluetoothHealthDeathRecipient(HealthService service,
- BluetoothHealthAppConfiguration config) {
- mService = service;
- mConfig = config;
- }
-
- @Override
- public void binderDied() {
- if (DBG) {
- Log.d(TAG, "Binder is dead.");
- }
- mService.unregisterAppConfiguration(mConfig);
- }
-
- public void cleanup() {
- mService = null;
- mConfig = null;
- }
- }
-
- /**
- * Handlers for incoming service calls
- */
- private static class BluetoothHealthBinder extends IBluetoothHealth.Stub
- implements IProfileServiceBinder {
- private HealthService mService;
-
- BluetoothHealthBinder(HealthService svc) {
- mService = svc;
- }
-
- @Override
- public void cleanup() {
- mService = null;
- }
-
- private HealthService getService() {
- if (!Utils.checkCaller()) {
- Log.w(TAG, "Health call not allowed for non-active user");
- return null;
- }
-
- if (mService != null && mService.isAvailable()) {
- return mService;
- }
- return null;
- }
-
- @Override
- public boolean registerAppConfiguration(BluetoothHealthAppConfiguration config,
- IBluetoothHealthCallback callback) {
- HealthService service = getService();
- if (service == null) {
- return false;
- }
- return service.registerAppConfiguration(config, callback);
- }
-
- @Override
- public boolean unregisterAppConfiguration(BluetoothHealthAppConfiguration config) {
- HealthService service = getService();
- if (service == null) {
- return false;
- }
- return service.unregisterAppConfiguration(config);
- }
-
- @Override
- public boolean connectChannelToSource(BluetoothDevice device,
- BluetoothHealthAppConfiguration config) {
- HealthService service = getService();
- if (service == null) {
- return false;
- }
- return service.connectChannelToSource(device, config);
- }
-
- @Override
- public boolean connectChannelToSink(BluetoothDevice device,
- BluetoothHealthAppConfiguration config, int channelType) {
- HealthService service = getService();
- if (service == null) {
- return false;
- }
- return service.connectChannelToSink(device, config, channelType);
- }
-
- @Override
- public boolean disconnectChannel(BluetoothDevice device,
- BluetoothHealthAppConfiguration config, int channelId) {
- HealthService service = getService();
- if (service == null) {
- return false;
- }
- return service.disconnectChannel(device, config, channelId);
- }
-
- @Override
- public ParcelFileDescriptor getMainChannelFd(BluetoothDevice device,
- BluetoothHealthAppConfiguration config) {
- HealthService service = getService();
- if (service == null) {
- return null;
- }
- return service.getMainChannelFd(device, config);
- }
-
- @Override
- public int getHealthDeviceConnectionState(BluetoothDevice device) {
- HealthService service = getService();
- if (service == null) {
- return BluetoothHealth.STATE_DISCONNECTED;
- }
- return service.getHealthDeviceConnectionState(device);
- }
-
- @Override
- public List<BluetoothDevice> getConnectedHealthDevices() {
- HealthService service = getService();
- if (service == null) {
- return new ArrayList<BluetoothDevice>(0);
- }
- return service.getConnectedHealthDevices();
- }
-
- @Override
- public List<BluetoothDevice> getHealthDevicesMatchingConnectionStates(int[] states) {
- HealthService service = getService();
- if (service == null) {
- return new ArrayList<BluetoothDevice>(0);
- }
- return service.getHealthDevicesMatchingConnectionStates(states);
- }
- }
-
- ;
-
- boolean registerAppConfiguration(BluetoothHealthAppConfiguration config,
- IBluetoothHealthCallback callback) {
- enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-
- if (config == null) {
- Log.e(TAG, "Trying to use a null config for registration");
- return false;
- }
-
- if (mApps.get(config) != null) {
- if (DBG) {
- Log.d(TAG, "Config has already been registered");
- }
- return false;
- }
- mApps.put(config, new AppInfo(callback));
- Message msg = mHandler.obtainMessage(MESSAGE_REGISTER_APPLICATION, config);
- mHandler.sendMessage(msg);
- return true;
- }
-
- boolean unregisterAppConfiguration(BluetoothHealthAppConfiguration config) {
- enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- if (mApps.get(config) == null) {
- if (DBG) {
- Log.d(TAG, "unregisterAppConfiguration: no app found");
- }
- return false;
- }
- Message msg = mHandler.obtainMessage(MESSAGE_UNREGISTER_APPLICATION, config);
- mHandler.sendMessage(msg);
- return true;
- }
-
- boolean connectChannelToSource(BluetoothDevice device, BluetoothHealthAppConfiguration config) {
- enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- return connectChannel(device, config, BluetoothHealth.CHANNEL_TYPE_ANY);
- }
-
- boolean connectChannelToSink(BluetoothDevice device, BluetoothHealthAppConfiguration config,
- int channelType) {
- enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- return connectChannel(device, config, channelType);
- }
-
- boolean disconnectChannel(BluetoothDevice device, BluetoothHealthAppConfiguration config,
- int channelId) {
- enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- HealthChannel chan = findChannelById(channelId);
- if (chan == null) {
- if (DBG) {
- Log.d(TAG, "disconnectChannel: no channel found");
- }
- return false;
- }
- Message msg = mHandler.obtainMessage(MESSAGE_DISCONNECT_CHANNEL, chan);
- mHandler.sendMessage(msg);
- return true;
- }
-
- ParcelFileDescriptor getMainChannelFd(BluetoothDevice device,
- BluetoothHealthAppConfiguration config) {
- enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- HealthChannel healthChan = null;
- for (HealthChannel chan : mHealthChannels) {
- if (chan.mDevice.equals(device) && chan.mConfig.equals(config)) {
- healthChan = chan;
- }
- }
- if (healthChan == null) {
- Log.e(TAG, "No channel found for device: " + device + " config: " + config);
- return null;
- }
- return healthChan.mChannelFd;
- }
-
- int getHealthDeviceConnectionState(BluetoothDevice device) {
- enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- return getConnectionState(device);
- }
-
- List<BluetoothDevice> getConnectedHealthDevices() {
- enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- List<BluetoothDevice> devices =
- lookupHealthDevicesMatchingStates(new int[]{BluetoothHealth.STATE_CONNECTED});
- return devices;
- }
-
- List<BluetoothDevice> getHealthDevicesMatchingConnectionStates(int[] states) {
- enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- List<BluetoothDevice> devices = lookupHealthDevicesMatchingStates(states);
- return devices;
- }
-
- private void onAppRegistrationState(int appId, int state) {
- Message msg = mHandler.obtainMessage(MESSAGE_APP_REGISTRATION_CALLBACK);
- msg.arg1 = appId;
- msg.arg2 = state;
- mHandler.sendMessage(msg);
- }
-
- private void onChannelStateChanged(int appId, byte[] addr, int cfgIndex, int channelId,
- int state, FileDescriptor pfd) {
- Message msg = mHandler.obtainMessage(MESSAGE_CHANNEL_STATE_CALLBACK);
- ChannelStateEvent channelStateEvent =
- new ChannelStateEvent(appId, addr, cfgIndex, channelId, state, pfd);
- msg.obj = channelStateEvent;
- mHandler.sendMessage(msg);
- }
-
- private String getStringChannelType(int type) {
- if (type == BluetoothHealth.CHANNEL_TYPE_RELIABLE) {
- return "Reliable";
- } else if (type == BluetoothHealth.CHANNEL_TYPE_STREAMING) {
- return "Streaming";
- } else {
- return "Any";
- }
- }
-
- private void callStatusCallback(BluetoothHealthAppConfiguration config, int status) {
- if (VDBG) {
- Log.d(TAG, "Health Device Application: " + config + " State Change: status:" + status);
- }
- AppInfo appInfo = mApps.get(config);
- if (appInfo == null) {
- Log.e(TAG, " No AppInfo found for AppConfig " + config);
- return;
- }
- IBluetoothHealthCallback callback = appInfo.mCallback;
- if (callback == null) {
- Log.e(TAG, "Callback object null");
- return;
- }
-
- try {
- callback.onHealthAppConfigurationStatusChange(config, status);
- } catch (RemoteException e) {
- Log.e(TAG, "Remote Exception:" + e);
- }
- }
-
- private BluetoothHealthAppConfiguration findAppConfigByAppId(int appId) {
- BluetoothHealthAppConfiguration appConfig = null;
- for (Entry<BluetoothHealthAppConfiguration, AppInfo> e : mApps.entrySet()) {
- if (appId == (e.getValue()).mAppId) {
- appConfig = e.getKey();
- break;
- }
- }
- if (appConfig == null) {
- Log.e(TAG, "No appConfig found for " + appId);
- }
- return appConfig;
- }
-
- private int convertHalRegStatus(int halRegStatus) {
- switch (halRegStatus) {
- case APP_REG_STATE_REG_SUCCESS:
- return BluetoothHealth.APP_CONFIG_REGISTRATION_SUCCESS;
- case APP_REG_STATE_REG_FAILED:
- return BluetoothHealth.APP_CONFIG_REGISTRATION_FAILURE;
- case APP_REG_STATE_DEREG_SUCCESS:
- return BluetoothHealth.APP_CONFIG_UNREGISTRATION_SUCCESS;
- case APP_REG_STATE_DEREG_FAILED:
- return BluetoothHealth.APP_CONFIG_UNREGISTRATION_FAILURE;
- }
- Log.e(TAG, "Unexpected App Registration state: " + halRegStatus);
- return BluetoothHealth.APP_CONFIG_REGISTRATION_FAILURE;
- }
-
- private int convertHalChannelState(int halChannelState) {
- switch (halChannelState) {
- case CONN_STATE_CONNECTED:
- return BluetoothHealth.STATE_CHANNEL_CONNECTED;
- case CONN_STATE_CONNECTING:
- return BluetoothHealth.STATE_CHANNEL_CONNECTING;
- case CONN_STATE_DISCONNECTING:
- return BluetoothHealth.STATE_CHANNEL_DISCONNECTING;
- case CONN_STATE_DISCONNECTED:
- return BluetoothHealth.STATE_CHANNEL_DISCONNECTED;
- case CONN_STATE_DESTROYED:
- // TODO(BT) add BluetoothHealth.STATE_CHANNEL_DESTROYED;
- return BluetoothHealth.STATE_CHANNEL_DISCONNECTED;
- default:
- Log.e(TAG, "Unexpected channel state: " + halChannelState);
- return BluetoothHealth.STATE_CHANNEL_DISCONNECTED;
- }
- }
-
- private boolean connectChannel(BluetoothDevice device, BluetoothHealthAppConfiguration config,
- int channelType) {
- if (mApps.get(config) == null) {
- Log.e(TAG, "connectChannel fail to get a app id from config");
- return false;
- }
-
- HealthChannel chan = new HealthChannel(device, config, channelType);
-
- Message msg = mHandler.obtainMessage(MESSAGE_CONNECT_CHANNEL);
- msg.obj = chan;
- mHandler.sendMessage(msg);
-
- return true;
- }
-
- private void callHealthChannelCallback(BluetoothHealthAppConfiguration config,
- BluetoothDevice device, int state, int prevState, ParcelFileDescriptor fd, int id) {
- broadcastHealthDeviceStateChange(device, state);
-
- Log.d(TAG,
- "Health Device Callback: " + device + " State Change: " + prevState + "->" + state);
-
- ParcelFileDescriptor dupedFd = null;
- if (fd != null) {
- try {
- dupedFd = fd.dup();
- } catch (IOException e) {
- dupedFd = null;
- Log.e(TAG, "Exception while duping: " + e);
- }
- }
- AppInfo appInfo = mApps.get(config);
- if (appInfo == null) {
- Log.e(TAG, "No AppInfo found for AppConfig " + config);
- return;
- }
- IBluetoothHealthCallback callback = appInfo.mCallback;
- if (callback == null) {
- Log.e(TAG, "No callback found for config: " + config);
- return;
- }
-
- try {
- callback.onHealthChannelStateChange(config, device, prevState, state, dupedFd, id);
- } catch (RemoteException e) {
- Log.e(TAG, "Remote Exception:" + e);
- }
- }
-
- /**
- * This function sends the intent for the updates on the connection status to the remote device.
- * Note that multiple channels can be connected to the remote device by multiple applications.
- * This sends an intent for the update to the device connection status and not the channel
- * connection status. Only the following state transitions are possible:
- *
- * {@link BluetoothHealth#STATE_DISCONNECTED} to {@link BluetoothHealth#STATE_CONNECTING}
- * {@link BluetoothHealth#STATE_CONNECTING} to {@link BluetoothHealth#STATE_CONNECTED}
- * {@link BluetoothHealth#STATE_CONNECTED} to {@link BluetoothHealth#STATE_DISCONNECTING}
- * {@link BluetoothHealth#STATE_DISCONNECTING} to {@link BluetoothHealth#STATE_DISCONNECTED}
- * {@link BluetoothHealth#STATE_DISCONNECTED} to {@link BluetoothHealth#STATE_CONNECTED}
- * {@link BluetoothHealth#STATE_CONNECTED} to {@link BluetoothHealth#STATE_DISCONNECTED}
- * {@link BluetoothHealth#STATE_CONNECTING} to {{@link BluetoothHealth#STATE_DISCONNECTED}
- *
- * @param device
- * @param prevChannelState
- * @param newChannelState
- * @hide
- */
- private void broadcastHealthDeviceStateChange(BluetoothDevice device, int newChannelState) {
- if (mHealthDevices.get(device) == null) {
- mHealthDevices.put(device, BluetoothHealth.STATE_DISCONNECTED);
- }
-
- int currDeviceState = mHealthDevices.get(device);
- int newDeviceState = convertState(newChannelState);
-
- if (currDeviceState == newDeviceState) {
- return;
- }
-
- boolean sendIntent = false;
- List<HealthChannel> chan;
- switch (currDeviceState) {
- case BluetoothHealth.STATE_DISCONNECTED:
- // there was no connection or connect/disconnect attemp with the remote device
- sendIntent = true;
- break;
- case BluetoothHealth.STATE_CONNECTING:
- // there was no connection, there was a connecting attempt going on
-
- // Channel got connected.
- if (newDeviceState == BluetoothHealth.STATE_CONNECTED) {
- sendIntent = true;
- } else {
- // Channel got disconnected
- chan = findChannelByStates(device, new int[]{
- BluetoothHealth.STATE_CHANNEL_CONNECTING,
- BluetoothHealth.STATE_CHANNEL_DISCONNECTING
- });
- if (chan.isEmpty()) {
- sendIntent = true;
- }
- }
- break;
- case BluetoothHealth.STATE_CONNECTED:
- // there was at least one connection
-
- // Channel got disconnected or is in disconnecting state.
- chan = findChannelByStates(device, new int[]{
- BluetoothHealth.STATE_CHANNEL_CONNECTING,
- BluetoothHealth.STATE_CHANNEL_CONNECTED
- });
- if (chan.isEmpty()) {
- sendIntent = true;
- }
- break;
- case BluetoothHealth.STATE_DISCONNECTING:
- // there was no connected channel with the remote device
- // We were disconnecting all the channels with the remote device
-
- // Channel got disconnected.
- chan = findChannelByStates(device, new int[]{
- BluetoothHealth.STATE_CHANNEL_CONNECTING,
- BluetoothHealth.STATE_CHANNEL_DISCONNECTING
- });
- if (chan.isEmpty()) {
- updateAndSendIntent(device, newDeviceState, currDeviceState);
- }
- break;
- }
- if (sendIntent) {
- updateAndSendIntent(device, newDeviceState, currDeviceState);
- }
- }
-
- private void updateAndSendIntent(BluetoothDevice device, int newDeviceState,
- int prevDeviceState) {
- if (newDeviceState == BluetoothHealth.STATE_DISCONNECTED) {
- mHealthDevices.remove(device);
- } else {
- mHealthDevices.put(device, newDeviceState);
- }
- if (newDeviceState != prevDeviceState
- && newDeviceState == BluetoothHealth.STATE_CONNECTED) {
- MetricsLogger.logProfileConnectionEvent(BluetoothMetricsProto.ProfileId.HEALTH);
- }
- }
-
- /**
- * This function converts the channel connection state to device connection state.
- *
- * @param state
- * @return
- */
- private int convertState(int state) {
- switch (state) {
- case BluetoothHealth.STATE_CHANNEL_CONNECTED:
- return BluetoothHealth.STATE_CONNECTED;
- case BluetoothHealth.STATE_CHANNEL_CONNECTING:
- return BluetoothHealth.STATE_CONNECTING;
- case BluetoothHealth.STATE_CHANNEL_DISCONNECTING:
- return BluetoothHealth.STATE_DISCONNECTING;
- case BluetoothHealth.STATE_CHANNEL_DISCONNECTED:
- return BluetoothHealth.STATE_DISCONNECTED;
- }
- Log.e(TAG, "Mismatch in Channel and Health Device State: " + state);
- return BluetoothHealth.STATE_DISCONNECTED;
- }
-
- private int convertRoleToHal(int role) {
- if (role == BluetoothHealth.SOURCE_ROLE) {
- return MDEP_ROLE_SOURCE;
- }
- if (role == BluetoothHealth.SINK_ROLE) {
- return MDEP_ROLE_SINK;
- }
- Log.e(TAG, "unkonw role: " + role);
- return MDEP_ROLE_SINK;
- }
-
- private int convertChannelTypeToHal(int channelType) {
- if (channelType == BluetoothHealth.CHANNEL_TYPE_RELIABLE) {
- return CHANNEL_TYPE_RELIABLE;
- }
- if (channelType == BluetoothHealth.CHANNEL_TYPE_STREAMING) {
- return CHANNEL_TYPE_STREAMING;
- }
- if (channelType == BluetoothHealth.CHANNEL_TYPE_ANY) {
- return CHANNEL_TYPE_ANY;
- }
- Log.e(TAG, "unkonw channel type: " + channelType);
- return CHANNEL_TYPE_ANY;
- }
-
- private HealthChannel findChannelById(int id) {
- for (HealthChannel chan : mHealthChannels) {
- if (chan.mChannelId == id) {
- return chan;
- }
- }
- Log.e(TAG, "No channel found by id: " + id);
- return null;
- }
-
- private List<HealthChannel> findChannelByStates(BluetoothDevice device, int[] states) {
- List<HealthChannel> channels = new ArrayList<HealthChannel>();
- for (HealthChannel chan : mHealthChannels) {
- if (chan.mDevice.equals(device)) {
- for (int state : states) {
- if (chan.mState == state) {
- channels.add(chan);
- }
- }
- }
- }
- return channels;
- }
-
- private int getConnectionState(BluetoothDevice device) {
- if (mHealthDevices.get(device) == null) {
- return BluetoothHealth.STATE_DISCONNECTED;
- }
- return mHealthDevices.get(device);
- }
-
- List<BluetoothDevice> lookupHealthDevicesMatchingStates(int[] states) {
- List<BluetoothDevice> healthDevices = new ArrayList<BluetoothDevice>();
-
- for (BluetoothDevice device : mHealthDevices.keySet()) {
- int healthDeviceState = getConnectionState(device);
- for (int state : states) {
- if (state == healthDeviceState) {
- healthDevices.add(device);
- break;
- }
- }
- }
- return healthDevices;
- }
-
- @Override
- public void dump(StringBuilder sb) {
- super.dump(sb);
- println(sb, "mHealthChannels:");
- for (HealthChannel channel : mHealthChannels) {
- println(sb, " " + channel);
- }
- println(sb, "mApps:");
- for (BluetoothHealthAppConfiguration conf : mApps.keySet()) {
- println(sb, " " + conf + " : " + mApps.get(conf));
- }
- println(sb, "mHealthDevices:");
- for (BluetoothDevice device : mHealthDevices.keySet()) {
- println(sb, " " + device + " : " + mHealthDevices.get(device));
- }
- }
-
- private static class AppInfo {
- private IBluetoothHealthCallback mCallback;
- private BluetoothHealthDeathRecipient mRcpObj;
- private int mAppId;
-
- private AppInfo(IBluetoothHealthCallback callback) {
- mCallback = callback;
- mRcpObj = null;
- mAppId = -1;
- }
-
- private void cleanup() {
- if (mCallback != null) {
- if (mRcpObj != null) {
- IBinder binder = mCallback.asBinder();
- try {
- binder.unlinkToDeath(mRcpObj, 0);
- } catch (NoSuchElementException e) {
- Log.e(TAG, "No death recipient registered" + e);
- }
- mRcpObj.cleanup();
- mRcpObj = null;
- }
- mCallback = null;
- } else if (mRcpObj != null) {
- mRcpObj.cleanup();
- mRcpObj = null;
- }
- }
- }
-
- private class HealthChannel {
- private ParcelFileDescriptor mChannelFd;
- private BluetoothDevice mDevice;
- private BluetoothHealthAppConfiguration mConfig;
- // BluetoothHealth channel state
- private int mState;
- private int mChannelType;
- private int mChannelId;
-
- private HealthChannel(BluetoothDevice device, BluetoothHealthAppConfiguration config,
- int channelType) {
- mChannelFd = null;
- mDevice = device;
- mConfig = config;
- mState = BluetoothHealth.STATE_CHANNEL_DISCONNECTED;
- mChannelType = channelType;
- mChannelId = -1;
- }
- }
-
- // Channel state event from Hal
- private class ChannelStateEvent {
- int mAppId;
- byte[] mAddr;
- int mCfgIndex;
- int mChannelId;
- int mState;
- FileDescriptor mFd;
-
- private ChannelStateEvent(int appId, byte[] addr, int cfgIndex, int channelId, int state,
- FileDescriptor fileDescriptor) {
- mAppId = appId;
- mAddr = addr;
- mCfgIndex = cfgIndex;
- mState = state;
- mChannelId = channelId;
- mFd = fileDescriptor;
- }
- }
-
- // Constants matching Hal header file bt_hl.h
- // bthl_app_reg_state_t
- private static final int APP_REG_STATE_REG_SUCCESS = 0;
- private static final int APP_REG_STATE_REG_FAILED = 1;
- private static final int APP_REG_STATE_DEREG_SUCCESS = 2;
- private static final int APP_REG_STATE_DEREG_FAILED = 3;
-
- // bthl_channel_state_t
- private static final int CONN_STATE_CONNECTING = 0;
- private static final int CONN_STATE_CONNECTED = 1;
- private static final int CONN_STATE_DISCONNECTING = 2;
- private static final int CONN_STATE_DISCONNECTED = 3;
- private static final int CONN_STATE_DESTROYED = 4;
-
- // bthl_mdep_role_t
- private static final int MDEP_ROLE_SOURCE = 0;
- private static final int MDEP_ROLE_SINK = 1;
-
- // bthl_channel_type_t
- private static final int CHANNEL_TYPE_RELIABLE = 0;
- private static final int CHANNEL_TYPE_STREAMING = 1;
- private static final int CHANNEL_TYPE_ANY = 2;
-
- private static native void classInitNative();
-
- private native void initializeNative();
-
- private native void cleanupNative();
-
- private native int registerHealthAppNative(int dataType, int role, String name,
- int channelType);
-
- private native boolean unregisterHealthAppNative(int appId);
-
- private native int connectChannelNative(byte[] btAddress, int appId);
-
- private native boolean disconnectChannelNative(int channelId);
-
-}
diff --git a/src/com/android/bluetooth/hearingaid/HearingAidNativeInterface.java b/src/com/android/bluetooth/hearingaid/HearingAidNativeInterface.java
index e735a88..b32d304 100644
--- a/src/com/android/bluetooth/hearingaid/HearingAidNativeInterface.java
+++ b/src/com/android/bluetooth/hearingaid/HearingAidNativeInterface.java
@@ -23,11 +23,11 @@
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
-import android.support.annotation.VisibleForTesting;
import android.util.Log;
import com.android.bluetooth.Utils;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
/**
* HearingAid Native Interface to/from JNI.
@@ -69,7 +69,7 @@
*
* priorities to configure.
*/
- @VisibleForTesting (otherwise = VisibleForTesting.PACKAGE_PRIVATE)
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
public void init() {
initNative();
}
@@ -77,7 +77,7 @@
/**
* Cleanup the native interface.
*/
- @VisibleForTesting (otherwise = VisibleForTesting.PACKAGE_PRIVATE)
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
public void cleanup() {
cleanupNative();
}
@@ -88,7 +88,7 @@
* @param device the remote device
* @return true on success, otherwise false.
*/
- @VisibleForTesting (otherwise = VisibleForTesting.PACKAGE_PRIVATE)
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
public boolean connectHearingAid(BluetoothDevice device) {
return connectHearingAidNative(getByteAddress(device));
}
@@ -99,7 +99,7 @@
* @param device the remote device
* @return true on success, otherwise false.
*/
- @VisibleForTesting (otherwise = VisibleForTesting.PACKAGE_PRIVATE)
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
public boolean disconnectHearingAid(BluetoothDevice device) {
return disconnectHearingAidNative(getByteAddress(device));
}
@@ -110,27 +110,16 @@
* @param device the remote device
* @return true on success, otherwise false.
*/
- @VisibleForTesting (otherwise = VisibleForTesting.PACKAGE_PRIVATE)
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
public boolean addToWhiteList(BluetoothDevice device) {
return addToWhiteListNative(getByteAddress(device));
}
/**
- * Remove a hearing aid device from white list.
- *
- * @param device the remote device
- * @return true on success, otherwise false.
- */
- @VisibleForTesting (otherwise = VisibleForTesting.PACKAGE_PRIVATE)
- public boolean removeFromWhiteList(BluetoothDevice device) {
- return removeFromWhiteListNative(getByteAddress(device));
- }
-
- /**
* Sets the HearingAid volume
* @param volume
*/
- @VisibleForTesting (otherwise = VisibleForTesting.PACKAGE_PRIVATE)
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
public void setVolume(int volume) {
setVolumeNative(volume);
}
@@ -191,6 +180,5 @@
private native boolean connectHearingAidNative(byte[] address);
private native boolean disconnectHearingAidNative(byte[] address);
private native boolean addToWhiteListNative(byte[] address);
- private native boolean removeFromWhiteListNative(byte[] address);
private native void setVolumeNative(int volume);
}
diff --git a/src/com/android/bluetooth/hearingaid/HearingAidService.java b/src/com/android/bluetooth/hearingaid/HearingAidService.java
index 7e2c21b..c38f92c 100644
--- a/src/com/android/bluetooth/hearingaid/HearingAidService.java
+++ b/src/com/android/bluetooth/hearingaid/HearingAidService.java
@@ -28,15 +28,17 @@
import android.media.AudioManager;
import android.os.HandlerThread;
import android.os.ParcelUuid;
-import android.provider.Settings;
-import android.support.annotation.VisibleForTesting;
import android.util.Log;
+import android.util.StatsLog;
import com.android.bluetooth.BluetoothMetricsProto;
import com.android.bluetooth.Utils;
+import com.android.bluetooth.a2dp.A2dpService;
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 java.util.ArrayList;
import java.util.HashMap;
@@ -51,16 +53,12 @@
* @hide
*/
public class HearingAidService extends ProfileService {
- private static final boolean DBG = false;
+ private static final boolean DBG = true;
private static final String TAG = "HearingAidService";
// Upper limit of all HearingAid devices: Bonded or Connected
private static final int MAX_HEARING_AID_STATE_MACHINES = 10;
private static HearingAidService sHearingAidService;
- @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
- static int sConnectTimeoutForEachSideMs = 8000;
- @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
- static int sCheckWhitelistTimeoutMs = 16000;
private AdapterService mAdapterService;
private HandlerThread mStateMachinesThread;
@@ -75,11 +73,14 @@
new HashMap<>();
private final Map<BluetoothDevice, Long> mDeviceHiSyncIdMap = new ConcurrentHashMap<>();
private final Map<BluetoothDevice, Integer> mDeviceCapabilitiesMap = new HashMap<>();
+ private final Map<Long, Boolean> mHiSyncIdConnectedMap = new HashMap<>();
private long mActiveDeviceHiSyncId = BluetoothHearingAid.HI_SYNC_ID_INVALID;
private BroadcastReceiver mBondStateChangedReceiver;
private BroadcastReceiver mConnectionStateChangedReceiver;
+ private final ServiceFactory mFactory = new ServiceFactory();
+
@Override
protected IProfileServiceBinder initBinder() {
return new BluetoothHearingAidBinder(this);
@@ -116,9 +117,10 @@
mStateMachinesThread = new HandlerThread("HearingAidService.StateMachines");
mStateMachinesThread.start();
- // Clear HiSyncId map and capabilities map
+ // Clear HiSyncId map, capabilities map and HiSyncId Connected map
mDeviceHiSyncIdMap.clear();
mDeviceCapabilitiesMap.clear();
+ mHiSyncIdConnectedMap.clear();
// Setup broadcast receivers
IntentFilter filter = new IntentFilter();
@@ -171,9 +173,10 @@
mStateMachines.clear();
}
- // Clear HiSyncId map and capabilities map
+ // Clear HiSyncId map, capabilities map and HiSyncId Connected map
mDeviceHiSyncIdMap.clear();
mDeviceCapabilitiesMap.clear();
+ mHiSyncIdConnectedMap.clear();
if (mStateMachinesThread != null) {
mStateMachinesThread.quitSafely();
@@ -248,6 +251,14 @@
}
}
+ synchronized (mStateMachines) {
+ HearingAidStateMachine smConnect = getOrCreateStateMachine(device);
+ if (smConnect == null) {
+ Log.e(TAG, "Cannot connect to " + device + " : no state machine");
+ }
+ smConnect.sendMessage(HearingAidStateMachine.CONNECT);
+ }
+
for (BluetoothDevice storedDevice : mDeviceHiSyncIdMap.keySet()) {
if (device.equals(storedDevice)) {
continue;
@@ -260,27 +271,14 @@
Log.e(TAG, "Ignored connect request for " + device + " : no state machine");
continue;
}
- sm.sendMessage(HearingAidStateMachine.CONNECT,
- sConnectTimeoutForEachSideMs);
- sm.sendMessageDelayed(HearingAidStateMachine.CHECK_WHITELIST_CONNECTION,
- sCheckWhitelistTimeoutMs);
+ sm.sendMessage(HearingAidStateMachine.CONNECT);
}
- break;
+ if (hiSyncId == BluetoothHearingAid.HI_SYNC_ID_INVALID
+ && !device.equals(storedDevice)) {
+ break;
+ }
}
}
-
- synchronized (mStateMachines) {
- HearingAidStateMachine smConnect = getOrCreateStateMachine(device);
- if (smConnect == null) {
- Log.e(TAG, "Cannot connect to " + device + " : no state machine");
- } else {
- smConnect.sendMessage(HearingAidStateMachine.CONNECT,
- sConnectTimeoutForEachSideMs * 2);
- smConnect.sendMessageDelayed(HearingAidStateMachine.CHECK_WHITELIST_CONNECTION,
- sCheckWhitelistTimeoutMs);
- }
- }
-
return true;
}
@@ -330,13 +328,28 @@
}
/**
+ * Check any peer device is connected.
+ * The check considers any peer device is connected.
+ *
+ * @param device the peer device to connect to
+ * @return true if there are any peer device connected.
+ */
+ public boolean isConnectedPeerDevices(BluetoothDevice device) {
+ long hiSyncId = getHiSyncId(device);
+ if (getConnectedPeerDevices(hiSyncId).isEmpty()) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
* Check whether can connect to a peer device.
* The check considers a number of factors during the evaluation.
*
* @param device the peer device to connect to
* @return true if connection is allowed, otherwise false
*/
- @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
public boolean okToConnect(BluetoothDevice device) {
// Check if this is an incoming connection in Quiet mode.
if (mAdapterService.isQuietModeEnabled()) {
@@ -344,18 +357,24 @@
return false;
}
// Check priority and accept or reject the connection.
+ // Note: Logic can be simplified, but keeping it this way for readability
int priority = getPriority(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);
+ // If priority is undefined, it is likely that service discovery has not completed and peer
+ // initiated the connection. Allow this connection only if the device is bonded or bonding
+ boolean serviceDiscoveryPending = (priority == BluetoothProfile.PRIORITY_UNDEFINED)
+ && (bondState == BluetoothDevice.BOND_BONDING
+ || bondState == BluetoothDevice.BOND_BONDED);
+ // Also allow connection when device is bonded/bonding and priority is ON/AUTO_CONNECT.
+ boolean isEnabled = (priority == BluetoothProfile.PRIORITY_ON
+ || priority == BluetoothProfile.PRIORITY_AUTO_CONNECT)
+ && (bondState == BluetoothDevice.BOND_BONDED
+ || bondState == BluetoothDevice.BOND_BONDING);
+ if (!serviceDiscoveryPending && !isEnabled) {
+ // Otherwise, reject the connection if no service discovery is pending and priority is
+ // neither PRIORITY_ON nor PRIORITY_AUTO_CONNECT
+ Log.w(TAG, "okToConnect: return false, priority=" + priority + ", bondState="
+ + bondState);
return false;
}
return true;
@@ -439,20 +458,18 @@
*/
public boolean setPriority(BluetoothDevice device, int priority) {
enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
- Settings.Global.putInt(getContentResolver(),
- Settings.Global.getBluetoothHearingAidPriorityKey(device.getAddress()), priority);
if (DBG) {
Log.d(TAG, "Saved priority " + device + " = " + priority);
}
+ mAdapterService.getDatabase()
+ .setProfilePriority(device, BluetoothProfile.HEARING_AID, priority);
return true;
}
public int getPriority(BluetoothDevice device) {
enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
- int priority = Settings.Global.getInt(getContentResolver(),
- Settings.Global.getBluetoothHearingAidPriorityKey(device.getAddress()),
- BluetoothProfile.PRIORITY_UNDEFINED);
- return priority;
+ return mAdapterService.getDatabase()
+ .getProfilePriority(device, BluetoothProfile.HEARING_AID);
}
void setVolume(int volume) {
@@ -495,6 +512,15 @@
Long deviceHiSyncId = mDeviceHiSyncIdMap.getOrDefault(device,
BluetoothHearingAid.HI_SYNC_ID_INVALID);
if (deviceHiSyncId != mActiveDeviceHiSyncId) {
+ // Give an early notification to A2DP that active device is being switched
+ // to Hearing Aids before the Audio Service.
+ final A2dpService a2dpService = mFactory.getA2dpService();
+ if (a2dpService != null) {
+ if (DBG) {
+ Log.d(TAG, "earlyNotifyHearingAidActive for " + device);
+ }
+ a2dpService.earlyNotifyHearingAidActive();
+ }
mActiveDeviceHiSyncId = deviceHiSyncId;
reportActiveDevice(device);
}
@@ -509,7 +535,7 @@
* device; the second element is the right active device. If either or both side
* is not active, it will be null on that position
*/
- List<BluetoothDevice> getActiveDevices() {
+ public List<BluetoothDevice> getActiveDevices() {
if (DBG) {
Log.d(TAG, "getActiveDevices");
}
@@ -612,6 +638,9 @@
Log.d(TAG, "reportActiveDevice(" + device + ")");
}
+ StatsLog.write(StatsLog.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);
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
@@ -732,10 +761,15 @@
MetricsLogger.logProfileConnectionEvent(
BluetoothMetricsProto.ProfileId.HEARING_AID);
}
- setActiveDevice(device);
+ if (!mHiSyncIdConnectedMap.getOrDefault(myHiSyncId, false)) {
+ setActiveDevice(device);
+ mHiSyncIdConnectedMap.put(myHiSyncId, true);
+ }
}
if (fromState == BluetoothProfile.STATE_CONNECTED && getConnectedDevices().isEmpty()) {
setActiveDevice(null);
+ long myHiSyncId = getHiSyncId(device);
+ mHiSyncIdConnectedMap.put(myHiSyncId, false);
}
// Check if the device is disconnected - if unbond, remove the state machine
if (toState == BluetoothProfile.STATE_DISCONNECTED) {
diff --git a/src/com/android/bluetooth/hearingaid/HearingAidStateMachine.java b/src/com/android/bluetooth/hearingaid/HearingAidStateMachine.java
index 3e0d617..602ca5c 100644
--- a/src/com/android/bluetooth/hearingaid/HearingAidStateMachine.java
+++ b/src/com/android/bluetooth/hearingaid/HearingAidStateMachine.java
@@ -51,10 +51,10 @@
import android.content.Intent;
import android.os.Looper;
import android.os.Message;
-import android.support.annotation.VisibleForTesting;
import android.util.Log;
import com.android.bluetooth.btservice.ProfileService;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.State;
import com.android.internal.util.StateMachine;
@@ -69,21 +69,18 @@
static final int CONNECT = 1;
static final int DISCONNECT = 2;
- static final int CHECK_WHITELIST_CONNECTION = 3;
@VisibleForTesting
static final int STACK_EVENT = 101;
private static final int CONNECT_TIMEOUT = 201;
- @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
- static int sConnectTimeoutMs = 16000;
- @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
- static int sDisconnectTimeoutMs = 16000;
+ // NOTE: the value is not "final" - it is modified in the unit tests
+ @VisibleForTesting
+ static int sConnectTimeoutMs = 30000; // 30s
private Disconnected mDisconnected;
private Connecting mConnecting;
private Disconnecting mDisconnecting;
private Connected mConnected;
- private int mConnectionState = BluetoothProfile.STATE_DISCONNECTED;
private int mLastConnectionState = -1;
private HearingAidService mService;
@@ -135,13 +132,13 @@
public void enter() {
Log.i(TAG, "Enter Disconnected(" + mDevice + "): " + messageWhatToString(
getCurrentMessage().what));
- mConnectionState = BluetoothProfile.STATE_DISCONNECTED;
removeDeferredMessages(DISCONNECT);
if (mLastConnectionState != -1) {
// Don't broadcast during startup
- broadcastConnectionState(mConnectionState, mLastConnectionState);
+ broadcastConnectionState(BluetoothProfile.STATE_DISCONNECTED,
+ mLastConnectionState);
}
}
@@ -174,12 +171,6 @@
case DISCONNECT:
Log.w(TAG, "Disconnected: DISCONNECT ignored: " + mDevice);
break;
- case CHECK_WHITELIST_CONNECTION:
- if (mService.getConnectedDevices().isEmpty()) {
- log("No device connected, remove this device from white list");
- mNativeInterface.removeFromWhiteList(mDevice);
- }
- break;
case STACK_EVENT:
HearingAidStackEvent event = (HearingAidStackEvent) message.obj;
if (DBG) {
@@ -246,11 +237,8 @@
public void enter() {
Log.i(TAG, "Enter Connecting(" + mDevice + "): "
+ messageWhatToString(getCurrentMessage().what));
- int timeout = getCurrentMessage().arg1 != 0
- ? getCurrentMessage().arg1 : sConnectTimeoutMs;
- sendMessageDelayed(CONNECT_TIMEOUT, timeout);
- mConnectionState = BluetoothProfile.STATE_CONNECTING;
- broadcastConnectionState(mConnectionState, mLastConnectionState);
+ sendMessageDelayed(CONNECT_TIMEOUT, sConnectTimeoutMs);
+ broadcastConnectionState(BluetoothProfile.STATE_CONNECTING, mLastConnectionState);
}
@Override
@@ -271,13 +259,18 @@
deferMessage(message);
break;
case CONNECT_TIMEOUT:
- Log.w(TAG, "Connecting connection timeout: " + mDevice + ". Try whitelist");
+ Log.w(TAG, "Connecting connection timeout: " + mDevice);
mNativeInterface.disconnectHearingAid(mDevice);
- mNativeInterface.addToWhiteList(mDevice);
- transitionTo(mDisconnected);
- break;
- case CHECK_WHITELIST_CONNECTION:
- deferMessage(message);
+ if (mService.isConnectedPeerDevices(mDevice)) {
+ Log.w(TAG, "One side connection timeout: " + mDevice + ". Try whitelist");
+ mNativeInterface.addToWhiteList(mDevice);
+ }
+ HearingAidStackEvent disconnectEvent =
+ new HearingAidStackEvent(
+ HearingAidStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
+ disconnectEvent.device = mDevice;
+ disconnectEvent.valueInt1 = HearingAidStackEvent.CONNECTION_STATE_DISCONNECTED;
+ sendMessage(STACK_EVENT, disconnectEvent);
break;
case DISCONNECT:
log("Connecting: connection canceled to " + mDevice);
@@ -334,9 +327,8 @@
public void enter() {
Log.i(TAG, "Enter Disconnecting(" + mDevice + "): "
+ messageWhatToString(getCurrentMessage().what));
- sendMessageDelayed(CONNECT_TIMEOUT, sDisconnectTimeoutMs);
- mConnectionState = BluetoothProfile.STATE_DISCONNECTING;
- broadcastConnectionState(mConnectionState, mLastConnectionState);
+ sendMessageDelayed(CONNECT_TIMEOUT, sConnectTimeoutMs);
+ broadcastConnectionState(BluetoothProfile.STATE_DISCONNECTING, mLastConnectionState);
}
@Override
@@ -433,9 +425,8 @@
public void enter() {
Log.i(TAG, "Enter Connected(" + mDevice + "): "
+ messageWhatToString(getCurrentMessage().what));
- mConnectionState = BluetoothProfile.STATE_CONNECTED;
removeDeferredMessages(CONNECT);
- broadcastConnectionState(mConnectionState, mLastConnectionState);
+ broadcastConnectionState(BluetoothProfile.STATE_CONNECTED, mLastConnectionState);
}
@Override
@@ -504,7 +495,20 @@
}
int getConnectionState() {
- return mConnectionState;
+ String currentState = getCurrentState().getName();
+ switch (currentState) {
+ case "Disconnected":
+ return BluetoothProfile.STATE_DISCONNECTED;
+ case "Connecting":
+ return BluetoothProfile.STATE_CONNECTING;
+ case "Connected":
+ return BluetoothProfile.STATE_CONNECTED;
+ case "Disconnecting":
+ return BluetoothProfile.STATE_DISCONNECTING;
+ default:
+ Log.e(TAG, "Bad currentState: " + currentState);
+ return BluetoothProfile.STATE_DISCONNECTED;
+ }
}
BluetoothDevice getDevice() {
diff --git a/src/com/android/bluetooth/hfp/HeadsetA2dpSync.java b/src/com/android/bluetooth/hfp/HeadsetA2dpSync.java
index 1b46dd7..8adbdf2 100644
--- a/src/com/android/bluetooth/hfp/HeadsetA2dpSync.java
+++ b/src/com/android/bluetooth/hfp/HeadsetA2dpSync.java
@@ -194,10 +194,9 @@
break;
case BluetoothA2dp.STATE_PLAYING:
mA2dpConnState.put(device, A2DP_PLAYING);
- // if call/ ring is ongoing and there is HFP connected device and we received playing,
+ // if call/ ring is ongoing and we received playing,
// we need to suspend
- if ((mHeadsetService.isInCall() || mHeadsetService.isRinging()) &&
- mHeadsetService.getConnectedDevices().size() != 0) {
+ if (mHeadsetService.isInCall() || mHeadsetService.isRinging()) {
Log.d(TAG," CALL/Ring is active ");
suspendA2DP(A2DP_SUSPENDED_BY_CS_CALL, mDummyDevice);
}
diff --git a/src/com/android/bluetooth/hfp/HeadsetCallState.java b/src/com/android/bluetooth/hfp/HeadsetCallState.java
index 3afd3c4..df45e75 100644
--- a/src/com/android/bluetooth/hfp/HeadsetCallState.java
+++ b/src/com/android/bluetooth/hfp/HeadsetCallState.java
@@ -42,13 +42,19 @@
* Phone number type
*/
int mType;
+ /**
+ * Caller display name
+ */
+ String mName;
- HeadsetCallState(int numActive, int numHeld, int callState, String number, int type) {
+ HeadsetCallState(int numActive, int numHeld, int callState, String number, int type,
+ String name) {
mNumActive = numActive;
mNumHeld = numHeld;
mCallState = callState;
mNumber = number;
mType = type;
+ mName = name;
}
@Override
@@ -69,7 +75,13 @@
} else {
builder.append("***");
}
- builder.append(mNumber).append(", type=").append(mType).append("]");
+ builder.append(", type=").append(mType).append(", name=");
+ if (mName == null) {
+ builder.append("null");
+ } else {
+ builder.append("***");
+ }
+ builder.append("]");
}
@Override
@@ -83,11 +95,11 @@
HeadsetCallState that = (HeadsetCallState) object;
return mNumActive == that.mNumActive && mNumHeld == that.mNumHeld
&& mCallState == that.mCallState && Objects.equals(mNumber, that.mNumber)
- && mType == that.mType;
+ && mType == that.mType && Objects.equals(mName, that.mName);
}
@Override
public int hashCode() {
- return Objects.hash(mNumActive, mNumHeld, mCallState, mNumber, mType);
+ return Objects.hash(mNumActive, mNumHeld, mCallState, mNumber, mType, mName);
}
}
diff --git a/src/com/android/bluetooth/hfp/HeadsetNativeInterface.java b/src/com/android/bluetooth/hfp/HeadsetNativeInterface.java
index b236d51..61c1163 100644
--- a/src/com/android/bluetooth/hfp/HeadsetNativeInterface.java
+++ b/src/com/android/bluetooth/hfp/HeadsetNativeInterface.java
@@ -18,10 +18,10 @@
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
-import android.support.annotation.VisibleForTesting;
import android.util.Log;
import com.android.bluetooth.Utils;
+import com.android.internal.annotations.VisibleForTesting;
/**
* Defines native calls that are used by state machine/service to either send or receive
@@ -127,9 +127,9 @@
sendMessageToService(event);
}
- private void onNoiceReductionEnable(boolean enable, byte[] address) {
+ private void onNoiseReductionEnable(boolean enable, byte[] address) {
HeadsetStackEvent event =
- new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_NOICE_REDUCTION, enable ? 1 : 0,
+ new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_NOISE_REDUCTION, enable ? 1 : 0,
getDevice(address));
sendMessageToService(event);
}
@@ -419,7 +419,7 @@
@VisibleForTesting
public boolean phoneStateChange(BluetoothDevice device, HeadsetCallState callState) {
return phoneStateChangeNative(callState.mNumActive, callState.mNumHeld,
- callState.mCallState, callState.mNumber, callState.mType,
+ callState.mCallState, callState.mNumber, callState.mType, callState.mName,
Utils.getByteAddress(device));
}
@@ -493,7 +493,7 @@
private native boolean copsResponseNative(String operatorName, byte[] address);
private native boolean phoneStateChangeNative(int numActive, int numHeld, int callState,
- String number, int type, byte[] address);
+ String number, int type, String name, byte[] address);
private native boolean setScoAllowedNative(boolean value);
diff --git a/src/com/android/bluetooth/hfp/HeadsetPhoneState.java b/src/com/android/bluetooth/hfp/HeadsetPhoneState.java
index 93012e2..23baac4 100644
--- a/src/com/android/bluetooth/hfp/HeadsetPhoneState.java
+++ b/src/com/android/bluetooth/hfp/HeadsetPhoneState.java
@@ -22,7 +22,6 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Looper;
-import android.support.annotation.VisibleForTesting;
import android.telephony.PhoneStateListener;
import android.telephony.ServiceState;
import android.telephony.SignalStrength;
@@ -31,6 +30,7 @@
import android.telephony.TelephonyManager;
import android.util.Log;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.IccCardConstants;
import com.android.internal.telephony.TelephonyIntents;
import com.android.internal.telephony.PhoneConstants;
@@ -83,7 +83,7 @@
// SIM is present
private int SIM_PRESENT = 1;
// Array to keep the SIM status
- private int[] mSimStatus = {0, 0};
+ private int[] mSimStatus;
private final HashMap<BluetoothDevice, Integer> mDeviceEventMap = new HashMap<>();
private PhoneStateListener mPhoneStateListener;
@@ -97,7 +97,8 @@
Objects.requireNonNull(mTelephonyManager, "TELEPHONY_SERVICE is null");
// Register for SubscriptionInfo list changes which is guaranteed to invoke
// onSubscriptionInfoChanged and which in turns calls loadInBackgroud.
- mSubscriptionManager = SubscriptionManager.from(mHeadsetService);
+ mSubscriptionManager = (SubscriptionManager) mHeadsetService.
+ getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE);
Objects.requireNonNull(mSubscriptionManager, "TELEPHONY_SUBSCRIPTION_SERVICE is null");
// Initialize subscription on the handler thread
mOnSubscriptionsChangedListener = new HeadsetPhoneStateOnSubscriptionChangedListener(
@@ -105,16 +106,17 @@
mSubscriptionManager.addOnSubscriptionsChangedListener(mOnSubscriptionsChangedListener);
IntentFilter simStateChangedFilter =
new IntentFilter(TelephonyIntents.ACTION_SIM_STATE_CHANGED);
+ mSimStatus = new int [mTelephonyManager.getPhoneCount()];
//Record the SIM states upon BT reset
try {
- for (int slotIndex = 0; slotIndex < mSimStatus.length; slotIndex++) {
- if (mTelephonyManager.getSimState(slotIndex) ==
+ for (int i = 0; i < mSimStatus.length; i++) {
+ if (mTelephonyManager.getSimState(i) ==
TelephonyManager.SIM_STATE_READY) {
- Log.d(TAG, "The sim in slotIndex: " + slotIndex + " is present");
- mSimStatus[slotIndex] = SIM_PRESENT;
+ Log.d(TAG, "The sim in i: " + i + " is present");
+ mSimStatus[i] = SIM_PRESENT;
} else {
- Log.d(TAG, "The sim in slotIndex: " + slotIndex + " is absent");
- mSimStatus[slotIndex] = SIM_ABSENT;
+ Log.d(TAG, "The sim in i: " + i + " is absent");
+ mSimStatus[i] = SIM_ABSENT;
}
}
mHeadsetService.registerReceiver(mPhoneStateChangeReceiver, simStateChangedFilter);
@@ -200,7 +202,7 @@
Log.e(TAG, "mTelephonyManager is null, "
+ "cannot start listening for phone state changes");
} else {
- mPhoneStateListener = new HeadsetPhoneStateListener(subId,
+ mPhoneStateListener = new HeadsetPhoneStateListener(
mHeadsetService.getStateMachinesThreadLooper());
try {
mTelephonyManager.listen(mPhoneStateListener, events);
@@ -238,6 +240,10 @@
mPhoneStateListener = null;
}
+ public boolean isValidPhoneId(int phoneId) {
+ return phoneId >= 0 && phoneId < mTelephonyManager.getPhoneCount();
+ }
+
private final BroadcastReceiver mPhoneStateChangeReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@@ -246,22 +252,35 @@
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.
+
+ // TODO (b/122116049) during platform update, a case emerged for an incoming
+ // slotId value of -1; it would likely be beneficial to code defensively
+ // because of this case's possibility from the caller, but the current root
+ // cause is as of yet unidentified
if (IccCardConstants.INTENT_VALUE_ICC_LOADED.equals(stateExtra)) {
- final int slotId = intent.getIntExtra(PhoneConstants.SLOT_KEY,
+ final int phoneId = intent.getIntExtra(PhoneConstants.PHONE_KEY,
SubscriptionManager.getDefaultVoicePhoneId());
- Log.d(TAG, "SIM loaded, making mIsSimStateLoaded to true for slotId = "
- + slotId);
- mSimStatus[slotId] = SIM_PRESENT;
+ if (isValidPhoneId(phoneId) != true) {
+ Log.d(TAG, "Received invalid phoneId " + phoneId +" for SIM loaded, no action");
+ return;
+ }
+ Log.d(TAG, "SIM loaded, making mIsSimStateLoaded to true for phoneId = "
+ + phoneId);
+ mSimStatus[phoneId] = SIM_PRESENT;
mIsSimStateLoaded = true;
sendDeviceStateChanged();
} else if (IccCardConstants.INTENT_VALUE_ICC_ABSENT.equals(stateExtra)
|| IccCardConstants.INTENT_VALUE_ICC_UNKNOWN.equals(stateExtra)
|| IccCardConstants.INTENT_VALUE_ICC_CARD_IO_ERROR.equals(stateExtra)) {
- final int slotId = intent.getIntExtra(PhoneConstants.SLOT_KEY,
+ final int phoneId = intent.getIntExtra(PhoneConstants.PHONE_KEY,
SubscriptionManager.getDefaultVoicePhoneId());
- Log.d(TAG, "SIM unloaded, making mIsSimStateLoaded to false for slotId = "
- + slotId);
- mSimStatus[slotId] = SIM_ABSENT;
+ if (isValidPhoneId(phoneId) != true) {
+ Log.d(TAG, "Received invalid phoneId " + phoneId +" for SIM unloaded, no action");
+ return;
+ }
+ Log.d(TAG, "SIM unloaded, making mIsSimStateLoaded to false for phoneId = "
+ + phoneId);
+ mSimStatus[phoneId] = SIM_ABSENT;
mIsSimStateLoaded = false;
for (int i = 0; i < mSimStatus.length; i++) {
if (mSimStatus[i] == SIM_PRESENT) {
@@ -285,7 +304,7 @@
return mNumActive;
}
- @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
public void setNumActiveCall(int numActive) {
mNumActive = numActive;
}
@@ -304,7 +323,7 @@
return mCallState;
}
- @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
public void setCallState(int callState) {
mCallState = callState;
}
@@ -313,7 +332,7 @@
return mNumHeld;
}
- @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
public void setNumHeldCall(int numHeldCall) {
mNumHeld = numHeldCall;
}
@@ -347,7 +366,7 @@
*
* @param batteryLevel battery level value
*/
- @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
public void setCindBatteryCharge(int batteryLevel) {
if (mCindBatteryCharge != batteryLevel) {
mCindBatteryCharge = batteryLevel;
@@ -393,8 +412,8 @@
}
private class HeadsetPhoneStateListener extends PhoneStateListener {
- HeadsetPhoneStateListener(Integer subId, Looper looper) {
- super(subId, looper);
+ HeadsetPhoneStateListener(Looper looper) {
+ super(looper);
}
@Override
diff --git a/src/com/android/bluetooth/hfp/HeadsetService.java b/src/com/android/bluetooth/hfp/HeadsetService.java
index f7fc3c2..0ca6734 100644
--- a/src/com/android/bluetooth/hfp/HeadsetService.java
+++ b/src/com/android/bluetooth/hfp/HeadsetService.java
@@ -42,9 +42,9 @@
import android.os.ServiceManager;
import android.os.SystemProperties;
import android.os.UserHandle;
-import android.provider.Settings;
import android.telecom.PhoneAccount;
import android.util.Log;
+import android.util.StatsLog;
import com.android.bluetooth.BluetoothMetricsProto;
import com.android.bluetooth.Utils;
@@ -56,6 +56,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
+import java.util.concurrent.Executor;
import java.util.HashMap;
import java.util.List;
import java.util.Objects;
@@ -66,7 +67,8 @@
* Provides Bluetooth Headset and Handsfree profile, as a service in the Bluetooth application.
*
* Three modes for SCO audio:
- * Mode 1: Telecom call through {@link #phoneStateChanged(int, int, int, String, int, boolean)}
+ * Mode 1: Telecom call through {@link #phoneStateChanged(int, int, int, String, int, String,
+ * boolean)}
* Mode 2: Virtual call through {@link #startScoUsingVirtualVoiceCall()}
* Mode 3: Voice recognition through {@link #startVoiceRecognition(BluetoothDevice)}
*
@@ -125,6 +127,10 @@
private static HeadsetService sHeadsetService;
private boolean mDisconnectAll;
private boolean mIsTwsPlusEnabled = false;
+ private boolean mIsTwsPlusShoEnabled = false;
+ private vendorhfservice mVendorHf;
+ private Context mContext = null;
+ private AudioServerStateCallback mServerStateCallback = new AudioServerStateCallback();
@Override
public IProfileServiceBinder initBinder() {
@@ -138,6 +144,7 @@
throw new IllegalStateException("create() called twice");
}
mCreated = true;
+ mVendorHf = new vendorhfservice(this);
}
@Override
@@ -160,24 +167,34 @@
mSetMaxConfig = mMaxHeadsetConnections = mAdapterService.getMaxConnectedAudioDevices();
if(mAdapterService.isVendorIntfEnabled()) {
String twsPlusEnabled = SystemProperties.get("persist.vendor.btstack.enable.twsplus");
+ String twsPlusShoEnabled =
+ SystemProperties.get("persist.vendor.btstack.enable.twsplussho");
+
if (!twsPlusEnabled.isEmpty() && "true".equals(twsPlusEnabled)) {
mIsTwsPlusEnabled = true;
}
+ if (!twsPlusShoEnabled.isEmpty() && "true".equals(twsPlusShoEnabled)) {
+ if (mIsTwsPlusEnabled) {
+ mIsTwsPlusShoEnabled = true;
+ } else {
+ Log.e(TAG, "no TWS+ SHO without TWS+ support!");
+ mIsTwsPlusShoEnabled = false;
+ }
+ }
Log.i(TAG, "mIsTwsPlusEnabled: " + mIsTwsPlusEnabled);
- if (mIsTwsPlusEnabled){
+ Log.i(TAG, "mIsTwsPlusShoEnabled: " + mIsTwsPlusShoEnabled);
+ if (mIsTwsPlusEnabled && mMaxHeadsetConnections < 2){
//set MaxConn to 2 if TWSPLUS enabled
mMaxHeadsetConnections = 2;
}
- if (mMaxHeadsetConnections > 2) {
- //If the set config is more than 2
- //limit it to 2
- mMaxHeadsetConnections = 2;
- mSetMaxConfig = 2;
+ if (mIsTwsPlusShoEnabled && mMaxHeadsetConnections < 3) {
+ //set MaxConn to 3 if TWSPLUS enabled
+ mMaxHeadsetConnections = 3;
}
//Only if the User set config 1 and TWS+ is enabled leaves
//these maxConn at 2 and setMaxConfig to 1. this is to avoid
//connecting to more than 1 legacy device even though max conns
- //is set 2 because of TWS+ requirement
+ //is set 2 or 3because of TWS+ requirement
Log.d(TAG, "Max_HFP_Connections " + mMaxHeadsetConnections);
Log.d(TAG, "mSetMaxConfig " + mSetMaxConfig);
}
@@ -207,6 +224,16 @@
mStarted = true;
mHfpA2dpSyncInterface = new HeadsetA2dpSync(mSystemInterface, this);
+ if (mVendorHf != null) {
+ mVendorHf.init();
+ mVendorHf.enableSwb(isSwbEnabled());
+ }
+
+ Log.d(TAG, "registering audio server state callback");
+ mContext = getApplicationContext();
+ Executor exec = mContext.getMainExecutor();
+ mSystemInterface.getAudioManager().setAudioServerStateCallback(exec, mServerStateCallback);
+
Log.i(TAG, " HeadsetService Started ");
return true;
}
@@ -288,6 +315,9 @@
if (!mCreated) {
Log.w(TAG, "cleanup() called before create()");
}
+ if (mVendorHf != null) {
+ mVendorHf.cleanup();
+ }
mCreated = false;
}
@@ -325,7 +355,7 @@
getDevicesMatchingConnectionStates(CONNECTING_CONNECTED_STATES)) {
HeadsetStateMachine stateMachine = mStateMachines.get(device);
if (stateMachine == null) {
- return;
+ continue;
}
task.execute(stateMachine);
}
@@ -402,6 +432,30 @@
}
}
+ private class AudioServerStateCallback extends AudioManager.AudioServerStateCallback {
+ @Override
+ public void onAudioServerDown() {
+ Log.d(TAG, "notifying onAudioServerDown");
+ }
+
+ @Override
+ public void onAudioServerUp() {
+ Log.d(TAG, "notifying onAudioServerUp");
+ if (isAudioOn()) {
+ Log.d(TAG, "onAudioServerUp: Audio is On, Notify HeadsetStateMachine");
+ synchronized (mStateMachines) {
+ for (HeadsetStateMachine stateMachine : mStateMachines.values()) {
+ if (stateMachine.getAudioState()
+ == BluetoothHeadset.STATE_AUDIO_CONNECTED) {
+ stateMachine.onAudioServerUp();
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+
private final BroadcastReceiver mHeadsetReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@@ -419,8 +473,8 @@
+ ", scale=" + scale);
return;
}
- batteryLevel = batteryLevel * 5 / scale;
- mSystemInterface.getHeadsetPhoneState().setCindBatteryCharge(batteryLevel);
+ int cindBatteryLevel = Math.round(batteryLevel * 5 / ((float) scale));
+ mSystemInterface.getHeadsetPhoneState().setCindBatteryCharge(cindBatteryLevel);
break;
}
case AudioManager.VOLUME_CHANGED_ACTION: {
@@ -714,12 +768,12 @@
@Override
public void phoneStateChanged(int numActive, int numHeld, int callState, String number,
- int type) {
+ int type, String name) {
HeadsetService service = getService();
if (service == null) {
return;
}
- service.phoneStateChanged(numActive, numHeld, callState, number, type, false);
+ service.phoneStateChanged(numActive, numHeld, callState, number, type, name, false);
}
@Override
@@ -807,11 +861,41 @@
return null;
}
+ private BluetoothDevice getConnectedTwspDevice() {
+ List<BluetoothDevice> connDevices = getConnectedDevices();
+ int size = connDevices.size();
+ for(int i = 0; i < size; i++) {
+ BluetoothDevice ConnectedDevice = connDevices.get(i);
+ if (mAdapterService.isTwsPlusDevice(ConnectedDevice)) {
+ logD("getConnectedTwspDevice: found" + ConnectedDevice);
+ return ConnectedDevice;
+ }
+ }
+ return null;
+ }
+
+ /*
+ * This function determines possible connections allowed with both Legacy
+ * TWS+ earbuds.
+ * N is the maximum audio connections set (defaults to 5)
+ * In TWS+ connections
+ * - There can be only ONE set of TWS+ earbud connected at any point
+ * of time
+ * - Once one of the TWS+ earbud is connected, another slot will be
+ * reserved for the TWS+ peer earbud. Hence there can be maximum
+ * of N-2 legacy device connections when an earbud is connected.
+ * - If user wants to connect to another TWS+ earbud set. Existing TWS+
+ * connection need to be removed explicitly
+ * In Legacy(Non-TWS+) Connections
+ * -Maximum allowed connections N set by user is used to determine number
+ * of Legacy connections
+ */
private boolean isConnectionAllowed(BluetoothDevice device,
List<BluetoothDevice> connDevices
) {
AdapterService adapterService = AdapterService.getAdapterService();
boolean allowSecondHfConnection = false;
+ int reservedSlotForTwspPeer = 0;
if (!mIsTwsPlusEnabled && adapterService.isTwsPlusDevice(device)) {
logD("No TWSPLUS connections as It is not Enabled");
@@ -821,28 +905,51 @@
if (connDevices.size() == 0) {
allowSecondHfConnection = true;
} else {
- BluetoothDevice connectedDev = connDevices.get(0);
- if (adapterService.isTwsPlusDevice(connectedDev)) {
- //If connected device is TWSPlus device
- //Allow connection only if the outgoing is peer of TWS connected earbud
- if (adapterService.isTwsPlusDevice(device)&&
- adapterService.getTwsPlusPeerAddress(device).equals(connectedDev.getAddress())) {
- allowSecondHfConnection = true;
+ BluetoothDevice connectedTwspDev = getConnectedTwspDevice();
+ if (connectedTwspDev != null) {
+ // There is TWSP connected earbud
+ if (adapterService.isTwsPlusDevice(device)) {
+ if (adapterService.getTwsPlusPeerAddress
+ (device).equals(connectedTwspDev.getAddress())) {
+ //Allow connection only if the outgoing
+ //is peer of TWS connected earbud
+ allowSecondHfConnection = true;
+ } else {
+ allowSecondHfConnection = false;
+ }
} else {
- allowSecondHfConnection = false;
- if (connDevices.size() == 1) {
- mDisconnectAll = true;
+ reservedSlotForTwspPeer = 0;
+ if (getTwsPlusConnectedPeer(connectedTwspDev) == null) {
+ //Peer of Connected Tws+ device is not Connected
+ //yet, reserve one slot
+ reservedSlotForTwspPeer = 1;
+ }
+ if (connDevices.size() <
+ (mMaxHeadsetConnections - reservedSlotForTwspPeer)
+ && mIsTwsPlusShoEnabled) {
+ allowSecondHfConnection = true;
+ } else {
+ allowSecondHfConnection = false;
+ if (!mIsTwsPlusShoEnabled) {
+ logD("Not Allowed as TWS+ SHO is not enabled");
+ } else {
+ logD("Max Connections have reached");
+ }
}
}
} else {
- //if Connected device is not TWS
+ //There is no TWSP connected device
if (adapterService.isTwsPlusDevice(device)) {
- //outgoing connection is TWSP
- allowSecondHfConnection = false;
- if (connDevices.size() == 1) {
- //only if connected devices is 1
- //disconnect it and override it with tws+
- mDisconnectAll = true;
+ if (mIsTwsPlusShoEnabled) {
+ //outgoing connection is TWSP
+ if ((mMaxHeadsetConnections-connDevices.size()) >= 2) {
+ allowSecondHfConnection = true;
+ } else {
+ allowSecondHfConnection = false;
+ logD("Not enough available slots for TWSP");
+ }
+ } else {
+ allowSecondHfConnection = false;
}
} else {
//Outgoing connection is legacy device
@@ -858,7 +965,11 @@
"is"+ adapterService.isTwsPlusDevice(device));
Log.v(TAG, "TWS Peer Addr: " +
adapterService.getTwsPlusPeerAddress(device));
- Log.v(TAG, "Connected device" + connectedDev.getAddress());
+ if (connectedTwspDev != null) {
+ Log.v(TAG, "Connected device" + connectedTwspDev.getAddress());
+ } else {
+ Log.v(TAG, "No Connected TWSP devices");
+ }
}
Log.v(TAG, "allowSecondHfConnection: " + allowSecondHfConnection);
@@ -900,7 +1011,6 @@
}
List<BluetoothDevice> connectingConnectedDevices =
getAllDevicesMatchingConnectionStates(CONNECTING_CONNECTED_STATES);
- addAllDevicesPendingRetryConnect(connectingConnectedDevices);
boolean disconnectExisting = false;
mDisconnectAll = false;
if (connectingConnectedDevices.size() == 0) {
@@ -908,7 +1018,7 @@
}
if (!isConnectionAllowed(device, connectingConnectedDevices)) {
// When there is maximum one device, we automatically disconnect the current one
- if (mMaxHeadsetConnections == 1) {
+ if (mSetMaxConfig == 1) {
if (!mIsTwsPlusEnabled && mAdapterService.isTwsPlusDevice(device)) {
Log.w(TAG, "Connection attemp to TWS+ when not enabled, Rejecting it");
return false;
@@ -980,21 +1090,6 @@
return devices;
}
- private void addAllDevicesPendingRetryConnect(List<BluetoothDevice> devices) {
- enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- Log.d(TAG, " add all devices pending retry connect");
- synchronized (mStateMachines) {
- for (HeadsetStateMachine stateMachine : mStateMachines.values()) {
- BluetoothDevice device = stateMachine.getDevice();
- if ((stateMachine.isPendingRetryConnect() == true) &&
- (!devices.contains(device))) {
- devices.add(device);
- Log.d(TAG, " add pending retry connect device: " + device);
- }
- }
- }
- }
-
/**
* Helper method to get all devices with matching connection state
*
@@ -1038,14 +1133,14 @@
public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
ArrayList<BluetoothDevice> devices = new ArrayList<>();
- if (states == null) {
- return devices;
- }
- final BluetoothDevice[] bondedDevices = mAdapterService.getBondedDevices();
- if (bondedDevices == null) {
- return devices;
- }
synchronized (mStateMachines) {
+ if (states == null || mAdapterService == null) {
+ return devices;
+ }
+ final BluetoothDevice[] bondedDevices = mAdapterService.getBondedDevices();
+ if (bondedDevices == null) {
+ return devices;
+ }
for (BluetoothDevice device : bondedDevices) {
int connectionState = getConnectionState(device);
@@ -1073,18 +1168,17 @@
public boolean setPriority(BluetoothDevice device, int priority) {
enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
- Settings.Global.putInt(getContentResolver(),
- Settings.Global.getBluetoothHeadsetPriorityKey(device.getAddress()), priority);
Log.i(TAG, "setPriority: device=" + device + ", priority=" + priority + ", "
+ Utils.getUidPidString());
+ mAdapterService.getDatabase()
+ .setProfilePriority(device, BluetoothProfile.HEADSET, priority);
return true;
}
public int getPriority(BluetoothDevice device) {
enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- return Settings.Global.getInt(getContentResolver(),
- Settings.Global.getBluetoothHeadsetPriorityKey(device.getAddress()),
- BluetoothProfile.PRIORITY_UNDEFINED);
+ return mAdapterService.getDatabase()
+ .getProfilePriority(device, BluetoothProfile.HEADSET);
}
boolean startVoiceRecognition(BluetoothDevice device) {
@@ -1164,6 +1258,10 @@
stateMachine.sendMessage(HeadsetStateMachine.VOICE_RECOGNITION_START, device);
}
}
+ /* VR calls always use SWB if supported on remote*/
+ if(isSwbEnabled()) {
+ enableSwbCodec(true);
+ }
return true;
}
@@ -1176,6 +1274,21 @@
+ " is not active, use active device " + mActiveDevice + " instead");
device = mActiveDevice;
}
+ if (mAdapterService.isTwsPlusDevice(device) &&
+ !isAudioConnected(device)) {
+ BluetoothDevice peerDevice = getTwsPlusConnectedPeer(device);
+ if (peerDevice != null && isAudioConnected(peerDevice)) {
+ Log.w(TAG, "startVoiceRecognition: requested TWS+ device " + device
+ + " is not audio connected, use TWS+ peer device " + peerDevice
+ + " instead");
+ device = peerDevice;
+ } else {
+ Log.w(TAG, "stopVoiceRecognition: both earbuds are not audio connected, resume A2DP");
+ mVoiceRecognitionStarted = false;
+ mHfpA2dpSyncInterface.releaseA2DP(null);
+ return false;
+ }
+ }
final HeadsetStateMachine stateMachine = mStateMachines.get(device);
if (stateMachine == null) {
Log.w(TAG, "stopVoiceRecognition: " + device + " is never connected");
@@ -1193,7 +1306,13 @@
}
mVoiceRecognitionStarted = false;
stateMachine.sendMessage(HeadsetStateMachine.VOICE_RECOGNITION_STOP, device);
- stateMachine.sendMessage(HeadsetStateMachine.DISCONNECT_AUDIO, device);
+
+ if (isAudioOn()) {
+ stateMachine.sendMessage(HeadsetStateMachine.DISCONNECT_AUDIO, device);
+ } else {
+ Log.w(TAG, "SCO is not connected and VR stopped, resuming A2DP");
+ stateMachine.sendMessage(HeadsetStateMachine.RESUME_A2DP);
+ }
}
return true;
}
@@ -1289,6 +1408,36 @@
}
/**
+ * Process a change in the silence mode for a {@link BluetoothDevice}.
+ *
+ * @param device the device to change silence mode
+ * @param silence true to enable silence mode, false to disable.
+ * @return true on success, false on error
+ */
+ @VisibleForTesting
+ public boolean setSilenceMode(BluetoothDevice device, boolean silence) {
+ Log.d(TAG, "setSilenceMode(" + device + "): " + silence);
+
+ if (silence && Objects.equals(mActiveDevice, device)) {
+ setActiveDevice(null);
+ } else if (!silence && mActiveDevice == null) {
+ // Set the device as the active device if currently no active device.
+ setActiveDevice(device);
+ }
+ synchronized (mStateMachines) {
+ final HeadsetStateMachine stateMachine = mStateMachines.get(device);
+ if (stateMachine == null) {
+ Log.w(TAG, "setSilenceMode: device " + device
+ + " was never connected/connecting");
+ return false;
+ }
+ stateMachine.setSilenceDevice(silence);
+ }
+
+ return true;
+ }
+
+ /**
* Set the active device.
*
* @param device the active device
@@ -1347,7 +1496,38 @@
}
BluetoothDevice previousActiveDevice = mActiveDevice;
mActiveDevice = device;
- if (getAudioState(previousActiveDevice) != BluetoothHeadset.STATE_AUDIO_DISCONNECTED) {
+ int audioStateOfPrevActiveDevice = BluetoothHeadset.STATE_AUDIO_DISCONNECTED;
+ boolean activeSwitchBetweenEbs = false;
+ if (previousActiveDevice != null &&
+ mAdapterService.isTwsPlusDevice(previousActiveDevice)) {
+ BluetoothDevice peerDevice =
+ getTwsPlusConnectedPeer(previousActiveDevice);
+ if (mActiveDevice != null &&
+ mAdapterService.isTwsPlusDevice(mActiveDevice)) {
+ Log.d(TAG, "Active device switch b/n ebs");
+ activeSwitchBetweenEbs = true;
+ } else {
+ if (getAudioState(previousActiveDevice) !=
+ BluetoothHeadset.STATE_AUDIO_DISCONNECTED ||
+ getAudioState(peerDevice) !=
+ BluetoothHeadset.STATE_AUDIO_DISCONNECTED) {
+ audioStateOfPrevActiveDevice =
+ BluetoothHeadset.STATE_AUDIO_CONNECTED;
+ }
+ }
+ if (audioStateOfPrevActiveDevice ==
+ BluetoothHeadset.STATE_AUDIO_CONNECTED &&
+ getAudioState(previousActiveDevice) ==
+ BluetoothHeadset.STATE_AUDIO_DISCONNECTED) {
+ Log.w(TAG, "Update previousActiveDevice with" + peerDevice);
+ previousActiveDevice = peerDevice;
+ }
+ } else {
+ audioStateOfPrevActiveDevice =
+ getAudioState(previousActiveDevice);
+ }
+ if (!activeSwitchBetweenEbs && audioStateOfPrevActiveDevice !=
+ BluetoothHeadset.STATE_AUDIO_DISCONNECTED) {
if (!disconnectAudio(previousActiveDevice)) {
Log.e(TAG, "setActiveDevice: fail to disconnectAudio from "
+ previousActiveDevice);
@@ -1423,7 +1603,7 @@
//SCO is connecting or connected.
//Return true to telephony
Log.w(TAG, "connectAudio: audio is not idle, current audio devices are: "
- + Arrays.toString(getNonIdleAudioDevices().toArray()) +
+ + Arrays.toString(getNonIdleAudioDevices().toArray()) +
" ,returning true");
return true;
}
@@ -1528,9 +1708,9 @@
mVirtualCallStarted = true;
mSystemInterface.getHeadsetPhoneState().setIsCsCall(false);
// Send virtual phone state changed to initialize SCO
- phoneStateChanged(0, 0, HeadsetHalConstants.CALL_STATE_DIALING, "", 0, true);
- phoneStateChanged(0, 0, HeadsetHalConstants.CALL_STATE_ALERTING, "", 0, true);
- phoneStateChanged(1, 0, HeadsetHalConstants.CALL_STATE_IDLE, "", 0, true);
+ phoneStateChanged(0, 0, HeadsetHalConstants.CALL_STATE_DIALING, "", 0, "", true);
+ phoneStateChanged(0, 0, HeadsetHalConstants.CALL_STATE_ALERTING, "", 0, "", true);
+ phoneStateChanged(1, 0, HeadsetHalConstants.CALL_STATE_IDLE, "", 0, "", true);
return true;
}
}
@@ -1546,7 +1726,7 @@
}
mVirtualCallStarted = false;
// 2. Send virtual phone state changed to close SCO
- phoneStateChanged(0, 0, HeadsetHalConstants.CALL_STATE_IDLE, "", 0, true);
+ phoneStateChanged(0, 0, HeadsetHalConstants.CALL_STATE_IDLE, "", 0, "", true);
}
return true;
}
@@ -1574,6 +1754,23 @@
}
}
+ public boolean isTwsPlusActive(BluetoothDevice device) {
+ boolean ret = false;
+ if (mAdapterService.isTwsPlusDevice(device)) {
+ if (device.equals(getActiveDevice())) {
+ ret = true;
+ } else {
+ BluetoothDevice peerTwsDevice = mAdapterService.getTwsPlusPeerDevice(device);
+ if (peerTwsDevice != null &&
+ peerTwsDevice.equals(getActiveDevice())) {
+ ret = true;
+ }
+ }
+ }
+ Log.d(TAG, "isTwsPlusActive returns" + ret);
+ return ret;
+ }
+
/**
* Dial an outgoing call as requested by the remote device
*
@@ -1599,7 +1796,7 @@
return false;
}
}
- if (!mAdapterService.isTwsPlusDevice(fromDevice) &&
+ if (!isTwsPlusActive(fromDevice) &&
!setActiveDevice(fromDevice)) {
Log.e(TAG, "dialOutgoingCall failed to set active device to " + fromDevice);
return false;
@@ -1693,7 +1890,7 @@
+ ", already pending by " + mVoiceRecognitionTimeoutEvent);
return false;
}
- if (!mAdapterService.isTwsPlusDevice(fromDevice) && !setActiveDevice(fromDevice)) {
+ if (!isTwsPlusActive(fromDevice) && !setActiveDevice(fromDevice)) {
Log.w(TAG, "startVoiceRecognitionByHeadset: failed to set " + fromDevice
+ " as active");
return false;
@@ -1727,6 +1924,10 @@
if (!mSystemInterface.getVoiceRecognitionWakeLock().isHeld()) {
mSystemInterface.getVoiceRecognitionWakeLock().acquire(sStartVrTimeoutMs);
}
+ /* VR calls always use SWB if supported on remote*/
+ if(isSwbEnabled()) {
+ enableSwbCodec(true);
+ }
return true;
}
}
@@ -1757,9 +1958,14 @@
mVoiceRecognitionTimeoutEvent = null;
}
if (mVoiceRecognitionStarted) {
- if (!disconnectAudio()) {
- Log.w(TAG, "stopVoiceRecognitionByHeadset: failed to disconnect audio from "
+ if (isAudioOn()) {
+ if (!disconnectAudio()) {
+ Log.w(TAG, "stopVoiceRecognitionByHeadset: failed to disconnect audio from "
+ fromDevice);
+ }
+ } else {
+ Log.w(TAG, "stopVoiceRecognitionByHeadset: No SCO connected, resume A2DP");
+ mHfpA2dpSyncInterface.releaseA2DP(null);
}
mVoiceRecognitionStarted = false;
}
@@ -1772,7 +1978,7 @@
}
private void phoneStateChanged(int numActive, int numHeld, int callState, String number,
- int type, boolean isVirtualCall) {
+ int type, String name, boolean isVirtualCall) {
enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, "Need MODIFY_PHONE_STATE permission");
synchronized (mStateMachines) {
if (mStateMachinesThread == null) {
@@ -1826,7 +2032,7 @@
"connected devices");
doForEachConnectedConnectingStateMachine(
stateMachine -> stateMachine.sendMessage(HeadsetStateMachine.CALL_STATE_CHANGED,
- new HeadsetCallState(numActive, numHeld, callState, number, type)));
+ new HeadsetCallState(numActive, numHeld, callState, number, type, name)));
mStateMachinesThread.getThreadHandler().post(() -> {
if (!(mSystemInterface.isInCall() || mSystemInterface.isRinging())) {
Log.i(TAG, "no call, sending resume A2DP message to state machines");
@@ -1848,6 +2054,12 @@
Log.i(TAG, "No device is connected and no call, " +
"set A2DPsuspended to false");
mHfpA2dpSyncInterface.releaseA2DP(null);
+ } else {
+ //if call/ ring is ongoing, suspendA2DP to true
+ Log.i(TAG, "No device is connected and call/ring is ongoing, " +
+ "set A2DPsuspended to true");
+ mHfpA2dpSyncInterface.suspendA2DP(HeadsetA2dpSync.
+ A2DP_SUSPENDED_BY_CS_CALL, null);
}
});
}
@@ -2028,47 +2240,80 @@
@VisibleForTesting
public void onAudioStateChangedFromStateMachine(BluetoothDevice device, int fromState,
int toState) {
+ Log.w(TAG, "onAudioStateChangedFromStateMachine: " + fromState + "->" + toState);
synchronized (mStateMachines) {
if (toState == BluetoothHeadset.STATE_AUDIO_DISCONNECTED) {
- if (mVoiceRecognitionStarted) {
- if (!stopVoiceRecognitionByHeadset(device)) {
- Log.w(TAG, "onAudioStateChangedFromStateMachine: failed to stop voice "
- + "recognition");
- } else {
- final HeadsetStateMachine stateMachine = mStateMachines.get(device);
- if (stateMachine != null) {
- Log.d(TAG, "onAudioStateChangedFromStateMachine: send +bvra:0");
- stateMachine.sendMessage(HeadsetStateMachine.VOICE_RECOGNITION_STOP,
- device);
- }
- }
- }
-
- if (mAdapterService.isTwsPlusDevice(device) &&
- isAudioConnected(getTwsPlusConnectedPeer(device))) {
- Log.w(TAG, "Ignore stop virtuall voice call if the other TWS+ device is "
- + "audio connected");
- } else {
- if (mVirtualCallStarted) {
- if (!stopScoUsingVirtualVoiceCall()) {
- Log.w(TAG, "onAudioStateChangedFromStateMachine: failed to stop "
- + "virtual voice call");
- }
- }
- }
-
//Transfer SCO is not needed for TWS+ devices
- if (!mAdapterService.isTwsPlusDevice(device)) {
+ if (mAdapterService.isTwsPlusDevice(device) &&
+ mActiveDevice != null &&
+ mAdapterService.isTwsPlusDevice(mActiveDevice) &&
+ isAudioOn()) {
+ Log.w(TAG, "Sco transfer is not needed btween earbuds");
+ } else {
// trigger SCO after SCO disconnected with previous active
// device
- if (mActiveDevice != null && !mActiveDevice.equals(device) &&
+ Log.w(TAG, "onAudioStateChangedFromStateMachine:"
+ + "shouldPersistAudio() returns"
+ + shouldPersistAudio());
+ if (mAdapterService.isTwsPlusDevice(device) &&
+ isAudioOn()) {
+ Log.w(TAG, "TWS: Don't stop VR or VOIP");
+ } else {
+ if (mVoiceRecognitionStarted) {
+ if (!stopVoiceRecognitionByHeadset(device)) {
+ Log.w(TAG,"onAudioStateChangedFromStateMachine:"
+ + " failed to stop voice"
+ + " recognition");
+ } else {
+ final HeadsetStateMachine stateMachine
+ = mStateMachines.get(device);
+ if (stateMachine != null) {
+ Log.d(TAG, "onAudioStateChanged" +
+ "FromStateMachine: send +bvra:0");
+ stateMachine.sendMessage(
+ HeadsetStateMachine.VOICE_RECOGNITION_STOP,
+ device);
+ }
+ }
+ }
+ if (mVirtualCallStarted) {
+ if (!stopScoUsingVirtualVoiceCall()) {
+ Log.w(TAG,"onAudioStateChangedFromStateMachine:"
+ + " failed to stop virtual "
+ + " voice call");
+ }
+ }
+ }
+
+ if (mActiveDevice != null &&
+ !mActiveDevice.equals(device) &&
shouldPersistAudio()) {
- Log.d(TAG, "onAudioStateChangedFromStateMachine: triggering SCO with device "
+ if (mAdapterService.isTwsPlusDevice(device)
+ && isAudioOn()) {
+ Log.d(TAG, "TWS: Wait for both eSCO closed");
+ } else {
+ if (mAdapterService.isTwsPlusDevice(device) &&
+ isTwsPlusActive(mActiveDevice)) {
+ /* If the device for which SCO got disconnected
+ is a TwsPlus device and TWS+ set is active
+ device. This should be the case where User
+ transferred from BT to Phone Speaker from
+ Call UI*/
+ Log.d(TAG,"don't transfer SCO. It is an" +
+ "explicit voice transfer from UI");
+ return;
+ }
+ Log.d(TAG, "onAudioStateChangedFromStateMachine:"
+ + " triggering SCO with device "
+ mActiveDevice);
- if (!connectAudio(mActiveDevice)) {
- Log.w(TAG, "onAudioStateChangedFromStateMachine, failed to connect"
- + " audio to new " + "active device " + mActiveDevice
- + ", after " + device + " is disconnected from SCO");
+ if (!connectAudio(mActiveDevice)) {
+ Log.w(TAG, "onAudioStateChangedFromStateMachine,"
+ + " failed to connect"
+ + " audio to new " + "active device "
+ + mActiveDevice
+ + ", after " + device
+ + " is disconnected from SCO");
+ }
}
}
}
@@ -2078,6 +2323,8 @@
private void broadcastActiveDevice(BluetoothDevice device) {
logD("broadcastActiveDevice: " + device);
+ StatsLog.write(StatsLog.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
@@ -2100,26 +2347,31 @@
}
if(!isPts) {
// Check priority and accept or reject the connection.
+ // Note: Logic can be simplified, but keeping it this way for readability
int priority = getPriority(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);
+ // If priority is undefined, it is likely that service discovery has not completed and peer
+ // initiated the connection. Allow this connection only if the device is bonded or bonding
+ boolean serviceDiscoveryPending = (priority == BluetoothProfile.PRIORITY_UNDEFINED) && (
+ bondState == BluetoothDevice.BOND_BONDING
+ || bondState == BluetoothDevice.BOND_BONDED);
+ // Also allow connection when device is bonded/bonding and priority is ON/AUTO_CONNECT.
+ boolean isEnabled = (priority == BluetoothProfile.PRIORITY_ON
+ || priority == BluetoothProfile.PRIORITY_AUTO_CONNECT) && (
+ bondState == BluetoothDevice.BOND_BONDED
+ || bondState == BluetoothDevice.BOND_BONDING);
+ if (!serviceDiscoveryPending && !isEnabled) {
+ // Otherwise, reject the connection if no service discovery is pending and priority is
+ // neither PRIORITY_ON nor PRIORITY_AUTO_CONNECT
+ Log.w(TAG,
+ "okToConnect: return false, priority=" + priority + ", bondState=" + bondState);
return false;
}
}
List<BluetoothDevice> connectingConnectedDevices =
getAllDevicesMatchingConnectionStates(CONNECTING_CONNECTED_STATES);
- addAllDevicesPendingRetryConnect(connectingConnectedDevices);
if (!isConnectionAllowed(device, connectingConnectedDevices)) {
- Log.w(TAG, "Maximum number of connections " + mMaxHeadsetConnections
+ Log.w(TAG, "Maximum number of connections " + mSetMaxConfig
+ " was reached, rejecting connection from " + device);
return false;
}
@@ -2230,6 +2482,25 @@
}
}
+ public void enableSwbCodec(boolean enable) {
+ mVendorHf.enableSwb(enable);
+ }
+
+ public boolean isSwbEnabled() {
+ if(mAdapterService.isSWBVoicewithAptxAdaptiveAG()) {
+ return mAdapterService.isSwbEnabled();
+ }
+ return false;
+ }
+
+ public boolean isSwbPmEnabled() {
+ if(mAdapterService.isSWBVoicewithAptxAdaptiveAG() &&
+ mAdapterService.isSwbEnabled()) {
+ return mAdapterService.isSwbPmEnabled();
+ }
+ return false;
+ }
+
private static void logD(String message) {
if (DBG) {
Log.d(TAG, message);
diff --git a/src/com/android/bluetooth/hfp/HeadsetStackEvent.java b/src/com/android/bluetooth/hfp/HeadsetStackEvent.java
index 200fb92..1bd5ae4 100644
--- a/src/com/android/bluetooth/hfp/HeadsetStackEvent.java
+++ b/src/com/android/bluetooth/hfp/HeadsetStackEvent.java
@@ -33,7 +33,7 @@
public static final int EVENT_TYPE_VOLUME_CHANGED = 6;
public static final int EVENT_TYPE_DIAL_CALL = 7;
public static final int EVENT_TYPE_SEND_DTMF = 8;
- public static final int EVENT_TYPE_NOICE_REDUCTION = 9;
+ public static final int EVENT_TYPE_NOISE_REDUCTION = 9;
public static final int EVENT_TYPE_AT_CHLD = 10;
public static final int EVENT_TYPE_SUBSCRIBER_NUMBER_REQUEST = 11;
public static final int EVENT_TYPE_AT_CIND = 12;
@@ -45,6 +45,8 @@
public static final int EVENT_TYPE_BIND = 18;
public static final int EVENT_TYPE_BIEV = 19;
public static final int EVENT_TYPE_BIA = 20;
+ public static final int EVENT_TYPE_SWB = 21;
+ public static final int EVENT_TYPE_TWSP_BATTERY_STATE = 22;
public final int type;
public final int valueInt;
@@ -153,8 +155,8 @@
return "EVENT_TYPE_DIAL_CALL";
case EVENT_TYPE_SEND_DTMF:
return "EVENT_TYPE_SEND_DTMF";
- case EVENT_TYPE_NOICE_REDUCTION:
- return "EVENT_TYPE_NOICE_REDUCTION";
+ case EVENT_TYPE_NOISE_REDUCTION:
+ return "EVENT_TYPE_NOISE_REDUCTION";
case EVENT_TYPE_AT_CHLD:
return "EVENT_TYPE_AT_CHLD";
case EVENT_TYPE_SUBSCRIBER_NUMBER_REQUEST:
@@ -177,6 +179,10 @@
return "EVENT_TYPE_BIEV";
case EVENT_TYPE_BIA:
return "EVENT_TYPE_BIA";
+ case EVENT_TYPE_SWB:
+ return "EVENT_TYPE_SWB";
+ case EVENT_TYPE_TWSP_BATTERY_STATE:
+ return "EVENT_TYPE_TWSP_BATTERY_STATE";
default:
return "UNKNOWN";
}
diff --git a/src/com/android/bluetooth/hfp/HeadsetStateMachine.java b/src/com/android/bluetooth/hfp/HeadsetStateMachine.java
index 8a2eab3..37c5c22 100644
--- a/src/com/android/bluetooth/hfp/HeadsetStateMachine.java
+++ b/src/com/android/bluetooth/hfp/HeadsetStateMachine.java
@@ -16,10 +16,13 @@
package com.android.bluetooth.hfp;
+import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothAssignedNumbers;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHeadset;
import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothProtoEnums;
+import android.bluetooth.hfp.BluetoothHfpProtoEnums;
import android.content.Intent;
import android.media.AudioManager;
import android.os.Looper;
@@ -27,16 +30,22 @@
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserHandle;
-import android.support.annotation.VisibleForTesting;
import android.telephony.PhoneNumberUtils;
import android.telephony.PhoneStateListener;
+import android.text.TextUtils;
import android.util.Log;
import android.os.SystemProperties;
import android.net.ConnectivityManager;
+import android.net.NetworkCapabilities;
+import android.net.ConnectivityManager.NetworkCallback;
import android.net.NetworkInfo;
+import android.net.Network;
+import android.util.StatsLog;
+import android.os.Build;
import com.android.bluetooth.btservice.AdapterService;
import com.android.bluetooth.btservice.ProfileService;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.State;
import com.android.internal.util.StateMachine;
@@ -77,10 +86,14 @@
public class HeadsetStateMachine extends StateMachine {
private static final String TAG = "HeadsetStateMachine";
private static final boolean DBG = true;
+ // TODO(b/122040733) variable created as a placeholder to make build green after merge conflict; re-address
+ private static final String MERGE_PLACEHOLDER = "";
private static final String HEADSET_NAME = "bt_headset_name";
private static final String HEADSET_NREC = "bt_headset_nrec";
private static final String HEADSET_WBS = "bt_wbs";
+ private static final String HEADSET_SWB = "bt_swb";
+ private static final String HEADSET_SWB_DISABLE = "65535";
private static final String HEADSET_AUDIO_FEATURE_ON = "on";
private static final String HEADSET_AUDIO_FEATURE_OFF = "off";
@@ -90,6 +103,7 @@
static final int DISCONNECT_AUDIO = 4;
static final int VOICE_RECOGNITION_START = 5;
static final int VOICE_RECOGNITION_STOP = 6;
+ static final int HEADSET_SWB_MAX_CODEC_IDS = 8;
// message.obj is an intent AudioManager.VOLUME_CHANGED_ACTION
// EXTRA_VOLUME_STREAM_TYPE is STREAM_BLUETOOTH_SCO
@@ -161,7 +175,21 @@
// maintain call states in state machine as well
private final HeadsetCallState mStateMachineCallState =
- new HeadsetCallState(0, 0, 0, "", 0);
+ new HeadsetCallState(0, 0, 0, "", 0, "");
+
+ private NetworkCallback mDefaultNetworkCallback = new NetworkCallback() {
+ @Override
+ public void onAvailable(Network network) {
+ mIsAvailable = true;
+ Log.d(TAG, "The current Network: "+network+" is avialable: "+mIsAvailable);
+ }
+ @Override
+ public void onLost(Network network) {
+ mIsAvailable = false;
+ Log.d(TAG, "The current Network:"+network+" is lost, mIsAvailable: "
+ +mIsAvailable);
+ }
+ };
// State machine states
private final Disconnected mDisconnected = new Disconnected();
@@ -187,6 +215,7 @@
// Runtime states
private int mSpeakerVolume;
private int mMicVolume;
+ private boolean mDeviceSilenced;
private HeadsetAgIndicatorEnableState mAgIndicatorEnableState;
private boolean mA2dpSuspend;
private boolean mIsCsCall = true;
@@ -194,6 +223,9 @@
private boolean mIsCallIndDelay = false;
private boolean mIsBlacklistedDevice = false;
private int retryConnectCount = 0;
+
+ private static boolean mIsAvailable = false;
+
//ConcurrentLinkeQueue is used so that it is threadsafe
private ConcurrentLinkedQueue<HeadsetCallState> mPendingCallStates =
new ConcurrentLinkedQueue<HeadsetCallState>();
@@ -250,6 +282,7 @@
mSystemInterface =
Objects.requireNonNull(systemInterface, "systemInterface cannot be null");
mAdapterService = Objects.requireNonNull(adapterService, "AdapterService cannot be null");
+ mDeviceSilenced = false;
// Create phonebook helper
mPhonebook = new AtPhonebook(mHeadsetService, mNativeInterface);
mConnectivityManager = (ConnectivityManager)
@@ -271,6 +304,8 @@
" active delay " + CS_CALL_ACTIVE_DELAY_TIME_MSEC);
}
+
+ mConnectivityManager.registerDefaultNetworkCallback(mDefaultNetworkCallback);
Log.i(TAG," Exiting HeadsetStateMachine constructor for device :" + device);
}
@@ -319,6 +354,7 @@
!mSystemInterface.getHeadsetPhoneState().getIsCsCall()) {
sendVoipConnectivityNetworktype(false);
}
+ mConnectivityManager.unregisterNetworkCallback(mDefaultNetworkCallback);
mAudioParams.clear();
}
@@ -415,6 +451,12 @@
// 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,
+ mAdapterService.obfuscateAddress(device),
+ getConnectionStateFromAudioState(toState),
+ TextUtils.equals(mAudioParams.get(HEADSET_WBS), HEADSET_AUDIO_FEATURE_ON)
+ ? BluetoothHfpProtoEnums.SCO_CODEC_MSBC
+ : BluetoothHfpProtoEnums.SCO_CODEC_CVSD);
mHeadsetService.onAudioStateChangedFromStateMachine(device, fromState, toState);
Intent intent = new Intent(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED);
intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, fromState);
@@ -671,8 +713,9 @@
}
// Per HFP 1.7.1 spec page 23/144, Pending state needs to handle
- // AT+BRSF, AT+CIND, AT+CMER, AT+BIND, +CHLD
+ // AT+BRSF, AT+CIND, AT+CMER, AT+BIND, AT+CHLD
// commands during SLC establishment
+ // AT+CHLD=? will be handled by statck directly
class Connecting extends HeadsetStateBase {
@Override
int getConnectionStateInt() {
@@ -757,9 +800,6 @@
case HeadsetStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED:
processConnectionEvent(message, event.valueInt);
break;
- case HeadsetStackEvent.EVENT_TYPE_AT_CHLD:
- processAtChld(event.valueInt, event.device);
- break;
case HeadsetStackEvent.EVENT_TYPE_AT_CIND:
processAtCind(event.device);
break;
@@ -769,6 +809,10 @@
case HeadsetStackEvent.EVENT_TYPE_BIND:
processAtBind(event.valueString, event.device);
break;
+ case HeadsetStackEvent.EVENT_TYPE_TWSP_BATTERY_STATE:
+ processTwsBatteryState(event.valueString,
+ event.device);
+ break;
// Unexpected AT commands, we only handle them for comparability reasons
case HeadsetStackEvent.EVENT_TYPE_VR_STATE_CHANGED:
stateLogW("Unexpected VR event, device=" + event.device + ", state="
@@ -817,6 +861,9 @@
stateLogW("Unexpected hangup event for " + event.device);
mSystemInterface.hangupCall(event.device);
break;
+ case HeadsetStackEvent.EVENT_TYPE_SWB:
+ processSWBEvent(event.valueInt);
+ break;
default:
stateLogE("Unexpected event: " + event);
break;
@@ -837,11 +884,13 @@
stateLogW("Disconnected");
processWBSEvent(HeadsetHalConstants.BTHF_WBS_NO);
stateLogD(" retryConnectCount = " + retryConnectCount);
- if(retryConnectCount == 1) {
- Log.d(TAG," retry once more ");
+ if(retryConnectCount == 1 && !hasDeferredMessages(DISCONNECT)) {
+ Log.d(TAG,"No deferred Disconnect, retry once more ");
sendMessageDelayed(CONNECT, mDevice, RETRY_CONNECT_TIME_SEC);
- } else if (retryConnectCount >= MAX_RETRY_CONNECT_COUNT) {
+ } else if (retryConnectCount >= MAX_RETRY_CONNECT_COUNT ||
+ hasDeferredMessages(DISCONNECT)) {
// we already tried twice.
+ Log.d(TAG,"Already tried twice or has deferred Disconnect");
retryConnectCount = 0;
}
transitionTo(mDisconnected);
@@ -1020,7 +1069,10 @@
break;
}
case CALL_STATE_CHANGED: {
+ if (mDeviceSilenced) break;
+
boolean isPts = SystemProperties.getBoolean("vendor.bt.pts.certification", false);
+
HeadsetCallState callState = (HeadsetCallState) message.obj;
// for PTS, send the indicators as is
if (isPts) {
@@ -1150,7 +1202,8 @@
HeadsetCallState callState =
new HeadsetCallState(0, 0, HeadsetHalConstants.CALL_STATE_INCOMING,
mSystemInterface.getHeadsetPhoneState().getNumber(),
- mSystemInterface.getHeadsetPhoneState().getType());
+ mSystemInterface.getHeadsetPhoneState().getType(),
+ MERGE_PLACEHOLDER);
mNativeInterface.phoneStateChange(mDevice, callState);
break;
case QUERY_PHONE_STATE_AT_SLC:
@@ -1194,7 +1247,7 @@
case HeadsetStackEvent.EVENT_TYPE_SEND_DTMF:
mSystemInterface.sendDtmf(event.valueInt, event.device);
break;
- case HeadsetStackEvent.EVENT_TYPE_NOICE_REDUCTION:
+ case HeadsetStackEvent.EVENT_TYPE_NOISE_REDUCTION:
processNoiseReductionEvent(event.valueInt == 1);
break;
case HeadsetStackEvent.EVENT_TYPE_WBS:
@@ -1224,6 +1277,10 @@
case HeadsetStackEvent.EVENT_TYPE_BIND:
processAtBind(event.valueString, event.device);
break;
+ case HeadsetStackEvent.EVENT_TYPE_TWSP_BATTERY_STATE:
+ processTwsBatteryState(event.valueString,
+ event.device);
+ break;
case HeadsetStackEvent.EVENT_TYPE_BIEV:
processAtBiev(event.valueInt, event.valueInt2, event.device);
break;
@@ -1231,6 +1288,9 @@
updateAgIndicatorEnableState(
(HeadsetAgIndicatorEnableState) event.valueObject);
break;
+ case HeadsetStackEvent.EVENT_TYPE_SWB:
+ processSWBEvent(event.valueInt);
+ break;
default:
stateLogE("Unknown stack event: " + event);
break;
@@ -1286,9 +1346,6 @@
@Override
public void enter() {
super.enter();
- if (mConnectingTimestampMs == Long.MIN_VALUE) {
- mConnectingTimestampMs = SystemClock.uptimeMillis();
- }
if (mPrevState == mConnecting) {
// Reset AG indicator subscriptions, HF can set this later using AT+BIA command
updateAgIndicatorEnableState(DEFAULT_AG_INDICATOR_ENABLE_STATE);
@@ -1353,6 +1410,18 @@
+ " or A2Dp is playing, not allowing SCO, device=" + mDevice);
break;
}
+
+ if (mHeadsetService.isSwbEnabled() && mHeadsetService.isSwbPmEnabled()) {
+ if (!mHeadsetService.isVirtualCallStarted() &&
+ mSystemInterface.isHighDefCallInProgress()) {
+ log("CONNECT_AUDIO: enable SWB for HD call ");
+ mHeadsetService.enableSwbCodec(true);
+ } else {
+ log("CONNECT_AUDIO: disable SWB for non-HD or Voip calls");
+ mHeadsetService.enableSwbCodec(false);
+ }
+ }
+
if (!mNativeInterface.connectAudio(mDevice)) {
stateLogE("Failed to connect SCO audio for " + mDevice);
// No state change involved, fire broadcast immediately
@@ -1484,6 +1553,12 @@
// ignore, there is no BluetoothHeadset.STATE_AUDIO_DISCONNECTING
break;
case HeadsetHalConstants.AUDIO_STATE_CONNECTED:
+ if (!mSystemInterface.getHeadsetPhoneState().getIsCsCall()) {
+ stateLogI("Sco connected for call other than CS, check network type");
+ sendVoipConnectivityNetworktype(true);
+ } else {
+ stateLogI("Sco connected for CS call, do not check network type");
+ }
stateLogI("processAudioEvent: audio connected");
transitionTo(mAudioOn);
break;
@@ -1519,12 +1594,9 @@
}
// If current device is TWSPLUS device and peer TWSPLUS device is already
// has SCO, dont need to update teh Audio Manager
- if (mAdapterService.isTwsPlusDevice(mDevice) &&
- mHeadsetService.isAudioConnected(mHeadsetService.getTwsPlusConnectedPeer(mDevice))) {
- stateLogW("Dont update Audio as this TWS peer eSCO");
- } else {
- setAudioParameters();
- }
+
+ setAudioParameters();
+
broadcastStateTransitions();
}
@@ -1604,6 +1676,9 @@
case HeadsetStackEvent.EVENT_TYPE_WBS:
stateLogE("Cannot change WBS state when audio is connected: " + event);
break;
+ case HeadsetStackEvent.EVENT_TYPE_SWB:
+ stateLogE("Cannot change SWB state when audio is connected: " + event);
+ break;
default:
super.processMessage(message);
break;
@@ -1784,12 +1859,33 @@
return mConnectingTimestampMs;
}
- public boolean isPendingRetryConnect() {
- if((getConnectionState() == BluetoothHeadset.STATE_DISCONNECTED) &&
- (retryConnectCount > 0) && hasMessages(CONNECT)) {
- return true;
+ /**
+ * Set the silence mode status of this state machine
+ *
+ * @param silence true to enter silence mode, false on exit
+ * @return true on success, false on error
+ */
+ @VisibleForTesting
+ public boolean setSilenceDevice(boolean silence) {
+ if (silence == mDeviceSilenced) {
+ return false;
}
- return false;
+ if (silence) {
+ mSystemInterface.getHeadsetPhoneState().listenForPhoneState(mDevice,
+ PhoneStateListener.LISTEN_NONE);
+ } else {
+ updateAgIndicatorEnableState(mAgIndicatorEnableState);
+ }
+ mDeviceSilenced = silence;
+ return true;
+ }
+
+ public void onAudioServerUp() {
+ Log.i(TAG, "onAudioSeverUp: restore audio parameters");
+ mSystemInterface.getAudioManager().setBluetoothScoOn(false);
+ mSystemInterface.getAudioManager().setParameters("A2dpSuspended=true");
+ setAudioParameters();
+ mSystemInterface.getAudioManager().setBluetoothScoOn(true);
}
/*
@@ -1815,7 +1911,9 @@
HEADSET_NREC + "=" + mAudioParams.getOrDefault(HEADSET_NREC,
HEADSET_AUDIO_FEATURE_OFF),
HEADSET_WBS + "=" + mAudioParams.getOrDefault(HEADSET_WBS,
- HEADSET_AUDIO_FEATURE_OFF)
+ HEADSET_AUDIO_FEATURE_OFF),
+ HEADSET_SWB + "=" + mAudioParams.getOrDefault(HEADSET_SWB,
+ HEADSET_SWB_DISABLE)
});
Log.i(TAG, "setAudioParameters for " + mDevice + ": " + keyValuePairs);
mSystemInterface.getAudioManager().setParameters(keyValuePairs);
@@ -1924,8 +2022,7 @@
private void processVolumeEvent(int volumeType, int volume) {
// Only current active device can change SCO volume
- AdapterService adapterService = AdapterService.getAdapterService();
- if (!adapterService.isTwsPlusDevice(mDevice) &&
+ if (!mHeadsetService.isTwsPlusActive(mDevice) &&
!mDevice.equals(mHeadsetService.getActiveDevice())) {
Log.w(TAG, "processVolumeEvent, ignored because " + mDevice + " is not active");
return;
@@ -2085,13 +2182,13 @@
log("Send Idle call indicators once Active call disconnected.");
// TODO: cross check this
- mStateMachineCallState.mCallState =
+ mStateMachineCallState.mCallState =
HeadsetHalConstants.CALL_STATE_IDLE;
HeadsetCallState updateCallState = new HeadsetCallState(callState.mNumActive,
callState.mNumHeld,
HeadsetHalConstants.CALL_STATE_IDLE,
callState.mNumber,
- callState.mType);
+ callState.mType, MERGE_PLACEHOLDER);
mNativeInterface.phoneStateChange(mDevice, updateCallState);
mIsCallIndDelay = true;
}
@@ -2117,6 +2214,22 @@
+ callState.mNumHeld + " mCallState: " + callState.mCallState);
log("processCallState: mNumber: " + callState.mNumber + " mType: " + callState.mType);
+ if (mHeadsetService.isSwbEnabled() && mHeadsetService.isSwbPmEnabled()) {
+ if (mHeadsetService.isVirtualCallStarted()) {
+ log("processCallState: enable SWB for all voip calls ");
+ mHeadsetService.enableSwbCodec(true);
+ } else if((callState.mCallState == HeadsetHalConstants.CALL_STATE_DIALING) ||
+ (callState.mCallState == HeadsetHalConstants.CALL_STATE_INCOMING)) {
+ if (!mSystemInterface.isHighDefCallInProgress()) {
+ log("processCallState: disable SWB for non-HD call ");
+ mHeadsetService.enableSwbCodec(false);
+ } else {
+ log("processCallState: enable SWB for HD call ");
+ mHeadsetService.enableSwbCodec(true);
+ }
+ }
+ }
+
processA2dpState(callState);
}
@@ -2233,6 +2346,9 @@
switch (wbsConfig) {
case HeadsetHalConstants.BTHF_WBS_YES:
mAudioParams.put(HEADSET_WBS, HEADSET_AUDIO_FEATURE_ON);
+ if (mHeadsetService.isSwbEnabled()) {
+ mAudioParams.put(HEADSET_SWB, HEADSET_SWB_DISABLE);
+ }
break;
case HeadsetHalConstants.BTHF_WBS_NO:
case HeadsetHalConstants.BTHF_WBS_NONE:
@@ -2246,6 +2362,15 @@
HEADSET_WBS));
}
+ private void processSWBEvent(int swbConfig) {
+ if (swbConfig < HEADSET_SWB_MAX_CODEC_IDS) {
+ mAudioParams.put(HEADSET_SWB, "0");
+ mAudioParams.put(HEADSET_WBS, HEADSET_AUDIO_FEATURE_OFF);
+ } else {
+ mAudioParams.put(HEADSET_SWB, HEADSET_SWB_DISABLE);
+ }
+ }
+
private void processAtChld(int chld, BluetoothDevice device) {
if (mSystemInterface.processChld(chld)) {
mNativeInterface.atResponseCode(device, HeadsetHalConstants.AT_RESPONSE_OK, 0);
@@ -2268,7 +2393,7 @@
private void processAtCind(BluetoothDevice device) {
int call, callSetup, call_state, service, signal;
- // get the top of the Q
+ // get the top of the Q
HeadsetCallState tempCallState = mDelayedCSCallStates.peek();
final HeadsetPhoneState phoneState = mSystemInterface.getHeadsetPhoneState();
@@ -2481,6 +2606,18 @@
Log.w(TAG, "processAtXapl() argument types not match");
return;
}
+ String[] deviceInfo = ((String) args[0]).split("-");
+ if (deviceInfo.length != 3) {
+ Log.w(TAG, "processAtXapl() deviceInfo length " + deviceInfo.length + " is wrong");
+ return;
+ }
+ String vendorId = deviceInfo[0];
+ String productId = deviceInfo[1];
+ String version = deviceInfo[2];
+ StatsLog.write(StatsLog.BLUETOOTH_DEVICE_INFO_REPORTED,
+ mAdapterService.obfuscateAddress(device), BluetoothProtoEnums.DEVICE_INFO_INTERNAL,
+ BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_XAPL, vendorId, productId, version,
+ null);
// feature = 2 indicates that we support battery level reporting only
mNativeInterface.atResponseString(device, "+XAPL=iPhone," + String.valueOf(2));
}
@@ -2501,6 +2638,10 @@
processAtCpbr(atCommand.substring(5), commandType, device);
} else if (atCommand.startsWith("+CSQ")) {
mNativeInterface.atResponseCode(device, HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
+ } else if (atCommand.equals("+CGMI")) {
+ mNativeInterface.atResponseString(device, "+CGMI: \"" + Build.MANUFACTURER + "\"");
+ } else if (atCommand.equals("+CGMM")) {
+ mNativeInterface.atResponseString(device, "+CGMM: " + Build.MODEL);
} else {
processVendorSpecificAt(atCommand, device);
}
@@ -2565,19 +2706,15 @@
private void processAtBind(String atString, BluetoothDevice device) {
log("processAtBind: " + atString);
- // Parse the AT String to find the Indicator Ids that are supported
- int indId = 0;
- int iter = 0;
- int iter1 = 0;
+ for (String id : atString.split(",")) {
- while (iter < atString.length()) {
- iter1 = findChar(',', atString, iter);
- String id = atString.substring(iter, iter1);
+ int indId;
try {
- indId = Integer.valueOf(id);
+ indId = Integer.parseInt(id);
} catch (NumberFormatException e) {
Log.e(TAG, Log.getStackTraceString(new Throwable()));
+ continue;
}
switch (indId) {
@@ -2593,11 +2730,58 @@
log("Invalid HF Indicator Received");
break;
}
-
- iter = iter1 + 1; // move past comma
}
}
+ /**
+ * Send TWSP Battery State changed intent
+ *
+ * @param device Device whose Battery State Changed
+ * @param batteryState Charging/Discharging [0/1]
+ * @param batteryLevel battery percentage, -1 means invalid
+ */
+ private void sendTwsBatteryStateIntent(BluetoothDevice device,
+ int batteryState, int batteryLevel) {
+ Intent intent = new Intent(
+ BluetoothHeadset.ACTION_HF_TWSP_BATTERY_STATE_CHANGED);
+ intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
+ intent.putExtra(BluetoothHeadset.EXTRA_HF_TWSP_BATTERY_STATE,
+ batteryState);
+ intent.putExtra(BluetoothHeadset.EXTRA_HF_TWSP_BATTERY_LEVEL,
+ batteryLevel);
+ mHeadsetService.sendBroadcast(intent, HeadsetService.BLUETOOTH_PERM);
+ }
+
+ private void processTwsBatteryState(String atString, BluetoothDevice device) {
+ log("processTwsBatteryState: " + atString);
+ int batteryState;
+ int batteryLevel;
+
+ String parts[] = atString.split(",");
+ if (parts.length != 2) {
+ Log.e(TAG, "Invalid battery status event");
+ return;
+ }
+
+ try {
+ batteryState = Integer.parseInt(parts[0]);
+ } catch (NumberFormatException e) {
+ Log.e(TAG, Log.getStackTraceString(new Throwable()));
+ return;
+ }
+
+ try {
+ batteryLevel = Integer.parseInt(parts[1]);
+ } catch (NumberFormatException e) {
+ Log.e(TAG, Log.getStackTraceString(new Throwable()));
+ return;
+ }
+
+ log("processTwsBatteryState: batteryState:" + batteryState
+ + "batteryLevel:" + batteryLevel);
+ sendTwsBatteryStateIntent(device, batteryState, batteryLevel);
+ }
+
private void processAtBiev(int indId, int indValue, BluetoothDevice device) {
log("processAtBiev: ind_id=" + indId + ", ind_value=" + indValue);
sendIndicatorIntent(device, indId, indValue);
@@ -2683,7 +2867,8 @@
private void updateAgIndicatorEnableState(
HeadsetAgIndicatorEnableState agIndicatorEnableState) {
- if (Objects.equals(mAgIndicatorEnableState, agIndicatorEnableState)) {
+ if (!mDeviceSilenced
+ && Objects.equals(mAgIndicatorEnableState, agIndicatorEnableState)) {
Log.i(TAG, "updateAgIndicatorEnableState, no change in indicator state "
+ mAgIndicatorEnableState);
return;
@@ -2726,16 +2911,26 @@
private void sendVoipConnectivityNetworktype(boolean isVoipStarted) {
Log.d(TAG, "Enter sendVoipConnectivityNetworktype()");
- NetworkInfo networkInfo = mConnectivityManager.getActiveNetworkInfo();
- if (networkInfo == null || !networkInfo.isAvailable() || !networkInfo.isConnected()) {
+ Network network = mConnectivityManager.getActiveNetwork();
+ if (network == null) {
+ Log.d(TAG, "No default network is currently active");
+ return;
+ }
+ NetworkCapabilities networkCapabilities =
+ mConnectivityManager.getNetworkCapabilities(network);
+ if (mIsAvailable == false) {
Log.d(TAG, "No connected/available connectivity network, don't update soc");
return;
}
-
- if (networkInfo.getType() == ConnectivityManager.TYPE_MOBILE) {
- Log.d(TAG, "Voip/VoLTE started/stopped on n/w TYPE_MOBILE, don't update to soc");
- } else if (networkInfo.getType() == ConnectivityManager.TYPE_WIFI) {
- Log.d(TAG, "Voip/VoLTE started/stopped on n/w TYPE_WIFI, update n/w type & start/stop to soc");
+ if (networkCapabilities == null) {
+ Log.d(TAG, "The capabilities of active network is NULL");
+ return;
+ }
+ if (networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) {
+ Log.d(TAG, "Voip/VoLTE started/stopped on n/w TRANSPORT_CELLULAR, don't update to soc");
+ } else if (networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) {
+ Log.d(TAG, "Voip/VoLTE started/stopped on n/w TRANSPORT_WIFI, "+
+ "update n/w type & start/stop to soc");
mAdapterService.voipNetworkWifiInfo(isVoipStarted, true);
} else {
Log.d(TAG, "Voip/VoLTE started/stopped on some other n/w, don't update to soc");
@@ -2789,6 +2984,18 @@
log("Exit handleAccessPermissionResult()");
}
+ private static int getConnectionStateFromAudioState(int audioState) {
+ switch (audioState) {
+ case BluetoothHeadset.STATE_AUDIO_CONNECTED:
+ return BluetoothAdapter.STATE_CONNECTED;
+ case BluetoothHeadset.STATE_AUDIO_CONNECTING:
+ return BluetoothAdapter.STATE_CONNECTING;
+ case BluetoothHeadset.STATE_AUDIO_DISCONNECTED:
+ return BluetoothAdapter.STATE_DISCONNECTED;
+ }
+ return BluetoothAdapter.STATE_DISCONNECTED;
+ }
+
private static String getMessageName(int what) {
switch (what) {
case CONNECT:
diff --git a/src/com/android/bluetooth/hfp/HeadsetSystemInterface.java b/src/com/android/bluetooth/hfp/HeadsetSystemInterface.java
index 2593adf..e4078ac 100644
--- a/src/com/android/bluetooth/hfp/HeadsetSystemInterface.java
+++ b/src/com/android/bluetooth/hfp/HeadsetSystemInterface.java
@@ -108,11 +108,6 @@
// Synchronization should make sure unbind can be successful
mHeadsetService.unbindService(mPhoneProxyConnection);
}
- //sometimes when BT is turned off while in call,
- // we don't get a chance to move out of AudioOn state
-
- mAudioManager.setParameters("BT_SCO=off");
- mAudioManager.setBluetoothScoOn(false);
mHeadsetPhoneState.cleanup();
}
@@ -243,6 +238,23 @@
}
/**
+ * Check for HD codec for voice call
+ */
+ @VisibleForTesting
+ public boolean isHighDefCallInProgress() {
+ if (mPhoneProxy != null) {
+ try {
+ return mPhoneProxy.isHighDefCallInProgress();
+ } catch (RemoteException e) {
+ Log.e(TAG, Log.getStackTraceString(new Throwable()));
+ }
+ } else {
+ Log.e(TAG, "Handsfree phone proxy null");
+ }
+ return false;
+ }
+
+ /**
* Get the the alphabetic name of current registered operator.
*
* @return null on error, empty string if not available
diff --git a/src/com/android/bluetooth/hfpclient/HeadsetClientService.java b/src/com/android/bluetooth/hfpclient/HeadsetClientService.java
index 46c017c..98fc322 100644
--- a/src/com/android/bluetooth/hfpclient/HeadsetClientService.java
+++ b/src/com/android/bluetooth/hfpclient/HeadsetClientService.java
@@ -29,10 +29,10 @@
import android.os.Bundle;
import android.os.HandlerThread;
import android.os.Message;
-import android.provider.Settings;
import android.util.Log;
import com.android.bluetooth.Utils;
+import com.android.bluetooth.btservice.AdapterService;
import com.android.bluetooth.btservice.ProfileService;
import com.android.bluetooth.hfpclient.connserv.HfpClientConnectionService;
@@ -64,10 +64,6 @@
public static final String HFP_CLIENT_STOP_TAG = "hfp_client_stop_tag";
- static {
- NativeInterface.classInitNative();
- }
-
@Override
public IProfileServiceBinder initBinder() {
return new BluetoothHeadsetClientBinder(this);
@@ -78,8 +74,15 @@
if (DBG) {
Log.d(TAG, "start()");
}
+ if (sHeadsetClientService != null) {
+ Log.w(TAG, "start(): start called without stop");
+ return false;
+ }
+
// Setup the JNI service
- NativeInterface.initializeNative();
+ mNativeInterface = new NativeInterface();
+ mNativeInterface.initializeNative();
+
mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
if (mAudioManager == null) {
Log.e(TAG, "AudioManager service doesn't exist?");
@@ -94,8 +97,6 @@
IntentFilter filter = new IntentFilter(AudioManager.VOLUME_CHANGED_ACTION);
registerReceiver(mBroadcastReceiver, filter);
- mNativeInterface = new NativeInterface();
-
// Start the HfpClientConnectionService to create connection with telecom when HFP
// connection is available.
Intent startIntent = new Intent(this, HfpClientConnectionService.class);
@@ -115,6 +116,11 @@
Log.w(TAG, "stop() called without start()");
return false;
}
+
+ // Stop the HfpClientConnectionService.
+ Intent stopIntent = new Intent(this, HfpClientConnectionService.class);
+ sHeadsetClientService.stopService(stopIntent);
+
setHeadsetClientService(null);
unregisterReceiver(mBroadcastReceiver);
@@ -127,17 +133,12 @@
it.remove();
}
- // Stop the HfpClientConnectionService.
- Intent stopIntent = new Intent(this, HfpClientConnectionService.class);
- stopIntent.putExtra(HFP_CLIENT_STOP_TAG, true);
- startService(stopIntent);
- mNativeInterface = null;
-
// Stop the handler thread
mSmThread.quit();
mSmThread = null;
- NativeInterface.cleanupNative();
+ mNativeInterface.cleanupNative();
+ mNativeInterface = null;
return true;
}
@@ -537,20 +538,18 @@
public boolean setPriority(BluetoothDevice device, int priority) {
enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
- Settings.Global.putInt(getContentResolver(),
- Settings.Global.getBluetoothHeadsetPriorityKey(device.getAddress()), priority);
if (DBG) {
Log.d(TAG, "Saved priority " + device + " = " + priority);
}
+ AdapterService.getAdapterService().getDatabase()
+ .setProfilePriority(device, BluetoothProfile.HEADSET_CLIENT, priority);
return true;
}
public int getPriority(BluetoothDevice device) {
enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
- int priority = Settings.Global.getInt(getContentResolver(),
- Settings.Global.getBluetoothHeadsetPriorityKey(device.getAddress()),
- BluetoothProfile.PRIORITY_UNDEFINED);
- return priority;
+ return AdapterService.getAdapterService().getDatabase()
+ .getProfilePriority(device, BluetoothProfile.HEADSET_CLIENT);
}
boolean startVoiceRecognition(BluetoothDevice device) {
@@ -914,8 +913,6 @@
super.dump(sb);
for (HeadsetClientStateMachine sm : mStateMachineMap.values()) {
if (sm != null) {
- println(sb, "State machine:");
- println(sb, "=============");
sm.dump(sb);
}
}
@@ -930,7 +927,7 @@
mSmFactory = factory;
}
- AudioManager getAudioManager() {
+ protected AudioManager getAudioManager() {
return mAudioManager;
}
}
diff --git a/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachine.java b/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachine.java
index 844c470..541bcf4 100644
--- a/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachine.java
+++ b/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachine.java
@@ -39,6 +39,7 @@
import android.bluetooth.BluetoothHeadsetClientCall;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.BluetoothUuid;
+import android.bluetooth.hfp.BluetoothHfpProtoEnums;
import android.content.Intent;
import android.media.AudioAttributes;
import android.media.AudioFocusRequest;
@@ -48,9 +49,9 @@
import android.os.Message;
import android.os.ParcelUuid;
import android.os.SystemClock;
-import android.support.annotation.VisibleForTesting;
import android.util.Log;
import android.util.Pair;
+import android.util.StatsLog;
import com.android.bluetooth.BluetoothMetricsProto;
import com.android.bluetooth.R;
@@ -58,6 +59,7 @@
import com.android.bluetooth.btservice.AdapterService;
import com.android.bluetooth.btservice.MetricsLogger;
import com.android.bluetooth.btservice.ProfileService;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.IState;
import com.android.internal.util.State;
import com.android.internal.util.StateMachine;
@@ -185,7 +187,9 @@
}
public void dump(StringBuilder sb) {
- ProfileService.println(sb, "mCurrentDevice: " + mCurrentDevice);
+ if (mCurrentDevice == null) return;
+ ProfileService.println(sb, "mCurrentDevice: " + mCurrentDevice.getAddress() + "("
+ + mCurrentDevice.getName() + ") " + this.toString());
ProfileService.println(sb, "mAudioState: " + mAudioState);
ProfileService.println(sb, "mAudioWbs: " + mAudioWbs);
ProfileService.println(sb, "mIndicatorNetworkState: " + mIndicatorNetworkState);
@@ -208,9 +212,6 @@
ProfileService.println(sb, " " + call);
}
}
-
- ProfileService.println(sb, "State machine stats:");
- ProfileService.println(sb, this.toString());
}
private void clearPendingAction() {
@@ -657,6 +658,10 @@
== HeadsetClientHalConstants.PEER_FEAT_3WAY) {
b.putBoolean(BluetoothHeadsetClient.EXTRA_AG_FEATURE_3WAY_CALLING, true);
}
+ if ((mPeerFeatures & HeadsetClientHalConstants.PEER_FEAT_VREC)
+ == HeadsetClientHalConstants.PEER_FEAT_VREC) {
+ b.putBoolean(BluetoothHeadsetClient.EXTRA_AG_FEATURE_VOICE_RECOGNITION, true);
+ }
if ((mPeerFeatures & HeadsetClientHalConstants.PEER_FEAT_REJECT)
== HeadsetClientHalConstants.PEER_FEAT_REJECT) {
b.putBoolean(BluetoothHeadsetClient.EXTRA_AG_FEATURE_REJECT_CALL, true);
@@ -777,6 +782,9 @@
public void doQuit() {
Log.d(TAG, "doQuit");
+ if (mCurrentDevice != null) {
+ NativeInterface.disconnectNative(getByteAddress(mCurrentDevice));
+ }
routeHfpAudio(false);
returnAudioFocusIfNecessary();
quitNow();
@@ -1738,6 +1746,11 @@
}
private void broadcastAudioState(BluetoothDevice device, int newState, int prevState) {
+ StatsLog.write(StatsLog.BLUETOOTH_SCO_CONNECTION_STATE_CHANGED,
+ AdapterService.getAdapterService().obfuscateAddress(device),
+ getConnectionStateFromAudioState(newState), mAudioWbs
+ ? BluetoothHfpProtoEnums.SCO_CODEC_MSBC
+ : BluetoothHfpProtoEnums.SCO_CODEC_CVSD);
Intent intent = new Intent(BluetoothHeadsetClient.ACTION_AUDIO_STATE_CHANGED);
intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState);
intent.putExtra(BluetoothProfile.EXTRA_STATE, newState);
@@ -1773,6 +1786,10 @@
== HeadsetClientHalConstants.PEER_FEAT_3WAY) {
intent.putExtra(BluetoothHeadsetClient.EXTRA_AG_FEATURE_3WAY_CALLING, true);
}
+ if ((mPeerFeatures & HeadsetClientHalConstants.PEER_FEAT_VREC)
+ == HeadsetClientHalConstants.PEER_FEAT_VREC) {
+ intent.putExtra(BluetoothHeadsetClient.EXTRA_AG_FEATURE_VOICE_RECOGNITION, true);
+ }
if ((mPeerFeatures & HeadsetClientHalConstants.PEER_FEAT_REJECT)
== HeadsetClientHalConstants.PEER_FEAT_REJECT) {
intent.putExtra(BluetoothHeadsetClient.EXTRA_AG_FEATURE_REJECT_CALL, true);
@@ -1890,4 +1907,16 @@
b.putString(BluetoothHeadsetClient.EXTRA_SUBSCRIBER_INFO, mSubscriberInfo);
return b;
}
+
+ private static int getConnectionStateFromAudioState(int audioState) {
+ switch (audioState) {
+ case BluetoothHeadsetClient.STATE_AUDIO_CONNECTED:
+ return BluetoothAdapter.STATE_CONNECTED;
+ case BluetoothHeadsetClient.STATE_AUDIO_CONNECTING:
+ return BluetoothAdapter.STATE_CONNECTING;
+ case BluetoothHeadsetClient.STATE_AUDIO_DISCONNECTED:
+ return BluetoothAdapter.STATE_DISCONNECTED;
+ }
+ return BluetoothAdapter.STATE_DISCONNECTED;
+ }
}
diff --git a/src/com/android/bluetooth/hfpclient/NativeInterface.java b/src/com/android/bluetooth/hfpclient/NativeInterface.java
index 77d9af7..0a6b9c6 100644
--- a/src/com/android/bluetooth/hfpclient/NativeInterface.java
+++ b/src/com/android/bluetooth/hfpclient/NativeInterface.java
@@ -28,14 +28,18 @@
private static final String TAG = "NativeInterface";
private static final boolean DBG = false;
+ static {
+ classInitNative();
+ }
+
NativeInterface() {}
// Native methods that call into the JNI interface
static native void classInitNative();
- static native void initializeNative();
+ native void initializeNative();
- static native void cleanupNative();
+ native void cleanupNative();
static native boolean connectNative(byte[] address);
diff --git a/src/com/android/bluetooth/hfpclient/connserv/HfpClientConnection.java b/src/com/android/bluetooth/hfpclient/connserv/HfpClientConnection.java
index 1d2add4..f3cb472 100644
--- a/src/com/android/bluetooth/hfpclient/connserv/HfpClientConnection.java
+++ b/src/com/android/bluetooth/hfpclient/connserv/HfpClientConnection.java
@@ -80,7 +80,6 @@
return;
}
- mHeadsetProfile.connectAudio(device);
setInitializing();
setDialing();
finishInitializing();
@@ -265,7 +264,6 @@
if (!mClosed) {
mHeadsetProfile.acceptCall(mDevice, BluetoothHeadsetClient.CALL_ACCEPT_NONE);
}
- mHeadsetProfile.connectAudio(mDevice);
}
@Override
diff --git a/src/com/android/bluetooth/hfpclient/connserv/HfpClientConnectionService.java b/src/com/android/bluetooth/hfpclient/connserv/HfpClientConnectionService.java
index af46122..0a02ce1 100644
--- a/src/com/android/bluetooth/hfpclient/connserv/HfpClientConnectionService.java
+++ b/src/com/android/bluetooth/hfpclient/connserv/HfpClientConnectionService.java
@@ -328,10 +328,23 @@
PhoneAccountHandle handle =
new PhoneAccountHandle(new ComponentName(context, HfpClientConnectionService.class),
device.getAddress());
+
+ int capabilities = PhoneAccount.CAPABILITY_CALL_PROVIDER;
+ if (context.getApplicationContext().getResources().getBoolean(
+ com.android.bluetooth.R.bool
+ .hfp_client_connection_service_support_emergency_call)) {
+ // Need to have an emergency call capability to place emergency call
+ capabilities |= PhoneAccount.CAPABILITY_PLACE_EMERGENCY_CALLS;
+ // Emergency call is processed in user 0 context in multi-user setting
+ // even if caller is from another user. Declare multi user
+ // capability for the account to be chosen.
+ capabilities |= PhoneAccount.CAPABILITY_MULTI_USER;
+ }
+
PhoneAccount account =
new PhoneAccount.Builder(handle, "HFP " + device.toString()).setAddress(addr)
.setSupportedUriSchemes(Arrays.asList(PhoneAccount.SCHEME_TEL))
- .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER)
+ .setCapabilities(capabilities)
.build();
if (DBG) {
Log.d(TAG, "phoneaccount: " + account);
diff --git a/src/com/android/bluetooth/hid/HidDeviceNativeInterface.java b/src/com/android/bluetooth/hid/HidDeviceNativeInterface.java
index c0f7042..db72a59 100644
--- a/src/com/android/bluetooth/hid/HidDeviceNativeInterface.java
+++ b/src/com/android/bluetooth/hid/HidDeviceNativeInterface.java
@@ -24,11 +24,11 @@
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
-import android.support.annotation.VisibleForTesting;
import android.util.Log;
import com.android.bluetooth.Utils;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
/**
* HID Device Native Interface to/from JNI.
diff --git a/src/com/android/bluetooth/hid/HidHostService.java b/src/com/android/bluetooth/hid/HidHostService.java
index 362d360..ca97556 100644
--- a/src/com/android/bluetooth/hid/HidHostService.java
+++ b/src/com/android/bluetooth/hid/HidHostService.java
@@ -25,8 +25,6 @@
import android.os.Handler;
import android.os.Message;
import android.os.UserHandle;
-import android.provider.Settings;
-import android.support.annotation.VisibleForTesting;
import android.util.Log;
import com.android.bluetooth.BluetoothMetricsProto;
@@ -181,7 +179,11 @@
if (DBG) {
Log.d(TAG, "Incoming HID connection rejected");
}
- disconnectHidNative(Utils.getByteAddress(device));
+ if (disconnectRemote(device)) {
+ disconnectHidNative(Utils.getByteAddress(device));
+ } else {
+ virtualUnPlugNative(Utils.getByteAddress(device));
+ }
} else {
broadcastConnectionState(device, convertHalState(halState));
}
@@ -523,11 +525,11 @@
if (DBG) {
Log.d(TAG, "setPriority: " + device.getAddress());
}
- Settings.Global.putInt(getContentResolver(),
- Settings.Global.getBluetoothHidHostPriorityKey(device.getAddress()), priority);
if (DBG) {
Log.d(TAG, "Saved priority " + device + " = " + priority);
}
+ AdapterService.getAdapterService().getDatabase()
+ .setProfilePriority(device, BluetoothProfile.HID_HOST, priority);
return true;
}
@@ -536,10 +538,8 @@
if (DBG) {
Log.d(TAG, "getPriority: " + device.getAddress());
}
- int priority = Settings.Global.getInt(getContentResolver(),
- Settings.Global.getBluetoothHidHostPriorityKey(device.getAddress()),
- BluetoothProfile.PRIORITY_UNDEFINED);
- return priority;
+ return AdapterService.getAdapterService().getDatabase()
+ .getProfilePriority(device, BluetoothProfile.HID_HOST);
}
/* The following APIs regarding test app for compliance */
@@ -794,42 +794,48 @@
}
}
+ private boolean okToConnect(BluetoothDevice device) {
+ AdapterService adapterService = AdapterService.getAdapterService();
+ //check if it is inbound connection in Quiet mode, priority and Bond status
+ //to decide if its ok to allow this connection
+ if ((adapterService == null) || ((adapterService.isQuietModeEnabled()) && (mTargetDevice
+ == null)) || (BluetoothProfile.PRIORITY_OFF == getPriority(device)) || (
+ device.getBondState() == BluetoothDevice.BOND_NONE)) {
+ return false;
+ }
+
+ return true;
+ }
+
/**
- * Check whether can connect to a peer device.
+ * Check whether need to disconnect or virtual unplug remote device
* The check considers a number of factors during the evaluation.
*
* @param device the peer device to connect to
- * @return true if connection is allowed, otherwise false
+ * @return true if remote device is to be disconnected, otherwise remote
+ * device needs to be virtually unplugged
*/
- @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
- public boolean okToConnect(BluetoothDevice device) {
+ private boolean disconnectRemote(BluetoothDevice device) {
AdapterService adapterService = AdapterService.getAdapterService();
// Check if adapter service is null.
if (adapterService == null) {
- Log.w(TAG, "okToConnect: adapter service is null");
+ Log.w(TAG, "disconnectRemote: adapter service is null");
return false;
}
// Check if this is an incoming connection in Quiet mode.
if (adapterService.isQuietModeEnabled() && mTargetDevice == null) {
- Log.w(TAG, "okToConnect: return false as quiet mode enabled");
+ Log.w(TAG, "disconnectRemote: return false as quiet mode enabled");
return false;
}
- // Check priority and accept or reject the connection.
int priority = getPriority(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);
- return false;
+ // Disconnect remote device if bonded and priroty is OFF
+ if (bondState == BluetoothDevice.BOND_BONDED &&
+ priority == BluetoothProfile.PRIORITY_OFF) {
+ Log.w(TAG, "disconnectRemote: return true");
+ return true;
}
- return true;
+ return false;
}
private static int convertHalState(int halState) {
diff --git a/src/com/android/bluetooth/map/BluetoothMapAccountLoader.java b/src/com/android/bluetooth/map/BluetoothMapAccountLoader.java
index 3ad600b..1fc4af9 100644
--- a/src/com/android/bluetooth/map/BluetoothMapAccountLoader.java
+++ b/src/com/android/bluetooth/map/BluetoothMapAccountLoader.java
@@ -185,7 +185,7 @@
return children;
} finally {
if (mProviderClient != null) {
- mProviderClient.release();
+ mProviderClient.close();
}
}
diff --git a/src/com/android/bluetooth/map/BluetoothMapAppParams.java b/src/com/android/bluetooth/map/BluetoothMapAppParams.java
index c1c2846..7c7c05e 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; // Unused
+ private static final int FILTER_CONVO_ID = 0x22; // Unused
+ private static final int CONVO_LISTING_SIZE = 0x36; // Unused
+ private static final int FILTER_PRESENCE = 0x37; // Unused
+ private static final int FILTER_UID_PRESENT = 0x38; // Unused
+ private static final int CHAT_STATE_CONVO_ID = 0x39; // Unused
+ private static final int FOLDER_VER_COUNTER = 0x23; // Unused
+ private static final int FILTER_MESSAGE_HANDLE = 0x24; // Unused
+ private static final int NOTIFICATION_FILTER = 0x25;
+ private static final int CONVO_PARAMETER_MASK = 0x26; // Unused
// 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; // Unused
+ private static final int FILTER_MESSAGE_HANDLE_LEN = 0x08; // Unused
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 de1c59d..e98fa40 100644
--- a/src/com/android/bluetooth/map/BluetoothMapContent.java
+++ b/src/com/android/bluetooth/map/BluetoothMapContent.java
@@ -148,10 +148,10 @@
private final BluetoothMapAccountItem mAccount;
/* The MasInstance reference is used to update persistent (over a connection) version counters*/
private final BluetoothMapMasInstance mMasInstance;
- private String mMessageVersion = BluetoothMapUtils.MAP_V10_STR;
+ protected String mMessageVersion = BluetoothMapUtils.MAP_V10_STR;
private int mRemoteFeatureMask = BluetoothMapUtils.MAP_FEATURE_DEFAULT_BITMASK;
- private int mMsgListingVersion = BluetoothMapUtils.MAP_MESSAGE_LISTING_FORMAT_V10;
+ protected int mMsgListingVersion = BluetoothMapUtils.MAP_MESSAGE_LISTING_FORMAT_V10;
static final String[] SMS_PROJECTION = new String[]{
BaseColumns._ID,
@@ -2458,7 +2458,6 @@
setDeliveryStatus(ele, tmpCursor, fi, ap);
setThreadId(ele, tmpCursor, fi, ap);
setThreadName(ele, tmpCursor, fi, ap);
- setFolderType(ele, tmpCursor, fi, ap);
}
}
}
@@ -4324,6 +4323,10 @@
}
}
+ public int getMsgListingVersion() {
+ return mMsgListingVersion;
+ }
+
public int getRemoteFeatureMask() {
return this.mRemoteFeatureMask;
}
diff --git a/src/com/android/bluetooth/map/BluetoothMapContentObserver.java b/src/com/android/bluetooth/map/BluetoothMapContentObserver.java
index 70e797d..65909b5 100644
--- a/src/com/android/bluetooth/map/BluetoothMapContentObserver.java
+++ b/src/com/android/bluetooth/map/BluetoothMapContentObserver.java
@@ -113,7 +113,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
@@ -149,7 +149,7 @@
* Actually we only ever use the lower 4 bytes of this variable,
* hence we could manage without the volatile keyword, but as
* we tend to copy ways of doing things, we better do it right:-) */
- private volatile long mEventFilter = 0xFFFFFFFFL;
+ protected volatile long mEventFilter = 0xFFFFFFFFL;
public static final int DELETED_THREAD_ID = -1;
@@ -312,8 +312,7 @@
}
public void setObserverRemoteFeatureMask(int remoteSupportedFeatures) {
- mMapSupportedFeatures =
- remoteSupportedFeatures & BluetoothMapMasInstance.SDP_MAP_MAS_FEATURES;
+ mMapSupportedFeatures = remoteSupportedFeatures;
if ((BluetoothMapUtils.MAP_FEATURE_EXTENDED_EVENT_REPORT_11_BIT & mMapSupportedFeatures)
!= 0) {
mMapEventReportVersion = BluetoothMapUtils.MAP_EVENT_REPORT_V11;
@@ -330,9 +329,9 @@
+ " were set, mMapSupportedFeatures=" + mMapSupportedFeatures);
}
if (D) {
- Log.d(TAG,
- "setObserverRemoteFeatureMask: mMapEventReportVersion=" + mMapEventReportVersion
- + " mMapSupportedFeatures=" + mMapSupportedFeatures);
+ Log.d(TAG, "setObserverRemoteFeatureMask: mMapEventReportVersion="
+ + mMapEventReportVersion + " mMapSupportedFeatures="
+ + Integer.toHexString(mMapSupportedFeatures));
}
}
@@ -1056,7 +1055,7 @@
mResolver.unregisterContentObserver(mObserver);
mObserverRegistered = false;
if (mProviderClient != null) {
- mProviderClient.release();
+ mProviderClient.close();
mProviderClient = null;
}
}
@@ -1534,10 +1533,14 @@
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;
diff --git a/src/com/android/bluetooth/map/BluetoothMapMasInstance.java b/src/com/android/bluetooth/map/BluetoothMapMasInstance.java
index 9d1d355..e4270b1 100644
--- a/src/com/android/bluetooth/map/BluetoothMapMasInstance.java
+++ b/src/com/android/bluetooth/map/BluetoothMapMasInstance.java
@@ -26,6 +26,7 @@
import com.android.bluetooth.BluetoothObexTransport;
import com.android.bluetooth.IObexConnectionHandler;
import com.android.bluetooth.ObexServerSockets;
+import com.android.bluetooth.Utils;
import com.android.bluetooth.map.BluetoothMapContentObserver.Msg;
import com.android.bluetooth.map.BluetoothMapUtils.TYPE;
import com.android.bluetooth.sdp.SdpManager;
@@ -56,6 +57,10 @@
/* TODO: Should these be adaptive for each MAS? - e.g. read from app? */
static final int SDP_MAP_MAS_FEATURES = 0x0000007F;
+ // Adv map version and supported features
+ private final int SDP_MAP_MAS_FEATURES_ADV = 0x603ff;
+ private final int SDP_MAP_MAS_VERSION_ADV = 0x0103;
+ private int mPeerProfileVersion = -1;
private ServerSession mServerSession = null;
// The handle to the socket registration with SDP
private ObexServerSockets mServerSockets = null;
@@ -80,7 +85,7 @@
private int mMasInstanceId = -1;
private boolean mEnableSmsMms = false;
BluetoothMapContentObserver mObserver;
-
+ private BluetoothMapObexServer mapServer;
private AtomicLong mDbIndetifier = new AtomicLong();
private AtomicLong mFolderVersionCounter = new AtomicLong(0);
private AtomicLong mSmsMmsConvoListVersionCounter = new AtomicLong(0);
@@ -363,7 +368,7 @@
}
mMnsClient = mnsClient;
- BluetoothMapObexServer mapServer;
+
if (mAccount != null && mAccount.getType() == TYPE.EMAIL) {
Log.d(mTag, "startObexServerSession getType = " + mAccount.getType());
mObserver = new BluetoothMapContentObserverEmail(mContext,
@@ -385,7 +390,7 @@
this,
mAccount,
mEnableSmsMms);
-
+ mapServer.setRemoteFeatureMask(mRemoteFeatureMask);
// setup transport
BluetoothObexTransport transport = new BluetoothObexTransport(mConnSocket);
mServerSession = new ServerSession(transport, mapServer, null);
@@ -473,15 +478,32 @@
}
public void setRemoteFeatureMask(int supportedFeatures, int remoteProfileVersion) {
- Log.d(mTag, "Current Feature Mask: " + Integer.toHexString(mRemoteFeatureMask)
- + ", remote feature Mask: " + Integer.toHexString(supportedFeatures));
- mRemoteFeatureMask = remoteProfileVersion > SDP_MAP_MAS_VERSION ?
- SDP_MAP_MAS_FEATURES : supportedFeatures;
+ Log.d(mTag, "setRemoteFeatureMask supportedFeatures : "
+ + Integer.toHexString(supportedFeatures) +", remoteProfileVersion: "
+ + Integer.toHexString(remoteProfileVersion));
+ mPeerProfileVersion = remoteProfileVersion;
+ if (Utils.isPtsTestMode()) {
+ mRemoteFeatureMask =
+ SDP_MAP_MAS_FEATURES_ADV;
+ } else if ((remoteProfileVersion > SDP_MAP_MAS_VERSION)
+ && (!BluetoothMapFixes.isMapAdvDisabled())){
+ mRemoteFeatureMask =
+ supportedFeatures & SDP_MAP_MAS_FEATURES_ADV;
+ } else {
+ mRemoteFeatureMask =
+ supportedFeatures & SDP_MAP_MAS_FEATURES;
+ }
+ BluetoothMapUtils.setUtcTimeStamp(mRemoteFeatureMask);
+
+ if (mapServer != null) {
+ mapServer.setRemoteFeatureMask(mRemoteFeatureMask);
+ }
if (mObserver != null) {
mObserver.setObserverRemoteFeatureMask(mRemoteFeatureMask);
- if (V) {
- Log.v(mTag, "setRemoteFeatureMask : set: " + mRemoteFeatureMask);
- }
+ }
+ if (D) {
+ Log.v(mTag, "setRemoteFeatureMask : modified mRemoteFeatureMask: "
+ + Integer.toHexString(mRemoteFeatureMask));
}
}
@@ -489,6 +511,10 @@
return this.mRemoteFeatureMask;
}
+ public int getRemoteProfileVersion() {
+ return mPeerProfileVersion;
+ }
+
@Override
public synchronized boolean onConnect(BluetoothDevice device, BluetoothSocket socket) {
if (!mAcceptNewConnections) {
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 dd8dd48..406cae5 100644
--- a/src/com/android/bluetooth/map/BluetoothMapObexServer.java
+++ b/src/com/android/bluetooth/map/BluetoothMapObexServer.java
@@ -325,11 +325,13 @@
}
public void setRemoteFeatureMask(int mRemoteFeatureMask) {
- if (D) {
- Log.d(TAG, "setRemoteFeatureMask() " + Integer.toHexString(mRemoteFeatureMask));
- }
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 (D) Log.d(TAG," setRemoteFeatureMask mMessageVersion :" + mMessageVersion);
}
@Override
@@ -402,8 +404,8 @@
mMessageVersion = BluetoothMapUtils.MAP_V11_STR;
}
- if (V) {
- Log.v(TAG, "onConnect(): uuid is ok, will send out " + "MSG_SESSION_ESTABLISHED msg.");
+ if (D) {
+ Log.d(TAG, "onConnect(): uuid is ok mMessageVersion :" + mMessageVersion);
}
if (mCallback != null) {
@@ -993,7 +995,7 @@
}
if (mProviderClient != null) {
- mProviderClient.release();
+ mProviderClient.close();
mProviderClient = null;
}
diff --git a/src/com/android/bluetooth/map/BluetoothMapService.java b/src/com/android/bluetooth/map/BluetoothMapService.java
index 4248435..076916f 100644
--- a/src/com/android/bluetooth/map/BluetoothMapService.java
+++ b/src/com/android/bluetooth/map/BluetoothMapService.java
@@ -39,7 +39,6 @@
import android.os.RemoteException;
import android.os.SystemProperties;
import android.provider.Settings;
-import android.support.annotation.VisibleForTesting;
import android.text.TextUtils;
import android.util.Log;
import android.util.SparseArray;
@@ -47,8 +46,10 @@
import com.android.bluetooth.BluetoothMetricsProto;
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.internal.annotations.VisibleForTesting;
import java.io.IOException;
import java.util.ArrayList;
@@ -589,14 +590,14 @@
if (VERBOSE) {
Log.v(TAG, "Saved priority " + device + " = " + priority);
}
- return Settings.Global.putInt(getContentResolver(),
- Settings.Global.getBluetoothMapPriorityKey(device.getAddress()), priority);
+ AdapterService.getAdapterService().getDatabase()
+ .setProfilePriority(device, BluetoothProfile.MAP, priority);
+ return true;
}
int getPriority(BluetoothDevice device) {
- return Settings.Global.getInt(getContentResolver(),
- Settings.Global.getBluetoothMapPriorityKey(device.getAddress()),
- BluetoothProfile.PRIORITY_UNDEFINED);
+ return AdapterService.getAdapterService().getDatabase()
+ .getProfilePriority(device, BluetoothProfile.MAP);
}
@Override
@@ -1081,6 +1082,8 @@
Log.d(TAG, "Received ACTION_SDP_RECORD.");
}
ParcelUuid uuid = intent.getParcelableExtra(BluetoothDevice.EXTRA_UUID);
+ BluetoothDevice remoteDevice = intent.getParcelableExtra(
+ BluetoothDevice.EXTRA_DEVICE);
if (VERBOSE) {
Log.v(TAG, "Received UUID: " + uuid.toString());
Log.v(TAG, "expected UUID: "
@@ -1102,6 +1105,7 @@
.setRemoteFeatureMask(mMnsRecord.getSupportedFeatures(),
mMnsRecord.getProfileVersion());
}
+ BluetoothMapFixes.showNotification(BluetoothMapService.this, remoteDevice);
}
if (mSdpSearchInitiated) {
mSdpSearchInitiated = false; // done searching
@@ -1146,7 +1150,7 @@
if (VERBOSE) {
Log.v(TAG, "ACL disconnected for " + device
- + " mIsWaitingAuthorization :" + mIsWaitingAuthorization);
+ + " mIsWaitingAuthorization :" + mIsWaitingAuthorization);
}
if (mIsWaitingAuthorization && device.equals(sRemoteDevice)) {
diff --git a/src/com/android/bluetooth/map/BluetoothMapUtils.java b/src/com/android/bluetooth/map/BluetoothMapUtils.java
index e867bd6..d973666 100644
--- a/src/com/android/bluetooth/map/BluetoothMapUtils.java
+++ b/src/com/android/bluetooth/map/BluetoothMapUtils.java
@@ -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 mSupportUtcTimeStamp = 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,21 @@
}
}
- 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 String getDateTimeString( long timestamp) {
+ String time = "";
+ if (mSupportUtcTimeStamp) {
+ SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd'T'HHmmssZ");
+ Date date = new Date(timestamp);
+ time = format.format(date); // Format to YYYYMMDDTHHMMSS±hhmm UTC time ± offset
+ } else {
+ SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd'T'HHmmss");
+ Date date = new Date(timestamp);
+ time = format.format(date); // Format to YYYYMMDDTHHMMSS local time
+ }
+ if (V) Log.v(TAG, "getDateTimeString timestamp :" + timestamp + " time:" + time);
+ return time;
}
-
public static void printCursor(Cursor c) {
if (D) {
StringBuilder sb = new StringBuilder();
@@ -671,5 +681,15 @@
}
}
+ public static void setUtcTimeStamp(int remoteFeatureMask) {
+ if ((remoteFeatureMask & MAP_FEATURE_DEFINED_TIMESTAMP_FORMAT_BIT)
+ == MAP_FEATURE_DEFINED_TIMESTAMP_FORMAT_BIT) {
+ mSupportUtcTimeStamp = true;
+ } else {
+ mSupportUtcTimeStamp = false;
+ }
+ if (V) Log.v(TAG, "setUtcTimeStamp " + mSupportUtcTimeStamp);
+ }
+
}
diff --git a/src/com/android/bluetooth/mapclient/MapClientService.java b/src/com/android/bluetooth/mapclient/MapClientService.java
index 0d77c8e..9467683 100644
--- a/src/com/android/bluetooth/mapclient/MapClientService.java
+++ b/src/com/android/bluetooth/mapclient/MapClientService.java
@@ -30,12 +30,12 @@
import android.content.IntentFilter;
import android.net.Uri;
import android.os.ParcelUuid;
-import android.provider.Settings;
-import android.support.annotation.VisibleForTesting;
import android.util.Log;
import com.android.bluetooth.Utils;
+import com.android.bluetooth.btservice.AdapterService;
import com.android.bluetooth.btservice.ProfileService;
+import com.android.internal.annotations.VisibleForTesting;
import java.util.ArrayList;
import java.util.Arrays;
@@ -101,6 +101,10 @@
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");
+ return false;
+ }
MceStateMachine mapStateMachine = mMapInstanceMap.get(device);
if (mapStateMachine == null) {
// a map state machine instance doesn't exist yet, create a new one if we can.
@@ -189,20 +193,20 @@
}
public synchronized List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
- Log.d(TAG, "getDevicesMatchingConnectionStates" + Arrays.toString(states));
+ if (DBG) Log.d(TAG, "getDevicesMatchingConnectionStates" + Arrays.toString(states));
List<BluetoothDevice> deviceList = new ArrayList<>();
Set<BluetoothDevice> bondedDevices = mAdapter.getBondedDevices();
int connectionState;
for (BluetoothDevice device : bondedDevices) {
connectionState = getConnectionState(device);
- Log.d(TAG, "Device: " + device + "State: " + connectionState);
+ if (DBG) Log.d(TAG, "Device: " + device + "State: " + connectionState);
for (int i = 0; i < states.length; i++) {
if (connectionState == states[i]) {
deviceList.add(device);
}
}
}
- Log.d(TAG, deviceList.toString());
+ if (DBG) Log.d(TAG, deviceList.toString());
return deviceList;
}
@@ -214,19 +218,17 @@
}
public boolean setPriority(BluetoothDevice device, int priority) {
- Settings.Global.putInt(getContentResolver(),
- Settings.Global.getBluetoothMapClientPriorityKey(device.getAddress()), priority);
if (VDBG) {
Log.v(TAG, "Saved priority " + device + " = " + priority);
}
+ AdapterService.getAdapterService().getDatabase()
+ .setProfilePriority(device, BluetoothProfile.MAP_CLIENT, priority);
return true;
}
public int getPriority(BluetoothDevice device) {
- int priority = Settings.Global.getInt(getContentResolver(),
- Settings.Global.getBluetoothMapClientPriorityKey(device.getAddress()),
- BluetoothProfile.PRIORITY_UNDEFINED);
- return priority;
+ return AdapterService.getAdapterService().getDatabase()
+ .getProfilePriority(device, BluetoothProfile.MAP_CLIENT);
}
public synchronized boolean sendMessage(BluetoothDevice device, Uri[] contacts, String message,
@@ -293,7 +295,13 @@
setMapClientService(null);
}
- void cleanupDevice(BluetoothDevice device) {
+ /**
+ * cleanupDevice removes the associated state machine from the instance map
+ *
+ * @param device BluetoothDevice address of remote device
+ */
+ @VisibleForTesting
+ public void cleanupDevice(BluetoothDevice device) {
if (DBG) {
StringBuilder sb = new StringBuilder();
dump(sb);
@@ -346,10 +354,23 @@
return mapStateMachine.getUnreadMessages();
}
+ /**
+ * Returns the SDP record's MapSupportedFeatures field (see Bluetooth MAP 1.4 spec, page 114).
+ * @param device The Bluetooth device to get this value for.
+ * @return the SDP record's MapSupportedFeatures field.
+ */
+ public synchronized int getSupportedFeatures(BluetoothDevice device) {
+ MceStateMachine mapStateMachine = mMapInstanceMap.get(device);
+ if (mapStateMachine == null) {
+ if (DBG) Log.d(TAG, "in getSupportedFeatures, returning 0");
+ return 0;
+ }
+ return mapStateMachine.getSupportedFeatures();
+ }
+
@Override
public void dump(StringBuilder sb) {
super.dump(sb);
- ProfileService.println(sb, "# Services Connected: " + mMapInstanceMap.size());
for (MceStateMachine stateMachine : mMapInstanceMap.values()) {
stateMachine.dump(sb);
}
@@ -487,7 +508,7 @@
if (service == null) {
return false;
}
- Log.d(TAG, "Checking Permission of sendMessage");
+ if (DBG) Log.d(TAG, "Checking Permission of sendMessage");
mService.enforceCallingOrSelfPermission(Manifest.permission.SEND_SMS,
"Need SEND_SMS permission");
@@ -504,6 +525,21 @@
"Need READ_SMS permission");
return service.getUnreadMessages(device);
}
+
+ @Override
+ public int getSupportedFeatures(BluetoothDevice device) {
+ MapClientService service = getService();
+ if (service == null) {
+ if (DBG) {
+ Log.d(TAG,
+ "in MapClientService getSupportedFeatures stub, returning 0");
+ }
+ return 0;
+ }
+ mService.enforceCallingOrSelfPermission(Manifest.permission.BLUETOOTH,
+ "Need BLUETOOTH permission");
+ return service.getSupportedFeatures(device);
+ }
}
private class MapBroadcastReceiver extends BroadcastReceiver {
diff --git a/src/com/android/bluetooth/mapclient/MapUtils.java b/src/com/android/bluetooth/mapclient/MapUtils.java
index 4ed26c8..b0a603b 100644
--- a/src/com/android/bluetooth/mapclient/MapUtils.java
+++ b/src/com/android/bluetooth/mapclient/MapUtils.java
@@ -15,7 +15,7 @@
*/
package com.android.bluetooth.mapclient;
-import android.support.annotation.VisibleForTesting;
+import com.android.internal.annotations.VisibleForTesting;
class MapUtils {
private static MnsService sMnsService = null;
diff --git a/src/com/android/bluetooth/mapclient/MasClient.java b/src/com/android/bluetooth/mapclient/MasClient.java
index d7283bf..6991704 100644
--- a/src/com/android/bluetooth/mapclient/MasClient.java
+++ b/src/com/android/bluetooth/mapclient/MasClient.java
@@ -104,7 +104,7 @@
+ mSdpMasRecord.getRfcommCannelNumber());
}
mSocket = mRemoteDevice.createRfcommSocket(mSdpMasRecord.getRfcommCannelNumber());
- Log.d(TAG, mRemoteDevice.toString() + "Socket: " + mSocket.toString());
+ if (DBG) Log.d(TAG, mRemoteDevice.toString() + "Socket: " + mSocket.toString());
mSocket.connect();
mTransport = new BluetoothObexTransport(mSocket);
@@ -118,7 +118,7 @@
oap.addToHeaderSet(headerset);
headerset = mSession.connect(headerset);
- Log.d(TAG, "Connection results" + headerset.getResponseCode());
+ if (DBG) Log.d(TAG, "Connection results" + headerset.getResponseCode());
if (headerset.getResponseCode() == ResponseCodes.OBEX_HTTP_OK) {
if (DBG) {
@@ -190,6 +190,10 @@
NATIVE, UTF_8;
}
+ SdpMasRecord getSdpMasRecord() {
+ return mSdpMasRecord;
+ }
+
private static class MasClientHandler extends Handler {
WeakReference<MasClient> mInst;
@@ -201,22 +205,23 @@
@Override
public void handleMessage(Message msg) {
MasClient inst = mInst.get();
- if (!inst.mConnected && msg.what != CONNECT) {
- Log.w(TAG, "Cannot execute " + msg + " when not CONNECTED.");
- return;
- }
-
switch (msg.what) {
case CONNECT:
- inst.connect();
+ if (!inst.mConnected) {
+ inst.connect();
+ }
break;
case DISCONNECT:
- inst.disconnect();
+ if (inst.mConnected) {
+ inst.disconnect();
+ }
break;
case REQUEST:
- inst.executeRequest((Request) msg.obj);
+ if (inst.mConnected) {
+ inst.executeRequest((Request) msg.obj);
+ }
break;
}
}
diff --git a/src/com/android/bluetooth/mapclient/MceStateMachine.java b/src/com/android/bluetooth/mapclient/MceStateMachine.java
index ae11d2a..0a428b4 100644
--- a/src/com/android/bluetooth/mapclient/MceStateMachine.java
+++ b/src/com/android/bluetooth/mapclient/MceStateMachine.java
@@ -51,6 +51,7 @@
import android.content.Intent;
import android.net.Uri;
import android.os.Message;
+import android.provider.Telephony;
import android.telecom.PhoneAccount;
import android.telephony.SmsManager;
import android.util.Log;
@@ -68,8 +69,10 @@
import java.util.ArrayList;
import java.util.Calendar;
+import java.util.Date;
import java.util.HashMap;
import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
/* The MceStateMachine is responsible for setting up and maintaining a connection to a single
* specific Messaging Server Equipment endpoint. Upon connect command an SDP record is retrieved,
@@ -122,6 +125,48 @@
new HashMap<>(MAX_MESSAGES);
private Bmessage.Type mDefaultMessageType = Bmessage.Type.SMS_CDMA;
+ /**
+ * An object to hold the necessary meta-data for each message so we can broadcast it alongside
+ * the message content.
+ *
+ * This is necessary because the metadata is inferred or received separately from the actual
+ * message content.
+ *
+ * Note: In the future it may be best to use the entries from the MessageListing in full instead
+ * of this small subset.
+ */
+ private class MessageMetadata {
+ private final String mHandle;
+ private final Long mTimestamp;
+ private boolean mRead;
+
+ MessageMetadata(String handle, Long timestamp, boolean read) {
+ mHandle = handle;
+ mTimestamp = timestamp;
+ mRead = read;
+ }
+
+ public String getHandle() {
+ return mHandle;
+ }
+
+ public Long getTimestamp() {
+ return mTimestamp;
+ }
+
+ public synchronized boolean getRead() {
+ return mRead;
+ }
+
+ public synchronized void setRead(boolean read) {
+ mRead = read;
+ }
+ }
+
+ // Map each message to its metadata via the handle
+ private ConcurrentHashMap<String, MessageMetadata> mMessages =
+ new ConcurrentHashMap<String, MessageMetadata>();
+
MceStateMachine(MapClientService service, BluetoothDevice device) {
this(service, device, null);
}
@@ -184,7 +229,7 @@
public synchronized int getState() {
IState currentState = this.getCurrentState();
- if (currentState.getClass() == Disconnected.class) {
+ if (currentState == null || currentState.getClass() == Disconnected.class) {
return BluetoothProfile.STATE_DISCONNECTED;
}
if (currentState.getClass() == Connected.class) {
@@ -280,6 +325,15 @@
return false;
}
+ synchronized int getSupportedFeatures() {
+ if (this.getCurrentState() == mConnected && mMasClient != null) {
+ if (DBG) Log.d(TAG, "returning getSupportedFeatures from SDP record");
+ return mMasClient.getSdpMasRecord().getSupportedFeatures();
+ }
+ if (DBG) Log.d(TAG, "in getSupportedFeatures, returning 0");
+ return 0;
+ }
+
private String getContactURIFromPhone(String number) {
return PhoneAccount.SCHEME_TEL + ":" + number;
}
@@ -302,8 +356,8 @@
}
public void dump(StringBuilder sb) {
- ProfileService.println(sb, "mCurrentDevice: " + mDevice.getAddress() + " (name = "
- + mDevice.getName() + "), StateMachine: " + this.toString());
+ ProfileService.println(sb, "mCurrentDevice: " + mDevice.getAddress() + "("
+ + mDevice.getName() + ") " + this.toString());
}
class Disconnected extends State {
@@ -331,7 +385,6 @@
}
onConnectionStateChanged(mPreviousState, BluetoothProfile.STATE_CONNECTING);
- BluetoothAdapter.getDefaultAdapter().cancelDiscovery();
// When commanded to connect begin SDP to find the MAS server.
mDevice.sdpSearch(BluetoothUuid.MAS);
sendMessageDelayed(MSG_CONNECTING_TIMEOUT, TIMEOUT);
@@ -349,9 +402,14 @@
Log.d(TAG, "SDP Complete");
}
if (mMasClient == null) {
- mMasClient = new MasClient(mDevice, MceStateMachine.this,
- (SdpMasRecord) message.obj);
- setDefaultMessageType((SdpMasRecord) message.obj);
+ SdpMasRecord record = (SdpMasRecord) message.obj;
+ if (record == null) {
+ Log.e(TAG, "Unexpected: SDP record is null for device "
+ + mDevice.getName());
+ return NOT_HANDLED;
+ }
+ mMasClient = new MasClient(mDevice, MceStateMachine.this, record);
+ setDefaultMessageType(record);
}
break;
@@ -360,6 +418,9 @@
break;
case MSG_MAS_DISCONNECTED:
+ if (mMasClient != null) {
+ mMasClient.shutdown();
+ }
transitionTo(mDisconnected);
break;
@@ -456,8 +517,12 @@
Log.d(TAG, "Message Sent......." + messageHandle);
}
// ignore the top-order byte (converted to string) in the handle for now
- mSentMessageLog.put(messageHandle.substring(2),
- ((RequestPushMessage) message.obj).getBMsg());
+ // some test devices don't populate messageHandle field.
+ // in such cases, no need to wait up for response for such messages.
+ if (messageHandle != null && messageHandle.length() > 2) {
+ mSentMessageLog.put(messageHandle.substring(2),
+ ((RequestPushMessage) message.obj).getBMsg());
+ }
} else if (message.obj instanceof RequestGetMessagesListing) {
processMessageListing((RequestGetMessagesListing) message.obj);
}
@@ -483,6 +548,15 @@
mPreviousState = BluetoothProfile.STATE_CONNECTED;
}
+ /**
+ * Given a message notification event, will ensure message caching and updating and update
+ * interested applications.
+ *
+ * Message notifications arrive for both remote message reception and Message-Listing object
+ * updates that are triggered by the server side.
+ *
+ * @param msg - A Message object containing a EventReport object describing the remote event
+ */
private void processNotification(Message msg) {
if (DBG) {
Log.d(TAG, "Handler: msg: " + msg.what);
@@ -491,16 +565,25 @@
switch (msg.what) {
case MSG_NOTIFICATION:
EventReport ev = (EventReport) msg.obj;
- if (DBG) {
- Log.d(TAG, "Message Type = " + ev.getType());
+ if (ev == null) {
+ Log.w(TAG, "MSG_NOTIFICATION event is null");
+ return;
}
if (DBG) {
- Log.d(TAG, "Message handle = " + ev.getHandle());
+ Log.d(TAG, "Message Type = " + ev.getType()
+ + ", Message handle = " + ev.getHandle());
}
switch (ev.getType()) {
case NEW_MESSAGE:
- //mService.get().sendNewMessageNotification(ev);
+ // Infer the timestamp for this message as 'now' and read status false
+ // instead of getting the message listing data for it
+ if (!mMessages.contains(ev.getHandle())) {
+ Calendar calendar = Calendar.getInstance();
+ MessageMetadata metadata = new MessageMetadata(ev.getHandle(),
+ calendar.getTime().getTime(), false);
+ mMessages.put(ev.getHandle(), metadata);
+ }
mMasClient.makeRequest(new RequestGetMessage(ev.getHandle(),
MasClient.CharsetType.UTF_8, false));
break;
@@ -516,6 +599,8 @@
// Sets the specified message status to "read" (from "unread" status, mostly)
private void markMessageRead(RequestGetMessage request) {
if (DBG) Log.d(TAG, "markMessageRead");
+ MessageMetadata metadata = mMessages.get(request.getHandle());
+ metadata.setRead(true);
mMasClient.makeRequest(new RequestSetMessageStatus(
request.getHandle(), RequestSetMessageStatus.StatusIndicator.READ));
}
@@ -527,21 +612,41 @@
request.getHandle(), RequestSetMessageStatus.StatusIndicator.DELETED));
}
+ /**
+ * Given the result of a Message Listing request, will cache the contents of each Message in
+ * the Message Listing Object and kick off requests to retrieve message contents from the
+ * remote device.
+ *
+ * @param request - A request object that has been resolved and returned with a message list
+ */
private void processMessageListing(RequestGetMessagesListing request) {
if (DBG) {
Log.d(TAG, "processMessageListing");
}
- ArrayList<com.android.bluetooth.mapclient.Message> messageHandles = request.getList();
- if (messageHandles != null) {
- for (com.android.bluetooth.mapclient.Message handle : messageHandles) {
+ ArrayList<com.android.bluetooth.mapclient.Message> messageListing = request.getList();
+ if (messageListing != null) {
+ for (com.android.bluetooth.mapclient.Message msg : messageListing) {
if (DBG) {
Log.d(TAG, "getting message ");
}
- getMessage(handle.getHandle());
+ // A message listing coming from the server should always have up to date data
+ mMessages.put(msg.getHandle(), new MessageMetadata(msg.getHandle(),
+ msg.getDateTime().getTime(), msg.isRead()));
+ getMessage(msg.getHandle());
}
}
}
+ /**
+ * Given the response of a GetMessage request, will broadcast the bMessage contents on to
+ * all registered applications.
+ *
+ * Inbound messages arrive as bMessage objects following a GetMessage request. GetMessage
+ * uses a message handle that can arrive from both a GetMessageListing request or a Message
+ * Notification event.
+ *
+ * @param request - A request object that has been resolved and returned with message data
+ */
private void processInboundMessage(RequestGetMessage request) {
Bmessage message = request.getMessage();
if (DBG) {
@@ -570,10 +675,18 @@
Log.d(TAG, "Recipients" + message.getRecipients().toString());
}
+ // Grab the message metadata and update the cached read status from the bMessage
+ MessageMetadata metadata = mMessages.get(request.getHandle());
+ metadata.setRead(request.getMessage().getStatus() == Bmessage.Status.READ);
+
Intent intent = new Intent();
intent.setAction(BluetoothMapClient.ACTION_MESSAGE_RECEIVED);
intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice);
intent.putExtra(BluetoothMapClient.EXTRA_MESSAGE_HANDLE, request.getHandle());
+ intent.putExtra(BluetoothMapClient.EXTRA_MESSAGE_TIMESTAMP,
+ metadata.getTimestamp());
+ intent.putExtra(BluetoothMapClient.EXTRA_MESSAGE_READ_STATUS,
+ metadata.getRead());
intent.putExtra(android.content.Intent.EXTRA_TEXT, message.getBodyContent());
VCardEntry originator = message.getOriginator();
if (originator != null) {
@@ -592,7 +705,12 @@
intent.putExtra(BluetoothMapClient.EXTRA_SENDER_CONTACT_NAME,
originator.getDisplayName());
}
- mService.sendBroadcast(intent);
+ // Only send to the current default SMS app if one exists
+ String defaultMessagingPackage = Telephony.Sms.getDefaultSmsPackage(mService);
+ if (defaultMessagingPackage != null) {
+ intent.setPackage(defaultMessagingPackage);
+ }
+ mService.sendBroadcast(intent, android.Manifest.permission.RECEIVE_SMS);
break;
case MMS:
@@ -607,6 +725,9 @@
if (DBG) {
Log.d(TAG, "got a status for " + handle + " Status = " + status);
}
+ // some test devices don't populate messageHandle field.
+ // in such cases, ignore such messages.
+ if (handle == null || handle.length() <= 2) return;
PendingIntent intentToSend = null;
// ignore the top-order byte (converted to string) in the handle for now
String shortHandle = handle.substring(2);
@@ -688,10 +809,8 @@
void receiveEvent(EventReport ev) {
if (DBG) {
- Log.d(TAG, "Message Type = " + ev.getType());
- }
- if (DBG) {
- Log.d(TAG, "Message handle = " + ev.getHandle());
+ Log.d(TAG, "Message Type = " + ev.getType()
+ + ", Message handle = " + ev.getHandle());
}
sendMessage(MSG_NOTIFICATION, ev);
}
diff --git a/src/com/android/bluetooth/opp/BluetoothOppFileProvider.java b/src/com/android/bluetooth/opp/BluetoothOppFileProvider.java
index 166983a..53b785f 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppFileProvider.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppFileProvider.java
@@ -23,9 +23,10 @@
import android.net.Uri;
import android.os.UserHandle;
import android.os.UserManager;
-import android.support.v4.content.FileProvider;
import android.util.Log;
+import androidx.core.content.FileProvider;
+
import java.io.File;
/**
diff --git a/src/com/android/bluetooth/opp/BluetoothOppLauncherActivity.java b/src/com/android/bluetooth/opp/BluetoothOppLauncherActivity.java
index d952478..d602f3b 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppLauncherActivity.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppLauncherActivity.java
@@ -81,6 +81,12 @@
return;
}
+ if (action == null) {
+ Log.w(TAG, "action is null");
+ finish();
+ return;
+ }
+
if (action.equals(Intent.ACTION_SEND) || action.equals(Intent.ACTION_SEND_MULTIPLE)) {
//Check if Bluetooth is available in the beginning instead of at the end
if (!isBluetoothAllowed()) {
@@ -267,7 +273,7 @@
Settings.System.getString(resolver, Settings.Global.AIRPLANE_MODE_RADIOS);
final boolean isAirplaneSensitive =
airplaneModeRadios == null || airplaneModeRadios.contains(
- Settings.System.RADIO_BLUETOOTH);
+ Settings.Global.RADIO_BLUETOOTH);
if (!isAirplaneSensitive) {
return true;
}
diff --git a/src/com/android/bluetooth/opp/BluetoothOppNotification.java b/src/com/android/bluetooth/opp/BluetoothOppNotification.java
index ee7cadd..ba47aeb 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppNotification.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppNotification.java
@@ -40,6 +40,7 @@
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
+import android.graphics.drawable.Icon;
import android.net.Uri;
import android.os.Handler;
import android.os.Message;
@@ -557,12 +558,14 @@
.setClassName(Constants.THIS_PACKAGE_NAME,
BluetoothOppReceiver.class.getName());
Notification.Action actionDecline =
- new Notification.Action.Builder(R.drawable.ic_decline,
+ new Notification.Action.Builder(Icon.createWithResource(mContext,
+ R.drawable.ic_decline),
mContext.getText(R.string.incoming_file_confirm_cancel),
PendingIntent.getBroadcast(mContext, 0,
new Intent(baseIntent).setAction(Constants.ACTION_DECLINE),
0)).build();
- Notification.Action actionAccept = new Notification.Action.Builder(R.drawable.ic_accept,
+ Notification.Action actionAccept = new Notification.Action.Builder(
+ Icon.createWithResource(mContext, R.drawable.ic_accept),
mContext.getText(R.string.incoming_file_confirm_ok),
PendingIntent.getBroadcast(mContext, 0,
new Intent(baseIntent).setAction(Constants.ACTION_ACCEPT), 0)).build();
@@ -589,7 +592,7 @@
.setStyle(new Notification.BigTextStyle().bigText(mContext.getString(
R.string.incoming_file_confirm_Notification_content,
info.mDeviceName, info.mFileName)))
- .setContentInfo(Formatter.formatFileSize(mContext, info.mTotalBytes))
+ .setSubText(Formatter.formatFileSize(mContext, info.mTotalBytes))
.setSmallIcon(R.drawable.bt_incomming_file_notification)
.setLocalOnly(true)
.build();
diff --git a/src/com/android/bluetooth/opp/BluetoothOppObexServerSession.java b/src/com/android/bluetooth/opp/BluetoothOppObexServerSession.java
index dc07478..91b62fd 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppObexServerSession.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppObexServerSession.java
@@ -116,7 +116,6 @@
PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
mPartialWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
mPartialWakeLock.setReferenceCounted(false);
- BTOppUtils.acquireFullWakeLock(pm, TAG);
}
@Override
@@ -642,7 +641,6 @@
}
private synchronized void releaseWakeLocks() {
- BTOppUtils.releaseFullWakeLock();
if (mPartialWakeLock.isHeld()) {
if (D) Log.d(TAG, "releasing partial wakelock");
mPartialWakeLock.release();
diff --git a/src/com/android/bluetooth/opp/BluetoothOppService.java b/src/com/android/bluetooth/opp/BluetoothOppService.java
index f432635..1324619 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppService.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppService.java
@@ -52,7 +52,6 @@
import android.os.Handler;
import android.os.Message;
import android.os.Process;
-import android.support.annotation.VisibleForTesting;
import android.util.Log;
import com.android.bluetooth.BluetoothObexTransport;
@@ -60,6 +59,7 @@
import com.android.bluetooth.ObexServerSockets;
import com.android.bluetooth.btservice.ProfileService;
import com.android.bluetooth.sdp.SdpManager;
+import com.android.internal.annotations.VisibleForTesting;
import com.google.android.collect.Lists;
@@ -244,6 +244,11 @@
@Override
public boolean stop() {
+ if (D) Log.d(TAG," stop");
+ if (sBluetoothOppService == null) {
+ Log.w(TAG, "stop() called before start()");
+ return true;
+ }
setBluetoothOppService(null);
mHandler.sendMessage(mHandler.obtainMessage(STOP_LISTENER));
return true;
@@ -255,8 +260,8 @@
if (V) {
Log.v(TAG, "Starting RfcommListener");
}
- mHandler.sendMessage(mHandler.obtainMessage(START_LISTENER));
mListenStarted = true;
+ mHandler.sendMessage(mHandler.obtainMessage(START_LISTENER));
}
}
}
@@ -319,8 +324,12 @@
Log.i(TAG, " handleMessage :" + msg.what);
switch (msg.what) {
case STOP_LISTENER:
- stopListeners();
+ if (!mListenStarted) { // Extra check to avoid redundant call
+ if (V) Log.v(TAG," stop_listener already called");
+ return;
+ }
mListenStarted = false;
+ stopListeners();
//Stop Active INBOUND Transfer
if (mServerTransfer != null) {
mServerTransfer.onBatchCanceled();
@@ -361,6 +370,7 @@
if (mShares != null) {
mShares.clear();
}
+
if (mNotifier != null) {
mNotifier.cancelNotifications();
}
@@ -664,9 +674,6 @@
}
}
- if (shouldScanFile(arrayPos)) {
- scanFile(arrayPos);
- }
deleteShare(arrayPos); // this advances in the array
} else {
int id = cursor.getInt(idColumn);
@@ -676,9 +683,7 @@
if (V) {
Log.v(TAG, "Array update: inserting " + id + " @ " + arrayPos);
}
- if (shouldScanFile(arrayPos)) {
- scanFile(arrayPos);
- }
+ scanFileIfNeeded(arrayPos);
++arrayPos;
cursor.moveToNext();
isAfterLast = cursor.isAfterLast();
@@ -693,17 +698,11 @@
Log.v(TAG,
"Array update: removing " + arrayId + " @ " + arrayPos);
}
- if (shouldScanFile(arrayPos)) {
- scanFile(arrayPos);
- }
deleteShare(arrayPos);
} else if (arrayId == id) {
// This cursor row already exists in the stored array.
updateShare(cursor, arrayPos);
-
- if (shouldScanFile(arrayPos)) {
- scanFile(arrayPos);
- }
+ scanFileIfNeeded(arrayPos);
++arrayPos;
cursor.moveToNext();
isAfterLast = cursor.isAfterLast();
@@ -714,10 +713,7 @@
Log.v(TAG, "Array update: appending " + id + " @ " + arrayPos);
}
insertShare(cursor, arrayPos);
-
- if (shouldScanFile(arrayPos)) {
- scanFile(arrayPos);
- }
+ scanFileIfNeeded(arrayPos);
++arrayPos;
cursor.moveToNext();
isAfterLast = cursor.isAfterLast();
@@ -1072,8 +1068,14 @@
}
}
- private boolean scanFile(int arrayPos) {
+ private void scanFileIfNeeded(int arrayPos) {
BluetoothOppShareInfo info = mShares.get(arrayPos);
+ boolean isFileReceived = BluetoothShare.isStatusSuccess(info.mStatus)
+ && info.mDirection == BluetoothShare.DIRECTION_INBOUND && !info.mMediaScanned
+ && info.mConfirm != BluetoothShare.USER_CONFIRMATION_HANDOVER_CONFIRMED;
+ if (!isFileReceived) {
+ return;
+ }
synchronized (BluetoothOppService.this) {
if (D) {
Log.d(TAG, "Scanning file " + info.mFilename);
@@ -1081,20 +1083,10 @@
if (!mMediaScanInProgress) {
mMediaScanInProgress = true;
new MediaScannerNotifier(this, info, mHandler);
- return true;
- } else {
- return false;
}
}
}
- private boolean shouldScanFile(int arrayPos) {
- BluetoothOppShareInfo info = mShares.get(arrayPos);
- return BluetoothShare.isStatusSuccess(info.mStatus)
- && info.mDirection == BluetoothShare.DIRECTION_INBOUND && !info.mMediaScanned
- && info.mConfirm != BluetoothShare.USER_CONFIRMATION_HANDOVER_CONFIRMED;
- }
-
// Run in a background thread at boot.
private static void trimDatabase(ContentResolver contentResolver) {
// remove the invisible/unconfirmed inbound shares
diff --git a/src/com/android/bluetooth/opp/BluetoothOppTransferActivity.java b/src/com/android/bluetooth/opp/BluetoothOppTransferActivity.java
index 74fd872..e4d98e8 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppTransferActivity.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppTransferActivity.java
@@ -309,7 +309,10 @@
} else if (mWhichDialog == DIALOG_RECEIVE_COMPLETE_FAIL) {
if (mTransInfo.mStatus == BluetoothShare.STATUS_ERROR_SDCARD_FULL) {
mLine1View = (TextView) mView.findViewById(R.id.line1_view);
- tmp = getString(R.string.bt_sm_2_1, mTransInfo.mDeviceName);
+ int id = BluetoothOppUtility.deviceHasNoSdCard()
+ ? R.string.bt_sm_2_1_nosdcard
+ : R.string.bt_sm_2_1_default;
+ tmp = getString(id);
mLine1View.setText(tmp);
mLine2View = (TextView) mView.findViewById(R.id.line2_view);
tmp = getString(R.string.download_fail_line2, mTransInfo.mFileName);
@@ -372,24 +375,13 @@
mTransInfo.mID);
} else if (mWhichDialog == DIALOG_SEND_COMPLETE_FAIL) {
// "try again"
-
// make current transfer "hidden"
BluetoothOppUtility.updateVisibilityToHidden(this, mUri);
// clear correspondent notification item
((NotificationManager) getSystemService(NOTIFICATION_SERVICE)).cancel(
mTransInfo.mID);
-
- // retry the failed transfer
- Uri uri = BluetoothOppUtility.originalUri(Uri.parse(mTransInfo.mFileUri));
- BluetoothOppSendFileInfo sendFileInfo =
- BluetoothOppSendFileInfo.generateFileInfo(BluetoothOppTransferActivity
- .this, uri, mTransInfo.mFileType, false);
- uri = BluetoothOppUtility.generateUri(uri, sendFileInfo);
- BluetoothOppUtility.putSendFileInfo(uri, sendFileInfo);
- mTransInfo.mFileUri = uri.toString();
- BluetoothOppUtility.retryTransfer(this, mTransInfo);
-
+ retryFailedTrasfer();
BluetoothDevice remoteDevice = mAdapter.getRemoteDevice(mTransInfo.mDestAddr);
// Display toast message
@@ -499,4 +491,22 @@
.setText(getString(R.string.upload_fail_cancel));
}
}
+
+ // Retry the failed transfer in background thread
+ private void retryFailedTrasfer() {
+ new Thread() {
+ @Override
+ public void run() {
+ super.run();
+ Uri uri = BluetoothOppUtility.originalUri(Uri.parse(mTransInfo.mFileUri));
+ BluetoothOppSendFileInfo sendFileInfo =
+ BluetoothOppSendFileInfo.generateFileInfo(BluetoothOppTransferActivity
+ .this, uri, mTransInfo.mFileType, false);
+ uri = BluetoothOppUtility.generateUri(uri, sendFileInfo);
+ BluetoothOppUtility.putSendFileInfo(uri, sendFileInfo);
+ mTransInfo.mFileUri = uri.toString();
+ BluetoothOppUtility.retryTransfer(BluetoothOppTransferActivity.this, mTransInfo);
+ }
+ }.start();
+ }
}
diff --git a/src/com/android/bluetooth/opp/BluetoothOppTransferAdapter.java b/src/com/android/bluetooth/opp/BluetoothOppTransferAdapter.java
index 7221c53..04c9dff 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppTransferAdapter.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppTransferAdapter.java
@@ -58,7 +58,7 @@
private Context mContext;
public BluetoothOppTransferAdapter(Context context, int layout, Cursor c) {
- super(context, layout, c);
+ super(context, layout, c, true);
mContext = context;
}
diff --git a/src/com/android/bluetooth/opp/BluetoothOppUtility.java b/src/com/android/bluetooth/opp/BluetoothOppUtility.java
index cb506d7..7cb6f80 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppUtility.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppUtility.java
@@ -46,6 +46,7 @@
import android.database.SQLException;
import android.net.Uri;
import android.os.Environment;
+import android.os.SystemProperties;
import android.util.Log;
import com.android.bluetooth.R;
@@ -57,6 +58,7 @@
import java.math.RoundingMode;
import java.text.DecimalFormat;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
@@ -67,6 +69,8 @@
private static final String TAG = "BluetoothOppUtility";
private static final boolean D = Constants.DEBUG;
private static final boolean V = Constants.VERBOSE;
+ /** Whether the device has the "nosdcard" characteristic, or null if not-yet-known. */
+ private static Boolean sNoSdCard = null;
private static final ConcurrentHashMap<Uri, BluetoothOppSendFileInfo> sSendFileMap =
new ConcurrentHashMap<Uri, BluetoothOppSendFileInfo>();
@@ -304,6 +308,17 @@
}
/**
+ * Whether the device has the "nosdcard" characteristic or not.
+ */
+ public static boolean deviceHasNoSdCard() {
+ if (sNoSdCard == null) {
+ String characteristics = SystemProperties.get("ro.build.characteristics", "");
+ sNoSdCard = Arrays.asList(characteristics).contains("nosdcard");
+ }
+ return sNoSdCard;
+ }
+
+ /**
* Get status description according to status code.
*/
public static String getStatusDescription(Context context, int statusCode, String deviceName) {
@@ -323,11 +338,15 @@
} else if (statusCode == BluetoothShare.STATUS_FILE_ERROR) {
ret = context.getString(R.string.status_file_error);
} else if (statusCode == BluetoothShare.STATUS_ERROR_NO_SDCARD) {
- ret = context.getString(R.string.status_no_sd_card);
+ int id = deviceHasNoSdCard()
+ ? R.string.status_no_sd_card_nosdcard
+ : R.string.status_no_sd_card_default;
+ ret = context.getString(id);
} else if (statusCode == BluetoothShare.STATUS_CONNECTION_ERROR) {
ret = context.getString(R.string.status_connection_error);
} else if (statusCode == BluetoothShare.STATUS_ERROR_SDCARD_FULL) {
- ret = context.getString(R.string.bt_sm_2_1, deviceName);
+ int id = deviceHasNoSdCard() ? R.string.bt_sm_2_1_nosdcard : R.string.bt_sm_2_1_default;
+ ret = context.getString(id);
} else if ((statusCode == BluetoothShare.STATUS_BAD_REQUEST) || (statusCode
== BluetoothShare.STATUS_LENGTH_REQUIRED) || (statusCode
== BluetoothShare.STATUS_PRECONDITION_FAILED) || (statusCode
diff --git a/src/com/android/bluetooth/pan/BluetoothTetheringNetworkFactory.java b/src/com/android/bluetooth/pan/BluetoothTetheringNetworkFactory.java
index f477dd9..72016f7 100644
--- a/src/com/android/bluetooth/pan/BluetoothTetheringNetworkFactory.java
+++ b/src/com/android/bluetooth/pan/BluetoothTetheringNetworkFactory.java
@@ -25,12 +25,17 @@
import android.net.NetworkFactory;
import android.net.NetworkInfo;
import android.net.NetworkInfo.DetailedState;
-import android.net.ip.IpClient;
-import android.net.ip.IpClient.WaitForProvisioningCallback;
+import android.net.ip.IIpClient;
+import android.net.ip.IpClientUtil;
+import android.net.ip.IpClientUtil.WaitForProvisioningCallbacks;
+import android.net.shared.ProvisioningConfiguration;
import android.os.Looper;
+import android.os.RemoteException;
import android.text.TextUtils;
import android.util.Slog;
+import com.android.internal.annotations.GuardedBy;
+
/**
* This class tracks the data connection associated with Bluetooth
* reverse tethering. PanService calls it when a reverse tethered
@@ -49,7 +54,9 @@
// All accesses to these must be synchronized(this).
private final NetworkInfo mNetworkInfo;
- private IpClient mIpClient;
+ private IIpClient mIpClient;
+ @GuardedBy("this")
+ private int mIpClientStartIndex = 0;
private String mInterfaceName;
private NetworkAgent mNetworkAgent;
@@ -65,13 +72,64 @@
setCapabilityFilter(mNetworkCapabilities);
}
+ private class BtIpClientCallback extends WaitForProvisioningCallbacks {
+ private final int mCurrentStartIndex;
+
+ private BtIpClientCallback(int currentStartIndex) {
+ mCurrentStartIndex = currentStartIndex;
+ }
+
+ @Override
+ public void onIpClientCreated(IIpClient ipClient) {
+ synchronized (BluetoothTetheringNetworkFactory.this) {
+ if (mCurrentStartIndex != mIpClientStartIndex) {
+ // Do not start IpClient: the current request is obsolete.
+ // IpClient will be GCed eventually as the IIpClient Binder token goes out
+ // of scope.
+ return;
+ }
+ mIpClient = ipClient;
+ try {
+ mIpClient.startProvisioning(new ProvisioningConfiguration.Builder()
+ .withoutMultinetworkPolicyTracker()
+ .withoutIpReachabilityMonitor()
+ .build().toStableParcelable());
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error starting IpClient provisioning", e);
+ }
+ }
+ }
+
+ @Override
+ public void onLinkPropertiesChange(LinkProperties newLp) {
+ synchronized (BluetoothTetheringNetworkFactory.this) {
+ if (mNetworkAgent != null && mNetworkInfo.isConnected()) {
+ mNetworkAgent.sendLinkProperties(newLp);
+ }
+ }
+ }
+ }
+
private void stopIpClientLocked() {
+ // Mark all previous start requests as obsolete
+ mIpClientStartIndex++;
if (mIpClient != null) {
- mIpClient.shutdown();
+ try {
+ mIpClient.shutdown();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error shutting down IpClient", e);
+ }
mIpClient = null;
}
}
+ private BtIpClientCallback startIpClientLocked() {
+ mIpClientStartIndex++;
+ final BtIpClientCallback callback = new BtIpClientCallback(mIpClientStartIndex);
+ IpClientUtil.makeIpClient(mContext, mInterfaceName, callback);
+ return callback;
+ }
+
// Called by NetworkFactory when PanService and NetworkFactory both desire a Bluetooth
// reverse-tether connection. A network interface for Bluetooth reverse-tethering can be
// assumed to be available because we only register our NetworkFactory when it is so.
@@ -84,16 +142,7 @@
@Override
public void run() {
LinkProperties linkProperties;
- final WaitForProvisioningCallback ipcCallback = new WaitForProvisioningCallback() {
- @Override
- public void onLinkPropertiesChange(LinkProperties newLp) {
- synchronized (BluetoothTetheringNetworkFactory.this) {
- if (mNetworkAgent != null && mNetworkInfo.isConnected()) {
- mNetworkAgent.sendLinkProperties(newLp);
- }
- }
- }
- };
+ final WaitForProvisioningCallbacks ipcCallback;
synchronized (BluetoothTetheringNetworkFactory.this) {
if (TextUtils.isEmpty(mInterfaceName)) {
@@ -102,11 +151,7 @@
}
log("ipProvisioningThread(+" + mInterfaceName + "): " + "mNetworkInfo="
+ mNetworkInfo);
- mIpClient = new IpClient(mContext, mInterfaceName, ipcCallback);
- mIpClient.startProvisioning(mIpClient.buildProvisioningConfiguration()
- .withoutMultinetworkPolicyTracker()
- .withoutIpReachabilityMonitor()
- .build());
+ ipcCallback = startIpClientLocked();
mNetworkInfo.setDetailedState(DetailedState.OBTAINING_IPADDR, null, null);
}
diff --git a/src/com/android/bluetooth/pan/PanService.java b/src/com/android/bluetooth/pan/PanService.java
index a7d682f..be19f10 100644
--- a/src/com/android/bluetooth/pan/PanService.java
+++ b/src/com/android/bluetooth/pan/PanService.java
@@ -34,11 +34,11 @@
import android.os.Message;
import android.os.ServiceManager;
import android.os.UserManager;
-import android.provider.Settings;
import android.util.Log;
import com.android.bluetooth.BluetoothMetricsProto;
import com.android.bluetooth.Utils;
+import com.android.bluetooth.btservice.AdapterService;
import com.android.bluetooth.btservice.MetricsLogger;
import com.android.bluetooth.btservice.ProfileService;
@@ -72,6 +72,7 @@
private static final int MESSAGE_CONNECT = 1;
private static final int MESSAGE_DISCONNECT = 2;
private static final int MESSAGE_CONNECT_STATE_CHANGED = 11;
+ private static final int STOP_LISTENER = 200;
private boolean mTetherOn = false;
private BluetoothTetheringNetworkFactory mNetworkFactory;
@@ -127,34 +128,15 @@
@Override
protected boolean stop() {
- mHandler.removeCallbacksAndMessages(null);
+ Log.i(TAG, " stop");
+ mHandler.sendMessage(mHandler.obtainMessage(STOP_LISTENER));
return true;
}
@Override
protected void cleanup() {
- // TODO(b/72948646): this should be moved to stop()
- setPanService(null);
- if (mNativeAvailable) {
- cleanupNative();
- mNativeAvailable = false;
- }
- if (mPanDevices != null) {
- int[] desiredStates = {BluetoothProfile.STATE_CONNECTING, BluetoothProfile.STATE_CONNECTED,
- BluetoothProfile.STATE_DISCONNECTING};
- List<BluetoothDevice> devList =
- getDevicesMatchingConnectionStates(desiredStates);
- for (BluetoothDevice device : devList) {
- BluetoothPanDevice panDevice = mPanDevices.get(device);
- Log.d(TAG, "panDevice: " + panDevice + " device address: " + device);
- if (panDevice != null) {
- handlePanDeviceStateChange(device, mPanIfName,
- BluetoothProfile.STATE_DISCONNECTED,
- panDevice.mLocalRole, panDevice.mRemoteRole);
- }
- }
- mPanDevices.clear();
- }
+ Log.i(TAG, " cleanup");
+ mHandler.removeCallbacksAndMessages(null);
}
private final Handler mHandler = new Handler() {
@@ -199,6 +181,30 @@
convertHalState(cs.state), cs.local_role, cs.remote_role);
}
break;
+ case STOP_LISTENER :
+ setPanService(null);
+ if (mNativeAvailable) {
+ cleanupNative();
+ mNativeAvailable = false;
+ }
+ if (mPanDevices != null) {
+ int[] desiredStates = {BluetoothProfile.STATE_CONNECTING,
+ BluetoothProfile.STATE_CONNECTED,
+ BluetoothProfile.STATE_DISCONNECTING};
+ List<BluetoothDevice> devList =
+ getDevicesMatchingConnectionStates(desiredStates);
+ for (BluetoothDevice device : devList) {
+ BluetoothPanDevice panDevice = mPanDevices.get(device);
+ Log.d(TAG, "panDevice: " + panDevice + " device address: " + device);
+ if (panDevice != null) {
+ handlePanDeviceStateChange(device, mPanIfName,
+ BluetoothProfile.STATE_DISCONNECTED,
+ panDevice.mLocalRole, panDevice.mRemoteRole);
+ }
+ }
+ mPanDevices.clear();
+ }
+ break;
}
}
};
@@ -404,11 +410,11 @@
throw new IllegalArgumentException("Null device");
}
enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
- Settings.Global.putInt(getContentResolver(),
- Settings.Global.getBluetoothPanPriorityKey(device.getAddress()), priority);
if (DBG) {
Log.d(TAG, "Saved priority " + device + " = " + priority);
}
+ AdapterService.getAdapterService().getDatabase()
+ .setProfilePriority(device, BluetoothProfile.PAN, priority);
return true;
}
@@ -417,9 +423,8 @@
throw new IllegalArgumentException("Null device");
}
enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
- return Settings.Global.getInt(getContentResolver(),
- Settings.Global.getBluetoothPanPriorityKey(device.getAddress()),
- BluetoothProfile.PRIORITY_UNDEFINED);
+ return AdapterService.getAdapterService().getDatabase()
+ .getProfilePriority(device, BluetoothProfile.PAN);
}
public List<BluetoothDevice> getConnectedDevices() {
diff --git a/src/com/android/bluetooth/pbap/BluetoothPbapCallLogComposer.java b/src/com/android/bluetooth/pbap/BluetoothPbapCallLogComposer.java
index 805f3ea..6185557 100644
--- a/src/com/android/bluetooth/pbap/BluetoothPbapCallLogComposer.java
+++ b/src/com/android/bluetooth/pbap/BluetoothPbapCallLogComposer.java
@@ -23,7 +23,6 @@
import android.provider.CallLog;
import android.provider.CallLog.Calls;
import android.text.TextUtils;
-import android.text.format.Time;
import android.util.Log;
import com.android.bluetooth.R;
@@ -32,13 +31,15 @@
import com.android.vcard.VCardConstants;
import com.android.vcard.VCardUtils;
+import java.text.SimpleDateFormat;
import java.util.Arrays;
+import java.util.Calendar;
/**
* VCard composer especially for Call Log used in Bluetooth.
*/
public class BluetoothPbapCallLogComposer {
- private static final String TAG = "CallLogComposer";
+ private static final String TAG = "PbapCallLogComposer";
private static final String FAILURE_REASON_FAILED_TO_GET_DATABASE_INFO =
"Failed to get database information";
@@ -198,9 +199,11 @@
* The format is: ("%Y%m%dT%H%M%S").
*/
private String toRfc2455Format(final long millSecs) {
- Time startDate = new Time();
- startDate.set(millSecs);
- return startDate.format2445();
+ Calendar cal = Calendar.getInstance();
+ cal.setTimeInMillis(millSecs);
+ String rfc2455Format = "yyyyMMdd'T'HHmmss";
+ SimpleDateFormat df = new SimpleDateFormat(rfc2455Format);
+ return df.format(cal.getTime());
}
/**
diff --git a/src/com/android/bluetooth/pbap/BluetoothPbapObexServer.java b/src/com/android/bluetooth/pbap/BluetoothPbapObexServer.java
old mode 100644
new mode 100755
index e78fe20..b40721d
--- a/src/com/android/bluetooth/pbap/BluetoothPbapObexServer.java
+++ b/src/com/android/bluetooth/pbap/BluetoothPbapObexServer.java
@@ -832,50 +832,65 @@
}
if (type.equals("number")) {
+ ArrayList<Integer> savedPosList = new ArrayList<>();
+ ArrayList<String> selectedNameList = new ArrayList<String>();
// query the number, to get the names
ArrayList<String> names =
mVcardManager.getContactNamesByNumber(appParamValue.searchValue);
if (mOrderBy == ORDER_BY_ALPHABETICAL) Collections.sort(names);
for (int i = 0; i < names.size(); i++) {
compareValue = names.get(i).trim();
- if (D) {
- Log.d(TAG, "compareValue=" + compareValue);
- }
- for (int pos = appParamValue.listStartOffset;
- pos < listSize && itemsFound < requestSize; pos++) {
+ if (D) Log.d(TAG, "compareValue=" + compareValue);
+ for (int pos = 0; pos < listSize; pos++) {
currentValue = nameList.get(pos);
if (V) {
Log.d(TAG, "currentValue=" + currentValue);
}
if (currentValue.equals(compareValue)) {
- itemsFound++;
if (currentValue.contains(",")) {
currentValue = currentValue.substring(0, currentValue.lastIndexOf(','));
}
- writeVCardEntry(pos, currentValue, result);
+ selectedNameList.add(currentValue);
+ savedPosList.add(pos);
}
}
- if (itemsFound >= requestSize) {
- break;
- }
}
+
+ for (int j = appParamValue.listStartOffset;
+ j < selectedNameList.size() && itemsFound < requestSize; j++) {
+ itemsFound++;
+ writeVCardEntry(savedPosList.get(j), selectedNameList.get(j), result);
+ }
+
} else {
+ ArrayList<Integer> savedPosList = new ArrayList<>();
+ ArrayList<String> selectedNameList = new ArrayList<String>();
if (appParamValue.searchValue != null) {
compareValue = appParamValue.searchValue.trim().toLowerCase();
}
- for (int pos = appParamValue.listStartOffset;
- pos < listSize && itemsFound < requestSize; pos++) {
+
+ for (int pos = 0; pos < listSize; pos++) {
currentValue = nameList.get(pos);
+
if (currentValue.contains(",")) {
currentValue = currentValue.substring(0, currentValue.lastIndexOf(','));
}
- if (appParamValue.searchValue.isEmpty() || ((currentValue.toLowerCase()).startsWith(
- compareValue))) {
- itemsFound++;
- writeVCardEntry(pos, currentValue, result);
+ if (appParamValue.searchValue != null) {
+ if (appParamValue.searchValue.isEmpty()
+ || ((currentValue.toLowerCase())
+ .startsWith(compareValue.toLowerCase()))) {
+ selectedNameList.add(currentValue);
+ savedPosList.add(pos);
+ }
}
}
+
+ for (int i = appParamValue.listStartOffset;
+ i < selectedNameList.size() && itemsFound < requestSize; i++) {
+ itemsFound++;
+ writeVCardEntry(savedPosList.get(i), selectedNameList.get(i), result);
+ }
}
return itemsFound;
}
diff --git a/src/com/android/bluetooth/pbap/BluetoothPbapService.java b/src/com/android/bluetooth/pbap/BluetoothPbapService.java
index 66ef200..dc8e054 100644
--- a/src/com/android/bluetooth/pbap/BluetoothPbapService.java
+++ b/src/com/android/bluetooth/pbap/BluetoothPbapService.java
@@ -51,7 +51,6 @@
import android.os.ParcelUuid;
import android.os.PowerManager;
import android.os.UserManager;
-import android.support.annotation.VisibleForTesting;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.Log;
@@ -63,6 +62,7 @@
import com.android.bluetooth.btservice.ProfileService;
import com.android.bluetooth.sdp.SdpManager;
import com.android.bluetooth.util.DevicePolicyUtils;
+import com.android.internal.annotations.VisibleForTesting;
import java.util.ArrayList;
import java.util.HashMap;
@@ -132,10 +132,12 @@
static final int ROLLOVER_COUNTERS = 7;
static final int GET_LOCAL_TELEPHONY_DETAILS = 8;
static final int CLEANUP_HANDLER_TASKS = 9;
+ static final int HANDLE_VERSION_UPDATE_NOTIFICATION = 10;
static final int USER_CONFIRM_TIMEOUT_VALUE = 30000;
static final int RELEASE_WAKE_LOCK_DELAY = 10000;
static final int CLEANUP_HANDLER_DELAY = 50;
+ static final int VERSION_UPDATE_NOTIFICATION_DELAY = 500;
private PowerManager.WakeLock mWakeLock;
@@ -263,39 +265,13 @@
} else if (BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(action)) {
int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE,
BluetoothDevice.ERROR);
- BluetoothDevice remoteDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
if (bondState == BluetoothDevice.BOND_BONDED && BluetoothPbapFixes.isSupportedPbap12) {
- if (VERBOSE) Log.v(TAG, "Remote Device Type: " + remoteDevice.getType());
- if (remoteDevice.getType() == BluetoothDevice.DEVICE_TYPE_CLASSIC ||
- remoteDevice.getType() == BluetoothDevice.DEVICE_TYPE_DUAL)
- remoteDevice.sdpSearch(BluetoothUuid.PBAP_PCE);
- }
- } else if (BluetoothDevice.ACTION_SDP_RECORD.equals(action)) {
- ParcelUuid uuid = intent.getParcelableExtra(BluetoothDevice.EXTRA_UUID);
- if (BluetoothUuid.PBAP_PCE.equals(uuid)) {
- Log.d(TAG, "Received SDP Response for PCE UUID: " + uuid.toString());
BluetoothDevice remoteDevice = intent.getParcelableExtra(
BluetoothDevice.EXTRA_DEVICE);
- boolean hasPbap12Support =
- BluetoothPbapFixes.remoteSupportsPbap1_2(remoteDevice);
- String isRebonded = BluetoothPbapFixes.PbapSdpResponse.get(
- remoteDevice.getAddress().substring(0,8));
- Integer remoteVersion = BluetoothPbapFixes.remoteVersion.get(
- remoteDevice.getAddress().substring(0,8));
- if (hasPbap12Support && (isRebonded.equals("N"))) {
- Log.d(TAG, "Remote Supports PBAP 1.2. Notify user");
- BluetoothPbapFixes.createNotification(this, true);
- } else if (!hasPbap12Support
- && (remoteVersion != null &&
- remoteVersion.intValue() < BluetoothPbapFixes.PBAP_ADV_VERSION)
- && (isRebonded != null && isRebonded.equals("N"))) {
- Log.d(TAG, "Remote PBAP profile support downgraded");
- BluetoothPbapFixes.createNotification(this, false);
- } else {
- Log.d(TAG, "Notification Not Required.");
- if (BluetoothPbapFixes.mNotificationManager != null)
- BluetoothPbapFixes.mNotificationManager.cancelAll();
- }
+ mSessionStatusHandler.sendMessageDelayed(
+ mSessionStatusHandler.obtainMessage(
+ HANDLE_VERSION_UPDATE_NOTIFICATION, remoteDevice),
+ VERSION_UPDATE_NOTIFICATION_DELAY);
}
} else {
Log.w(TAG, "Unhandled intent action: " + action);
@@ -467,6 +443,11 @@
case CLEANUP_HANDLER_TASKS:
cleanUpHandlerTasks();
break;
+ case HANDLE_VERSION_UPDATE_NOTIFICATION:
+ BluetoothDevice remoteDev = (BluetoothDevice) msg.obj;
+ BluetoothPbapFixes.handleNotificationTask(
+ sBluetoothPbapService, remoteDev);
+ break;
default:
break;
}
@@ -554,7 +535,6 @@
filter.addAction(AUTH_RESPONSE_ACTION);
filter.addAction(AUTH_CANCELLED_ACTION);
filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
- filter.addAction(BluetoothDevice.ACTION_SDP_RECORD);
BluetoothPbapConfig.init(this);
registerReceiver(mPbapReceiver, filter);
try {
@@ -571,6 +551,7 @@
}
setBluetoothPbapService(this);
+
mSessionStatusHandler.sendMessage(
mSessionStatusHandler.obtainMessage(GET_LOCAL_TELEPHONY_DETAILS));
mSessionStatusHandler.sendMessage(mSessionStatusHandler.obtainMessage(LOAD_CONTACTS));
@@ -742,7 +723,7 @@
* Send the result to the state machine.
* @param stateMachine PbapStateMachine which sends the request
*/
- @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
public void checkOrGetPhonebookPermission(PbapStateMachine stateMachine) {
BluetoothDevice device = stateMachine.getRemoteDevice();
int permission = device.getPhonebookAccessPermission();
diff --git a/src/com/android/bluetooth/pbap/BluetoothPbapVcardManager.java b/src/com/android/bluetooth/pbap/BluetoothPbapVcardManager.java
old mode 100644
new mode 100755
index c118b18..6e431a1
--- a/src/com/android/bluetooth/pbap/BluetoothPbapVcardManager.java
+++ b/src/com/android/bluetooth/pbap/BluetoothPbapVcardManager.java
@@ -348,6 +348,9 @@
ArrayList<String> contactNameIdList = new ArrayList<String>();
contactCursor = getContactNameIdList(contactCursor,
contactNameIdList, mContext.getString(android.R.string.unknownName));
+ appendDistinctNameIdList(contactNameIdList,
+ mContext.getString(android.R.string.unknownName), contactCursor);
+
if (contactCursor != null) {
if (!composer.initWithCallback(contactCursor,
new EnterpriseRawContactEntitlesInfoCallback())) {
diff --git a/src/com/android/bluetooth/pbap/PbapStateMachine.java b/src/com/android/bluetooth/pbap/PbapStateMachine.java
index c850d8c..52d2a46 100644
--- a/src/com/android/bluetooth/pbap/PbapStateMachine.java
+++ b/src/com/android/bluetooth/pbap/PbapStateMachine.java
@@ -342,10 +342,6 @@
}
BluetoothObexTransport transport = new BluetoothObexTransport(mConnSocket);
mServerSession = new ServerSession(transport, mPbapServer, mObexAuth);
- BluetoothPbapFixes.updateMtu(mServerSession, transport.isSrmSupported(),
- mConnSocket.getMaxReceivePacketSize());
- // It's ok to just use one wake lock
- // Message MSG_ACQUIRE_WAKE_LOCK is always surrounded by RELEASE. safe.
}
private void stopObexServerSession() {
diff --git a/src/com/android/bluetooth/pbapclient/Authenticator.java b/src/com/android/bluetooth/pbapclient/Authenticator.java
index 0811499..3a096f1 100644
--- a/src/com/android/bluetooth/pbapclient/Authenticator.java
+++ b/src/com/android/bluetooth/pbapclient/Authenticator.java
@@ -26,6 +26,7 @@
public class Authenticator extends AbstractAccountAuthenticator {
private static final String TAG = "PbapAuthenticator";
+ private static final boolean DBG = Utils.DBG;
public Authenticator(Context context) {
super(context);
@@ -34,7 +35,7 @@
// Editing properties is not supported
@Override
public Bundle editProperties(AccountAuthenticatorResponse r, String s) {
- Log.d(TAG, "got call", new Exception());
+ if (DBG) Log.d(TAG, "got call", new Exception());
throw new UnsupportedOperationException();
}
@@ -42,7 +43,7 @@
@Override
public Bundle addAccount(AccountAuthenticatorResponse r, String s, String s2, String[] strings,
Bundle bundle) throws NetworkErrorException {
- Log.d(TAG, "got call", new Exception());
+ if (DBG) Log.d(TAG, "got call", new Exception());
// Don't allow accounts to be added.
throw new UnsupportedOperationException();
}
@@ -51,7 +52,7 @@
@Override
public Bundle confirmCredentials(AccountAuthenticatorResponse r, Account account, Bundle bundle)
throws NetworkErrorException {
- Log.d(TAG, "got call", new Exception());
+ if (DBG) Log.d(TAG, "got call", new Exception());
return null;
}
@@ -59,14 +60,14 @@
@Override
public Bundle getAuthToken(AccountAuthenticatorResponse r, Account account, String s,
Bundle bundle) throws NetworkErrorException {
- Log.d(TAG, "got call", new Exception());
+ if (DBG) Log.d(TAG, "got call", new Exception());
throw new UnsupportedOperationException();
}
// Getting a label for the auth token is not supported
@Override
public String getAuthTokenLabel(String s) {
- Log.d(TAG, "got call", new Exception());
+ if (DBG) Log.d(TAG, "got call", new Exception());
return null;
}
@@ -74,7 +75,7 @@
@Override
public Bundle updateCredentials(AccountAuthenticatorResponse r, Account account, String s,
Bundle bundle) throws NetworkErrorException {
- Log.d(TAG, "got call", new Exception());
+ if (DBG) Log.d(TAG, "got call", new Exception());
return null;
}
@@ -82,7 +83,7 @@
@Override
public Bundle hasFeatures(AccountAuthenticatorResponse r, Account account, String[] strings)
throws NetworkErrorException {
- Log.d(TAG, "got call", new Exception());
+ if (DBG) Log.d(TAG, "got call", new Exception());
final Bundle result = new Bundle();
result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, false);
diff --git a/src/com/android/bluetooth/pbapclient/BluetoothPbapObexAuthenticator.java b/src/com/android/bluetooth/pbapclient/BluetoothPbapObexAuthenticator.java
index ab8091a..f14a805 100644
--- a/src/com/android/bluetooth/pbapclient/BluetoothPbapObexAuthenticator.java
+++ b/src/com/android/bluetooth/pbapclient/BluetoothPbapObexAuthenticator.java
@@ -30,7 +30,8 @@
class BluetoothPbapObexAuthenticator implements Authenticator {
- private static final String TAG = "BluetoothPbapObexAuthenticator";
+ private static final String TAG = "BtPbapObexAuthenticator";
+ private static final boolean DBG = Utils.DBG;
//Default session key for legacy devices is 0000
private String mSessionKey = "0000";
@@ -45,13 +46,16 @@
public PasswordAuthentication onAuthenticationChallenge(String description,
boolean isUserIdRequired, boolean isFullAccess) {
PasswordAuthentication pa = null;
- Log.v(TAG, "onAuthenticationChallenge: starting");
+ if (DBG) Log.v(TAG, "onAuthenticationChallenge: starting");
if (mSessionKey != null && mSessionKey.length() != 0) {
- Log.v(TAG, "onAuthenticationChallenge: mSessionKey=" + mSessionKey);
+ if (DBG) Log.v(TAG, "onAuthenticationChallenge: mSessionKey=" + mSessionKey);
pa = new PasswordAuthentication(null, mSessionKey.getBytes());
} else {
- Log.v(TAG, "onAuthenticationChallenge: mSessionKey is empty, timeout/cancel occured");
+ if (DBG) {
+ Log.v(TAG,
+ "onAuthenticationChallenge: mSessionKey is empty, timeout/cancel occured");
+ }
}
return pa;
@@ -59,7 +63,7 @@
@Override
public byte[] onAuthenticationResponse(byte[] userName) {
- Log.v(TAG, "onAuthenticationResponse: " + userName);
+ if (DBG) Log.v(TAG, "onAuthenticationResponse: " + userName);
/* required only in case PCE challenges PSE which we don't do now */
return null;
}
diff --git a/src/com/android/bluetooth/pbapclient/BluetoothPbapRequest.java b/src/com/android/bluetooth/pbapclient/BluetoothPbapRequest.java
index 1bd71ce..8b8db79 100644
--- a/src/com/android/bluetooth/pbapclient/BluetoothPbapRequest.java
+++ b/src/com/android/bluetooth/pbapclient/BluetoothPbapRequest.java
@@ -29,6 +29,7 @@
abstract class BluetoothPbapRequest {
private static final String TAG = "BluetoothPbapRequest";
+ private static final boolean DBG = Utils.DBG;
protected static final byte OAP_TAGID_ORDER = 0x01;
protected static final byte OAP_TAGID_SEARCH_VALUE = 0x02;
@@ -58,7 +59,7 @@
}
public void execute(ClientSession session) throws IOException {
- Log.v(TAG, "execute");
+ if (DBG) Log.v(TAG, "execute");
/* in case request is aborted before can be executed */
if (mAborted) {
@@ -88,7 +89,7 @@
mResponseCode = mOp.getResponseCode();
- Log.d(TAG, "mResponseCode=" + mResponseCode);
+ if (DBG) Log.d(TAG, "mResponseCode=" + mResponseCode);
checkResponseCode(mResponseCode);
} catch (IOException e) {
@@ -112,19 +113,19 @@
}
protected void readResponse(InputStream stream) throws IOException {
- Log.v(TAG, "readResponse");
+ if (DBG) Log.v(TAG, "readResponse");
/* nothing here by default */
}
protected void readResponseHeaders(HeaderSet headerset) {
- Log.v(TAG, "readResponseHeaders");
+ if (DBG) Log.v(TAG, "readResponseHeaders");
/* nothing here by dafault */
}
protected void checkResponseCode(int responseCode) throws IOException {
- Log.v(TAG, "checkResponseCode");
+ if (DBG) Log.v(TAG, "checkResponseCode");
/* nothing here by dafault */
}
diff --git a/src/com/android/bluetooth/pbapclient/BluetoothPbapRequestPullPhoneBook.java b/src/com/android/bluetooth/pbapclient/BluetoothPbapRequestPullPhoneBook.java
index ddbf215..34ab7d5 100644
--- a/src/com/android/bluetooth/pbapclient/BluetoothPbapRequestPullPhoneBook.java
+++ b/src/com/android/bluetooth/pbapclient/BluetoothPbapRequestPullPhoneBook.java
@@ -29,9 +29,9 @@
final class BluetoothPbapRequestPullPhoneBook extends BluetoothPbapRequest {
- private static final boolean VDBG = false;
+ private static final boolean VDBG = Utils.VDBG;
- private static final String TAG = "BluetoothPbapRequestPullPhoneBook";
+ private static final String TAG = "BtPbapReqPullPhoneBook";
private static final String TYPE = "x-bt/phonebook";
@@ -93,7 +93,7 @@
@Override
protected void readResponse(InputStream stream) throws IOException {
- Log.v(TAG, "readResponse");
+ if (VDBG) Log.v(TAG, "readResponse");
mResponse = new BluetoothPbapVcardList(mAccount, stream, mFormat);
if (VDBG) {
@@ -103,7 +103,7 @@
@Override
protected void readResponseHeaders(HeaderSet headerset) {
- Log.v(TAG, "readResponseHeaders");
+ if (VDBG) Log.v(TAG, "readResponseHeaders");
ObexAppParameters oap = ObexAppParameters.fromHeaderSet(headerset);
diff --git a/src/com/android/bluetooth/pbapclient/CallLogPullRequest.java b/src/com/android/bluetooth/pbapclient/CallLogPullRequest.java
index 77665a5..8ab9a1a 100644
--- a/src/com/android/bluetooth/pbapclient/CallLogPullRequest.java
+++ b/src/com/android/bluetooth/pbapclient/CallLogPullRequest.java
@@ -39,8 +39,8 @@
import java.util.List;
public class CallLogPullRequest extends PullRequest {
- private static final boolean DBG = true;
- private static final boolean VDBG = false;
+ private static final boolean DBG = Utils.DBG;
+ private static final boolean VDBG = Utils.VDBG;
private static final String TAG = "PbapCallLogPullRequest";
private static final String TIMESTAMP_PROPERTY = "X-IRMC-CALL-DATETIME";
private static final String TIMESTAMP_FORMAT = "yyyyMMdd'T'HHmmss";
@@ -88,9 +88,10 @@
ContentValues values = new ContentValues();
values.put(CallLog.Calls.TYPE, type);
- values.put(Calls.PHONE_ACCOUNT_ID, mAccount.hashCode());
+ values.put(Calls.PHONE_ACCOUNT_ID, mAccount.name);
List<PhoneData> phones = vcard.getPhoneList();
- if (phones == null || phones.get(0).getNumber().equals(";")) {
+ if (phones == null || phones.get(0).getNumber().equals(";")
+ || phones.get(0).getNumber().length() == 0) {
values.put(CallLog.Calls.NUMBER, "");
} else {
String phoneNumber = phones.get(0).getNumber();
diff --git a/src/com/android/bluetooth/pbapclient/PbapClientConnectionHandler.java b/src/com/android/bluetooth/pbapclient/PbapClientConnectionHandler.java
index 913ba0e..6d14e59 100644
--- a/src/com/android/bluetooth/pbapclient/PbapClientConnectionHandler.java
+++ b/src/com/android/bluetooth/pbapclient/PbapClientConnectionHandler.java
@@ -46,8 +46,9 @@
* controlling state machine.
*/
class PbapClientConnectionHandler extends Handler {
- static final String TAG = "PBAP PCE handler";
- static final boolean DBG = true;
+ static final String TAG = "PbapClientConnHandler";
+ static final boolean DBG = Utils.DBG;
+ static final boolean VDBG = Utils.VDBG;
static final int MSG_CONNECT = 1;
static final int MSG_DISCONNECT = 2;
static final int MSG_DOWNLOAD = 3;
@@ -91,7 +92,6 @@
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;
- private static final int PBAP_V1_2 = 0x0102;
private static final int L2CAP_INVALID_PSM = -1;
public static final String PB_PATH = "telecom/pb.vcf";
@@ -99,6 +99,7 @@
public static final String ICH_PATH = "telecom/ich.vcf";
public static final String OCH_PATH = "telecom/och.vcf";
+ public static final int PBAP_V1_2 = 0x0102;
public static final byte VCARD_TYPE_21 = 0;
public static final byte VCARD_TYPE_30 = 1;
@@ -194,17 +195,17 @@
}
} else {
Log.w(TAG, "Socket CONNECT Failure ");
- mPbapClientStateMachine.obtainMessage(
- PbapClientStateMachine.MSG_CONNECTION_FAILED).sendToTarget();
+ mPbapClientStateMachine.sendMessage(
+ PbapClientStateMachine.MSG_CONNECTION_FAILED);
return;
}
if (connectObexSession()) {
- mPbapClientStateMachine.obtainMessage(
- PbapClientStateMachine.MSG_CONNECTION_COMPLETE).sendToTarget();
+ mPbapClientStateMachine.sendMessage(
+ PbapClientStateMachine.MSG_CONNECTION_COMPLETE);
} else {
- mPbapClientStateMachine.obtainMessage(
- PbapClientStateMachine.MSG_CONNECTION_FAILED).sendToTarget();
+ mPbapClientStateMachine.sendMessage(
+ PbapClientStateMachine.MSG_CONNECTION_FAILED);
}
break;
@@ -234,8 +235,7 @@
removeAccount(mAccount);
removeCallLog(mAccount);
- mPbapClientStateMachine.obtainMessage(PbapClientStateMachine.MSG_CONNECTION_CLOSED)
- .sendToTarget();
+ mPbapClientStateMachine.sendMessage(PbapClientStateMachine.MSG_CONNECTION_CLOSED);
break;
case MSG_DOWNLOAD:
@@ -272,19 +272,19 @@
/* Utilize SDP, if available, to create a socket connection over L2CAP, RFCOMM specified
* channel, or RFCOMM default channel. */
- private boolean connectSocket() {
+ private synchronized boolean connectSocket() {
try {
/* Use BluetoothSocket to connect */
if (mPseRec == null) {
// BackWardCompatability: Fall back to create RFCOMM through UUID.
- Log.v(TAG, "connectSocket: UUID: " + BluetoothUuid.PBAP_PSE.getUuid());
+ if (VDBG) Log.v(TAG, "connectSocket: UUID: " + BluetoothUuid.PBAP_PSE.getUuid());
mSocket =
mDevice.createRfcommSocketToServiceRecord(BluetoothUuid.PBAP_PSE.getUuid());
} else if (mPseRec.getL2capPsm() != L2CAP_INVALID_PSM) {
- Log.v(TAG, "connectSocket: PSM: " + mPseRec.getL2capPsm());
+ if (VDBG) Log.v(TAG, "connectSocket: PSM: " + mPseRec.getL2capPsm());
mSocket = mDevice.createL2capSocket(mPseRec.getL2capPsm());
} else {
- Log.v(TAG, "connectSocket: channel: " + mPseRec.getRfcommChannelNumber());
+ if (VDBG) Log.v(TAG, "connectSocket: channel: " + mPseRec.getRfcommChannelNumber());
mSocket = mDevice.createRfcommSocket(mPseRec.getRfcommChannelNumber());
}
@@ -306,7 +306,7 @@
boolean connectionSuccessful = false;
try {
- if (DBG) {
+ if (VDBG) {
Log.v(TAG, "Start Obex Client Session");
}
BluetoothObexTransport transport = new BluetoothObexTransport(mSocket);
@@ -351,7 +351,7 @@
this.getLooper().getThread().interrupt();
}
- private void closeSocket() {
+ private synchronized void closeSocket() {
try {
if (mSocket != null) {
if (DBG) {
@@ -410,8 +410,8 @@
}
return;
}
- String where = Calls.PHONE_ACCOUNT_ID + "=" + account.hashCode();
- mContext.getContentResolver().delete(CallLog.Calls.CONTENT_URI, where, null);
+ mContext.getContentResolver().delete(CallLog.Calls.CONTENT_URI,
+ Calls.PHONE_ACCOUNT_ID + "=?", new String[]{mAccount.name});
} catch (IllegalArgumentException e) {
Log.d(TAG, "Call Logs could not be deleted, they may not exist yet.");
}
diff --git a/src/com/android/bluetooth/pbapclient/PbapClientService.java b/src/com/android/bluetooth/pbapclient/PbapClientService.java
index 0a9541c..f150cdd 100644
--- a/src/com/android/bluetooth/pbapclient/PbapClientService.java
+++ b/src/com/android/bluetooth/pbapclient/PbapClientService.java
@@ -26,12 +26,12 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.provider.CallLog;
-import android.provider.Settings;
import android.util.Log;
import com.android.bluetooth.R;
-import com.android.bluetooth.Utils;
+import com.android.bluetooth.btservice.AdapterService;
import com.android.bluetooth.btservice.ProfileService;
+import com.android.bluetooth.sdp.SdpManager;
import java.util.ArrayList;
import java.util.List;
@@ -44,14 +44,18 @@
* @hide
*/
public class PbapClientService extends ProfileService {
- private static final boolean DBG = false;
+ private static final boolean DBG = Utils.DBG;
+ private static final boolean VDBG = Utils.VDBG;
+
private static final String TAG = "PbapClientService";
+ private static final String SERVICE_NAME = "Phonebook Access PCE";
// MAXIMUM_DEVICES set to 10 to prevent an excessive number of simultaneous devices.
private static final int MAXIMUM_DEVICES = 10;
private Map<BluetoothDevice, PbapClientStateMachine> mPbapClientStateMachineMap =
new ConcurrentHashMap<>();
private static PbapClientService sPbapClientService;
private PbapBroadcastReceiver mPbapBroadcastReceiver = new PbapBroadcastReceiver();
+ private int mSdpHandle = -1;
@Override
public IProfileServiceBinder initBinder() {
@@ -60,8 +64,8 @@
@Override
protected boolean start() {
- if (DBG) {
- Log.d(TAG, "onStart");
+ if (VDBG) {
+ Log.v(TAG, "onStart");
}
IntentFilter filter = new IntentFilter();
filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
@@ -72,13 +76,17 @@
} catch (Exception e) {
Log.w(TAG, "Unable to register pbapclient receiver", e);
}
+
removeUncleanAccounts();
+ registerSdpRecord();
setPbapClientService(this);
return true;
}
@Override
protected boolean stop() {
+ setPbapClientService(null);
+ cleanUpSdpRecord();
try {
unregisterReceiver(mPbapBroadcastReceiver);
} catch (Exception e) {
@@ -87,18 +95,12 @@
for (PbapClientStateMachine pbapClientStateMachine : mPbapClientStateMachineMap.values()) {
pbapClientStateMachine.doQuit();
}
+ removeUncleanAccounts();
return true;
}
- @Override
- protected void cleanup() {
- removeUncleanAccounts();
- // TODO: Should move to stop()
- setPbapClientService(null);
- }
-
void cleanupDevice(BluetoothDevice device) {
- Log.w(TAG, "Cleanup device: " + device);
+ if (DBG) Log.d(TAG, "Cleanup device: " + device);
synchronized (mPbapClientStateMachineMap) {
PbapClientStateMachine pbapClientStateMachine = mPbapClientStateMachineMap.get(device);
if (pbapClientStateMachine != null) {
@@ -112,24 +114,54 @@
AccountManager accountManager = AccountManager.get(this);
Account[] accounts =
accountManager.getAccountsByType(getString(R.string.pbap_account_type));
- Log.w(TAG, "Found " + accounts.length + " unclean accounts");
+ if (VDBG) Log.v(TAG, "Found " + accounts.length + " unclean accounts");
for (Account acc : accounts) {
Log.w(TAG, "Deleting " + acc);
+ try {
+ getContentResolver().delete(CallLog.Calls.CONTENT_URI,
+ CallLog.Calls.PHONE_ACCOUNT_ID + "=?", new String[]{acc.name});
+ } catch (IllegalArgumentException e) {
+ Log.w(TAG, "Call Logs could not be deleted, they may not exist yet.");
+ }
// The device ID is the name of the account.
accountManager.removeAccountExplicitly(acc);
}
- try {
- getContentResolver().delete(CallLog.Calls.CONTENT_URI, null, null);
- } 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) {
+ Log.e(TAG, "SdpManager is null");
+ return;
+ }
+ mSdpHandle = sdpManager.createPbapPceRecord(SERVICE_NAME,
+ PbapClientConnectionHandler.PBAP_V1_2);
+ }
+
+ private void cleanUpSdpRecord() {
+ if (mSdpHandle < 0) {
+ Log.e(TAG, "cleanUpSdpRecord, SDP record never created");
+ return;
+ }
+ int sdpHandle = mSdpHandle;
+ mSdpHandle = -1;
+ SdpManager sdpManager = SdpManager.getDefaultManager();
+ if (sdpManager == null) {
+ Log.e(TAG, "cleanUpSdpRecord failed, sdpManager is null, sdpHandle=" + sdpHandle);
+ return;
+ }
+ Log.i(TAG, "cleanUpSdpRecord, mSdpHandle=" + sdpHandle);
+ if (!sdpManager.removeSdpRecord(sdpHandle)) {
+ Log.e(TAG, "cleanUpSdpRecord, removeSdpRecord failed, sdpHandle=" + sdpHandle);
}
}
+
private class PbapBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
- Log.v(TAG, "onReceive" + action);
+ if (DBG) Log.v(TAG, "onReceive" + action);
if (action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED)) {
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
if (getConnectionState(device) == BluetoothProfile.STATE_CONNECTED) {
@@ -160,7 +192,7 @@
}
private PbapClientService getService() {
- if (!Utils.checkCaller()) {
+ if (!com.android.bluetooth.Utils.checkCaller()) {
Log.w(TAG, "PbapClient call not allowed for non-active user");
return null;
}
@@ -255,8 +287,8 @@
}
private static synchronized void setPbapClientService(PbapClientService instance) {
- if (DBG) {
- Log.d(TAG, "setPbapClientService(): set to: " + instance);
+ if (VDBG) {
+ Log.v(TAG, "setPbapClientService(): set to: " + instance);
}
sPbapClientService = instance;
}
@@ -266,7 +298,7 @@
throw new IllegalArgumentException("Null device");
}
enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
- Log.d(TAG, "Received request to ConnectPBAPPhonebook " + device.getAddress());
+ if (DBG) Log.d(TAG, "Received request to ConnectPBAPPhonebook " + device.getAddress());
if (getPriority(device) <= BluetoothProfile.PRIORITY_OFF) {
return false;
}
@@ -342,11 +374,11 @@
throw new IllegalArgumentException("Null device");
}
enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
- Settings.Global.putInt(getContentResolver(),
- Settings.Global.getBluetoothPbapClientPriorityKey(device.getAddress()), priority);
if (DBG) {
Log.d(TAG, "Saved priority " + device + " = " + priority);
}
+ AdapterService.getAdapterService().getDatabase()
+ .setProfilePriority(device, BluetoothProfile.PBAP_CLIENT, priority);
return true;
}
@@ -355,10 +387,8 @@
throw new IllegalArgumentException("Null device");
}
enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
- int priority = Settings.Global.getInt(getContentResolver(),
- Settings.Global.getBluetoothPbapClientPriorityKey(device.getAddress()),
- BluetoothProfile.PRIORITY_UNDEFINED);
- return priority;
+ return AdapterService.getAdapterService().getDatabase()
+ .getProfilePriority(device, BluetoothProfile.PBAP_CLIENT);
}
@Override
diff --git a/src/com/android/bluetooth/pbapclient/PbapClientStateMachine.java b/src/com/android/bluetooth/pbapclient/PbapClientStateMachine.java
index 3cc9094..2eaf8e5 100644
--- a/src/com/android/bluetooth/pbapclient/PbapClientStateMachine.java
+++ b/src/com/android/bluetooth/pbapclient/PbapClientStateMachine.java
@@ -67,7 +67,7 @@
import java.util.List;
final class PbapClientStateMachine extends StateMachine {
- private static final boolean DBG = true;
+ private static final boolean DBG = Utils.DBG;
private static final String TAG = "PbapClientStateMachine";
// Messages for handling connect/disconnect requests.
@@ -126,7 +126,7 @@
class Disconnected extends State {
@Override
public void enter() {
- Log.d(TAG, "Enter Disconnected: " + getCurrentMessage().what);
+ if (DBG) Log.d(TAG, "Enter Disconnected: " + getCurrentMessage().what);
onConnectionStateChanged(mCurrentDevice, mMostRecentState,
BluetoothProfile.STATE_DISCONNECTED);
mMostRecentState = BluetoothProfile.STATE_DISCONNECTED;
@@ -224,8 +224,6 @@
ParcelUuid uuid = intent.getParcelableExtra(BluetoothDevice.EXTRA_UUID);
if (DBG) {
Log.v(TAG, "Received UUID: " + uuid.toString());
- }
- if (DBG) {
Log.v(TAG, "expected UUID: " + BluetoothUuid.PBAP_PSE.toString());
}
if (uuid.equals(BluetoothUuid.PBAP_PSE)) {
@@ -250,7 +248,7 @@
class Disconnecting extends State {
@Override
public void enter() {
- Log.d(TAG, "Enter Disconnecting: " + getCurrentMessage().what);
+ if (DBG) Log.d(TAG, "Enter Disconnecting: " + getCurrentMessage().what);
onConnectionStateChanged(mCurrentDevice, mMostRecentState,
BluetoothProfile.STATE_DISCONNECTING);
mMostRecentState = BluetoothProfile.STATE_DISCONNECTING;
@@ -295,7 +293,7 @@
class Connected extends State {
@Override
public void enter() {
- Log.d(TAG, "Enter Connected: " + getCurrentMessage().what);
+ if (DBG) Log.d(TAG, "Enter Connected: " + getCurrentMessage().what);
onConnectionStateChanged(mCurrentDevice, mMostRecentState,
BluetoothProfile.STATE_CONNECTED);
mMostRecentState = BluetoothProfile.STATE_CONNECTED;
@@ -349,7 +347,7 @@
}
public void disconnect(BluetoothDevice device) {
- Log.d(TAG, "Disconnect Request " + device);
+ if (DBG) Log.d(TAG, "Disconnect Request " + device);
sendMessage(MSG_DISCONNECT, device);
}
@@ -433,7 +431,7 @@
}
public void dump(StringBuilder sb) {
- ProfileService.println(sb, "mCurrentDevice: " + mCurrentDevice);
- ProfileService.println(sb, "StateMachine: " + this.toString());
+ ProfileService.println(sb, "mCurrentDevice: " + mCurrentDevice.getAddress() + "("
+ + mCurrentDevice.getName() + ") " + this.toString());
}
}
diff --git a/src/com/android/bluetooth/pbapclient/PhonebookPullRequest.java b/src/com/android/bluetooth/pbapclient/PhonebookPullRequest.java
index 49fbd44..48ccf37 100644
--- a/src/com/android/bluetooth/pbapclient/PhonebookPullRequest.java
+++ b/src/com/android/bluetooth/pbapclient/PhonebookPullRequest.java
@@ -30,8 +30,8 @@
public class PhonebookPullRequest extends PullRequest {
private static final int MAX_OPS = 250;
- private static final boolean VDBG = false;
- private static final String TAG = "PbapPhonebookPullRequest";
+ private static final boolean VDBG = Utils.VDBG;
+ private static final String TAG = "PbapPbPullRequest";
private final Account mAccount;
private final Context mContext;
diff --git a/tests/robotests/src/com/android/bluetooth/TestConfig.java b/src/com/android/bluetooth/pbapclient/Utils.java
similarity index 66%
rename from tests/robotests/src/com/android/bluetooth/TestConfig.java
rename to src/com/android/bluetooth/pbapclient/Utils.java
index de51f5f..c59d8d5 100644
--- a/tests/robotests/src/com/android/bluetooth/TestConfig.java
+++ b/src/com/android/bluetooth/pbapclient/Utils.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2017 The Android Open Source Project
+ * Copyright (C) 2018 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.
@@ -14,10 +14,9 @@
* limitations under the License.
*/
-package com.android.bluetooth;
+package com.android.bluetooth.pbapclient;
-public class TestConfig {
- public static final int SDK_VERSION = 23;
- public static final String MANIFEST_PATH =
- "packages/apps/Bluetooth/tests/robotests/AndroidManifest.xml";
+class Utils {
+ static final boolean DBG = false;
+ static final boolean VDBG = false;
}
diff --git a/src/com/android/bluetooth/sap/SapServer.java b/src/com/android/bluetooth/sap/SapServer.java
index 69f683f..63e1d08 100644
--- a/src/com/android/bluetooth/sap/SapServer.java
+++ b/src/com/android/bluetooth/sap/SapServer.java
@@ -11,6 +11,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.graphics.drawable.Icon;
import android.hardware.radio.V1_0.ISap;
import android.os.Handler;
import android.os.Handler.Callback;
@@ -252,16 +253,17 @@
sapDisconnectIntent.putExtra(SapServer.SAP_DISCONNECT_TYPE_EXTRA, type);
PendingIntent pIntentDisconnect =
PendingIntent.getBroadcast(mContext, type, sapDisconnectIntent, flags);
+ Notification.Action actionDisconnect =
+ new Notification.Action.Builder(Icon.createWithResource(mContext,
+ android.R.drawable.stat_sys_data_bluetooth), button,pIntentDisconnect).build();
notification =
new Notification.Builder(mContext, SAP_NOTIFICATION_CHANNEL).setOngoing(true)
- .addAction(android.R.drawable.stat_sys_data_bluetooth, button,
- pIntentDisconnect)
+ .addAction(actionDisconnect)
.setContentTitle(title)
.setTicker(ticker)
.setContentText(text)
.setSmallIcon(android.R.drawable.stat_sys_data_bluetooth)
.setAutoCancel(false)
- .setPriority(Notification.PRIORITY_MAX)
.setOnlyAlertOnce(true)
.setLocalOnly(true)
.build();
@@ -277,22 +279,24 @@
PendingIntent pIntentForceDisconnect =
PendingIntent.getBroadcast(mContext, SapMessage.DISC_IMMEDIATE,
sapForceDisconnectIntent, flags);
+ Notification.Action actionDisconnect = new Notification.Action.Builder(
+ Icon.createWithResource(mContext, android.R.drawable.stat_sys_data_bluetooth),
+ mContext.getString(R.string.bluetooth_sap_notif_disconnect_button),
+ pIntentDisconnect).build();
+ Notification.Action actionForceDisconnect =
+ new Notification.Action.Builder(Icon.createWithResource(mContext,
+ android.R.drawable.stat_sys_data_bluetooth),
+ mContext.getString(R.string.bluetooth_sap_notif_force_disconnect_button),
+ pIntentForceDisconnect).build();
notification =
new Notification.Builder(mContext, SAP_NOTIFICATION_CHANNEL).setOngoing(true)
- .addAction(android.R.drawable.stat_sys_data_bluetooth,
- mContext.getString(
- R.string.bluetooth_sap_notif_disconnect_button),
- pIntentDisconnect)
- .addAction(android.R.drawable.stat_sys_data_bluetooth,
- mContext.getString(
- R.string.bluetooth_sap_notif_force_disconnect_button),
- pIntentForceDisconnect)
+ .addAction(actionDisconnect)
+ .addAction(actionForceDisconnect)
.setContentTitle(title)
.setTicker(ticker)
.setContentText(text)
.setSmallIcon(android.R.drawable.stat_sys_data_bluetooth)
.setAutoCancel(false)
- .setPriority(Notification.PRIORITY_MAX)
.setOnlyAlertOnce(true)
.setLocalOnly(true)
.build();
@@ -491,7 +495,7 @@
if (mHandlerThread != null) {
try {
- mHandlerThread.quit();
+ mHandlerThread.quitSafely();
mHandlerThread.join();
mHandlerThread = null;
} catch (InterruptedException e) {
diff --git a/src/com/android/bluetooth/sap/SapService.java b/src/com/android/bluetooth/sap/SapService.java
index fea5002..35716d8 100644
--- a/src/com/android/bluetooth/sap/SapService.java
+++ b/src/com/android/bluetooth/sap/SapService.java
@@ -20,17 +20,17 @@
import android.os.Message;
import android.os.ParcelUuid;
import android.os.PowerManager;
-import android.provider.Settings;
-import android.support.annotation.VisibleForTesting;
import android.text.TextUtils;
import android.util.Log;
import com.android.bluetooth.BluetoothMetricsProto;
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.sdp.SdpManager;
+import com.android.internal.annotations.VisibleForTesting;
import java.io.IOException;
import java.util.ArrayList;
@@ -538,7 +538,7 @@
public boolean disconnect(BluetoothDevice device) {
boolean result = false;
synchronized (SapService.this) {
- if (getRemoteDevice().equals(device)) {
+ if (mRemoteDevice != null && mRemoteDevice.equals(device)) {
switch (mState) {
case BluetoothSap.STATE_CONNECTED:
closeConnectionSocket();
@@ -595,19 +595,17 @@
}
public boolean setPriority(BluetoothDevice device, int priority) {
- Settings.Global.putInt(getContentResolver(),
- Settings.Global.getBluetoothSapPriorityKey(device.getAddress()), priority);
if (DEBUG) {
Log.d(TAG, "Saved priority " + device + " = " + priority);
}
+ AdapterService.getAdapterService().getDatabase()
+ .setProfilePriority(device, BluetoothProfile.SAP, priority);
return true;
}
public int getPriority(BluetoothDevice device) {
- int priority = Settings.Global.getInt(getContentResolver(),
- Settings.Global.getBluetoothSapPriorityKey(device.getAddress()),
- BluetoothProfile.PRIORITY_UNDEFINED);
- return priority;
+ return AdapterService.getAdapterService().getDatabase()
+ .getProfilePriority(device, BluetoothProfile.SAP);
}
@Override
diff --git a/src/com/android/bluetooth/sdp/SdpManager.java b/src/com/android/bluetooth/sdp/SdpManager.java
index 340fac0..15a9cc1 100644
--- a/src/com/android/bluetooth/sdp/SdpManager.java
+++ b/src/com/android/bluetooth/sdp/SdpManager.java
@@ -100,6 +100,9 @@
private native int sdpCreateMapMnsRecordNative(String serviceName, int rfcommChannel,
int l2capPsm, int version, int features);
+ private native int sdpCreatePbapPceRecordNative(String serviceName,
+ int version);
+
private native int sdpCreatePbapPseRecordNative(String serviceName, int rfcommChannel,
int l2capPsm, int version, int repositories, int features);
@@ -552,6 +555,27 @@
return sdpCreateMapMnsRecordNative(serviceName, rfcommChannel, l2capPsm, version, features);
}
+ /**
+ * Create a Client side Phone Book Access Profile Service Record.
+ * Create the record once, and reuse it for all connections.
+ * If changes to a record is needed remove the old record using {@link removeSdpRecord}
+ * and then create a new one.
+ * @param serviceName The textual name of the service
+ * @param version The Profile version number (As specified in the Bluetooth
+ * PBAP specification)
+ * @return a handle to the record created. The record can be removed again
+ * using {@link removeSdpRecord}(). The record is not linked to the
+ * creation/destruction of BluetoothSockets, hence SDP record cleanup
+ * is a separate process.
+ */
+ public int createPbapPceRecord(String serviceName, int version) {
+ if (!sNativeAvailable) {
+ throw new RuntimeException(TAG + " sNativeAvailable == false - native not initialized");
+ }
+ return sdpCreatePbapPceRecordNative(serviceName, version);
+ }
+
+
/**
* Create a Server side Phone Book Access Profile Service Record.
* Create the record once, and reuse it for all connections.
diff --git a/tests/robotests/Android.mk b/tests/robotests/Android.mk
index 5a6c0bb..0cb6917 100644
--- a/tests/robotests/Android.mk
+++ b/tests/robotests/Android.mk
@@ -1,41 +1,47 @@
-# Bluetooth Robolectric test target.
-
-LOCAL_PATH:= $(call my-dir)
+#############################################################
+# Bluetooth Robolectric test target. #
+#############################################################
+LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
+LOCAL_MODULE := BluetoothRoboTests
+
LOCAL_SRC_FILES := $(call all-java-files-under, src)
-# Include the testing libraries (JUnit4 + Robolectric libs).
-LOCAL_STATIC_JAVA_LIBRARIES := \
- truth-prebuilt \
- mockito-robolectric-prebuilt
+LOCAL_RESOURCE_DIR := \
+ $(LOCAL_PATH)/res
+LOCAL_JAVA_RESOURCE_DIRS := config
+
+# Include the testing libraries
LOCAL_JAVA_LIBRARIES := \
- junit \
- platform-robolectric-3.6.1-prebuilt \
- sdk_vcurrent
+ robolectric_android-all-stub \
+ Robolectric_all-target \
+ mockito-robolectric-prebuilt \
+ truth-prebuilt
LOCAL_INSTRUMENTATION_FOR := Bluetooth
-LOCAL_MODULE := BluetoothRoboTests
LOCAL_MODULE_TAGS := optional
include $(BUILD_STATIC_JAVA_LIBRARY)
-
-# Bluetooth runner target to run the previous target.
-
+#############################################################
+# Bluetooth runner target to run the previous target. #
+#############################################################
include $(CLEAR_VARS)
LOCAL_MODULE := RunBluetoothRoboTests
-LOCAL_SDK_VERSION := current
-
-LOCAL_STATIC_JAVA_LIBRARIES := \
- BluetoothRoboTests
+LOCAL_JAVA_LIBRARIES := \
+ BluetoothRoboTests \
+ robolectric_android-all-stub \
+ Robolectric_all-target \
+ mockito-robolectric-prebuilt \
+ truth-prebuilt
LOCAL_TEST_PACKAGE := Bluetooth
LOCAL_INSTRUMENT_SOURCE_DIRS := $(dir $(LOCAL_PATH))../src
-include prebuilts/misc/common/robolectric/3.6.1/run_robotests.mk
+include external/robolectric-shadows/run_robotests.mk
diff --git a/tests/robotests/config/robolectric.properties b/tests/robotests/config/robolectric.properties
new file mode 100644
index 0000000..5217804
--- /dev/null
+++ b/tests/robotests/config/robolectric.properties
@@ -0,0 +1,2 @@
+manifest=packages/apps/Bluetooth/tests/robotests/AndroidManifest.xml
+sdk=NEWEST_SDK
diff --git a/tests/robotests/src/com/android/bluetooth/opp/OppSendFileInfoTest.java b/tests/robotests/src/com/android/bluetooth/opp/OppSendFileInfoTest.java
index 46d9444..22a4284 100644
--- a/tests/robotests/src/com/android/bluetooth/opp/OppSendFileInfoTest.java
+++ b/tests/robotests/src/com/android/bluetooth/opp/OppSendFileInfoTest.java
@@ -26,30 +26,26 @@
import android.content.Context;
import android.net.Uri;
-import com.android.bluetooth.TestConfig;
-
-import java.io.FileInputStream;
-import java.io.IOException;
-
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
-import org.robolectric.annotation.Config;
+
+import java.io.FileInputStream;
+import java.io.IOException;
@RunWith(RobolectricTestRunner.class)
-@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
public class OppSendFileInfoTest {
// Constants for test file size.
private static final int TEST_FILE_SIZE = 10;
private static final int MAXIMUM_FILE_SIZE = 0xFFFFFFFF;
- @Mock Context mContext;
- @Mock ContentResolver mContentResolver;
- @Mock FileInputStream mFileInputStream;
+ @Mock private Context mContext;
+ @Mock private ContentResolver mContentResolver;
+ @Mock private FileInputStream mFileInputStream;
@Before
public void setUp() {
@@ -104,7 +100,5 @@
BluetoothOppSendFileInfo sendFileInfo = BluetoothOppSendFileInfo.generateFileInfo(
mContext, uri, "text/plain", false);
assertThat(sendFileInfo).isEqualTo(BluetoothOppSendFileInfo.SEND_FILE_INFO_ERROR);
-
}
-
}
diff --git a/tests/unit/Android.mk b/tests/unit/Android.mk
index ef948a6..98c88d4 100644
--- a/tests/unit/Android.mk
+++ b/tests/unit/Android.mk
@@ -15,9 +15,9 @@
LOCAL_STATIC_JAVA_LIBRARIES := \
com.android.emailcommon \
- android-support-test \
+ androidx.test.rules \
mockito-target \
- espresso-intents
+ androidx.test.espresso.intents
# Include all test java files.
LOCAL_SRC_FILES := $(call all-java-files-under, src)
diff --git a/tests/unit/AndroidManifest.xml b/tests/unit/AndroidManifest.xml
index e344399..10c5d6d 100644
--- a/tests/unit/AndroidManifest.xml
+++ b/tests/unit/AndroidManifest.xml
@@ -54,19 +54,14 @@
<application
android:allowBackup="true" >
<uses-library android:name="android.test.runner" />
- <uses-permission android:name="android.permission.ACCESS_BLUETOOTH_SHARE" />
<uses-library android:name="org.apache.http.legacy" android:required="false" />
- <uses-permission android:name="com.android.permission.WHITELIST_BLUETOOTH_DEVICE" />
- <path-permission
- android:pathPrefix="/btopp"
- android:permission="android.permission.ACCESS_BLUETOOTH_SHARE" />
</application>
<!--
This declares that this application uses the instrumentation test runner targeting
the package of com.android.bluetooth. To run the tests use the command:
"adb shell am instrument -w com.android.bluetooth.tests/android.test.InstrumentationTestRunner"
-->
- <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
android:targetPackage="com.android.bluetooth"
android:label="Tests for com.android.bluetooth"/>
</manifest>
diff --git a/tests/unit/AndroidTest.xml b/tests/unit/AndroidTest.xml
index 230f2b1..6edd9a5 100644
--- a/tests/unit/AndroidTest.xml
+++ b/tests/unit/AndroidTest.xml
@@ -26,7 +26,9 @@
<option name="teardown-command" value="svc bluetooth enable" />
<option name="teardown-command" value="settings put global ble_scan_always_enabled 1" />
</target_preparer>
-
+ <target_preparer class="com.android.tradefed.targetprep.FolderSaver">
+ <option name="device-path" value="/data/vendor/ssrdump" />
+ </target_preparer>
<option name="test-tag" value="BluetoothInstrumentationTests" />
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
<option name="package" value="com.android.bluetooth.tests" />
diff --git a/tests/unit/src/com/android/bluetooth/FileSystemWriteTest.java b/tests/unit/src/com/android/bluetooth/FileSystemWriteTest.java
index 7841e80..a0215a8 100644
--- a/tests/unit/src/com/android/bluetooth/FileSystemWriteTest.java
+++ b/tests/unit/src/com/android/bluetooth/FileSystemWriteTest.java
@@ -15,8 +15,8 @@
*/
package com.android.bluetooth;
-import android.support.test.filters.MediumTest;
-import android.support.test.runner.AndroidJUnit4;
+import androidx.test.filters.MediumTest;
+import androidx.test.runner.AndroidJUnit4;
import org.junit.Assert;
import org.junit.Test;
diff --git a/tests/unit/src/com/android/bluetooth/TestUtils.java b/tests/unit/src/com/android/bluetooth/TestUtils.java
index d0e3324..fef4d0e 100644
--- a/tests/unit/src/com/android/bluetooth/TestUtils.java
+++ b/tests/unit/src/com/android/bluetooth/TestUtils.java
@@ -23,8 +23,9 @@
import android.content.Intent;
import android.os.Handler;
import android.os.Looper;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.rule.ServiceTestRule;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.rule.ServiceTestRule;
import com.android.bluetooth.btservice.AdapterService;
import com.android.bluetooth.btservice.ProfileService;
@@ -33,9 +34,13 @@
import org.mockito.ArgumentCaptor;
import org.mockito.internal.util.MockUtil;
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
+import java.util.HashMap;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
@@ -271,6 +276,41 @@
}
/**
+ * Read Bluetooth adapter configuration from the filesystem
+ *
+ * @return A {@link HashMap} of Bluetooth configs in the format:
+ * section -> key1 -> value1
+ * -> key2 -> value2
+ * Assume no empty section name, no duplicate keys in the same section
+ */
+ public static HashMap<String, HashMap<String, String>> readAdapterConfig() {
+ HashMap<String, HashMap<String, String>> adapterConfig = new HashMap<>();
+ try (BufferedReader reader =
+ new BufferedReader(new FileReader("/data/misc/bluedroid/bt_config.conf"))) {
+ String section = "";
+ for (String line; (line = reader.readLine()) != null;) {
+ line = line.trim();
+ if (line.isEmpty() || line.startsWith("#")) {
+ continue;
+ }
+ if (line.startsWith("[")) {
+ if (line.charAt(line.length() - 1) != ']') {
+ return null;
+ }
+ section = line.substring(1, line.length() - 1);
+ adapterConfig.put(section, new HashMap<>());
+ } else {
+ String[] keyValue = line.split("=");
+ adapterConfig.get(section).put(keyValue[0].trim(), keyValue[1].trim());
+ }
+ }
+ } catch (IOException e) {
+ return null;
+ }
+ return adapterConfig;
+ }
+
+ /**
* Helper class used to run synchronously a runnable action on a looper.
*/
private static final class SyncRunnable implements Runnable {
diff --git a/tests/unit/src/com/android/bluetooth/a2dp/A2dpCodecConfigTest.java b/tests/unit/src/com/android/bluetooth/a2dp/A2dpCodecConfigTest.java
new file mode 100644
index 0000000..a97a5b0
--- /dev/null
+++ b/tests/unit/src/com/android/bluetooth/a2dp/A2dpCodecConfigTest.java
@@ -0,0 +1,901 @@
+/*
+ * Copyright 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.a2dp;
+
+import static org.mockito.Mockito.*;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothCodecConfig;
+import android.bluetooth.BluetoothCodecStatus;
+import android.bluetooth.BluetoothDevice;
+import android.content.Context;
+import android.content.res.Resources;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.MediumTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.bluetooth.R;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class A2dpCodecConfigTest {
+ private Context mTargetContext;
+ private BluetoothDevice mTestDevice;
+ private A2dpCodecConfig mA2dpCodecConfig;
+
+ @Mock private Context mMockContext;
+ @Mock private Resources mMockResources;
+ @Mock private A2dpNativeInterface mA2dpNativeInterface;
+
+ private static final int[] sOptionalCodecTypes = new int[] {
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC,
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX,
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD,
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC
+ };
+
+ // Not use the default value to make sure it reads from config
+ private static final int SBC_PRIORITY_DEFAULT = 1001;
+ private static final int AAC_PRIORITY_DEFAULT = 3001;
+ private static final int APTX_PRIORITY_DEFAULT = 5001;
+ private static final int APTX_HD_PRIORITY_DEFAULT = 7001;
+ private static final int LDAC_PRIORITY_DEFAULT = 9001;
+ private static final int PRIORITY_HIGH = 1000000;
+
+ private static final BluetoothCodecConfig[] sCodecCapabilities = new BluetoothCodecConfig[] {
+ new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+ SBC_PRIORITY_DEFAULT,
+ BluetoothCodecConfig.SAMPLE_RATE_44100,
+ BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+ BluetoothCodecConfig.CHANNEL_MODE_MONO
+ | BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+ 0, 0, 0, 0), // Codec-specific fields
+ new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC,
+ AAC_PRIORITY_DEFAULT,
+ BluetoothCodecConfig.SAMPLE_RATE_44100,
+ BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+ BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+ 0, 0, 0, 0), // Codec-specific fields
+ new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX,
+ APTX_PRIORITY_DEFAULT,
+ BluetoothCodecConfig.SAMPLE_RATE_44100
+ | BluetoothCodecConfig.SAMPLE_RATE_48000,
+ BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+ BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+ 0, 0, 0, 0), // Codec-specific fields
+ new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD,
+ APTX_HD_PRIORITY_DEFAULT,
+ BluetoothCodecConfig.SAMPLE_RATE_44100
+ | BluetoothCodecConfig.SAMPLE_RATE_48000,
+ BluetoothCodecConfig.BITS_PER_SAMPLE_24,
+ BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+ 0, 0, 0, 0), // Codec-specific fields
+ new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+ LDAC_PRIORITY_DEFAULT,
+ BluetoothCodecConfig.SAMPLE_RATE_44100
+ | BluetoothCodecConfig.SAMPLE_RATE_48000
+ | BluetoothCodecConfig.SAMPLE_RATE_88200
+ | BluetoothCodecConfig.SAMPLE_RATE_96000,
+ BluetoothCodecConfig.BITS_PER_SAMPLE_16
+ | BluetoothCodecConfig.BITS_PER_SAMPLE_24
+ | BluetoothCodecConfig.BITS_PER_SAMPLE_32,
+ BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+ 0, 0, 0, 0) // Codec-specific fields
+ };
+
+ private static final BluetoothCodecConfig[] sDefaultCodecConfigs = new BluetoothCodecConfig[] {
+ new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+ SBC_PRIORITY_DEFAULT,
+ BluetoothCodecConfig.SAMPLE_RATE_44100,
+ BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+ BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+ 0, 0, 0, 0), // Codec-specific fields
+ new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC,
+ AAC_PRIORITY_DEFAULT,
+ BluetoothCodecConfig.SAMPLE_RATE_44100,
+ BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+ BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+ 0, 0, 0, 0), // Codec-specific fields
+ new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX,
+ APTX_PRIORITY_DEFAULT,
+ BluetoothCodecConfig.SAMPLE_RATE_48000,
+ BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+ BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+ 0, 0, 0, 0), // Codec-specific fields
+ new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD,
+ APTX_HD_PRIORITY_DEFAULT,
+ BluetoothCodecConfig.SAMPLE_RATE_48000,
+ BluetoothCodecConfig.BITS_PER_SAMPLE_24,
+ BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+ 0, 0, 0, 0), // Codec-specific fields
+ new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+ LDAC_PRIORITY_DEFAULT,
+ BluetoothCodecConfig.SAMPLE_RATE_96000,
+ BluetoothCodecConfig.BITS_PER_SAMPLE_32,
+ BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+ 0, 0, 0, 0) // Codec-specific fields
+ };
+
+ @Before
+ public void setUp() throws Exception {
+ // Set up mocks and test assets
+ MockitoAnnotations.initMocks(this);
+ mTargetContext = InstrumentationRegistry.getTargetContext();
+
+ when(mMockContext.getResources()).thenReturn(mMockResources);
+ when(mMockResources.getInteger(R.integer.a2dp_source_codec_priority_sbc))
+ .thenReturn(SBC_PRIORITY_DEFAULT);
+ when(mMockResources.getInteger(R.integer.a2dp_source_codec_priority_aac))
+ .thenReturn(AAC_PRIORITY_DEFAULT);
+ when(mMockResources.getInteger(R.integer.a2dp_source_codec_priority_aptx))
+ .thenReturn(APTX_PRIORITY_DEFAULT);
+ when(mMockResources.getInteger(R.integer.a2dp_source_codec_priority_aptx_hd))
+ .thenReturn(APTX_HD_PRIORITY_DEFAULT);
+ when(mMockResources.getInteger(R.integer.a2dp_source_codec_priority_ldac))
+ .thenReturn(LDAC_PRIORITY_DEFAULT);
+
+ mA2dpCodecConfig = new A2dpCodecConfig(mMockContext, mA2dpNativeInterface);
+ mTestDevice = BluetoothAdapter.getDefaultAdapter().getRemoteDevice("00:01:02:03:04:05");
+
+ doReturn(true).when(mA2dpNativeInterface).setCodecConfigPreference(
+ any(BluetoothDevice.class),
+ any(BluetoothCodecConfig[].class));
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ }
+
+ @Test
+ public void testAssignCodecConfigPriorities() {
+ BluetoothCodecConfig[] codecConfigs = mA2dpCodecConfig.codecConfigPriorities();
+ for (BluetoothCodecConfig config : codecConfigs) {
+ switch(config.getCodecType()) {
+ case BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC:
+ Assert.assertEquals(config.getCodecPriority(), SBC_PRIORITY_DEFAULT);
+ break;
+ case BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC:
+ Assert.assertEquals(config.getCodecPriority(), AAC_PRIORITY_DEFAULT);
+ break;
+ case BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX:
+ Assert.assertEquals(config.getCodecPriority(), APTX_PRIORITY_DEFAULT);
+ break;
+ case BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD:
+ Assert.assertEquals(config.getCodecPriority(), APTX_HD_PRIORITY_DEFAULT);
+ break;
+ case BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC:
+ Assert.assertEquals(config.getCodecPriority(), LDAC_PRIORITY_DEFAULT);
+ break;
+ }
+ }
+ }
+
+ /**
+ * Test that we can fallback to default codec by lower the codec priority we changed before.
+ */
+ @Test
+ public void testSetCodecPreference_priorityHighToDefault() {
+ testCodecPriorityChangeHelper(
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, SBC_PRIORITY_DEFAULT,
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, PRIORITY_HIGH,
+ true);
+ testCodecPriorityChangeHelper(
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC, AAC_PRIORITY_DEFAULT,
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC, PRIORITY_HIGH,
+ true);
+ testCodecPriorityChangeHelper(
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX, APTX_PRIORITY_DEFAULT,
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX, PRIORITY_HIGH,
+ true);
+ testCodecPriorityChangeHelper(
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD, APTX_HD_PRIORITY_DEFAULT,
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD, PRIORITY_HIGH,
+ true);
+ testCodecPriorityChangeHelper(
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC, LDAC_PRIORITY_DEFAULT,
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC, PRIORITY_HIGH,
+ false);
+ }
+
+ /**
+ * Test that we can change the default codec to another by raising the codec priority.
+ * LDAC is the default highest codec, so no need to test others.
+ */
+ @Test
+ public void testSetCodecPreference_priorityDefaultToRaiseHigh() {
+ testCodecPriorityChangeHelper(
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, PRIORITY_HIGH,
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC, LDAC_PRIORITY_DEFAULT,
+ true);
+ testCodecPriorityChangeHelper(
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC, PRIORITY_HIGH,
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC, LDAC_PRIORITY_DEFAULT,
+ true);
+ testCodecPriorityChangeHelper(
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX, PRIORITY_HIGH,
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC, LDAC_PRIORITY_DEFAULT,
+ true);
+ testCodecPriorityChangeHelper(
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD, PRIORITY_HIGH,
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC, LDAC_PRIORITY_DEFAULT,
+ true);
+ testCodecPriorityChangeHelper(
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC, PRIORITY_HIGH,
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC, LDAC_PRIORITY_DEFAULT,
+ false);
+ }
+
+ @Test
+ public void testSetCodecPreference_prioritySbcHighToOthersHigh() {
+ testCodecPriorityChangeHelper(
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, PRIORITY_HIGH,
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, PRIORITY_HIGH,
+ false);
+ testCodecPriorityChangeHelper(
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC, PRIORITY_HIGH,
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, PRIORITY_HIGH,
+ true);
+ testCodecPriorityChangeHelper(
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX, PRIORITY_HIGH,
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, PRIORITY_HIGH,
+ true);
+ testCodecPriorityChangeHelper(
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD, PRIORITY_HIGH,
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, PRIORITY_HIGH,
+ true);
+ testCodecPriorityChangeHelper(
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC, PRIORITY_HIGH,
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, PRIORITY_HIGH,
+ true);
+ }
+
+ @Test
+ public void testSetCodecPreference_priorityAacHighToOthersHigh() {
+ testCodecPriorityChangeHelper(
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, PRIORITY_HIGH,
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC, PRIORITY_HIGH,
+ true);
+ testCodecPriorityChangeHelper(
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC, PRIORITY_HIGH,
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC, PRIORITY_HIGH,
+ false);
+ testCodecPriorityChangeHelper(
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX, PRIORITY_HIGH,
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC, PRIORITY_HIGH,
+ true);
+ testCodecPriorityChangeHelper(
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD, PRIORITY_HIGH,
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC, PRIORITY_HIGH,
+ true);
+ testCodecPriorityChangeHelper(
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC, PRIORITY_HIGH,
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC, PRIORITY_HIGH,
+ true);
+ }
+
+ @Test
+ public void testSetCodecPreference_parametersChangedInSameCodec() {
+ // The SBC default / preferred config is Stereo
+ testCodecParametersChangeHelper(
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+ BluetoothCodecConfig.SAMPLE_RATE_44100,
+ BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+ BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+ false);
+ // SBC Mono is mandatory
+ testCodecParametersChangeHelper(
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+ BluetoothCodecConfig.SAMPLE_RATE_44100,
+ BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+ BluetoothCodecConfig.CHANNEL_MODE_MONO,
+ true);
+
+ // The LDAC default / preferred config within mDefaultCodecConfigs
+ testCodecParametersChangeHelper(
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+ BluetoothCodecConfig.SAMPLE_RATE_96000,
+ BluetoothCodecConfig.BITS_PER_SAMPLE_32,
+ BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+ false);
+
+ testCodecParametersChangeHelper(
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+ BluetoothCodecConfig.SAMPLE_RATE_44100,
+ BluetoothCodecConfig.BITS_PER_SAMPLE_32,
+ BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+ true);
+ testCodecParametersChangeHelper(
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+ BluetoothCodecConfig.SAMPLE_RATE_96000,
+ BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+ BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+ true);
+ testCodecParametersChangeHelper(
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+ BluetoothCodecConfig.SAMPLE_RATE_44100,
+ BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+ BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+ true);
+
+ // None for system default (Developer options)
+ testCodecParametersChangeHelper(
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+ BluetoothCodecConfig.SAMPLE_RATE_NONE,
+ BluetoothCodecConfig.BITS_PER_SAMPLE_32,
+ BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+ false);
+ testCodecParametersChangeHelper(
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+ BluetoothCodecConfig.SAMPLE_RATE_96000,
+ BluetoothCodecConfig.BITS_PER_SAMPLE_NONE,
+ BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+ false);
+ testCodecParametersChangeHelper(
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+ BluetoothCodecConfig.SAMPLE_RATE_96000,
+ BluetoothCodecConfig.BITS_PER_SAMPLE_32,
+ BluetoothCodecConfig.CHANNEL_MODE_NONE,
+ false);
+
+ int unsupportedParameter = 0xc0;
+ testCodecParametersChangeHelper(
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+ unsupportedParameter,
+ BluetoothCodecConfig.BITS_PER_SAMPLE_32,
+ BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+ false);
+ testCodecParametersChangeHelper(
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+ BluetoothCodecConfig.SAMPLE_RATE_96000,
+ unsupportedParameter,
+ BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+ false);
+ testCodecParametersChangeHelper(
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+ BluetoothCodecConfig.SAMPLE_RATE_96000,
+ BluetoothCodecConfig.BITS_PER_SAMPLE_32,
+ unsupportedParameter,
+ false);
+
+ int multipleSupportedParameters = 0x03;
+ testCodecParametersChangeHelper(
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+ multipleSupportedParameters,
+ BluetoothCodecConfig.BITS_PER_SAMPLE_32,
+ BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+ false);
+ testCodecParametersChangeHelper(
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+ BluetoothCodecConfig.SAMPLE_RATE_96000,
+ multipleSupportedParameters,
+ BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+ false);
+ testCodecParametersChangeHelper(
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+ BluetoothCodecConfig.SAMPLE_RATE_96000,
+ BluetoothCodecConfig.BITS_PER_SAMPLE_32,
+ multipleSupportedParameters,
+ false);
+ }
+
+ @Test
+ public void testSetCodecPreference_parametersChangedToAnotherCodec() {
+ // different sample rate (44.1 kHz -> 96 kHz)
+ testCodecParametersChangeHelper(
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC,
+ BluetoothCodecConfig.SAMPLE_RATE_96000,
+ BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+ BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+ true);
+ // different bits per channel (16 bits -> 32 bits)
+ testCodecParametersChangeHelper(
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC,
+ BluetoothCodecConfig.SAMPLE_RATE_44100,
+ BluetoothCodecConfig.BITS_PER_SAMPLE_32,
+ BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+ true);
+ // change all PCM parameters
+ testCodecParametersChangeHelper(
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+ BluetoothCodecConfig.SAMPLE_RATE_44100,
+ BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+ BluetoothCodecConfig.CHANNEL_MODE_MONO,
+ true);
+
+ // None for system default (Developer options)
+ testCodecParametersChangeHelper(
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+ BluetoothCodecConfig.SAMPLE_RATE_NONE,
+ BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+ BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+ true);
+ testCodecParametersChangeHelper(
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+ BluetoothCodecConfig.SAMPLE_RATE_44100,
+ BluetoothCodecConfig.BITS_PER_SAMPLE_NONE,
+ BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+ true);
+ testCodecParametersChangeHelper(
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+ BluetoothCodecConfig.SAMPLE_RATE_44100,
+ BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+ BluetoothCodecConfig.CHANNEL_MODE_NONE,
+ true);
+ testCodecParametersChangeHelper(
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+ BluetoothCodecConfig.SAMPLE_RATE_NONE,
+ BluetoothCodecConfig.BITS_PER_SAMPLE_NONE,
+ BluetoothCodecConfig.CHANNEL_MODE_NONE,
+ true);
+
+ int unsupportedParameter = 0xc0;
+ testCodecParametersChangeHelper(
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC,
+ unsupportedParameter,
+ BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+ BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+ false);
+ testCodecParametersChangeHelper(
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC,
+ BluetoothCodecConfig.SAMPLE_RATE_44100,
+ unsupportedParameter,
+ BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+ false);
+ testCodecParametersChangeHelper(
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC,
+ BluetoothCodecConfig.SAMPLE_RATE_44100,
+ BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+ unsupportedParameter,
+ false);
+
+ int multipleSupportedParameters = 0x03;
+ testCodecParametersChangeHelper(
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC,
+ multipleSupportedParameters,
+ BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+ BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+ false);
+ testCodecParametersChangeHelper(
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC,
+ BluetoothCodecConfig.SAMPLE_RATE_44100,
+ multipleSupportedParameters,
+ BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+ false);
+ testCodecParametersChangeHelper(
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC,
+ BluetoothCodecConfig.SAMPLE_RATE_44100,
+ BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+ multipleSupportedParameters,
+ false);
+ }
+
+ @Test
+ public void testSetCodecPreference_ldacCodecSpecificFieldChanged() {
+ int ldacAudioQualityHigh = 1000;
+ int ldacAudioQualityABR = 1003;
+ int sbcCodecSpecificParameter = 0;
+ testCodecSpecificParametersChangeHelper(
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+ ldacAudioQualityABR,
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+ ldacAudioQualityABR,
+ false);
+ testCodecSpecificParametersChangeHelper(
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+ ldacAudioQualityHigh,
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+ ldacAudioQualityABR,
+ true);
+ testCodecSpecificParametersChangeHelper(
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+ ldacAudioQualityABR,
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+ sbcCodecSpecificParameter,
+ true);
+
+ // Only LDAC will check the codec specific1 field, but not SBC
+ testCodecSpecificParametersChangeHelper(
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+ sbcCodecSpecificParameter,
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+ ldacAudioQualityABR,
+ true);
+ testCodecSpecificParametersChangeHelper(
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+ ldacAudioQualityABR,
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+ ldacAudioQualityABR,
+ true);
+ testCodecSpecificParametersChangeHelper(
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+ ldacAudioQualityHigh,
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+ sbcCodecSpecificParameter,
+ false);
+ }
+
+ @Test
+ public void testDisableOptionalCodecs() {
+ BluetoothCodecConfig[] codecConfigsArray =
+ new BluetoothCodecConfig[BluetoothCodecConfig.SOURCE_CODEC_TYPE_MAX];
+ codecConfigsArray[0] = new BluetoothCodecConfig(
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+ BluetoothCodecConfig.CODEC_PRIORITY_HIGHEST,
+ BluetoothCodecConfig.SAMPLE_RATE_NONE,
+ BluetoothCodecConfig.BITS_PER_SAMPLE_NONE,
+ BluetoothCodecConfig.CHANNEL_MODE_NONE,
+ 0, 0, 0, 0); // Codec-specific fields
+
+ // shouldn't invoke to native when current codec is SBC
+ mA2dpCodecConfig.disableOptionalCodecs(
+ mTestDevice,
+ getDefaultCodecConfigByType(
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+ BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT));
+ verify(mA2dpNativeInterface, times(0)).setCodecConfigPreference(mTestDevice,
+ codecConfigsArray);
+
+ // should invoke to native when current codec is an optional codec
+ int invokedCounter = 0;
+ for (int codecType : sOptionalCodecTypes) {
+ mA2dpCodecConfig.disableOptionalCodecs(
+ mTestDevice,
+ getDefaultCodecConfigByType(codecType,
+ BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT));
+ verify(mA2dpNativeInterface, times(++invokedCounter))
+ .setCodecConfigPreference(mTestDevice, codecConfigsArray);
+ }
+ }
+
+ @Test
+ public void testEnableOptionalCodecs() {
+ BluetoothCodecConfig[] codecConfigsArray =
+ new BluetoothCodecConfig[BluetoothCodecConfig.SOURCE_CODEC_TYPE_MAX];
+ codecConfigsArray[0] = new BluetoothCodecConfig(
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+ SBC_PRIORITY_DEFAULT,
+ BluetoothCodecConfig.SAMPLE_RATE_NONE,
+ BluetoothCodecConfig.BITS_PER_SAMPLE_NONE,
+ BluetoothCodecConfig.CHANNEL_MODE_NONE,
+ 0, 0, 0, 0); // Codec-specific fields
+
+ // should invoke to native when current codec is SBC
+ mA2dpCodecConfig.enableOptionalCodecs(
+ mTestDevice,
+ getDefaultCodecConfigByType(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+ BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT));
+ verify(mA2dpNativeInterface, times(1))
+ .setCodecConfigPreference(mTestDevice, codecConfigsArray);
+
+ // shouldn't invoke to native when current codec is already an optional
+ for (int codecType : sOptionalCodecTypes) {
+ mA2dpCodecConfig.enableOptionalCodecs(
+ mTestDevice,
+ getDefaultCodecConfigByType(codecType,
+ BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT));
+ verify(mA2dpNativeInterface, times(1))
+ .setCodecConfigPreference(mTestDevice, codecConfigsArray);
+ }
+ }
+
+ private BluetoothCodecConfig getDefaultCodecConfigByType(int codecType, int codecPriority) {
+ for (BluetoothCodecConfig codecConfig : sDefaultCodecConfigs) {
+ if (codecConfig.getCodecType() != codecType) {
+ continue;
+ }
+ return new BluetoothCodecConfig(
+ codecConfig.getCodecType(),
+ (codecPriority != BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT
+ ? codecPriority : codecConfig.getCodecPriority()),
+ codecConfig.getSampleRate(), codecConfig.getBitsPerSample(),
+ codecConfig.getChannelMode(), codecConfig.getCodecSpecific1(),
+ codecConfig.getCodecSpecific2(), codecConfig.getCodecSpecific3(),
+ codecConfig.getCodecSpecific4());
+ }
+ Assert.fail("getDefaultCodecConfigByType: No such codecType=" + codecType
+ + " in sDefaultCodecConfigs");
+ return null;
+ }
+
+ private BluetoothCodecConfig getCodecCapabilitiesByType(int codecType) {
+ for (BluetoothCodecConfig codecCapabilities : sCodecCapabilities) {
+ if (codecCapabilities.getCodecType() != codecType) {
+ continue;
+ }
+ return new BluetoothCodecConfig(
+ codecCapabilities.getCodecType(), codecCapabilities.getCodecPriority(),
+ codecCapabilities.getSampleRate(), codecCapabilities.getBitsPerSample(),
+ codecCapabilities.getChannelMode(), codecCapabilities.getCodecSpecific1(),
+ codecCapabilities.getCodecSpecific2(), codecCapabilities.getCodecSpecific3(),
+ codecCapabilities.getCodecSpecific4());
+ }
+ Assert.fail("getCodecCapabilitiesByType: No such codecType=" + codecType
+ + " in sCodecCapabilities");
+ return null;
+ }
+
+ private void testCodecParametersChangeHelper(int newCodecType, int oldCodecType,
+ int sampleRate, int bitsPerSample, int channelMode, boolean invokeNative) {
+ BluetoothCodecConfig oldCodecConfig =
+ getDefaultCodecConfigByType(oldCodecType, PRIORITY_HIGH);
+ BluetoothCodecConfig[] newCodecConfigsArray = new BluetoothCodecConfig[] {
+ new BluetoothCodecConfig(newCodecType,
+ PRIORITY_HIGH,
+ sampleRate, bitsPerSample, channelMode,
+ 0, 0, 0, 0) // Codec-specific fields
+ };
+
+ // Test cases: 1. no mandatory; 2. mandatory + old + new; 3. all codecs
+ BluetoothCodecConfig[] minimumCodecsArray;
+ if (!oldCodecConfig.isMandatoryCodec() && !newCodecConfigsArray[0].isMandatoryCodec()) {
+ BluetoothCodecConfig[] optionalCodecsArray;
+ if (oldCodecType != newCodecType) {
+ optionalCodecsArray = new BluetoothCodecConfig[] {
+ getCodecCapabilitiesByType(oldCodecType),
+ getCodecCapabilitiesByType(newCodecType)
+ };
+ minimumCodecsArray = new BluetoothCodecConfig[] {
+ getCodecCapabilitiesByType(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC),
+ getCodecCapabilitiesByType(oldCodecType),
+ getCodecCapabilitiesByType(newCodecType)
+ };
+ } else {
+ optionalCodecsArray = new BluetoothCodecConfig[]
+ {getCodecCapabilitiesByType(oldCodecType)};
+ minimumCodecsArray = new BluetoothCodecConfig[] {
+ getCodecCapabilitiesByType(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC),
+ getCodecCapabilitiesByType(oldCodecType)
+ };
+ }
+ BluetoothCodecStatus codecStatus = new BluetoothCodecStatus(oldCodecConfig,
+ sCodecCapabilities,
+ optionalCodecsArray);
+ mA2dpCodecConfig.setCodecConfigPreference(mTestDevice,
+ codecStatus,
+ newCodecConfigsArray[0]);
+ // no mandatory codec in selectable, and should not apply
+ verify(mA2dpNativeInterface, times(0)).setCodecConfigPreference(mTestDevice,
+ newCodecConfigsArray);
+
+
+ } else {
+ if (oldCodecType != newCodecType) {
+ minimumCodecsArray = new BluetoothCodecConfig[] {
+ getCodecCapabilitiesByType(oldCodecType),
+ getCodecCapabilitiesByType(newCodecType)
+ };
+ } else {
+ minimumCodecsArray = new BluetoothCodecConfig[] {
+ getCodecCapabilitiesByType(oldCodecType),
+ };
+ }
+ }
+
+ // 2. mandatory + old + new codecs only
+ BluetoothCodecStatus codecStatus =
+ new BluetoothCodecStatus(oldCodecConfig, sCodecCapabilities, minimumCodecsArray);
+ mA2dpCodecConfig.setCodecConfigPreference(mTestDevice,
+ codecStatus,
+ newCodecConfigsArray[0]);
+ verify(mA2dpNativeInterface, times(invokeNative ? 1 : 0))
+ .setCodecConfigPreference(mTestDevice, newCodecConfigsArray);
+
+ // 3. all codecs were selectable
+ codecStatus = new BluetoothCodecStatus(oldCodecConfig,
+ sCodecCapabilities,
+ sCodecCapabilities);
+ mA2dpCodecConfig.setCodecConfigPreference(mTestDevice,
+ codecStatus,
+ newCodecConfigsArray[0]);
+ verify(mA2dpNativeInterface, times(invokeNative ? 2 : 0))
+ .setCodecConfigPreference(mTestDevice, newCodecConfigsArray);
+ }
+
+ private void testCodecSpecificParametersChangeHelper(int newCodecType, int newCodecSpecific,
+ int oldCodecType, int oldCodecSpecific, boolean invokeNative) {
+ BluetoothCodecConfig codecDefaultTemp =
+ getDefaultCodecConfigByType(oldCodecType, PRIORITY_HIGH);
+ BluetoothCodecConfig oldCodecConfig =
+ new BluetoothCodecConfig(codecDefaultTemp.getCodecType(),
+ codecDefaultTemp.getCodecPriority(),
+ codecDefaultTemp.getSampleRate(),
+ codecDefaultTemp.getBitsPerSample(),
+ codecDefaultTemp.getChannelMode(),
+ oldCodecSpecific, 0, 0, 0); // Codec-specific fields
+ codecDefaultTemp = getDefaultCodecConfigByType(newCodecType, PRIORITY_HIGH);
+ BluetoothCodecConfig[] newCodecConfigsArray = new BluetoothCodecConfig[] {
+ new BluetoothCodecConfig(codecDefaultTemp.getCodecType(),
+ codecDefaultTemp.getCodecPriority(),
+ codecDefaultTemp.getSampleRate(),
+ codecDefaultTemp.getBitsPerSample(),
+ codecDefaultTemp.getChannelMode(),
+ newCodecSpecific, 0, 0, 0) // Codec-specific fields
+ };
+ BluetoothCodecStatus codecStatus = new BluetoothCodecStatus(oldCodecConfig,
+ sCodecCapabilities,
+ sCodecCapabilities);
+ mA2dpCodecConfig.setCodecConfigPreference(mTestDevice,
+ codecStatus,
+ newCodecConfigsArray[0]);
+ verify(mA2dpNativeInterface, times(invokeNative ? 1 : 0))
+ .setCodecConfigPreference(mTestDevice, newCodecConfigsArray);
+ }
+
+ private void testCodecPriorityChangeHelper(int newCodecType, int newCodecPriority,
+ int oldCodecType, int oldCodecPriority, boolean shouldApplyWhenAllSelectable) {
+
+ BluetoothCodecConfig[] newCodecConfigsArray =
+ new BluetoothCodecConfig[] {
+ getDefaultCodecConfigByType(newCodecType, newCodecPriority)
+ };
+ BluetoothCodecConfig oldCodecConfig = getDefaultCodecConfigByType(oldCodecType,
+ oldCodecPriority);
+
+ // Test cases: 1. no mandatory; 2. no new codec; 3. mandatory + old + new; 4. all codecs
+ BluetoothCodecConfig[] minimumCodecsArray;
+ boolean isMinimumCodecsArraySelectable;
+ if (!oldCodecConfig.isMandatoryCodec()) {
+ if (oldCodecType == newCodecType || newCodecConfigsArray[0].isMandatoryCodec()) {
+ // selectable: {-mandatory, +oldCodec = newCodec}, or
+ // selectable: {-mandatory = newCodec, +oldCodec}. Not applied
+ BluetoothCodecConfig[] poorCodecsArray = new BluetoothCodecConfig[]
+ {getCodecCapabilitiesByType(oldCodecType)};
+ BluetoothCodecStatus codecStatus = new BluetoothCodecStatus(oldCodecConfig,
+ sCodecCapabilities,
+ poorCodecsArray);
+ mA2dpCodecConfig.setCodecConfigPreference(mTestDevice,
+ codecStatus,
+ newCodecConfigsArray[0]);
+ verify(mA2dpNativeInterface, times(0))
+ .setCodecConfigPreference(mTestDevice, newCodecConfigsArray);
+
+ // selectable: {+mandatory, +oldCodec = newCodec}, or
+ // selectable: {+mandatory = newCodec, +oldCodec}.
+ minimumCodecsArray = new BluetoothCodecConfig[] {
+ getCodecCapabilitiesByType(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC),
+ getCodecCapabilitiesByType(oldCodecType)
+ };
+ } else {
+ // selectable: {-mandatory, +oldCodec, +newCodec}. Not applied
+ BluetoothCodecConfig[] poorCodecsArray = new BluetoothCodecConfig[] {
+ getCodecCapabilitiesByType(oldCodecType),
+ getCodecCapabilitiesByType(newCodecType)
+ };
+ BluetoothCodecStatus codecStatus = new BluetoothCodecStatus(oldCodecConfig,
+ sCodecCapabilities,
+ poorCodecsArray);
+ mA2dpCodecConfig.setCodecConfigPreference(
+ mTestDevice, codecStatus, newCodecConfigsArray[0]);
+ verify(mA2dpNativeInterface, times(0))
+ .setCodecConfigPreference(mTestDevice, newCodecConfigsArray);
+
+ // selectable: {+mandatory, +oldCodec, -newCodec}. Not applied
+ poorCodecsArray = new BluetoothCodecConfig[] {
+ getCodecCapabilitiesByType(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC),
+ getCodecCapabilitiesByType(oldCodecType)
+ };
+ codecStatus = new BluetoothCodecStatus(oldCodecConfig,
+ sCodecCapabilities,
+ poorCodecsArray);
+ mA2dpCodecConfig.setCodecConfigPreference(mTestDevice,
+ codecStatus,
+ newCodecConfigsArray[0]);
+ verify(mA2dpNativeInterface, times(0))
+ .setCodecConfigPreference(mTestDevice, newCodecConfigsArray);
+
+ // selectable: {+mandatory, +oldCodec, +newCodec}.
+ minimumCodecsArray = new BluetoothCodecConfig[] {
+ getCodecCapabilitiesByType(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC),
+ getCodecCapabilitiesByType(oldCodecType),
+ getCodecCapabilitiesByType(newCodecType)
+ };
+ }
+ // oldCodec priority should be reset to default, so compare with the default
+ if (newCodecConfigsArray[0].getCodecPriority()
+ > getDefaultCodecConfigByType(
+ oldCodecType,
+ BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT).getCodecPriority()
+ && oldCodecType != newCodecType) {
+ isMinimumCodecsArraySelectable = true;
+ } else {
+ // the old codec was still the highest priority after reset to default
+ isMinimumCodecsArraySelectable = false;
+ }
+ } else if (oldCodecType != newCodecType) {
+ // selectable: {+mandatory = oldCodec, -newCodec}. Not applied
+ BluetoothCodecConfig[] poorCodecsArray = new BluetoothCodecConfig[]
+ {getCodecCapabilitiesByType(oldCodecType)};
+ BluetoothCodecStatus codecStatus =
+ new BluetoothCodecStatus(oldCodecConfig, sCodecCapabilities, poorCodecsArray);
+ mA2dpCodecConfig.setCodecConfigPreference(mTestDevice,
+ codecStatus,
+ newCodecConfigsArray[0]);
+ verify(mA2dpNativeInterface, times(0))
+ .setCodecConfigPreference(mTestDevice, newCodecConfigsArray);
+
+ // selectable: {+mandatory = oldCodec, +newCodec}.
+ minimumCodecsArray = new BluetoothCodecConfig[] {
+ getCodecCapabilitiesByType(oldCodecType),
+ getCodecCapabilitiesByType(newCodecType)
+ };
+ isMinimumCodecsArraySelectable = true;
+ } else {
+ // selectable: {mandatory = oldCodec = newCodec}.
+ minimumCodecsArray = new BluetoothCodecConfig[]
+ {getCodecCapabilitiesByType(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC)};
+ isMinimumCodecsArraySelectable = false;
+ }
+
+ // 3. mandatory + old + new codecs only
+ int invokedCounter = (isMinimumCodecsArraySelectable ? 1 : 0);
+ BluetoothCodecStatus codecStatus =
+ new BluetoothCodecStatus(oldCodecConfig, sCodecCapabilities, minimumCodecsArray);
+ mA2dpCodecConfig.setCodecConfigPreference(mTestDevice,
+ codecStatus,
+ newCodecConfigsArray[0]);
+ verify(mA2dpNativeInterface, times(invokedCounter))
+ .setCodecConfigPreference(mTestDevice, newCodecConfigsArray);
+
+ // 4. all codecs were selectable
+ invokedCounter += (shouldApplyWhenAllSelectable ? 1 : 0);
+ codecStatus = new BluetoothCodecStatus(oldCodecConfig,
+ sCodecCapabilities,
+ sCodecCapabilities);
+ mA2dpCodecConfig.setCodecConfigPreference(mTestDevice,
+ codecStatus,
+ newCodecConfigsArray[0]);
+ verify(mA2dpNativeInterface, times(invokedCounter))
+ .setCodecConfigPreference(mTestDevice, newCodecConfigsArray);
+ }
+}
diff --git a/tests/unit/src/com/android/bluetooth/a2dp/A2dpServiceTest.java b/tests/unit/src/com/android/bluetooth/a2dp/A2dpServiceTest.java
index bef3bdb..df99505 100644
--- a/tests/unit/src/com/android/bluetooth/a2dp/A2dpServiceTest.java
+++ b/tests/unit/src/com/android/bluetooth/a2dp/A2dpServiceTest.java
@@ -31,14 +31,18 @@
import android.content.IntentFilter;
import android.os.Looper;
import android.os.ParcelUuid;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.MediumTest;
-import android.support.test.rule.ServiceTestRule;
-import android.support.test.runner.AndroidJUnit4;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.MediumTest;
+import androidx.test.rule.ServiceTestRule;
+import androidx.test.runner.AndroidJUnit4;
import com.android.bluetooth.R;
import com.android.bluetooth.TestUtils;
+import com.android.bluetooth.avrcp.AvrcpTargetService;
import com.android.bluetooth.btservice.AdapterService;
+import com.android.bluetooth.btservice.ServiceFactory;
+import com.android.bluetooth.btservice.storage.DatabaseManager;
import org.junit.After;
import org.junit.Assert;
@@ -73,6 +77,9 @@
@Mock private AdapterService mAdapterService;
@Mock private A2dpNativeInterface mA2dpNativeInterface;
+ @Mock private DatabaseManager mDatabaseManager;
+ @Mock private AvrcpTargetService mAvrcpTargetService;
+ @Mock private ServiceFactory mFactory;
@Rule public final ServiceTestRule mServiceRule = new ServiceTestRule();
@@ -91,11 +98,13 @@
TestUtils.setAdapterService(mAdapterService);
doReturn(MAX_CONNECTED_AUDIO_DEVICES).when(mAdapterService).getMaxConnectedAudioDevices();
doReturn(false).when(mAdapterService).isQuietModeEnabled();
+ doReturn(mAvrcpTargetService).when(mFactory).getAvrcpTargetService();
mAdapter = BluetoothAdapter.getDefaultAdapter();
startService();
mA2dpService.mA2dpNativeInterface = mA2dpNativeInterface;
+ mA2dpService.mFactory = mFactory;
// Override the timeout value to speed up the test
A2dpStateMachine.sConnectTimeoutMs = TIMEOUT_MS; // 1s
@@ -110,7 +119,6 @@
// Get a device for testing
mTestDevice = mAdapter.getRemoteDevice("00:01:02:03:04:05");
- mA2dpService.setPriority(mTestDevice, BluetoothProfile.PRIORITY_UNDEFINED);
doReturn(BluetoothDevice.BOND_BONDED).when(mAdapterService)
.getBondState(any(BluetoothDevice.class));
doReturn(new ParcelUuid[]{BluetoothUuid.AudioSink}).when(mAdapterService)
@@ -244,6 +252,8 @@
});
// Verify that setActiveDevice(null) was called during shutdown
verify(mA2dpNativeInterface).setActiveDevice(null);
+ // Verify that storeVolumeForDevice(mTestDevice) was called during shutdown
+ verify(mAvrcpTargetService).storeVolumeForDevice(mTestDevice);
// Try to restart the service. Note: must be done on the main thread.
InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
public void run() {
@@ -253,26 +263,34 @@
}
/**
- * Test get/set priority for BluetoothDevice
+ * Test get priority for BluetoothDevice
*/
@Test
- public void testGetSetPriority() {
+ public void testGetPriority() {
+ when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
+ when(mDatabaseManager.getProfilePriority(mTestDevice, BluetoothProfile.A2DP))
+ .thenReturn(BluetoothProfile.PRIORITY_UNDEFINED);
Assert.assertEquals("Initial device priority",
BluetoothProfile.PRIORITY_UNDEFINED,
mA2dpService.getPriority(mTestDevice));
- Assert.assertTrue(mA2dpService.setPriority(mTestDevice, BluetoothProfile.PRIORITY_OFF));
+ when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
+ when(mDatabaseManager.getProfilePriority(mTestDevice, BluetoothProfile.A2DP))
+ .thenReturn(BluetoothProfile.PRIORITY_OFF);
Assert.assertEquals("Setting device priority to PRIORITY_OFF",
BluetoothProfile.PRIORITY_OFF,
mA2dpService.getPriority(mTestDevice));
- Assert.assertTrue(mA2dpService.setPriority(mTestDevice, BluetoothProfile.PRIORITY_ON));
+ when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
+ when(mDatabaseManager.getProfilePriority(mTestDevice, BluetoothProfile.A2DP))
+ .thenReturn(BluetoothProfile.PRIORITY_ON);
Assert.assertEquals("Setting device priority to PRIORITY_ON",
BluetoothProfile.PRIORITY_ON,
mA2dpService.getPriority(mTestDevice));
- Assert.assertTrue(mA2dpService.setPriority(mTestDevice,
- BluetoothProfile.PRIORITY_AUTO_CONNECT));
+ 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));
@@ -296,13 +314,13 @@
testOkToConnectCase(mTestDevice,
BluetoothDevice.BOND_NONE, badPriorityValue, false);
testOkToConnectCase(mTestDevice,
- BluetoothDevice.BOND_BONDING, BluetoothProfile.PRIORITY_UNDEFINED, false);
+ BluetoothDevice.BOND_BONDING, BluetoothProfile.PRIORITY_UNDEFINED, true);
testOkToConnectCase(mTestDevice,
BluetoothDevice.BOND_BONDING, BluetoothProfile.PRIORITY_OFF, false);
testOkToConnectCase(mTestDevice,
- BluetoothDevice.BOND_BONDING, BluetoothProfile.PRIORITY_ON, false);
+ BluetoothDevice.BOND_BONDING, BluetoothProfile.PRIORITY_ON, true);
testOkToConnectCase(mTestDevice,
- BluetoothDevice.BOND_BONDING, BluetoothProfile.PRIORITY_AUTO_CONNECT, false);
+ BluetoothDevice.BOND_BONDING, BluetoothProfile.PRIORITY_AUTO_CONNECT, true);
testOkToConnectCase(mTestDevice,
BluetoothDevice.BOND_BONDING, badPriorityValue, false);
testOkToConnectCase(mTestDevice,
@@ -325,9 +343,6 @@
badBondState, BluetoothProfile.PRIORITY_AUTO_CONNECT, false);
testOkToConnectCase(mTestDevice,
badBondState, badPriorityValue, false);
- // Restore prirority to undefined for this test device
- Assert.assertTrue(mA2dpService.setPriority(
- mTestDevice, BluetoothProfile.PRIORITY_UNDEFINED));
}
@@ -337,7 +352,9 @@
@Test
public void testOutgoingConnectMissingAudioSinkUuid() {
// Update the device priority so okToConnect() returns true
- mA2dpService.setPriority(mTestDevice, BluetoothProfile.PRIORITY_ON);
+ when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
+ when(mDatabaseManager.getProfilePriority(mTestDevice, BluetoothProfile.A2DP))
+ .thenReturn(BluetoothProfile.PRIORITY_ON);
doReturn(true).when(mA2dpNativeInterface).connectA2dp(any(BluetoothDevice.class));
doReturn(true).when(mA2dpNativeInterface).disconnectA2dp(any(BluetoothDevice.class));
@@ -358,7 +375,9 @@
doReturn(true).when(mA2dpNativeInterface).disconnectA2dp(any(BluetoothDevice.class));
// Set the device priority to PRIORITY_OFF so connect() should fail
- mA2dpService.setPriority(mTestDevice, BluetoothProfile.PRIORITY_OFF);
+ when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
+ when(mDatabaseManager.getProfilePriority(mTestDevice, BluetoothProfile.A2DP))
+ .thenReturn(BluetoothProfile.PRIORITY_OFF);
// Send a connect request
Assert.assertFalse("Connect expected to fail", mA2dpService.connect(mTestDevice));
@@ -370,7 +389,9 @@
@Test
public void testOutgoingConnectTimeout() {
// Update the device priority so okToConnect() returns true
- mA2dpService.setPriority(mTestDevice, BluetoothProfile.PRIORITY_ON);
+ when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
+ when(mDatabaseManager.getProfilePriority(mTestDevice, BluetoothProfile.A2DP))
+ .thenReturn(BluetoothProfile.PRIORITY_ON);
doReturn(true).when(mA2dpNativeInterface).connectA2dp(any(BluetoothDevice.class));
doReturn(true).when(mA2dpNativeInterface).disconnectA2dp(any(BluetoothDevice.class));
@@ -399,7 +420,9 @@
A2dpStackEvent connCompletedEvent;
// Update the device priority so okToConnect() returns true
- mA2dpService.setPriority(mTestDevice, BluetoothProfile.PRIORITY_ON);
+ when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
+ when(mDatabaseManager.getProfilePriority(mTestDevice, BluetoothProfile.A2DP))
+ .thenReturn(BluetoothProfile.PRIORITY_ON);
doReturn(true).when(mA2dpNativeInterface).connectA2dp(any(BluetoothDevice.class));
doReturn(true).when(mA2dpNativeInterface).disconnectA2dp(any(BluetoothDevice.class));
@@ -468,7 +491,9 @@
for (int i = 0; i < MAX_CONNECTED_AUDIO_DEVICES; i++) {
BluetoothDevice testDevice = TestUtils.getTestDevice(mAdapter, i);
testDevices[i] = testDevice;
- mA2dpService.setPriority(testDevice, BluetoothProfile.PRIORITY_ON);
+ when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
+ when(mDatabaseManager.getProfilePriority(testDevice, BluetoothProfile.A2DP))
+ .thenReturn(BluetoothProfile.PRIORITY_ON);
// Send a connect request
Assert.assertTrue("Connect failed", mA2dpService.connect(testDevice));
// Verify the connection state broadcast, and that we are in Connecting state
@@ -494,7 +519,9 @@
// Prepare and connect the extra test device. The connect request should fail
extraTestDevice = TestUtils.getTestDevice(mAdapter, MAX_CONNECTED_AUDIO_DEVICES);
- mA2dpService.setPriority(extraTestDevice, BluetoothProfile.PRIORITY_ON);
+ when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
+ when(mDatabaseManager.getProfilePriority(extraTestDevice, BluetoothProfile.A2DP))
+ .thenReturn(BluetoothProfile.PRIORITY_ON);
// Send a connect request
Assert.assertFalse("Connect expected to fail", mA2dpService.connect(extraTestDevice));
}
@@ -508,7 +535,9 @@
A2dpStackEvent stackEvent;
// Update the device priority so okToConnect() returns true
- mA2dpService.setPriority(mTestDevice, BluetoothProfile.PRIORITY_ON);
+ when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
+ when(mDatabaseManager.getProfilePriority(mTestDevice, BluetoothProfile.A2DP))
+ .thenReturn(BluetoothProfile.PRIORITY_ON);
doReturn(true).when(mA2dpNativeInterface).connectA2dp(any(BluetoothDevice.class));
doReturn(true).when(mA2dpNativeInterface).disconnectA2dp(any(BluetoothDevice.class));
@@ -586,7 +615,9 @@
codecsSelectableCapabilities);
// Update the device priority so okToConnect() returns true
- mA2dpService.setPriority(mTestDevice, BluetoothProfile.PRIORITY_ON);
+ when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
+ when(mDatabaseManager.getProfilePriority(mTestDevice, BluetoothProfile.A2DP))
+ .thenReturn(BluetoothProfile.PRIORITY_ON);
doReturn(true).when(mA2dpNativeInterface).connectA2dp(any(BluetoothDevice.class));
doReturn(true).when(mA2dpNativeInterface).disconnectA2dp(any(BluetoothDevice.class));
@@ -649,7 +680,9 @@
A2dpStackEvent stackEvent;
// Update the device priority so okToConnect() returns true
- mA2dpService.setPriority(mTestDevice, BluetoothProfile.PRIORITY_ON);
+ when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
+ when(mDatabaseManager.getProfilePriority(mTestDevice, BluetoothProfile.A2DP))
+ .thenReturn(BluetoothProfile.PRIORITY_ON);
doReturn(true).when(mA2dpNativeInterface).connectA2dp(any(BluetoothDevice.class));
doReturn(true).when(mA2dpNativeInterface).disconnectA2dp(any(BluetoothDevice.class));
@@ -706,7 +739,9 @@
A2dpStackEvent stackEvent;
// Update the device priority so okToConnect() returns true
- mA2dpService.setPriority(mTestDevice, BluetoothProfile.PRIORITY_ON);
+ when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
+ when(mDatabaseManager.getProfilePriority(mTestDevice, BluetoothProfile.A2DP))
+ .thenReturn(BluetoothProfile.PRIORITY_ON);
doReturn(true).when(mA2dpNativeInterface).connectA2dp(any(BluetoothDevice.class));
doReturn(true).when(mA2dpNativeInterface).disconnectA2dp(any(BluetoothDevice.class));
@@ -744,15 +779,173 @@
Assert.assertFalse(mA2dpService.getDevices().contains(mTestDevice));
}
+ /**
+ * Test that whether active device been removed after enable silence mode
+ */
+ @Test
+ public void testSetSilenceMode() {
+ BluetoothDevice otherDevice = mAdapter.getRemoteDevice("05:04:03:02:01:00");
+ connectDevice(mTestDevice);
+ connectDevice(otherDevice);
+ doReturn(true).when(mA2dpNativeInterface).setActiveDevice(any(BluetoothDevice.class));
+ doReturn(true).when(mA2dpNativeInterface).setSilenceDevice(any(BluetoothDevice.class),
+ anyBoolean());
+
+ // Test whether active device been removed after enable silence mode.
+ Assert.assertTrue(mA2dpService.setActiveDevice(mTestDevice));
+ Assert.assertEquals(mTestDevice, mA2dpService.getActiveDevice());
+ Assert.assertTrue(mA2dpService.setSilenceMode(mTestDevice, true));
+ verify(mA2dpNativeInterface).setSilenceDevice(mTestDevice, true);
+ verify(mAvrcpTargetService).storeVolumeForDevice(mTestDevice);
+ Assert.assertNull(mA2dpService.getActiveDevice());
+
+ // Test whether active device been resumeed after disable silence mode.
+ Assert.assertTrue(mA2dpService.setSilenceMode(mTestDevice, false));
+ verify(mA2dpNativeInterface).setSilenceDevice(mTestDevice, false);
+ verify(mAvrcpTargetService).storeVolumeForDevice(mTestDevice);
+ Assert.assertEquals(mTestDevice, mA2dpService.getActiveDevice());
+
+ // Test that active device should not be changed when silence a non-active device
+ Assert.assertTrue(mA2dpService.setActiveDevice(mTestDevice));
+ Assert.assertEquals(mTestDevice, mA2dpService.getActiveDevice());
+ Assert.assertTrue(mA2dpService.setSilenceMode(otherDevice, true));
+ verify(mA2dpNativeInterface).setSilenceDevice(otherDevice, true);
+ verify(mAvrcpTargetService).storeVolumeForDevice(mTestDevice);
+ Assert.assertEquals(mTestDevice, mA2dpService.getActiveDevice());
+
+ // Test that active device should not be changed when another device exits silence mode
+ Assert.assertTrue(mA2dpService.setSilenceMode(otherDevice, false));
+ verify(mA2dpNativeInterface).setSilenceDevice(otherDevice, false);
+ verify(mAvrcpTargetService).storeVolumeForDevice(mTestDevice);
+ Assert.assertEquals(mTestDevice, mA2dpService.getActiveDevice());
+ }
+
+ /**
+ * Test that whether updateOptionalCodecsSupport() method is working as intended
+ * when a Bluetooth device is connected with A2DP.
+ */
+ @Test
+ public void testUpdateOptionalCodecsSupport() {
+ int verifySupportTime = 0;
+ int verifyNotSupportTime = 0;
+ int verifyEnabledTime = 0;
+ // Test for device supports optional codec
+ testUpdateOptionalCodecsSupportCase(
+ BluetoothA2dp.OPTIONAL_CODECS_SUPPORT_UNKNOWN, true,
+ BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN,
+ ++verifySupportTime, verifyNotSupportTime, ++verifyEnabledTime);
+ testUpdateOptionalCodecsSupportCase(
+ BluetoothA2dp.OPTIONAL_CODECS_SUPPORT_UNKNOWN, true,
+ BluetoothA2dp.OPTIONAL_CODECS_PREF_ENABLED,
+ ++verifySupportTime, verifyNotSupportTime, verifyEnabledTime);
+ testUpdateOptionalCodecsSupportCase(
+ BluetoothA2dp.OPTIONAL_CODECS_SUPPORT_UNKNOWN, true,
+ BluetoothA2dp.OPTIONAL_CODECS_PREF_DISABLED,
+ ++verifySupportTime, verifyNotSupportTime, verifyEnabledTime);
+ testUpdateOptionalCodecsSupportCase(
+ BluetoothA2dp.OPTIONAL_CODECS_SUPPORTED, true,
+ BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN,
+ verifySupportTime, verifyNotSupportTime, ++verifyEnabledTime);
+ testUpdateOptionalCodecsSupportCase(
+ BluetoothA2dp.OPTIONAL_CODECS_SUPPORTED, true,
+ BluetoothA2dp.OPTIONAL_CODECS_PREF_ENABLED,
+ verifySupportTime, verifyNotSupportTime, verifyEnabledTime);
+ testUpdateOptionalCodecsSupportCase(
+ BluetoothA2dp.OPTIONAL_CODECS_SUPPORTED, true,
+ BluetoothA2dp.OPTIONAL_CODECS_PREF_DISABLED,
+ verifySupportTime, verifyNotSupportTime, verifyEnabledTime);
+ testUpdateOptionalCodecsSupportCase(
+ BluetoothA2dp.OPTIONAL_CODECS_NOT_SUPPORTED, true,
+ BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN,
+ ++verifySupportTime, verifyNotSupportTime, ++verifyEnabledTime);
+ testUpdateOptionalCodecsSupportCase(
+ BluetoothA2dp.OPTIONAL_CODECS_NOT_SUPPORTED, true,
+ BluetoothA2dp.OPTIONAL_CODECS_PREF_ENABLED,
+ ++verifySupportTime, verifyNotSupportTime, verifyEnabledTime);
+ testUpdateOptionalCodecsSupportCase(
+ BluetoothA2dp.OPTIONAL_CODECS_NOT_SUPPORTED, true,
+ BluetoothA2dp.OPTIONAL_CODECS_PREF_DISABLED,
+ ++verifySupportTime, verifyNotSupportTime, verifyEnabledTime);
+
+ // Test for device not supports optional codec
+ testUpdateOptionalCodecsSupportCase(
+ BluetoothA2dp.OPTIONAL_CODECS_SUPPORT_UNKNOWN, false,
+ BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN,
+ verifySupportTime, ++verifyNotSupportTime, verifyEnabledTime);
+ testUpdateOptionalCodecsSupportCase(
+ BluetoothA2dp.OPTIONAL_CODECS_SUPPORT_UNKNOWN, false,
+ BluetoothA2dp.OPTIONAL_CODECS_PREF_ENABLED,
+ verifySupportTime, ++verifyNotSupportTime, verifyEnabledTime);
+ testUpdateOptionalCodecsSupportCase(
+ BluetoothA2dp.OPTIONAL_CODECS_SUPPORT_UNKNOWN, false,
+ BluetoothA2dp.OPTIONAL_CODECS_PREF_DISABLED,
+ verifySupportTime, ++verifyNotSupportTime, verifyEnabledTime);
+ testUpdateOptionalCodecsSupportCase(
+ BluetoothA2dp.OPTIONAL_CODECS_SUPPORTED, false,
+ BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN,
+ verifySupportTime, ++verifyNotSupportTime, verifyEnabledTime);
+ testUpdateOptionalCodecsSupportCase(
+ BluetoothA2dp.OPTIONAL_CODECS_SUPPORTED, false,
+ BluetoothA2dp.OPTIONAL_CODECS_PREF_ENABLED,
+ verifySupportTime, ++verifyNotSupportTime, verifyEnabledTime);
+ testUpdateOptionalCodecsSupportCase(
+ BluetoothA2dp.OPTIONAL_CODECS_SUPPORTED, false,
+ BluetoothA2dp.OPTIONAL_CODECS_PREF_DISABLED,
+ verifySupportTime, ++verifyNotSupportTime, verifyEnabledTime);
+ testUpdateOptionalCodecsSupportCase(
+ BluetoothA2dp.OPTIONAL_CODECS_NOT_SUPPORTED, false,
+ BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN,
+ verifySupportTime, verifyNotSupportTime, verifyEnabledTime);
+ testUpdateOptionalCodecsSupportCase(
+ BluetoothA2dp.OPTIONAL_CODECS_NOT_SUPPORTED, false,
+ BluetoothA2dp.OPTIONAL_CODECS_PREF_ENABLED,
+ verifySupportTime, verifyNotSupportTime, verifyEnabledTime);
+ testUpdateOptionalCodecsSupportCase(
+ BluetoothA2dp.OPTIONAL_CODECS_NOT_SUPPORTED, false,
+ BluetoothA2dp.OPTIONAL_CODECS_PREF_DISABLED,
+ verifySupportTime, verifyNotSupportTime, verifyEnabledTime);
+ }
+
+ /**
+ * Test that volume level of previous active device will be stored after set active device.
+ */
+ @Test
+ public void testStoreVolumeAfterSetActiveDevice() {
+ BluetoothDevice otherDevice = mAdapter.getRemoteDevice("05:04:03:02:01:00");
+ connectDevice(otherDevice);
+ connectDevice(mTestDevice);
+ doReturn(true).when(mA2dpNativeInterface).setActiveDevice(any(BluetoothDevice.class));
+ doReturn(true).when(mA2dpNativeInterface).setActiveDevice(null);
+ Assert.assertTrue(mA2dpService.setActiveDevice(mTestDevice));
+
+ // Test volume stored for previous active device an adjust for current active device
+ Assert.assertTrue(mA2dpService.setActiveDevice(otherDevice));
+ verify(mAvrcpTargetService).storeVolumeForDevice(mTestDevice);
+ verify(mAvrcpTargetService).getRememberedVolumeForDevice(otherDevice);
+
+ // Test volume store for previous active device when set active device to null
+ Assert.assertTrue(mA2dpService.setActiveDevice(null));
+ verify(mAvrcpTargetService).storeVolumeForDevice(otherDevice);
+ }
+
private void connectDevice(BluetoothDevice device) {
+ connectDeviceWithCodecStatus(device, null);
+ }
+
+ private void connectDeviceWithCodecStatus(BluetoothDevice device,
+ BluetoothCodecStatus codecStatus) {
A2dpStackEvent connCompletedEvent;
List<BluetoothDevice> prevConnectedDevices = mA2dpService.getConnectedDevices();
// Update the device priority so okToConnect() returns true
- mA2dpService.setPriority(device, BluetoothProfile.PRIORITY_ON);
+ when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
+ when(mDatabaseManager.getProfilePriority(device, BluetoothProfile.A2DP))
+ .thenReturn(BluetoothProfile.PRIORITY_ON);
doReturn(true).when(mA2dpNativeInterface).connectA2dp(device);
doReturn(true).when(mA2dpNativeInterface).disconnectA2dp(device);
+ doReturn(true).when(mA2dpNativeInterface).setCodecConfigPreference(
+ any(BluetoothDevice.class), any(BluetoothCodecConfig[].class));
// Send a connect request
Assert.assertTrue("Connect failed", mA2dpService.connect(device));
@@ -763,6 +956,10 @@
Assert.assertEquals(BluetoothProfile.STATE_CONNECTING,
mA2dpService.getConnectionState(device));
+ if (codecStatus != null) {
+ generateCodecMessageFromNative(device, codecStatus);
+ }
+
// Send a message to trigger connection completed
connCompletedEvent = new A2dpStackEvent(A2dpStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
connCompletedEvent.device = device;
@@ -862,7 +1059,9 @@
private void testOkToConnectCase(BluetoothDevice device, int bondState, int priority,
boolean expected) {
doReturn(bondState).when(mAdapterService).getBondState(device);
- Assert.assertTrue(mA2dpService.setPriority(device, priority));
+ when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
+ when(mDatabaseManager.getProfilePriority(device, BluetoothProfile.A2DP))
+ .thenReturn(priority);
// Test when the AdapterService is in non-quiet mode: the result should not depend
// on whether the connection request is outgoing or incoming.
@@ -876,4 +1075,85 @@
Assert.assertEquals(expected, mA2dpService.okToConnect(device, true)); // Outgoing
Assert.assertEquals(false, mA2dpService.okToConnect(device, false)); // Incoming
}
+
+ /**
+ * Helper function to test updateOptionalCodecsSupport() method
+ *
+ * @param previousSupport previous optional codec support status
+ * @param support new optional codec support status
+ * @param previousEnabled previous optional codec enable status
+ * @param verifySupportTime verify times of optional codec set to support
+ * @param verifyNotSupportTime verify times of optional codec set to not support
+ * @param verifyEnabledTime verify times of optional codec set to enabled
+ */
+ private void testUpdateOptionalCodecsSupportCase(int previousSupport, boolean support,
+ int previousEnabled, int verifySupportTime, int verifyNotSupportTime,
+ int verifyEnabledTime) {
+ when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
+ doReturn(true).when(mA2dpNativeInterface).setActiveDevice(any(BluetoothDevice.class));
+
+ BluetoothCodecConfig codecConfigSbc =
+ new BluetoothCodecConfig(
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+ BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
+ BluetoothCodecConfig.SAMPLE_RATE_44100,
+ BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+ BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+ 0, 0, 0, 0); // Codec-specific fields
+ BluetoothCodecConfig codecConfigAac =
+ new BluetoothCodecConfig(
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC,
+ BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
+ BluetoothCodecConfig.SAMPLE_RATE_44100,
+ BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+ BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+ 0, 0, 0, 0); // Codec-specific fields
+
+ BluetoothCodecConfig[] codecsLocalCapabilities;
+ BluetoothCodecConfig[] codecsSelectableCapabilities;
+ if (support) {
+ codecsLocalCapabilities = new BluetoothCodecConfig[2];
+ codecsSelectableCapabilities = new BluetoothCodecConfig[2];
+ codecsLocalCapabilities[0] = codecConfigSbc;
+ codecsLocalCapabilities[1] = codecConfigAac;
+ codecsSelectableCapabilities[0] = codecConfigSbc;
+ codecsSelectableCapabilities[1] = codecConfigAac;
+ } else {
+ codecsLocalCapabilities = new BluetoothCodecConfig[1];
+ codecsSelectableCapabilities = new BluetoothCodecConfig[1];
+ codecsLocalCapabilities[0] = codecConfigSbc;
+ codecsSelectableCapabilities[0] = codecConfigSbc;
+ }
+ BluetoothCodecConfig[] badCodecsSelectableCapabilities;
+ badCodecsSelectableCapabilities = new BluetoothCodecConfig[1];
+ badCodecsSelectableCapabilities[0] = codecConfigAac;
+
+ BluetoothCodecStatus codecStatus = new BluetoothCodecStatus(codecConfigSbc,
+ codecsLocalCapabilities, codecsSelectableCapabilities);
+ BluetoothCodecStatus badCodecStatus = new BluetoothCodecStatus(codecConfigAac,
+ codecsLocalCapabilities, badCodecsSelectableCapabilities);
+
+ when(mDatabaseManager.getA2dpSupportsOptionalCodecs(mTestDevice))
+ .thenReturn(previousSupport);
+ when(mDatabaseManager.getA2dpOptionalCodecsEnabled(mTestDevice))
+ .thenReturn(previousEnabled);
+
+ // Generate connection request from native with bad codec status
+ connectDeviceWithCodecStatus(mTestDevice, badCodecStatus);
+ generateConnectionMessageFromNative(mTestDevice, BluetoothProfile.STATE_DISCONNECTED,
+ BluetoothProfile.STATE_CONNECTED);
+
+ // Generate connection request from native with good codec status
+ connectDeviceWithCodecStatus(mTestDevice, codecStatus);
+ generateConnectionMessageFromNative(mTestDevice, BluetoothProfile.STATE_DISCONNECTED,
+ BluetoothProfile.STATE_CONNECTED);
+
+ // Check optional codec status is set properly
+ verify(mDatabaseManager, times(verifyNotSupportTime)).setA2dpSupportsOptionalCodecs(
+ mTestDevice, BluetoothA2dp.OPTIONAL_CODECS_NOT_SUPPORTED);
+ verify(mDatabaseManager, times(verifySupportTime)).setA2dpSupportsOptionalCodecs(
+ mTestDevice, BluetoothA2dp.OPTIONAL_CODECS_SUPPORTED);
+ verify(mDatabaseManager, times(verifyEnabledTime)).setA2dpOptionalCodecsEnabled(
+ mTestDevice, BluetoothA2dp.OPTIONAL_CODECS_PREF_ENABLED);
+ }
}
diff --git a/tests/unit/src/com/android/bluetooth/a2dp/A2dpStateMachineTest.java b/tests/unit/src/com/android/bluetooth/a2dp/A2dpStateMachineTest.java
index 0b825bf..cacd71a 100644
--- a/tests/unit/src/com/android/bluetooth/a2dp/A2dpStateMachineTest.java
+++ b/tests/unit/src/com/android/bluetooth/a2dp/A2dpStateMachineTest.java
@@ -20,14 +20,17 @@
import android.bluetooth.BluetoothA2dp;
import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothCodecConfig;
+import android.bluetooth.BluetoothCodecStatus;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
import android.content.Context;
import android.content.Intent;
import android.os.HandlerThread;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.MediumTest;
-import android.support.test.runner.AndroidJUnit4;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.MediumTest;
+import androidx.test.runner.AndroidJUnit4;
import com.android.bluetooth.R;
import com.android.bluetooth.TestUtils;
@@ -54,6 +57,9 @@
private BluetoothDevice mTestDevice;
private static final int TIMEOUT_MS = 1000; // 1s
+ private BluetoothCodecConfig mCodecConfigSbc;
+ private BluetoothCodecConfig mCodecConfigAac;
+
@Mock private AdapterService mAdapterService;
@Mock private A2dpService mA2dpService;
@Mock private A2dpNativeInterface mA2dpNativeInterface;
@@ -72,6 +78,22 @@
// Get a device for testing
mTestDevice = mAdapter.getRemoteDevice("00:01:02:03:04:05");
+ // Set up sample codec config
+ mCodecConfigSbc = new BluetoothCodecConfig(
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+ BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
+ BluetoothCodecConfig.SAMPLE_RATE_44100,
+ BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+ BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+ 0, 0, 0, 0); // Codec-specific fields
+ mCodecConfigAac = new BluetoothCodecConfig(
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC,
+ BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
+ BluetoothCodecConfig.SAMPLE_RATE_48000,
+ BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+ BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+ 0, 0, 0, 0); // Codec-specific fields
+
// Set up thread and looper
mHandlerThread = new HandlerThread("A2dpStateMachineTestHandlerThread");
mHandlerThread.start();
@@ -255,4 +277,87 @@
Assert.assertThat(mA2dpStateMachine.getCurrentState(),
IsInstanceOf.instanceOf(A2dpStateMachine.Disconnected.class));
}
+
+ /**
+ * Test that codec config change been reported to A2dpService properly.
+ */
+ @Test
+ public void testProcessCodecConfigEvent() {
+ testProcessCodecConfigEventCase(false);
+ }
+
+ /**
+ * Test that codec config change been reported to A2dpService properly when
+ * A2DP hardware offloading is enabled.
+ */
+ @Test
+ public void testProcessCodecConfigEvent_OffloadEnabled() {
+ testProcessCodecConfigEventCase(true);
+ }
+
+ /**
+ * Helper methold to test processCodecConfigEvent()
+ */
+ public void testProcessCodecConfigEventCase(boolean offloadEnabled) {
+ if (offloadEnabled) {
+ mA2dpStateMachine.mA2dpOffloadEnabled = true;
+ }
+
+ doNothing().when(mA2dpService).codecConfigUpdated(any(BluetoothDevice.class),
+ any(BluetoothCodecStatus.class), anyBoolean());
+ doNothing().when(mA2dpService).updateOptionalCodecsSupport(any(BluetoothDevice.class));
+ allowConnection(true);
+
+ BluetoothCodecConfig[] codecsSelectableSbc;
+ codecsSelectableSbc = new BluetoothCodecConfig[1];
+ codecsSelectableSbc[0] = mCodecConfigSbc;
+
+ BluetoothCodecConfig[] codecsSelectableSbcAac;
+ codecsSelectableSbcAac = new BluetoothCodecConfig[2];
+ codecsSelectableSbcAac[0] = mCodecConfigSbc;
+ codecsSelectableSbcAac[1] = mCodecConfigAac;
+
+ BluetoothCodecStatus codecStatusSbcAndSbc = new BluetoothCodecStatus(mCodecConfigSbc,
+ codecsSelectableSbcAac, codecsSelectableSbc);
+ BluetoothCodecStatus codecStatusSbcAndSbcAac = new BluetoothCodecStatus(mCodecConfigSbc,
+ codecsSelectableSbcAac, codecsSelectableSbcAac);
+ BluetoothCodecStatus codecStatusAacAndSbcAac = new BluetoothCodecStatus(mCodecConfigAac,
+ codecsSelectableSbcAac, codecsSelectableSbcAac);
+
+ // Set default codec status when device disconnected
+ // Selected codec = SBC, selectable codec = SBC
+ mA2dpStateMachine.processCodecConfigEvent(codecStatusSbcAndSbc);
+ verify(mA2dpService).codecConfigUpdated(mTestDevice, codecStatusSbcAndSbc, false);
+
+ // Inject an event to change state machine to connected state
+ A2dpStackEvent connStCh =
+ new A2dpStackEvent(A2dpStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
+ connStCh.device = mTestDevice;
+ connStCh.valueInt = A2dpStackEvent.CONNECTION_STATE_CONNECTED;
+ mA2dpStateMachine.sendMessage(A2dpStateMachine.STACK_EVENT, connStCh);
+
+ // Verify that the expected number of broadcasts are executed:
+ // - two calls to broadcastConnectionState(): Disconnected -> Conecting -> Connected
+ // - one call to broadcastAudioState() when entering Connected state
+ ArgumentCaptor<Intent> intentArgument2 = ArgumentCaptor.forClass(Intent.class);
+ verify(mA2dpService, timeout(TIMEOUT_MS).times(2)).sendBroadcast(intentArgument2.capture(),
+ anyString());
+
+ // Verify that state machine update optional codec when enter connected state
+ verify(mA2dpService, times(1)).updateOptionalCodecsSupport(mTestDevice);
+
+ // Change codec status when device connected.
+ // Selected codec = SBC, selectable codec = SBC+AAC
+ mA2dpStateMachine.processCodecConfigEvent(codecStatusSbcAndSbcAac);
+ if (!offloadEnabled) {
+ verify(mA2dpService).codecConfigUpdated(mTestDevice, codecStatusSbcAndSbcAac, true);
+ }
+ verify(mA2dpService, times(2)).updateOptionalCodecsSupport(mTestDevice);
+
+ // Update selected codec with selectable codec unchanged.
+ // Selected codec = AAC, selectable codec = SBC+AAC
+ mA2dpStateMachine.processCodecConfigEvent(codecStatusAacAndSbcAac);
+ verify(mA2dpService).codecConfigUpdated(mTestDevice, codecStatusAacAndSbcAac, false);
+ verify(mA2dpService, times(2)).updateOptionalCodecsSupport(mTestDevice);
+ }
}
diff --git a/tests/unit/src/com/android/bluetooth/a2dpsink/A2dpSinkServiceTest.java b/tests/unit/src/com/android/bluetooth/a2dpsink/A2dpSinkServiceTest.java
index 2735c11..d82d28b 100644
--- a/tests/unit/src/com/android/bluetooth/a2dpsink/A2dpSinkServiceTest.java
+++ b/tests/unit/src/com/android/bluetooth/a2dpsink/A2dpSinkServiceTest.java
@@ -15,16 +15,22 @@
*/
package com.android.bluetooth.a2dpsink;
+import static org.mockito.Mockito.*;
+
import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
import android.content.Context;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.MediumTest;
-import android.support.test.rule.ServiceTestRule;
-import android.support.test.runner.AndroidJUnit4;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.MediumTest;
+import androidx.test.rule.ServiceTestRule;
+import androidx.test.runner.AndroidJUnit4;
import com.android.bluetooth.R;
import com.android.bluetooth.TestUtils;
import com.android.bluetooth.btservice.AdapterService;
+import com.android.bluetooth.btservice.storage.DatabaseManager;
import org.junit.After;
import org.junit.Assert;
@@ -46,6 +52,7 @@
@Rule public final ServiceTestRule mServiceRule = new ServiceTestRule();
@Mock private AdapterService mAdapterService;
+ @Mock private DatabaseManager mDatabaseManager;
@Before
public void setUp() throws Exception {
@@ -60,6 +67,7 @@
// Try getting the Bluetooth adapter
mAdapter = BluetoothAdapter.getDefaultAdapter();
Assert.assertNotNull(mAdapter);
+ when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
}
@After
@@ -73,8 +81,43 @@
TestUtils.clearAdapterService(mAdapterService);
}
+ private BluetoothDevice makeBluetoothDevice(String address) {
+ return mAdapter.getRemoteDevice(address);
+ }
+
+ /**
+ * Mock the priority of a bluetooth device
+ *
+ * @param device - The bluetooth device you wish to mock the priority of
+ * @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))
+ .thenReturn(priority);
+ }
+
@Test
public void testInitialize() {
Assert.assertNotNull(A2dpSinkService.getA2dpSinkService());
}
+
+ /**
+ * Test that a PRIORITY_ON device is connected to
+ */
+ @Test
+ public void testConnect() {
+ BluetoothDevice device = makeBluetoothDevice("11:11:11:11:11:11");
+ mockDevicePriority(device, BluetoothProfile.PRIORITY_ON);
+ Assert.assertTrue(mService.connect(device));
+ }
+
+ /**
+ * Test that a PRIORITY_OFF device is not connected to
+ */
+ @Test
+ public void testConnectPriorityOffDevice() {
+ BluetoothDevice device = makeBluetoothDevice("11:11:11:11:11:11");
+ mockDevicePriority(device, BluetoothProfile.PRIORITY_OFF);
+ 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 eddab48..53e8419 100644
--- a/tests/unit/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandlerTest.java
+++ b/tests/unit/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandlerTest.java
@@ -24,9 +24,10 @@
import android.media.AudioManager;
import android.os.HandlerThread;
import android.os.Looper;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.MediumTest;
-import android.support.test.runner.AndroidJUnit4;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.MediumTest;
+import androidx.test.runner.AndroidJUnit4;
import com.android.bluetooth.R;
@@ -47,7 +48,7 @@
@Mock private Context mMockContext;
- @Mock private A2dpSinkStateMachine mMockA2dpSink;
+ @Mock private A2dpSinkService mMockA2dpSink;
@Mock private AudioManager mMockAudioManager;
@@ -189,7 +190,8 @@
mStreamHandler.obtainMessage(A2dpSinkStreamHandler.AUDIO_FOCUS_CHANGE,
AudioManager.AUDIOFOCUS_LOSS_TRANSIENT));
verify(mMockAudioManager, times(0)).abandonAudioFocus(any());
- verify(mMockA2dpSink, times(1)).informAudioFocusStateNative(0);
+ verify(mMockA2dpSink, times(0)).informAudioFocusStateNative(0);
+ verify(mMockA2dpSink, times(1)).informAudioTrackGainNative(0);
}
@Test
diff --git a/tests/unit/src/com/android/bluetooth/avrcp/AvrcpTest.java b/tests/unit/src/com/android/bluetooth/avrcp/AvrcpTest.java
index 5af45e1..871dde0 100644
--- a/tests/unit/src/com/android/bluetooth/avrcp/AvrcpTest.java
+++ b/tests/unit/src/com/android/bluetooth/avrcp/AvrcpTest.java
@@ -9,9 +9,10 @@
import android.content.pm.ServiceInfo;
import android.media.AudioManager;
import android.os.Looper;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.MediumTest;
-import android.support.test.runner.AndroidJUnit4;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.MediumTest;
+import androidx.test.runner.AndroidJUnit4;
import org.junit.Assert;
import org.junit.Test;
diff --git a/tests/unit/src/com/android/bluetooth/avrcp/EvictingQueueTest.java b/tests/unit/src/com/android/bluetooth/avrcp/EvictingQueueTest.java
index ff44f06..e60ea0f 100644
--- a/tests/unit/src/com/android/bluetooth/avrcp/EvictingQueueTest.java
+++ b/tests/unit/src/com/android/bluetooth/avrcp/EvictingQueueTest.java
@@ -1,7 +1,7 @@
package com.android.bluetooth.avrcp;
-import android.support.test.filters.MediumTest;
-import android.support.test.runner.AndroidJUnit4;
+import androidx.test.filters.MediumTest;
+import androidx.test.runner.AndroidJUnit4;
import org.junit.Assert;
import org.junit.Test;
diff --git a/tests/unit/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerServiceTest.java b/tests/unit/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerServiceTest.java
index 8bf4679..e9440f5 100644
--- a/tests/unit/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerServiceTest.java
+++ b/tests/unit/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerServiceTest.java
@@ -17,10 +17,11 @@
import android.bluetooth.BluetoothAdapter;
import android.content.Context;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.MediumTest;
-import android.support.test.rule.ServiceTestRule;
-import android.support.test.runner.AndroidJUnit4;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.MediumTest;
+import androidx.test.rule.ServiceTestRule;
+import androidx.test.runner.AndroidJUnit4;
import com.android.bluetooth.R;
import com.android.bluetooth.TestUtils;
diff --git a/tests/unit/src/com/android/bluetooth/btservice/ActiveDeviceManagerTest.java b/tests/unit/src/com/android/bluetooth/btservice/ActiveDeviceManagerTest.java
index 2d01bef..5580155 100644
--- a/tests/unit/src/com/android/bluetooth/btservice/ActiveDeviceManagerTest.java
+++ b/tests/unit/src/com/android/bluetooth/btservice/ActiveDeviceManagerTest.java
@@ -27,9 +27,10 @@
import android.content.Context;
import android.content.Intent;
import android.media.AudioManager;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.MediumTest;
-import android.support.test.runner.AndroidJUnit4;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.MediumTest;
+import androidx.test.runner.AndroidJUnit4;
import com.android.bluetooth.R;
import com.android.bluetooth.TestUtils;
@@ -216,7 +217,8 @@
@Test
public void hearingAidActive_clearA2dpAndHeadsetActive() {
Assume.assumeTrue("Ignore test when HearingAidService is not enabled",
- mContext.getResources().getBoolean(R.bool.profile_supported_hearing_aid));
+ mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_hearing_aid_profile_supported));
a2dpConnected(mA2dpHeadsetDevice);
headsetConnected(mA2dpHeadsetDevice);
@@ -234,7 +236,8 @@
@Test
public void hearingAidActive_dontSetA2dpAndHeadsetActive() {
Assume.assumeTrue("Ignore test when HearingAidService is not enabled",
- mContext.getResources().getBoolean(R.bool.profile_supported_hearing_aid));
+ mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_hearing_aid_profile_supported));
hearingAidActiveDeviceChanged(mHearingAidDevice);
a2dpConnected(mA2dpHeadsetDevice);
@@ -251,7 +254,8 @@
@Test
public void hearingAidActive_setA2dpActiveExplicitly() {
Assume.assumeTrue("Ignore test when HearingAidService is not enabled",
- mContext.getResources().getBoolean(R.bool.profile_supported_hearing_aid));
+ mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_hearing_aid_profile_supported));
hearingAidActiveDeviceChanged(mHearingAidDevice);
a2dpConnected(mA2dpHeadsetDevice);
@@ -271,7 +275,8 @@
@Test
public void hearingAidActive_setHeadsetActiveExplicitly() {
Assume.assumeTrue("Ignore test when HearingAidService is not enabled",
- mContext.getResources().getBoolean(R.bool.profile_supported_hearing_aid));
+ mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_hearing_aid_profile_supported));
hearingAidActiveDeviceChanged(mHearingAidDevice);
headsetConnected(mA2dpHeadsetDevice);
diff --git a/tests/unit/src/com/android/bluetooth/btservice/AdapterServiceTest.java b/tests/unit/src/com/android/bluetooth/btservice/AdapterServiceTest.java
index 2b19a20..82fa163 100644
--- a/tests/unit/src/com/android/bluetooth/btservice/AdapterServiceTest.java
+++ b/tests/unit/src/com/android/bluetooth/btservice/AdapterServiceTest.java
@@ -17,15 +17,11 @@
package com.android.bluetooth.btservice;
import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.timeout;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.*;
import android.app.AlarmManager;
import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
import android.bluetooth.IBluetoothCallback;
import android.content.Context;
import android.content.pm.ApplicationInfo;
@@ -38,24 +34,41 @@
import android.os.Process;
import android.os.SystemProperties;
import android.os.UserManager;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.MediumTest;
-import android.support.test.runner.AndroidJUnit4;
import android.test.mock.MockContentResolver;
+import android.util.ByteStringUtils;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.MediumTest;
+import androidx.test.runner.AndroidJUnit4;
import com.android.bluetooth.R;
+import com.android.bluetooth.TestUtils;
+import com.android.bluetooth.Utils;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.util.Arrays;
+import java.util.HashMap;
+
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
+
@MediumTest
@RunWith(AndroidJUnit4.class)
public class AdapterServiceTest {
+ private static final String TAG = AdapterServiceTest.class.getSimpleName();
+
private AdapterService mAdapterService;
private @Mock Context mMockContext;
@@ -73,10 +86,28 @@
private static final int CONTEXT_SWITCH_MS = 100;
private static final int ONE_SECOND_MS = 1000;
private static final int NATIVE_INIT_MS = 8000;
+ private static final int NATIVE_DISABLE_MS = 1000;
private PowerManager mPowerManager;
private PackageManager mMockPackageManager;
private MockContentResolver mMockContentResolver;
+ private HashMap<String, HashMap<String, String>> mAdapterConfig;
+
+ @BeforeClass
+ public static void setupClass() {
+ // Bring native layer up and down to make sure config files are properly loaded
+ if (Looper.myLooper() == null) {
+ Looper.prepare();
+ }
+ Assert.assertNotNull(Looper.myLooper());
+ AdapterService adapterService = new AdapterService();
+ adapterService.initNative(false /* is_restricted */, false /* is_single_user_mode */);
+ adapterService.cleanupNative();
+ HashMap<String, HashMap<String, String>> adapterConfig = TestUtils.readAdapterConfig();
+ Assert.assertNotNull(adapterConfig);
+ Assert.assertNotNull("metrics salt is null: " + adapterConfig.toString(),
+ getMetricsSalt(adapterConfig));
+ }
@Before
public void setUp() throws PackageManager.NameNotFoundException {
@@ -85,12 +116,8 @@
}
Assert.assertNotNull(Looper.myLooper());
- InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
- @Override
- public void run() {
- mAdapterService = new AdapterService();
- }
- });
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(
+ () -> mAdapterService = new AdapterService());
mMockPackageManager = mock(PackageManager.class);
mMockContentResolver = new MockContentResolver(mMockContext);
MockitoAnnotations.initMocks(this);
@@ -107,6 +134,10 @@
when(mMockContext.getSystemService(Context.POWER_SERVICE)).thenReturn(mPowerManager);
when(mMockContext.getSystemService(Context.ALARM_SERVICE)).thenReturn(mMockAlarmManager);
when(mMockContext.getSystemService(Context.AUDIO_SERVICE)).thenReturn(mAudioManager);
+ doAnswer(invocation -> {
+ Object[] args = invocation.getArguments();
+ return InstrumentationRegistry.getTargetContext().getDatabasePath((String) args[0]);
+ }).when(mMockContext).getDatabasePath(anyString());
when(mMockResources.getBoolean(R.bool.profile_supported_gatt)).thenReturn(true);
when(mMockResources.getBoolean(R.bool.profile_supported_pbap)).thenReturn(true);
@@ -128,6 +159,9 @@
mAdapterService.registerCallback(mIBluetoothCallback);
Config.init(mMockContext);
+
+ mAdapterConfig = TestUtils.readAdapterConfig();
+ Assert.assertNotNull(mAdapterConfig);
}
@After
@@ -183,6 +217,8 @@
verifyStateChange(BluetoothAdapter.STATE_TURNING_ON, BluetoothAdapter.STATE_ON,
invocationNumber + 1, CONTEXT_SWITCH_MS);
+ verify(mMockContext, timeout(CONTEXT_SWITCH_MS).times(2 * invocationNumber + 2))
+ .sendBroadcast(any(), eq(android.Manifest.permission.BLUETOOTH));
final int scanMode = mAdapterService.getScanMode();
Assert.assertTrue(scanMode == BluetoothAdapter.SCAN_MODE_CONNECTABLE
|| scanMode == BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE);
@@ -221,7 +257,7 @@
mAdapterService.onProfileServiceStateChanged(mMockGattService, BluetoothAdapter.STATE_OFF);
verifyStateChange(BluetoothAdapter.STATE_BLE_TURNING_OFF, BluetoothAdapter.STATE_OFF,
- invocationNumber + 1, CONTEXT_SWITCH_MS);
+ invocationNumber + 1, NATIVE_DISABLE_MS);
Assert.assertFalse(mAdapterService.isEnabled());
}
@@ -297,7 +333,7 @@
.times(2)).startService(any());
verifyStateChange(BluetoothAdapter.STATE_BLE_TURNING_OFF, BluetoothAdapter.STATE_OFF, 1,
- CONTEXT_SWITCH_MS);
+ NATIVE_DISABLE_MS);
Assert.assertFalse(mAdapterService.isEnabled());
}
@@ -333,7 +369,7 @@
verify(mMockContext, timeout(ONE_SECOND_MS).times(6)).startService(any());
verifyStateChange(BluetoothAdapter.STATE_BLE_TURNING_OFF, BluetoothAdapter.STATE_OFF, 1,
- AdapterState.BLE_STOP_TIMEOUT_DELAY + CONTEXT_SWITCH_MS);
+ AdapterState.BLE_STOP_TIMEOUT_DELAY + NATIVE_DISABLE_MS);
Assert.assertFalse(mAdapterService.isEnabled());
}
@@ -409,7 +445,7 @@
mAdapterService.onProfileServiceStateChanged(mMockGattService, BluetoothAdapter.STATE_OFF);
verifyStateChange(BluetoothAdapter.STATE_BLE_TURNING_OFF, BluetoothAdapter.STATE_OFF, 1,
- AdapterState.BLE_STOP_TIMEOUT_DELAY + CONTEXT_SWITCH_MS);
+ AdapterState.BLE_STOP_TIMEOUT_DELAY + NATIVE_DISABLE_MS);
Assert.assertFalse(mAdapterService.isEnabled());
}
@@ -421,17 +457,17 @@
@Test
public void testSnoopLoggingChange() {
String snoopSetting =
- SystemProperties.get(AdapterService.BLUETOOTH_BTSNOOP_ENABLE_PROPERTY, "");
- SystemProperties.set(AdapterService.BLUETOOTH_BTSNOOP_ENABLE_PROPERTY, "false");
+ SystemProperties.get(AdapterService.BLUETOOTH_BTSNOOP_LOG_MODE_PROPERTY, "");
+ SystemProperties.set(AdapterService.BLUETOOTH_BTSNOOP_LOG_MODE_PROPERTY, "false");
doEnable(0, false);
Assert.assertTrue(mAdapterService.isEnabled());
Assert.assertFalse(
- SystemProperties.getBoolean(AdapterService.BLUETOOTH_BTSNOOP_ENABLE_PROPERTY,
- true));
+ SystemProperties.get(AdapterService.BLUETOOTH_BTSNOOP_LOG_MODE_PROPERTY,
+ "true").equals("true"));
- SystemProperties.set(AdapterService.BLUETOOTH_BTSNOOP_ENABLE_PROPERTY, "true");
+ SystemProperties.set(AdapterService.BLUETOOTH_BTSNOOP_LOG_MODE_PROPERTY, "true");
mAdapterService.disable();
@@ -456,11 +492,225 @@
mAdapterService.onProfileServiceStateChanged(mMockGattService, BluetoothAdapter.STATE_OFF);
verifyStateChange(BluetoothAdapter.STATE_BLE_TURNING_OFF, BluetoothAdapter.STATE_OFF, 1,
- CONTEXT_SWITCH_MS);
+ NATIVE_DISABLE_MS);
Assert.assertFalse(mAdapterService.isEnabled());
// Restore earlier setting
- SystemProperties.set(AdapterService.BLUETOOTH_BTSNOOP_ENABLE_PROPERTY, snoopSetting);
+ SystemProperties.set(AdapterService.BLUETOOTH_BTSNOOP_LOG_MODE_PROPERTY, snoopSetting);
+ }
+
+
+ /**
+ * Test: Obfuscate a null Bluetooth
+ * Check if returned value from {@link AdapterService#obfuscateAddress(BluetoothDevice)} is
+ * an empty array when device address is null
+ */
+ @Test
+ public void testObfuscateBluetoothAddress_NullAddress() {
+ Assert.assertArrayEquals(mAdapterService.obfuscateAddress(null), new byte[0]);
+ }
+
+ /**
+ * Test: Obfuscate Bluetooth address when Bluetooth is disabled
+ * Check whether the returned value meets expectation
+ */
+ @Test
+ public void testObfuscateBluetoothAddress_BluetoothDisabled() {
+ Assert.assertFalse(mAdapterService.isEnabled());
+ byte[] metricsSalt = getMetricsSalt(mAdapterConfig);
+ Assert.assertNotNull(metricsSalt);
+ BluetoothDevice device = TestUtils.getTestDevice(BluetoothAdapter.getDefaultAdapter(), 0);
+ byte[] obfuscatedAddress = mAdapterService.obfuscateAddress(device);
+ Assert.assertTrue(obfuscatedAddress.length > 0);
+ Assert.assertFalse(isByteArrayAllZero(obfuscatedAddress));
+ Assert.assertArrayEquals(obfuscateInJava(metricsSalt, device), obfuscatedAddress);
+ }
+
+ /**
+ * Test: Obfuscate Bluetooth address when Bluetooth is enabled
+ * Check whether the returned value meets expectation
+ */
+ @Test
+ public void testObfuscateBluetoothAddress_BluetoothEnabled() {
+ Assert.assertFalse(mAdapterService.isEnabled());
+ doEnable(0, false);
+ Assert.assertTrue(mAdapterService.isEnabled());
+ byte[] metricsSalt = getMetricsSalt(mAdapterConfig);
+ Assert.assertNotNull(metricsSalt);
+ BluetoothDevice device = TestUtils.getTestDevice(BluetoothAdapter.getDefaultAdapter(), 0);
+ byte[] obfuscatedAddress = mAdapterService.obfuscateAddress(device);
+ Assert.assertTrue(obfuscatedAddress.length > 0);
+ Assert.assertFalse(isByteArrayAllZero(obfuscatedAddress));
+ Assert.assertArrayEquals(obfuscateInJava(metricsSalt, device), obfuscatedAddress);
+ }
+
+ /**
+ * Test: Check if obfuscated Bluetooth address stays the same after toggling Bluetooth
+ */
+ @Test
+ public void testObfuscateBluetoothAddress_PersistentBetweenToggle() {
+ Assert.assertFalse(mAdapterService.isEnabled());
+ byte[] metricsSalt = getMetricsSalt(mAdapterConfig);
+ Assert.assertNotNull(metricsSalt);
+ BluetoothDevice device = TestUtils.getTestDevice(BluetoothAdapter.getDefaultAdapter(), 0);
+ byte[] obfuscatedAddress1 = mAdapterService.obfuscateAddress(device);
+ Assert.assertTrue(obfuscatedAddress1.length > 0);
+ Assert.assertFalse(isByteArrayAllZero(obfuscatedAddress1));
+ Assert.assertArrayEquals(obfuscateInJava(metricsSalt, device),
+ obfuscatedAddress1);
+ // Enable
+ doEnable(0, false);
+ Assert.assertTrue(mAdapterService.isEnabled());
+ byte[] obfuscatedAddress3 = mAdapterService.obfuscateAddress(device);
+ Assert.assertTrue(obfuscatedAddress3.length > 0);
+ Assert.assertFalse(isByteArrayAllZero(obfuscatedAddress3));
+ Assert.assertArrayEquals(obfuscatedAddress3,
+ obfuscatedAddress1);
+ // Disable
+ doDisable(0, false);
+ Assert.assertFalse(mAdapterService.isEnabled());
+ byte[] obfuscatedAddress4 = mAdapterService.obfuscateAddress(device);
+ Assert.assertTrue(obfuscatedAddress4.length > 0);
+ Assert.assertFalse(isByteArrayAllZero(obfuscatedAddress4));
+ Assert.assertArrayEquals(obfuscatedAddress4,
+ obfuscatedAddress1);
+ }
+
+ /**
+ * Test: Check if obfuscated Bluetooth address stays the same after re-initializing
+ * {@link AdapterService}
+ */
+ @Test
+ public void testObfuscateBluetoothAddress_PersistentBetweenAdapterServiceInitialization() throws
+ PackageManager.NameNotFoundException {
+ byte[] metricsSalt = getMetricsSalt(mAdapterConfig);
+ Assert.assertNotNull(metricsSalt);
+ Assert.assertFalse(mAdapterService.isEnabled());
+ BluetoothDevice device = TestUtils.getTestDevice(BluetoothAdapter.getDefaultAdapter(), 0);
+ byte[] obfuscatedAddress1 = mAdapterService.obfuscateAddress(device);
+ Assert.assertTrue(obfuscatedAddress1.length > 0);
+ Assert.assertFalse(isByteArrayAllZero(obfuscatedAddress1));
+ Assert.assertArrayEquals(obfuscateInJava(metricsSalt, device),
+ obfuscatedAddress1);
+ tearDown();
+ setUp();
+ Assert.assertFalse(mAdapterService.isEnabled());
+ byte[] obfuscatedAddress2 = mAdapterService.obfuscateAddress(device);
+ Assert.assertTrue(obfuscatedAddress2.length > 0);
+ Assert.assertFalse(isByteArrayAllZero(obfuscatedAddress2));
+ Assert.assertArrayEquals(obfuscatedAddress2,
+ obfuscatedAddress1);
+ }
+
+ /**
+ * Test: Verify that obfuscated Bluetooth address changes after factory reset
+ *
+ * There are 4 types of factory reset that we are talking about:
+ * 1. Factory reset all user data from Settings -> Will restart phone
+ * 2. Factory reset WiFi and Bluetooth from Settings -> Will only restart WiFi and BT
+ * 3. Call BluetoothAdapter.factoryReset() -> Will disable Bluetooth and reset config in
+ * memory and disk
+ * 4. Call AdapterService.factoryReset() -> Will only reset config in memory
+ *
+ * We can only use No. 4 here
+ */
+ @Ignore("AdapterService.factoryReset() does not reload config into memory and hence old salt"
+ + " is still used until next time Bluetooth library is initialized. However Bluetooth"
+ + " cannot be used until Bluetooth process restart any way. Thus it is almost"
+ + " guaranteed that user has to re-enable Bluetooth and hence re-generate new salt"
+ + " after factory reset")
+ @Test
+ public void testObfuscateBluetoothAddress_FactoryReset() {
+ Assert.assertFalse(mAdapterService.isEnabled());
+ BluetoothDevice device = TestUtils.getTestDevice(BluetoothAdapter.getDefaultAdapter(), 0);
+ byte[] obfuscatedAddress1 = mAdapterService.obfuscateAddress(device);
+ Assert.assertTrue(obfuscatedAddress1.length > 0);
+ Assert.assertFalse(isByteArrayAllZero(obfuscatedAddress1));
+ mAdapterService.factoryReset();
+ byte[] obfuscatedAddress2 = mAdapterService.obfuscateAddress(device);
+ Assert.assertTrue(obfuscatedAddress2.length > 0);
+ Assert.assertFalse(isByteArrayAllZero(obfuscatedAddress2));
+ Assert.assertFalse(Arrays.equals(obfuscatedAddress2,
+ obfuscatedAddress1));
+ doEnable(0, false);
+ byte[] obfuscatedAddress3 = mAdapterService.obfuscateAddress(device);
+ Assert.assertTrue(obfuscatedAddress3.length > 0);
+ Assert.assertFalse(isByteArrayAllZero(obfuscatedAddress3));
+ Assert.assertArrayEquals(obfuscatedAddress3,
+ obfuscatedAddress2);
+ mAdapterService.factoryReset();
+ byte[] obfuscatedAddress4 = mAdapterService.obfuscateAddress(device);
+ Assert.assertTrue(obfuscatedAddress4.length > 0);
+ Assert.assertFalse(isByteArrayAllZero(obfuscatedAddress4));
+ Assert.assertFalse(Arrays.equals(obfuscatedAddress4,
+ obfuscatedAddress3));
+ }
+
+ /**
+ * Test: Verify that obfuscated Bluetooth address changes after factory reset and reloading
+ * native layer
+ */
+ @Test
+ public void testObfuscateBluetoothAddress_FactoryResetAndReloadNativeLayer() throws
+ PackageManager.NameNotFoundException {
+ byte[] metricsSalt1 = getMetricsSalt(mAdapterConfig);
+ Assert.assertNotNull(metricsSalt1);
+ Assert.assertFalse(mAdapterService.isEnabled());
+ 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();
+ tearDown();
+ setUp();
+ // Cannot verify metrics salt since it is not written to disk until native cleanup
+ byte[] obfuscatedAddress2 = mAdapterService.obfuscateAddress(device);
+ Assert.assertTrue(obfuscatedAddress2.length > 0);
+ Assert.assertFalse(isByteArrayAllZero(obfuscatedAddress2));
+ Assert.assertFalse(Arrays.equals(obfuscatedAddress2,
+ obfuscatedAddress1));
+ }
+
+ private static byte[] getMetricsSalt(HashMap<String, HashMap<String, String>> adapterConfig) {
+ HashMap<String, String> metricsSection = adapterConfig.get("Metrics");
+ if (metricsSection == null) {
+ Log.e(TAG, "Metrics section is null: " + adapterConfig.toString());
+ return null;
+ }
+ String saltString = metricsSection.get("Salt256Bit");
+ if (saltString == null) {
+ Log.e(TAG, "Salt256Bit is null: " + metricsSection.toString());
+ return null;
+ }
+ byte[] metricsSalt = ByteStringUtils.fromHexToByteArray(saltString);
+ if (metricsSalt.length != 32) {
+ Log.e(TAG, "Salt length is not 32 bit, but is " + metricsSalt.length);
+ return null;
+ }
+ return metricsSalt;
+ }
+
+ private static byte[] obfuscateInJava(byte[] key, BluetoothDevice device) {
+ String algorithm = "HmacSHA256";
+ try {
+ Mac hmac256 = Mac.getInstance(algorithm);
+ hmac256.init(new SecretKeySpec(key, algorithm));
+ return hmac256.doFinal(Utils.getByteAddress(device));
+ } catch (NoSuchAlgorithmException | IllegalStateException | InvalidKeyException exp) {
+ exp.printStackTrace();
+ return null;
+ }
+ }
+
+ private static boolean isByteArrayAllZero(byte[] byteArray) {
+ for (byte i : byteArray) {
+ if (i != 0) {
+ return false;
+ }
+ }
+ return true;
}
}
diff --git a/tests/unit/src/com/android/bluetooth/btservice/BondStateMachineTest.java b/tests/unit/src/com/android/bluetooth/btservice/BondStateMachineTest.java
deleted file mode 100644
index 29585ff..0000000
--- a/tests/unit/src/com/android/bluetooth/btservice/BondStateMachineTest.java
+++ /dev/null
@@ -1,315 +0,0 @@
-/*
- * Copyright 2018 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.btservice;
-
-import static org.mockito.Mockito.*;
-
-import android.bluetooth.BluetoothDevice;
-import android.content.Context;
-import android.content.Intent;
-import android.os.HandlerThread;
-import android.os.ParcelUuid;
-import android.os.UserHandle;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.MediumTest;
-import android.support.test.runner.AndroidJUnit4;
-
-import com.android.bluetooth.TestUtils;
-
-import org.junit.After;
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-@MediumTest
-@RunWith(AndroidJUnit4.class)
-public class BondStateMachineTest {
- private static final int TEST_BOND_REASON = 0;
- private static final byte[] TEST_BT_ADDR_BYTES = {00, 11, 22, 33, 44, 55};
- private static final ParcelUuid[] TEST_UUIDS =
- {ParcelUuid.fromString("0000111E-0000-1000-8000-00805F9B34FB")};
-
- private static final int BOND_NONE = BluetoothDevice.BOND_NONE;
- private static final int BOND_BONDING = BluetoothDevice.BOND_BONDING;
- private static final int BOND_BONDED = BluetoothDevice.BOND_BONDED;
-
- private AdapterProperties mAdapterProperties;
- private BluetoothDevice mDevice;
- private Context mTargetContext;
- private RemoteDevices mRemoteDevices;
- private BondStateMachine mBondStateMachine;
- private HandlerThread mHandlerThread;
- private RemoteDevices.DeviceProperties mDeviceProperties;
- private int mVerifyCount = 0;
-
- @Mock private AdapterService mAdapterService;
-
- @Before
- public void setUp() throws Exception {
- mTargetContext = InstrumentationRegistry.getTargetContext();
- MockitoAnnotations.initMocks(this);
- TestUtils.setAdapterService(mAdapterService);
- mHandlerThread = new HandlerThread("BondStateMachineTestHandlerThread");
- mHandlerThread.start();
-
- mRemoteDevices = new RemoteDevices(mAdapterService, mHandlerThread.getLooper());
- mRemoteDevices.reset();
- when(mAdapterService.getResources()).thenReturn(
- mTargetContext.getResources());
- mAdapterProperties = new AdapterProperties(mAdapterService);
- mAdapterProperties.init(mRemoteDevices);
- mBondStateMachine = BondStateMachine.make(mAdapterService, mAdapterProperties,
- mRemoteDevices);
- }
-
- @After
- public void tearDown() throws Exception {
- mHandlerThread.quit();
- TestUtils.clearAdapterService(mAdapterService);
- }
-
- @Test
- public void testSendIntent() {
- int badBondState = 42;
- mVerifyCount = 0;
-
- // Reset mRemoteDevices for the test.
- mRemoteDevices.reset();
- mDeviceProperties = mRemoteDevices.addDeviceProperties(TEST_BT_ADDR_BYTES);
- mDevice = mDeviceProperties.getDevice();
- Assert.assertNotNull(mDevice);
-
- /* Classic / Dualmode test cases*/
- // Uuid not available, mPendingBondedDevice is empty.
- testSendIntentNoPendingDevice(BOND_NONE, BOND_NONE, BOND_NONE,
- false, BOND_NONE, BOND_NONE);
- testSendIntentNoPendingDevice(BOND_NONE, BOND_BONDING, BOND_BONDING,
- true, BOND_NONE, BOND_BONDING);
- testSendIntentNoPendingDevice(BOND_NONE, BOND_BONDED, BOND_BONDED,
- true, BOND_NONE, BOND_BONDING);
- testSendIntentNoPendingDevice(BOND_NONE, badBondState, BOND_NONE,
- false, BOND_NONE, BOND_NONE);
- testSendIntentNoPendingDevice(BOND_BONDING, BOND_NONE, BOND_NONE,
- true, BOND_BONDING, BOND_NONE);
- testSendIntentNoPendingDevice(BOND_BONDING, BOND_BONDING, BOND_BONDING,
- false, BOND_NONE, BOND_NONE);
- testSendIntentNoPendingDevice(BOND_BONDING, BOND_BONDED, BOND_BONDED,
- false, BOND_NONE, BOND_NONE);
- testSendIntentNoPendingDevice(BOND_BONDING, badBondState, BOND_BONDING,
- false, BOND_NONE, BOND_NONE);
- testSendIntentNoPendingDevice(BOND_BONDED, BOND_NONE, BOND_NONE,
- true, BOND_BONDED, BOND_NONE);
- testSendIntentNoPendingDevice(BOND_BONDED, BOND_BONDING, BOND_BONDING,
- true, BOND_BONDED, BOND_BONDING);
- testSendIntentNoPendingDevice(BOND_BONDED, BOND_BONDED, BOND_BONDED,
- false, BOND_NONE, BOND_NONE);
- testSendIntentNoPendingDevice(BOND_BONDED, badBondState, BOND_BONDED,
- false, BOND_NONE, BOND_NONE);
-
- // Uuid not available, mPendingBondedDevice contains a remote device.
- testSendIntentPendingDevice(BOND_NONE, BOND_NONE, BOND_NONE,
- false, BOND_NONE, BOND_NONE);
- testSendIntentPendingDevice(BOND_NONE, BOND_BONDING, BOND_NONE,
- false, BOND_NONE, BOND_NONE);
- testSendIntentPendingDevice(BOND_NONE, BOND_BONDED, BOND_NONE,
- false, BOND_NONE, BOND_NONE);
- testSendIntentPendingDevice(BOND_NONE, badBondState, BOND_NONE,
- false, BOND_NONE, BOND_NONE);
- testSendIntentPendingDevice(BOND_BONDING, BOND_NONE, BOND_BONDING,
- false, BOND_NONE, BOND_NONE);
- testSendIntentPendingDevice(BOND_BONDING, BOND_BONDING, BOND_BONDING,
- false, BOND_NONE, BOND_NONE);
- testSendIntentPendingDevice(BOND_BONDING, BOND_BONDED, BOND_BONDING,
- false, BOND_NONE, BOND_NONE);
- testSendIntentPendingDevice(BOND_BONDING, badBondState, BOND_BONDING,
- false, BOND_NONE, BOND_NONE);
- testSendIntentPendingDevice(BOND_BONDED, BOND_NONE, BOND_NONE,
- true, BOND_BONDING, BOND_NONE);
- testSendIntentPendingDevice(BOND_BONDED, BOND_BONDING, BOND_BONDING,
- false, BOND_NONE, BOND_NONE);
- testSendIntentPendingDevice(BOND_BONDED, BOND_BONDED, BOND_BONDED,
- false, BOND_NONE, BOND_NONE);
- testSendIntentPendingDevice(BOND_BONDED, badBondState, BOND_BONDED,
- false, BOND_NONE, BOND_NONE);
-
- // Uuid available, mPendingBondedDevice is empty.
- testSendIntentNoPendingDeviceWithUuid(BOND_NONE, BOND_NONE, BOND_NONE,
- false, BOND_NONE, BOND_NONE);
- testSendIntentNoPendingDeviceWithUuid(BOND_NONE, BOND_BONDING, BOND_BONDING,
- true, BOND_NONE, BOND_BONDING);
- testSendIntentNoPendingDeviceWithUuid(BOND_NONE, BOND_BONDED, BOND_BONDED,
- true, BOND_NONE, BOND_BONDED);
- testSendIntentNoPendingDeviceWithUuid(BOND_NONE, badBondState, BOND_NONE,
- false, BOND_NONE, BOND_NONE);
- testSendIntentNoPendingDeviceWithUuid(BOND_BONDING, BOND_NONE, BOND_NONE,
- true, BOND_BONDING, BOND_NONE);
- testSendIntentNoPendingDeviceWithUuid(BOND_BONDING, BOND_BONDING, BOND_BONDING,
- false, BOND_NONE, BOND_NONE);
- testSendIntentNoPendingDeviceWithUuid(BOND_BONDING, BOND_BONDED, BOND_BONDED,
- true, BOND_BONDING, BOND_BONDED);
- testSendIntentNoPendingDeviceWithUuid(BOND_BONDING, badBondState, BOND_BONDING,
- false, BOND_NONE, BOND_NONE);
- testSendIntentNoPendingDeviceWithUuid(BOND_BONDED, BOND_NONE, BOND_NONE,
- true, BOND_BONDED, BOND_NONE);
- testSendIntentNoPendingDeviceWithUuid(BOND_BONDED, BOND_BONDING, BOND_BONDING,
- true, BOND_BONDED, BOND_BONDING);
- testSendIntentNoPendingDeviceWithUuid(BOND_BONDED, BOND_BONDED, BOND_BONDED,
- false, BOND_NONE, BOND_NONE);
- testSendIntentNoPendingDeviceWithUuid(BOND_BONDED, badBondState, BOND_BONDED,
- false, BOND_NONE, BOND_NONE);
-
- // Uuid available, mPendingBondedDevice contains a remote device.
- testSendIntentPendingDeviceWithUuid(BOND_NONE, BOND_NONE, BOND_NONE,
- false, BOND_NONE, BOND_NONE);
- testSendIntentPendingDeviceWithUuid(BOND_NONE, BOND_BONDING, BOND_NONE,
- false, BOND_NONE, BOND_NONE);
- testSendIntentPendingDeviceWithUuid(BOND_NONE, BOND_BONDED, BOND_NONE,
- false, BOND_NONE, BOND_NONE);
- testSendIntentPendingDeviceWithUuid(BOND_NONE, badBondState, BOND_NONE,
- false, BOND_NONE, BOND_NONE);
- testSendIntentPendingDeviceWithUuid(BOND_BONDING, BOND_NONE, BOND_BONDING,
- false, BOND_NONE, BOND_NONE);
- testSendIntentPendingDeviceWithUuid(BOND_BONDING, BOND_BONDING, BOND_BONDING,
- false, BOND_NONE, BOND_NONE);
- testSendIntentPendingDeviceWithUuid(BOND_BONDING, BOND_BONDED, BOND_BONDING,
- false, BOND_NONE, BOND_NONE);
- testSendIntentPendingDeviceWithUuid(BOND_BONDING, badBondState, BOND_BONDING,
- false, BOND_NONE, BOND_NONE);
- testSendIntentPendingDeviceWithUuid(BOND_BONDED, BOND_NONE, BOND_NONE,
- true, BOND_BONDING, BOND_NONE);
- testSendIntentPendingDeviceWithUuid(BOND_BONDED, BOND_BONDING, BOND_BONDING,
- false, BOND_NONE, BOND_NONE);
- testSendIntentPendingDeviceWithUuid(BOND_BONDED, BOND_BONDED, BOND_BONDED,
- true, BOND_BONDING, BOND_BONDED);
- testSendIntentPendingDeviceWithUuid(BOND_BONDED, badBondState, BOND_BONDED,
- false, BOND_NONE, BOND_NONE);
-
- /* Low energy test cases */
- testSendIntentBle(BOND_NONE, BOND_NONE, BOND_NONE);
- testSendIntentBle(BOND_NONE, BOND_BONDING, BOND_BONDING);
- testSendIntentBle(BOND_NONE, BOND_BONDED, BOND_BONDED);
- testSendIntentBle(BOND_BONDING, BOND_NONE, BOND_NONE);
- testSendIntentBle(BOND_BONDING, BOND_BONDING, BOND_BONDING);
- testSendIntentBle(BOND_BONDING, BOND_BONDED, BOND_BONDED);
- testSendIntentBle(BOND_BONDED, BOND_NONE, BOND_NONE);
- testSendIntentBle(BOND_BONDED, BOND_BONDING, BOND_BONDING);
- testSendIntentBle(BOND_BONDED, BOND_BONDED, BOND_BONDED);
- }
-
- private void testSendIntentCase(int oldState, int newState, int expectedNewState,
- boolean shouldBroadcast, int broadcastOldState, int broadcastNewState) {
- ArgumentCaptor<Intent> intentArgument = ArgumentCaptor.forClass(Intent.class);
-
- // Setup old state before start test.
- mDeviceProperties.mBondState = oldState;
-
- try {
- mBondStateMachine.sendIntent(mDevice, newState, TEST_BOND_REASON);
- } catch (IllegalArgumentException e) {
- // Do nothing.
- }
- Assert.assertEquals(expectedNewState, mDeviceProperties.getBondState());
-
- // Check for bond state Intent status.
- if (shouldBroadcast) {
- verify(mAdapterService, times(++mVerifyCount)).sendBroadcastAsUser(
- intentArgument.capture(), eq(UserHandle.ALL),
- eq(AdapterService.BLUETOOTH_PERM));
- verifyBondStateChangeIntent(broadcastOldState, broadcastNewState,
- intentArgument.getValue());
- } else {
- verify(mAdapterService, times(mVerifyCount)).sendBroadcastAsUser(any(Intent.class),
- any(UserHandle.class), anyString());
- }
- }
-
- private void testSendIntentNoPendingDeviceWithUuid(int oldState, int newState,
- int expectedNewState, boolean shouldBroadcast, int broadcastOldState,
- int broadcastNewState) {
- // Add dummy UUID for the device.
- mDeviceProperties.mUuids = TEST_UUIDS;
- testSendIntentNoPendingDevice(oldState, newState, expectedNewState, shouldBroadcast,
- broadcastOldState, broadcastNewState);
- }
-
- private void testSendIntentPendingDeviceWithUuid(int oldState, int newState,
- int expectedNewState, boolean shouldBroadcast, int broadcastOldState,
- int broadcastNewState) {
- // Add dummy UUID for the device.
- mDeviceProperties.mUuids = TEST_UUIDS;
- testSendIntentPendingDevice(oldState, newState, expectedNewState, shouldBroadcast,
- broadcastOldState, broadcastNewState);
- }
-
- private void testSendIntentPendingDevice(int oldState, int newState, int expectedNewState,
- boolean shouldBroadcast, int broadcastOldState, int broadcastNewState) {
- // Test for classic remote device.
- mDeviceProperties.mDeviceType = BluetoothDevice.DEVICE_TYPE_CLASSIC;
- mBondStateMachine.mPendingBondedDevices.clear();
- mBondStateMachine.mPendingBondedDevices.add(mDevice);
- testSendIntentCase(oldState, newState, expectedNewState, shouldBroadcast,
- broadcastOldState, broadcastNewState);
-
- // Test for dual-mode remote device.
- mDeviceProperties.mDeviceType = BluetoothDevice.DEVICE_TYPE_DUAL;
- mBondStateMachine.mPendingBondedDevices.clear();
- mBondStateMachine.mPendingBondedDevices.add(mDevice);
- testSendIntentCase(oldState, newState, expectedNewState, shouldBroadcast,
- broadcastOldState, broadcastNewState);
- }
-
- private void testSendIntentNoPendingDevice(int oldState, int newState, int expectedNewState,
- boolean shouldBroadcast, int broadcastOldState, int broadcastNewState) {
- // Test for classic remote device.
- mDeviceProperties.mDeviceType = BluetoothDevice.DEVICE_TYPE_CLASSIC;
- mBondStateMachine.mPendingBondedDevices.clear();
- testSendIntentCase(oldState, newState, expectedNewState, shouldBroadcast,
- broadcastOldState, broadcastNewState);
-
- // Test for dual-mode remote device.
- mDeviceProperties.mDeviceType = BluetoothDevice.DEVICE_TYPE_DUAL;
- mBondStateMachine.mPendingBondedDevices.clear();
- testSendIntentCase(oldState, newState, expectedNewState, shouldBroadcast,
- broadcastOldState, broadcastNewState);
- }
-
- private void testSendIntentBle(int oldState, int newState, int expectedNewState) {
- // Test for low energy remote device.
- mDeviceProperties.mDeviceType = BluetoothDevice.DEVICE_TYPE_LE;
- mBondStateMachine.mPendingBondedDevices.clear();
- testSendIntentCase(oldState, newState, newState, (oldState != newState),
- oldState, newState);
- }
-
- private void verifyBondStateChangeIntent(int oldState, int newState, Intent intent) {
- Assert.assertNotNull(intent);
- Assert.assertEquals(BluetoothDevice.ACTION_BOND_STATE_CHANGED, intent.getAction());
- Assert.assertEquals(mDevice, intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE));
- Assert.assertEquals(newState, intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, -1));
- Assert.assertEquals(oldState, intent.getIntExtra(BluetoothDevice.EXTRA_PREVIOUS_BOND_STATE,
- -1));
- if (newState == BOND_NONE) {
- Assert.assertEquals(TEST_BOND_REASON, intent.getIntExtra(BluetoothDevice.EXTRA_REASON,
- -1));
- } else {
- Assert.assertEquals(-1, intent.getIntExtra(BluetoothDevice.EXTRA_REASON, -1));
- }
- }
-}
diff --git a/tests/unit/src/com/android/bluetooth/btservice/MetricsLoggerTest.java b/tests/unit/src/com/android/bluetooth/btservice/MetricsLoggerTest.java
index b15c7b4..cc4e8b7 100644
--- a/tests/unit/src/com/android/bluetooth/btservice/MetricsLoggerTest.java
+++ b/tests/unit/src/com/android/bluetooth/btservice/MetricsLoggerTest.java
@@ -15,8 +15,8 @@
*/
package com.android.bluetooth.btservice;
-import android.support.test.filters.MediumTest;
-import android.support.test.runner.AndroidJUnit4;
+import androidx.test.filters.MediumTest;
+import androidx.test.runner.AndroidJUnit4;
import com.android.bluetooth.BluetoothMetricsProto.BluetoothLog;
import com.android.bluetooth.BluetoothMetricsProto.ProfileConnectionStats;
diff --git a/tests/unit/src/com/android/bluetooth/btservice/PhonePolicyTest.java b/tests/unit/src/com/android/bluetooth/btservice/PhonePolicyTest.java
index 510b9af..dafdc5a 100644
--- a/tests/unit/src/com/android/bluetooth/btservice/PhonePolicyTest.java
+++ b/tests/unit/src/com/android/bluetooth/btservice/PhonePolicyTest.java
@@ -24,12 +24,12 @@
import android.bluetooth.BluetoothHeadset;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.BluetoothUuid;
-import android.content.BroadcastReceiver;
import android.content.Intent;
import android.os.HandlerThread;
import android.os.ParcelUuid;
-import android.support.test.filters.MediumTest;
-import android.support.test.runner.AndroidJUnit4;
+
+import androidx.test.filters.MediumTest;
+import androidx.test.runner.AndroidJUnit4;
import com.android.bluetooth.TestUtils;
import com.android.bluetooth.a2dp.A2dpService;
@@ -234,21 +234,20 @@
intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
mPhonePolicy.getBroadcastReceiver().onReceive(null /* context */, intent);
- // This should not have any effect
+ // 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);
// Make the current active device fail to connect
- when(mHeadsetService.getConnectionState(bondedDevices[1])).thenReturn(
- BluetoothProfile.STATE_DISCONNECTED);
when(mA2dpService.getConnectionState(bondedDevices[1])).thenReturn(
BluetoothProfile.STATE_DISCONNECTED);
- intent = new Intent(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
- intent.putExtra(BluetoothDevice.EXTRA_DEVICE, bondedDevices[1]);
- intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, BluetoothProfile.STATE_CONNECTING);
- intent.putExtra(BluetoothProfile.EXTRA_STATE, BluetoothProfile.STATE_DISCONNECTED);
- intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
- mPhonePolicy.getBroadcastReceiver().onReceive(null /* context */, intent);
+ updateProfileConnectionStateHelper(bondedDevices[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(
@@ -259,8 +258,8 @@
}
/**
- * Test that we will try to re-connect to a profile on a device if an attempt failed previously.
- * This is to add robustness to the connection mechanism
+ * Test that we will try to re-connect to a profile on a device if other profile(s) are
+ * connected. This is to add robustness to the connection mechanism
*/
@Test
public void testReconnectOnPartialConnect() {
@@ -290,12 +289,8 @@
// We send a connection successful for one profile since the re-connect *only* works if we
// have already connected successfully over one of the profiles
- Intent intent = new Intent(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
- intent.putExtra(BluetoothDevice.EXTRA_DEVICE, bondedDevices[0]);
- intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, BluetoothProfile.STATE_DISCONNECTED);
- intent.putExtra(BluetoothProfile.EXTRA_STATE, BluetoothProfile.STATE_CONNECTED);
- intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
- mPhonePolicy.getBroadcastReceiver().onReceive(null /* context */, intent);
+ updateProfileConnectionStateHelper(bondedDevices[0], BluetoothProfile.HEADSET,
+ BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_DISCONNECTED);
// Check that we get a call to A2DP connect
verify(mA2dpService, timeout(CONNECT_OTHER_PROFILES_TIMEOUT_WAIT_MILLIS)).connect(
@@ -303,6 +298,81 @@
}
/**
+ * Test that we will try to re-connect to a profile on a device next time if a previous attempt
+ * failed partially. This will make sure the connection mechanism still works at next try while
+ * the previous attempt is some profiles connected on a device but some not.
+ */
+ @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);
+
+ // 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(mAdapterService.getState()).thenReturn(BluetoothAdapter.STATE_ON);
+
+ // 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 among a list
+ // of connected devices
+ ArrayList<BluetoothDevice> hsConnectedDevices = new ArrayList<>();
+ hsConnectedDevices.add(bondedDevices[0]);
+ when(mHeadsetService.getConnectedDevices()).thenReturn(hsConnectedDevices);
+ // Also the A2DP should say that its not connected for same device
+ when(mA2dpService.getConnectionState(bondedDevices[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,
+ 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]);
+
+ // We send a connection failure event for the attempted profile, and keep the connected
+ // profile connected.
+ updateProfileConnectionStateHelper(bondedDevices[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());
+
+ // Send a connection success event for one profile again without disconnecting all profiles
+ updateProfileConnectionStateHelper(bondedDevices[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]);
+
+ // Send a disconnection event for all connected profiles
+ hsConnectedDevices.remove(bondedDevices[0]);
+ updateProfileConnectionStateHelper(bondedDevices[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,
+ 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]);
+ }
+
+ /**
* Test that a second device will auto-connect if there is already one connected device.
*
* Even though we currently only set one device to be auto connect. The consumer of the auto
@@ -352,21 +422,13 @@
// We send a connection successful for one profile since the re-connect *only* works if we
// have already connected successfully over one of the profiles
- Intent intent = new Intent(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
- intent.putExtra(BluetoothDevice.EXTRA_DEVICE, a2dpNotConnectedDevice1);
- intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, BluetoothProfile.STATE_DISCONNECTED);
- intent.putExtra(BluetoothProfile.EXTRA_STATE, BluetoothProfile.STATE_CONNECTED);
- intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
- mPhonePolicy.getBroadcastReceiver().onReceive(null /* context */, intent);
+ updateProfileConnectionStateHelper(a2dpNotConnectedDevice1, BluetoothProfile.HEADSET,
+ BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_DISCONNECTED);
// We send a connection successful for one profile since the re-connect *only* works if we
// have already connected successfully over one of the profiles
- intent = new Intent(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
- intent.putExtra(BluetoothDevice.EXTRA_DEVICE, a2dpNotConnectedDevice2);
- intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, BluetoothProfile.STATE_DISCONNECTED);
- intent.putExtra(BluetoothProfile.EXTRA_STATE, BluetoothProfile.STATE_CONNECTED);
- intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
- mPhonePolicy.getBroadcastReceiver().onReceive(null /* context */, intent);
+ updateProfileConnectionStateHelper(a2dpNotConnectedDevice2, BluetoothProfile.HEADSET,
+ BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_DISCONNECTED);
// Check that we get a call to A2DP connect
verify(mA2dpService, timeout(CONNECT_OTHER_PROFILES_TIMEOUT_WAIT_MILLIS)).connect(
@@ -453,17 +515,12 @@
when(mA2dpService.getConnectionState(testDevices[3])).thenReturn(
BluetoothProfile.STATE_DISCONNECTED);
- // Get the broadcast receiver to inject events
- BroadcastReceiver injector = mPhonePolicy.getBroadcastReceiver();
// Generate connection state changed for HFP for testDevices[1] and trigger
// auto-connect for A2DP.
- Intent intent = new Intent(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
- intent.putExtra(BluetoothDevice.EXTRA_DEVICE, testDevices[1]);
- intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, BluetoothProfile.STATE_DISCONNECTED);
- intent.putExtra(BluetoothProfile.EXTRA_STATE, BluetoothProfile.STATE_CONNECTED);
- intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
- injector.onReceive(null /* context */, intent);
+ updateProfileConnectionStateHelper(testDevices[1], BluetoothProfile.HEADSET,
+ BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_DISCONNECTED);
+
// Check that we get a call to A2DP connect
verify(mA2dpService, timeout(CONNECT_OTHER_PROFILES_TIMEOUT_WAIT_MILLIS)).connect(
eq(testDevices[1]));
@@ -494,12 +551,9 @@
// Generate connection state changed for A2DP for testDevices[2] and trigger
// auto-connect for HFP.
- intent = new Intent(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
- intent.putExtra(BluetoothDevice.EXTRA_DEVICE, testDevices[2]);
- intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, BluetoothProfile.STATE_DISCONNECTED);
- intent.putExtra(BluetoothProfile.EXTRA_STATE, BluetoothProfile.STATE_CONNECTED);
- intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
- injector.onReceive(null /* context */, intent);
+ updateProfileConnectionStateHelper(testDevices[2], BluetoothProfile.A2DP,
+ BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_DISCONNECTED);
+
// Check that we get a call to HFP connect
verify(mHeadsetService, timeout(CONNECT_OTHER_PROFILES_TIMEOUT_WAIT_MILLIS)).connect(
eq(testDevices[2]));
@@ -665,17 +719,11 @@
BluetoothProfile.STATE_DISCONNECTED);
when(mA2dpService.getConnectionState(bondedDevices[1])).thenReturn(
BluetoothProfile.STATE_DISCONNECTED);
- when(mHeadsetService.getConnectionState(bondedDevices[1])).thenReturn(
- BluetoothProfile.STATE_CONNECTED);
// We send a connection successful for one profile since the re-connect *only* works if we
// have already connected successfully over one of the profiles
- Intent intent = new Intent(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
- intent.putExtra(BluetoothDevice.EXTRA_DEVICE, bondedDevices[1]);
- intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, BluetoothProfile.STATE_DISCONNECTED);
- intent.putExtra(BluetoothProfile.EXTRA_STATE, BluetoothProfile.STATE_CONNECTED);
- intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
- mPhonePolicy.getBroadcastReceiver().onReceive(null /* context */, intent);
+ updateProfileConnectionStateHelper(bondedDevices[1], BluetoothProfile.HEADSET,
+ BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_DISCONNECTED);
// Check that we don't get any calls to reconnect
verify(mA2dpService, timeout(CONNECT_OTHER_PROFILES_TIMEOUT_WAIT_MILLIS)).connect(
@@ -708,4 +756,27 @@
eq(BluetoothProfile.PRIORITY_ON));
verify(mA2dpService, never()).setPriority(eq(device), eq(BluetoothProfile.PRIORITY_ON));
}
+
+ private void updateProfileConnectionStateHelper(BluetoothDevice device, int profileId,
+ int nextState, int prevState) {
+ Intent intent;
+ switch (profileId) {
+ case BluetoothProfile.A2DP:
+ when(mA2dpService.getConnectionState(device)).thenReturn(nextState);
+ intent = new Intent(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
+ break;
+ case BluetoothProfile.HEADSET:
+ when(mHeadsetService.getConnectionState(device)).thenReturn(nextState);
+ intent = new Intent(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
+ break;
+ default:
+ intent = new Intent(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED);
+ break;
+ }
+ intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
+ intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState);
+ intent.putExtra(BluetoothProfile.EXTRA_STATE, nextState);
+ intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
+ mPhonePolicy.getBroadcastReceiver().onReceive(null /* context */, intent);
+ }
}
diff --git a/tests/unit/src/com/android/bluetooth/btservice/ProfileServiceTest.java b/tests/unit/src/com/android/bluetooth/btservice/ProfileServiceTest.java
index ca20fc3..6567398 100644
--- a/tests/unit/src/com/android/bluetooth/btservice/ProfileServiceTest.java
+++ b/tests/unit/src/com/android/bluetooth/btservice/ProfileServiceTest.java
@@ -22,10 +22,11 @@
import android.bluetooth.BluetoothAdapter;
import android.content.Intent;
import android.os.Looper;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.MediumTest;
-import android.support.test.rule.ServiceTestRule;
-import android.support.test.runner.AndroidJUnit4;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.MediumTest;
+import androidx.test.rule.ServiceTestRule;
+import androidx.test.runner.AndroidJUnit4;
import com.android.bluetooth.TestUtils;
@@ -95,7 +96,7 @@
mProfiles = Config.getSupportedProfiles();
- mMockAdapterService.initNative();
+ mMockAdapterService.initNative(false /* is_restricted */, false /* is_single_user_mode */);
TestUtils.setAdapterService(mMockAdapterService);
diff --git a/tests/unit/src/com/android/bluetooth/btservice/RemoteDevicesTest.java b/tests/unit/src/com/android/bluetooth/btservice/RemoteDevicesTest.java
index 35215ef..f1d9d16 100644
--- a/tests/unit/src/com/android/bluetooth/btservice/RemoteDevicesTest.java
+++ b/tests/unit/src/com/android/bluetooth/btservice/RemoteDevicesTest.java
@@ -11,9 +11,10 @@
import android.os.HandlerThread;
import android.os.Message;
import android.os.TestLooperManager;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.MediumTest;
-import android.support.test.runner.AndroidJUnit4;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.MediumTest;
+import androidx.test.runner.AndroidJUnit4;
import com.android.bluetooth.Utils;
import com.android.bluetooth.hfp.HeadsetHalConstants;
@@ -274,6 +275,7 @@
// Verify ACTION_ACL_DISCONNECTED and BATTERY_LEVEL_CHANGED intent are sent
verify(mAdapterService, times(3)).sendBroadcast(mIntentArgument.capture(),
mStringArgument.capture());
+ verify(mAdapterService, times(2)).obfuscateAddress(mDevice1);
verifyBatteryLevelChangedIntent(mDevice1, BluetoothDevice.BATTERY_LEVEL_UNKNOWN,
mIntentArgument.getAllValues().get(mIntentArgument.getAllValues().size() - 2));
Assert.assertEquals(AdapterService.BLUETOOTH_PERM,
@@ -336,7 +338,7 @@
BluetoothAssignedNumbers.PLANTRONICS, BluetoothHeadset.AT_CMD_TYPE_SET,
getXEventArray(3, 8), mDevice1));
verify(mAdapterService).sendBroadcast(mIntentArgument.capture(), mStringArgument.capture());
- verifyBatteryLevelChangedIntent(mDevice1, 37, mIntentArgument);
+ verifyBatteryLevelChangedIntent(mDevice1, 42, mIntentArgument);
Assert.assertEquals(AdapterService.BLUETOOTH_PERM, mStringArgument.getValue());
}
@@ -364,8 +366,11 @@
@Test
public void testGetBatteryLevelFromXEventVsc() {
- Assert.assertEquals(37, RemoteDevices.getBatteryLevelFromXEventVsc(getXEventArray(3, 8)));
- Assert.assertEquals(100, RemoteDevices.getBatteryLevelFromXEventVsc(getXEventArray(1, 1)));
+ Assert.assertEquals(42, RemoteDevices.getBatteryLevelFromXEventVsc(getXEventArray(3, 8)));
+ Assert.assertEquals(100,
+ RemoteDevices.getBatteryLevelFromXEventVsc(getXEventArray(10, 11)));
+ Assert.assertEquals(BluetoothDevice.BATTERY_LEVEL_UNKNOWN,
+ RemoteDevices.getBatteryLevelFromXEventVsc(getXEventArray(1, 1)));
Assert.assertEquals(BluetoothDevice.BATTERY_LEVEL_UNKNOWN,
RemoteDevices.getBatteryLevelFromXEventVsc(getXEventArray(3, 1)));
Assert.assertEquals(BluetoothDevice.BATTERY_LEVEL_UNKNOWN,
@@ -436,7 +441,8 @@
Assert.assertEquals(device, intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE));
Assert.assertEquals(batteryLevel,
intent.getIntExtra(BluetoothDevice.EXTRA_BATTERY_LEVEL, -15));
- Assert.assertEquals(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT, intent.getFlags());
+ Assert.assertEquals(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
+ | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND, intent.getFlags());
}
private static Intent getHeadsetConnectionStateChangedIntent(BluetoothDevice device,
diff --git a/tests/unit/src/com/android/bluetooth/btservice/SilenceDeviceManagerTest.java b/tests/unit/src/com/android/bluetooth/btservice/SilenceDeviceManagerTest.java
new file mode 100644
index 0000000..0f7864a
--- /dev/null
+++ b/tests/unit/src/com/android/bluetooth/btservice/SilenceDeviceManagerTest.java
@@ -0,0 +1,217 @@
+/*
+ * Copyright 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.btservice;
+
+import static org.mockito.Mockito.*;
+
+import android.bluetooth.BluetoothA2dp;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHeadset;
+import android.bluetooth.BluetoothProfile;
+import android.content.Context;
+import android.content.Intent;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.UserHandle;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.MediumTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.bluetooth.TestUtils;
+import com.android.bluetooth.a2dp.A2dpService;
+import com.android.bluetooth.hfp.HeadsetService;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class SilenceDeviceManagerTest {
+ private BluetoothAdapter mAdapter;
+ private Context mContext;
+ private BluetoothDevice mTestDevice;
+ private SilenceDeviceManager mSilenceDeviceManager;
+ private HandlerThread mHandlerThread;
+ private Looper mLooper;
+ private static final String TEST_BT_ADDR = "11:22:33:44:55:66";
+ private int mVerifyCount = 0;
+
+ @Mock private AdapterService mAdapterService;
+ @Mock private ServiceFactory mServiceFactory;
+ @Mock private A2dpService mA2dpService;
+ @Mock private HeadsetService mHeadsetService;
+
+
+ @Before
+ public void setUp() throws Exception {
+ mContext = InstrumentationRegistry.getTargetContext();
+
+ // Set up mocks and test assets
+ MockitoAnnotations.initMocks(this);
+ TestUtils.setAdapterService(mAdapterService);
+ when(mServiceFactory.getA2dpService()).thenReturn(mA2dpService);
+ when(mServiceFactory.getHeadsetService()).thenReturn(mHeadsetService);
+
+ // Get devices for testing
+ mTestDevice = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(TEST_BT_ADDR);
+
+ mHandlerThread = new HandlerThread("SilenceManagerTestHandlerThread");
+ mHandlerThread.start();
+ mLooper = mHandlerThread.getLooper();
+ mSilenceDeviceManager = new SilenceDeviceManager(mAdapterService, mServiceFactory,
+ mLooper);
+ mSilenceDeviceManager.start();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ mSilenceDeviceManager.cleanup();
+ mHandlerThread.quit();
+ TestUtils.clearAdapterService(mAdapterService);
+ }
+
+ @Test
+ public void testSetGetDeviceSilence() {
+ testSetGetDeviceSilenceConnectedCase(false, true);
+ testSetGetDeviceSilenceConnectedCase(false, false);
+ testSetGetDeviceSilenceConnectedCase(true, true);
+ testSetGetDeviceSilenceConnectedCase(true, false);
+
+ testSetGetDeviceSilenceDisconnectedCase(false);
+ testSetGetDeviceSilenceDisconnectedCase(true);
+ }
+
+ void testSetGetDeviceSilenceConnectedCase(boolean wasSilenced, boolean enableSilence) {
+ ArgumentCaptor<Intent> intentArgument = ArgumentCaptor.forClass(Intent.class);
+ doReturn(true).when(mA2dpService).setSilenceMode(mTestDevice, enableSilence);
+ doReturn(true).when(mHeadsetService).setSilenceMode(mTestDevice, enableSilence);
+
+ // Send A2DP/HFP connected intent
+ a2dpConnected(mTestDevice);
+ headsetConnected(mTestDevice);
+
+ // Set pre-state for mSilenceDeviceManager
+ if (wasSilenced) {
+ Assert.assertTrue(mSilenceDeviceManager.setSilenceMode(mTestDevice, true));
+ TestUtils.waitForLooperToFinishScheduledTask(mLooper);
+ verify(mAdapterService, times(++mVerifyCount)).sendBroadcastAsUser(
+ intentArgument.capture(), eq(UserHandle.ALL),
+ eq(AdapterService.BLUETOOTH_PERM));
+ }
+
+ // Set silence state and check whether state changed successfully
+ Assert.assertTrue(mSilenceDeviceManager.setSilenceMode(mTestDevice, enableSilence));
+ TestUtils.waitForLooperToFinishScheduledTask(mLooper);
+ Assert.assertEquals(enableSilence, mSilenceDeviceManager.getSilenceMode(mTestDevice));
+
+ // Check for silence state changed intent
+ if (wasSilenced != enableSilence) {
+ verify(mAdapterService, times(++mVerifyCount)).sendBroadcastAsUser(
+ intentArgument.capture(), eq(UserHandle.ALL),
+ eq(AdapterService.BLUETOOTH_PERM));
+ verifySilenceStateIntent(intentArgument.getValue());
+ }
+
+ // Remove test devices
+ a2dpDisconnected(mTestDevice);
+ headsetDisconnected(mTestDevice);
+
+ Assert.assertFalse(mSilenceDeviceManager.getSilenceMode(mTestDevice));
+ if (enableSilence) {
+ // If the silence mode is enabled, it should be automatically disabled
+ // after device is disconnected.
+ verify(mAdapterService, times(++mVerifyCount)).sendBroadcastAsUser(
+ intentArgument.capture(), eq(UserHandle.ALL),
+ eq(AdapterService.BLUETOOTH_PERM));
+ }
+ }
+
+ void testSetGetDeviceSilenceDisconnectedCase(boolean enableSilence) {
+ ArgumentCaptor<Intent> intentArgument = ArgumentCaptor.forClass(Intent.class);
+ // Set silence mode and it should stay disabled
+ Assert.assertTrue(mSilenceDeviceManager.setSilenceMode(mTestDevice, enableSilence));
+ TestUtils.waitForLooperToFinishScheduledTask(mLooper);
+ Assert.assertFalse(mSilenceDeviceManager.getSilenceMode(mTestDevice));
+
+ // Should be no intent been broadcasted
+ verify(mAdapterService, times(mVerifyCount)).sendBroadcastAsUser(
+ intentArgument.capture(), eq(UserHandle.ALL),
+ eq(AdapterService.BLUETOOTH_PERM));
+ }
+
+ void verifySilenceStateIntent(Intent intent) {
+ Assert.assertEquals(BluetoothDevice.ACTION_SILENCE_MODE_CHANGED, intent.getAction());
+ Assert.assertEquals(mTestDevice, intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE));
+ }
+
+ /**
+ * Helper to indicate A2dp connected for a device.
+ */
+ private void a2dpConnected(BluetoothDevice device) {
+ Intent intent = new Intent(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
+ intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
+ intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, BluetoothProfile.STATE_DISCONNECTED);
+ intent.putExtra(BluetoothProfile.EXTRA_STATE, BluetoothProfile.STATE_CONNECTED);
+ mSilenceDeviceManager.getBroadcastReceiver().onReceive(mContext, intent);
+ TestUtils.waitForLooperToFinishScheduledTask(mLooper);
+ }
+
+ /**
+ * Helper to indicate A2dp disconnected for a device.
+ */
+ private void a2dpDisconnected(BluetoothDevice device) {
+ Intent intent = new Intent(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
+ intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
+ intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, BluetoothProfile.STATE_CONNECTED);
+ intent.putExtra(BluetoothProfile.EXTRA_STATE, BluetoothProfile.STATE_DISCONNECTED);
+ mSilenceDeviceManager.getBroadcastReceiver().onReceive(mContext, intent);
+ TestUtils.waitForLooperToFinishScheduledTask(mLooper);
+ }
+
+ /**
+ * Helper to indicate Headset connected for a device.
+ */
+ private void headsetConnected(BluetoothDevice device) {
+ Intent intent = new Intent(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
+ intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
+ intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, BluetoothProfile.STATE_DISCONNECTED);
+ intent.putExtra(BluetoothProfile.EXTRA_STATE, BluetoothProfile.STATE_CONNECTED);
+ mSilenceDeviceManager.getBroadcastReceiver().onReceive(mContext, intent);
+ TestUtils.waitForLooperToFinishScheduledTask(mLooper);
+ }
+
+ /**
+ * Helper to indicate Headset disconnected for a device.
+ */
+ private void headsetDisconnected(BluetoothDevice device) {
+ Intent intent = new Intent(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
+ intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
+ intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, BluetoothProfile.STATE_CONNECTED);
+ intent.putExtra(BluetoothProfile.EXTRA_STATE, BluetoothProfile.STATE_DISCONNECTED);
+ mSilenceDeviceManager.getBroadcastReceiver().onReceive(mContext, intent);
+ TestUtils.waitForLooperToFinishScheduledTask(mLooper);
+ }
+}
+
diff --git a/tests/unit/src/com/android/bluetooth/btservice/storage/DatabaseManagerTest.java b/tests/unit/src/com/android/bluetooth/btservice/storage/DatabaseManagerTest.java
new file mode 100644
index 0000000..7f65c28
--- /dev/null
+++ b/tests/unit/src/com/android/bluetooth/btservice/storage/DatabaseManagerTest.java
@@ -0,0 +1,512 @@
+/*
+ * Copyright 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.btservice.storage;
+
+import static org.mockito.Mockito.*;
+
+import android.bluetooth.BluetoothA2dp;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
+
+import androidx.room.Room;
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.MediumTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.bluetooth.TestUtils;
+import com.android.bluetooth.btservice.AdapterService;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.List;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public final class DatabaseManagerTest {
+
+ @Mock private AdapterService mAdapterService;
+
+ private MetadataDatabase mDatabase;
+ private DatabaseManager mDatabaseManager;
+ private BluetoothDevice mTestDevice;
+ private BluetoothDevice mTestDevice2;
+
+ private static final String LOCAL_STORAGE = "LocalStorage";
+ private static final String TEST_BT_ADDR = "11:22:33:44:55:66";
+ 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 int A2DP_SUPPORT_OP_CODEC_TEST = 0;
+ private static final int A2DP_ENALBED_OP_CODEC_TEST = 1;
+ private static final int MAX_META_ID = 16;
+ private static final byte[] TEST_BYTE_ARRAY = "TEST_VALUE".getBytes();
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ TestUtils.setAdapterService(mAdapterService);
+
+ mTestDevice = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(TEST_BT_ADDR);
+
+ // Create a memory database for DatabaseManager instead of use a real database.
+ mDatabase = Room.inMemoryDatabaseBuilder(InstrumentationRegistry.getTargetContext(),
+ MetadataDatabase.class).build();
+
+ when(mAdapterService.getPackageManager()).thenReturn(
+ InstrumentationRegistry.getTargetContext().getPackageManager());
+ mDatabaseManager = new DatabaseManager(mAdapterService);
+
+ BluetoothDevice[] bondedDevices = {mTestDevice};
+ doReturn(bondedDevices).when(mAdapterService).getBondedDevices();
+ doNothing().when(mAdapterService).metadataChanged(
+ anyString(), anyInt(), any(byte[].class));
+
+ restartDatabaseManagerHelper();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ TestUtils.clearAdapterService(mAdapterService);
+ mDatabase.deleteAll();
+ mDatabaseManager.cleanup();
+ }
+
+ @Test
+ public void testMetadataDefault() {
+ Metadata data = new Metadata(TEST_BT_ADDR);
+ mDatabase.insert(data);
+ restartDatabaseManagerHelper();
+
+ for (int id = 0; id < BluetoothProfile.MAX_PROFILE_ID; id++) {
+ Assert.assertEquals(BluetoothProfile.PRIORITY_UNDEFINED,
+ mDatabaseManager.getProfilePriority(mTestDevice, id));
+ }
+
+ Assert.assertEquals(BluetoothA2dp.OPTIONAL_CODECS_SUPPORT_UNKNOWN,
+ mDatabaseManager.getA2dpSupportsOptionalCodecs(mTestDevice));
+
+ Assert.assertEquals(BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN,
+ mDatabaseManager.getA2dpOptionalCodecsEnabled(mTestDevice));
+
+ for (int id = 0; id < MAX_META_ID; id++) {
+ Assert.assertNull(mDatabaseManager.getCustomMeta(mTestDevice, id));
+ }
+ }
+
+ @Test
+ public void testSetGetProfilePriority() {
+ int badPriority = -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);
+
+ // 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);
+ }
+
+ @Test
+ public void testSetGetA2dpSupportsOptionalCodecs() {
+ int badValue = -100;
+
+ // Cases of device not in database
+ testSetGetA2dpOptionalCodecsCase(A2DP_SUPPORT_OP_CODEC_TEST, false,
+ BluetoothA2dp.OPTIONAL_CODECS_SUPPORT_UNKNOWN,
+ BluetoothA2dp.OPTIONAL_CODECS_SUPPORT_UNKNOWN);
+ testSetGetA2dpOptionalCodecsCase(A2DP_SUPPORT_OP_CODEC_TEST, false,
+ BluetoothA2dp.OPTIONAL_CODECS_NOT_SUPPORTED,
+ BluetoothA2dp.OPTIONAL_CODECS_SUPPORT_UNKNOWN);
+ testSetGetA2dpOptionalCodecsCase(A2DP_SUPPORT_OP_CODEC_TEST, false,
+ BluetoothA2dp.OPTIONAL_CODECS_SUPPORTED,
+ BluetoothA2dp.OPTIONAL_CODECS_SUPPORT_UNKNOWN);
+ testSetGetA2dpOptionalCodecsCase(A2DP_SUPPORT_OP_CODEC_TEST, false,
+ badValue, BluetoothA2dp.OPTIONAL_CODECS_SUPPORT_UNKNOWN);
+
+ // Cases of device already in database
+ testSetGetA2dpOptionalCodecsCase(A2DP_SUPPORT_OP_CODEC_TEST, true,
+ BluetoothA2dp.OPTIONAL_CODECS_SUPPORT_UNKNOWN,
+ BluetoothA2dp.OPTIONAL_CODECS_SUPPORT_UNKNOWN);
+ testSetGetA2dpOptionalCodecsCase(A2DP_SUPPORT_OP_CODEC_TEST, true,
+ BluetoothA2dp.OPTIONAL_CODECS_NOT_SUPPORTED,
+ BluetoothA2dp.OPTIONAL_CODECS_NOT_SUPPORTED);
+ testSetGetA2dpOptionalCodecsCase(A2DP_SUPPORT_OP_CODEC_TEST, true,
+ BluetoothA2dp.OPTIONAL_CODECS_SUPPORTED,
+ BluetoothA2dp.OPTIONAL_CODECS_SUPPORTED);
+ testSetGetA2dpOptionalCodecsCase(A2DP_SUPPORT_OP_CODEC_TEST, true,
+ badValue, BluetoothA2dp.OPTIONAL_CODECS_SUPPORT_UNKNOWN);
+ }
+
+ @Test
+ public void testSetGetA2dpOptionalCodecsEnabled() {
+ int badValue = -100;
+
+ // Cases of device not in database
+ testSetGetA2dpOptionalCodecsCase(A2DP_ENALBED_OP_CODEC_TEST, false,
+ BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN,
+ BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN);
+ testSetGetA2dpOptionalCodecsCase(A2DP_ENALBED_OP_CODEC_TEST, false,
+ BluetoothA2dp.OPTIONAL_CODECS_PREF_DISABLED,
+ BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN);
+ testSetGetA2dpOptionalCodecsCase(A2DP_ENALBED_OP_CODEC_TEST, false,
+ BluetoothA2dp.OPTIONAL_CODECS_PREF_ENABLED,
+ BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN);
+ testSetGetA2dpOptionalCodecsCase(A2DP_ENALBED_OP_CODEC_TEST, false,
+ badValue, BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN);
+
+ // Cases of device already in database
+ testSetGetA2dpOptionalCodecsCase(A2DP_ENALBED_OP_CODEC_TEST, true,
+ BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN,
+ BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN);
+ testSetGetA2dpOptionalCodecsCase(A2DP_ENALBED_OP_CODEC_TEST, true,
+ BluetoothA2dp.OPTIONAL_CODECS_PREF_DISABLED,
+ BluetoothA2dp.OPTIONAL_CODECS_PREF_DISABLED);
+ testSetGetA2dpOptionalCodecsCase(A2DP_ENALBED_OP_CODEC_TEST, true,
+ BluetoothA2dp.OPTIONAL_CODECS_PREF_ENABLED,
+ BluetoothA2dp.OPTIONAL_CODECS_PREF_ENABLED);
+ testSetGetA2dpOptionalCodecsCase(A2DP_ENALBED_OP_CODEC_TEST, true,
+ badValue, BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN);
+ }
+
+ @Test
+ public void testRemoveUnusedMetadata_WithSingleBondedDevice() {
+ // Insert two devices to database and cache, only mTestDevice is
+ // in the bonded list
+ Metadata otherData = new Metadata(OTHER_BT_ADDR1);
+ // Add metadata for otherDevice
+ otherData.setCustomizedMeta(0, TEST_BYTE_ARRAY);
+ mDatabaseManager.mMetadataCache.put(OTHER_BT_ADDR1, otherData);
+ mDatabase.insert(otherData);
+
+ Metadata data = new Metadata(TEST_BT_ADDR);
+ mDatabaseManager.mMetadataCache.put(TEST_BT_ADDR, data);
+ mDatabase.insert(data);
+
+ mDatabaseManager.removeUnusedMetadata();
+ // Wait for database update
+ TestUtils.waitForLooperToFinishScheduledTask(mDatabaseManager.getHandlerLooper());
+
+ // Check removed device report metadata changed to null
+ verify(mAdapterService).metadataChanged(OTHER_BT_ADDR1, 0, null);
+
+ List<Metadata> list = mDatabase.load();
+
+ // Check number of metadata in the database
+ Assert.assertEquals(1, list.size());
+
+ // Check whether the device is in database
+ Metadata checkData = list.get(0);
+ Assert.assertEquals(TEST_BT_ADDR, checkData.getAddress());
+
+ mDatabase.deleteAll();
+ // Wait for clear database
+ TestUtils.waitForLooperToFinishScheduledTask(mDatabaseManager.getHandlerLooper());
+ mDatabaseManager.mMetadataCache.clear();
+ }
+
+ @Test
+ public void testRemoveUnusedMetadata_WithMultiBondedDevices() {
+ // Insert three devices to database and cache, otherDevice1 and otherDevice2
+ // are in the bonded list
+
+ // Add metadata for TEST_BT_ADDR
+ Metadata testData = new Metadata(TEST_BT_ADDR);
+ testData.setCustomizedMeta(0, TEST_BYTE_ARRAY);
+ mDatabaseManager.mMetadataCache.put(TEST_BT_ADDR, testData);
+ mDatabase.insert(testData);
+
+ // Add metadata for OTHER_BT_ADDR1
+ Metadata otherData1 = new Metadata(OTHER_BT_ADDR1);
+ otherData1.setCustomizedMeta(0, TEST_BYTE_ARRAY);
+ mDatabaseManager.mMetadataCache.put(OTHER_BT_ADDR1, otherData1);
+ mDatabase.insert(otherData1);
+
+ // Add metadata for OTHER_BT_ADDR2
+ Metadata otherData2 = new Metadata(OTHER_BT_ADDR2);
+ otherData2.setCustomizedMeta(0, TEST_BYTE_ARRAY);
+ mDatabaseManager.mMetadataCache.put(OTHER_BT_ADDR2, otherData2);
+ mDatabase.insert(otherData2);
+
+ // Add OTHER_BT_ADDR1 OTHER_BT_ADDR2 to bonded devices
+ BluetoothDevice otherDevice1 = BluetoothAdapter.getDefaultAdapter()
+ .getRemoteDevice(OTHER_BT_ADDR1);
+ BluetoothDevice otherDevice2 = BluetoothAdapter.getDefaultAdapter()
+ .getRemoteDevice(OTHER_BT_ADDR2);
+ BluetoothDevice[] bondedDevices = {otherDevice1, otherDevice2};
+ doReturn(bondedDevices).when(mAdapterService).getBondedDevices();
+
+ mDatabaseManager.removeUnusedMetadata();
+ TestUtils.waitForLooperToFinishScheduledTask(mDatabaseManager.getHandlerLooper());
+
+ // Check TEST_BT_ADDR report metadata changed to null
+ verify(mAdapterService).metadataChanged(TEST_BT_ADDR, 0, null);
+
+ // Check number of metadata in the database
+ List<Metadata> list = mDatabase.load();
+ // OTHER_BT_ADDR1 and OTHER_BT_ADDR2 should still in database
+ Assert.assertEquals(2, list.size());
+
+ // Check whether the devices are in the database
+ Metadata checkData1 = list.get(0);
+ Assert.assertEquals(OTHER_BT_ADDR1, checkData1.getAddress());
+ Metadata checkData2 = list.get(1);
+ Assert.assertEquals(OTHER_BT_ADDR2, checkData2.getAddress());
+
+ mDatabase.deleteAll();
+ // Wait for clear database
+ TestUtils.waitForLooperToFinishScheduledTask(mDatabaseManager.getHandlerLooper());
+ mDatabaseManager.mMetadataCache.clear();
+
+ }
+
+ @Test
+ public void testSetGetCustomMeta() {
+ int badKey = 100;
+ byte[] value = "input value".getBytes();
+
+ // Device is not in database
+ testSetGetCustomMetaCase(false, BluetoothDevice.METADATA_MANUFACTURER_NAME,
+ value, true);
+ testSetGetCustomMetaCase(false, BluetoothDevice.METADATA_MODEL_NAME,
+ value, true);
+ testSetGetCustomMetaCase(false, BluetoothDevice.METADATA_SOFTWARE_VERSION,
+ value, true);
+ testSetGetCustomMetaCase(false, BluetoothDevice.METADATA_HARDWARE_VERSION,
+ value, true);
+ testSetGetCustomMetaCase(false, BluetoothDevice.METADATA_COMPANION_APP,
+ value, true);
+ testSetGetCustomMetaCase(false, BluetoothDevice.METADATA_MAIN_ICON,
+ value, true);
+ testSetGetCustomMetaCase(false, BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET,
+ value, true);
+ testSetGetCustomMetaCase(false, BluetoothDevice.METADATA_UNTETHERED_LEFT_ICON,
+ value, true);
+ testSetGetCustomMetaCase(false, BluetoothDevice.METADATA_UNTETHERED_RIGHT_ICON,
+ value, true);
+ testSetGetCustomMetaCase(false, BluetoothDevice.METADATA_UNTETHERED_CASE_ICON,
+ value, true);
+ testSetGetCustomMetaCase(false, BluetoothDevice.METADATA_UNTETHERED_LEFT_BATTERY,
+ value, true);
+ testSetGetCustomMetaCase(false, BluetoothDevice.METADATA_UNTETHERED_RIGHT_BATTERY,
+ value, true);
+ testSetGetCustomMetaCase(false, BluetoothDevice.METADATA_UNTETHERED_CASE_BATTERY,
+ value, true);
+ testSetGetCustomMetaCase(false, BluetoothDevice.METADATA_UNTETHERED_LEFT_CHARGING,
+ value, true);
+ testSetGetCustomMetaCase(false, BluetoothDevice.METADATA_UNTETHERED_RIGHT_CHARGING,
+ value, true);
+ testSetGetCustomMetaCase(false, BluetoothDevice.METADATA_UNTETHERED_CASE_CHARGING,
+ value, true);
+ testSetGetCustomMetaCase(false, BluetoothDevice.METADATA_ENHANCED_SETTINGS_UI_URI,
+ value, true);
+ testSetGetCustomMetaCase(false, badKey, value, false);
+
+ // Device is in database
+ testSetGetCustomMetaCase(true, BluetoothDevice.METADATA_MANUFACTURER_NAME,
+ value, true);
+ testSetGetCustomMetaCase(true, BluetoothDevice.METADATA_MODEL_NAME,
+ value, true);
+ testSetGetCustomMetaCase(true, BluetoothDevice.METADATA_SOFTWARE_VERSION,
+ value, true);
+ testSetGetCustomMetaCase(true, BluetoothDevice.METADATA_HARDWARE_VERSION,
+ value, true);
+ testSetGetCustomMetaCase(true, BluetoothDevice.METADATA_COMPANION_APP,
+ value, true);
+ testSetGetCustomMetaCase(true, BluetoothDevice.METADATA_MAIN_ICON,
+ value, true);
+ testSetGetCustomMetaCase(true, BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET,
+ value, true);
+ testSetGetCustomMetaCase(true, BluetoothDevice.METADATA_UNTETHERED_LEFT_ICON,
+ value, true);
+ testSetGetCustomMetaCase(true, BluetoothDevice.METADATA_UNTETHERED_RIGHT_ICON,
+ value, true);
+ testSetGetCustomMetaCase(true, BluetoothDevice.METADATA_UNTETHERED_CASE_ICON,
+ value, true);
+ testSetGetCustomMetaCase(true, BluetoothDevice.METADATA_UNTETHERED_LEFT_BATTERY,
+ value, true);
+ testSetGetCustomMetaCase(true, BluetoothDevice.METADATA_UNTETHERED_RIGHT_BATTERY,
+ value, true);
+ testSetGetCustomMetaCase(true, BluetoothDevice.METADATA_UNTETHERED_CASE_BATTERY,
+ value, true);
+ testSetGetCustomMetaCase(true, BluetoothDevice.METADATA_UNTETHERED_LEFT_CHARGING,
+ value, true);
+ testSetGetCustomMetaCase(true, BluetoothDevice.METADATA_UNTETHERED_RIGHT_CHARGING,
+ value, true);
+ testSetGetCustomMetaCase(true, BluetoothDevice.METADATA_UNTETHERED_CASE_CHARGING,
+ value, true);
+ testSetGetCustomMetaCase(true, BluetoothDevice.METADATA_ENHANCED_SETTINGS_UI_URI,
+ value, true);
+ }
+
+ void restartDatabaseManagerHelper() {
+ Metadata data = new Metadata(LOCAL_STORAGE);
+ data.migrated = true;
+ mDatabase.insert(data);
+
+ mDatabaseManager.cleanup();
+ mDatabaseManager.start(mDatabase);
+ // Wait for handler thread finish its task.
+ TestUtils.waitForLooperToFinishScheduledTask(mDatabaseManager.getHandlerLooper());
+
+ // Remove local storage
+ mDatabaseManager.mMetadataCache.remove(LOCAL_STORAGE);
+ mDatabase.delete(LOCAL_STORAGE);
+ }
+
+ void testSetGetProfilePriorityCase(boolean stored, int priority, int expectedPriority,
+ 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));
+ // Wait for database update
+ TestUtils.waitForLooperToFinishScheduledTask(mDatabaseManager.getHandlerLooper());
+
+ List<Metadata> list = mDatabase.load();
+
+ // Check number of metadata in the database
+ if (!stored) {
+ if (priority != BluetoothProfile.PRIORITY_OFF
+ && priority != BluetoothProfile.PRIORITY_ON
+ && priority != BluetoothProfile.PRIORITY_AUTO_CONNECT) {
+ // Database won't be updated
+ Assert.assertEquals(0, list.size());
+ return;
+ }
+ }
+ Assert.assertEquals(1, list.size());
+
+ // Check whether the device is in database
+ restartDatabaseManagerHelper();
+ Assert.assertEquals(expectedPriority,
+ mDatabaseManager.getProfilePriority(mTestDevice, BluetoothProfile.HEADSET));
+
+ mDatabase.deleteAll();
+ // Wait for clear database
+ TestUtils.waitForLooperToFinishScheduledTask(mDatabaseManager.getHandlerLooper());
+ mDatabaseManager.mMetadataCache.clear();
+ }
+
+ void testSetGetA2dpOptionalCodecsCase(int test, boolean stored, int value, int expectedValue) {
+ if (stored) {
+ Metadata data = new Metadata(TEST_BT_ADDR);
+ mDatabaseManager.mMetadataCache.put(TEST_BT_ADDR, data);
+ mDatabase.insert(data);
+ }
+ if (test == A2DP_SUPPORT_OP_CODEC_TEST) {
+ mDatabaseManager.setA2dpSupportsOptionalCodecs(mTestDevice, value);
+ Assert.assertEquals(expectedValue,
+ mDatabaseManager.getA2dpSupportsOptionalCodecs(mTestDevice));
+ } else {
+ mDatabaseManager.setA2dpOptionalCodecsEnabled(mTestDevice, value);
+ Assert.assertEquals(expectedValue,
+ mDatabaseManager.getA2dpOptionalCodecsEnabled(mTestDevice));
+ }
+ // Wait for database update
+ TestUtils.waitForLooperToFinishScheduledTask(mDatabaseManager.getHandlerLooper());
+
+ List<Metadata> list = mDatabase.load();
+
+ // Check number of metadata in the database
+ if (!stored) {
+ // Database won't be updated
+ Assert.assertEquals(0, list.size());
+ return;
+ }
+ Assert.assertEquals(1, list.size());
+
+ // Check whether the device is in database
+ restartDatabaseManagerHelper();
+ if (test == A2DP_SUPPORT_OP_CODEC_TEST) {
+ Assert.assertEquals(expectedValue,
+ mDatabaseManager.getA2dpSupportsOptionalCodecs(mTestDevice));
+ } else {
+ Assert.assertEquals(expectedValue,
+ mDatabaseManager.getA2dpOptionalCodecsEnabled(mTestDevice));
+ }
+
+ mDatabase.deleteAll();
+ // Wait for clear database
+ TestUtils.waitForLooperToFinishScheduledTask(mDatabaseManager.getHandlerLooper());
+ mDatabaseManager.mMetadataCache.clear();
+ }
+
+ void testSetGetCustomMetaCase(boolean stored, int key, byte[] value, boolean expectedResult) {
+ byte[] testValue = "test value".getBytes();
+ int verifyTime = 1;
+ if (stored) {
+ Metadata data = new Metadata(TEST_BT_ADDR);
+ mDatabaseManager.mMetadataCache.put(TEST_BT_ADDR, data);
+ mDatabase.insert(data);
+ Assert.assertEquals(expectedResult,
+ mDatabaseManager.setCustomMeta(mTestDevice, key, testValue));
+ verify(mAdapterService).metadataChanged(TEST_BT_ADDR, key, testValue);
+ verifyTime++;
+ }
+ Assert.assertEquals(expectedResult,
+ mDatabaseManager.setCustomMeta(mTestDevice, key, value));
+ if (expectedResult) {
+ // Check for callback and get value
+ verify(mAdapterService, times(verifyTime)).metadataChanged(TEST_BT_ADDR, key, value);
+ Assert.assertEquals(value,
+ mDatabaseManager.getCustomMeta(mTestDevice, key));
+ } else {
+ Assert.assertNull(mDatabaseManager.getCustomMeta(mTestDevice, key));
+ return;
+ }
+ // Wait for database update
+ TestUtils.waitForLooperToFinishScheduledTask(mDatabaseManager.getHandlerLooper());
+
+ // Check whether the value is saved in database
+ restartDatabaseManagerHelper();
+ Assert.assertArrayEquals(value,
+ mDatabaseManager.getCustomMeta(mTestDevice, key));
+
+ mDatabase.deleteAll();
+ // Wait for clear database
+ TestUtils.waitForLooperToFinishScheduledTask(mDatabaseManager.getHandlerLooper());
+ mDatabaseManager.mMetadataCache.clear();
+ }
+}
diff --git a/tests/unit/src/com/android/bluetooth/gatt/GattServiceTest.java b/tests/unit/src/com/android/bluetooth/gatt/GattServiceTest.java
index 63b6d4d..6882b50 100644
--- a/tests/unit/src/com/android/bluetooth/gatt/GattServiceTest.java
+++ b/tests/unit/src/com/android/bluetooth/gatt/GattServiceTest.java
@@ -3,10 +3,11 @@
import static org.mockito.Mockito.*;
import android.content.Context;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.SmallTest;
-import android.support.test.rule.ServiceTestRule;
-import android.support.test.runner.AndroidJUnit4;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.rule.ServiceTestRule;
+import androidx.test.runner.AndroidJUnit4;
import com.android.bluetooth.R;
import com.android.bluetooth.TestUtils;
diff --git a/tests/unit/src/com/android/bluetooth/hdp/HealthServiceTest.java b/tests/unit/src/com/android/bluetooth/hdp/HealthServiceTest.java
deleted file mode 100644
index 04023fc..0000000
--- a/tests/unit/src/com/android/bluetooth/hdp/HealthServiceTest.java
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * Copyright 2018 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.hdp;
-
-import android.bluetooth.BluetoothAdapter;
-import android.content.Context;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.MediumTest;
-import android.support.test.rule.ServiceTestRule;
-import android.support.test.runner.AndroidJUnit4;
-
-import com.android.bluetooth.R;
-import com.android.bluetooth.TestUtils;
-import com.android.bluetooth.btservice.AdapterService;
-
-import org.junit.After;
-import org.junit.Assert;
-import org.junit.Assume;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-@MediumTest
-@RunWith(AndroidJUnit4.class)
-public class HealthServiceTest {
- private HealthService mService = null;
- private BluetoothAdapter mAdapter = null;
- private Context mTargetContext;
-
- @Rule public final ServiceTestRule mServiceRule = new ServiceTestRule();
-
- @Mock private AdapterService mAdapterService;
-
- @Before
- public void setUp() throws Exception {
- mTargetContext = InstrumentationRegistry.getTargetContext();
- Assume.assumeTrue("Ignore test when HealthService is not enabled",
- mTargetContext.getResources().getBoolean(R.bool.profile_supported_hdp));
- MockitoAnnotations.initMocks(this);
- TestUtils.setAdapterService(mAdapterService);
- TestUtils.startService(mServiceRule, HealthService.class);
- mService = HealthService.getHealthService();
- Assert.assertNotNull(mService);
- // Try getting the Bluetooth adapter
- mAdapter = BluetoothAdapter.getDefaultAdapter();
- Assert.assertNotNull(mAdapter);
- }
-
- @After
- public void tearDown() throws Exception {
- if (!mTargetContext.getResources().getBoolean(R.bool.profile_supported_hdp)) {
- return;
- }
- TestUtils.stopService(mServiceRule, HealthService.class);
- mService = HealthService.getHealthService();
- Assert.assertNull(mService);
- TestUtils.clearAdapterService(mAdapterService);
- }
-
- @Test
- public void testInitialize() {
- Assert.assertNotNull(HealthService.getHealthService());
- }
-
- @Test
- public void testRegisterAppConfiguration() {
- // Test registering a null config
- Assert.assertEquals(false, mService.registerAppConfiguration(null, null));
- }
-}
diff --git a/tests/unit/src/com/android/bluetooth/hearingaid/HearingAidServiceTest.java b/tests/unit/src/com/android/bluetooth/hearingaid/HearingAidServiceTest.java
index d4a978f..8efe285 100644
--- a/tests/unit/src/com/android/bluetooth/hearingaid/HearingAidServiceTest.java
+++ b/tests/unit/src/com/android/bluetooth/hearingaid/HearingAidServiceTest.java
@@ -30,14 +30,16 @@
import android.media.AudioManager;
import android.os.Looper;
import android.os.ParcelUuid;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.MediumTest;
-import android.support.test.rule.ServiceTestRule;
-import android.support.test.runner.AndroidJUnit4;
-import com.android.bluetooth.R;
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.MediumTest;
+import androidx.test.rule.ServiceTestRule;
+import androidx.test.runner.AndroidJUnit4;
+
import com.android.bluetooth.TestUtils;
import com.android.bluetooth.btservice.AdapterService;
+import com.android.bluetooth.btservice.storage.DatabaseManager;
+import com.android.internal.R;
import org.junit.After;
import org.junit.Assert;
@@ -69,6 +71,7 @@
private BroadcastReceiver mHearingAidIntentReceiver;
@Mock private AdapterService mAdapterService;
+ @Mock private DatabaseManager mDatabaseManager;
@Mock private HearingAidNativeInterface mNativeInterface;
@Mock private AudioManager mAudioManager;
@@ -78,7 +81,8 @@
public void setUp() throws Exception {
mTargetContext = InstrumentationRegistry.getTargetContext();
Assume.assumeTrue("Ignore test when HearingAidService is not enabled",
- mTargetContext.getResources().getBoolean(R.bool.profile_supported_hearing_aid));
+ mTargetContext.getResources().getBoolean(
+ R.bool.config_hearing_aid_profile_supported));
// Set up mocks and test assets
MockitoAnnotations.initMocks(this);
@@ -111,20 +115,16 @@
mDeviceQueueMap.put(mLeftDevice, new LinkedBlockingQueue<>());
mDeviceQueueMap.put(mRightDevice, new LinkedBlockingQueue<>());
mDeviceQueueMap.put(mSingleDevice, new LinkedBlockingQueue<>());
- mService.setPriority(mLeftDevice, BluetoothProfile.PRIORITY_UNDEFINED);
- mService.setPriority(mRightDevice, BluetoothProfile.PRIORITY_UNDEFINED);
- mService.setPriority(mSingleDevice, BluetoothProfile.PRIORITY_UNDEFINED);
doReturn(BluetoothDevice.BOND_BONDED).when(mAdapterService)
.getBondState(any(BluetoothDevice.class));
doReturn(new ParcelUuid[]{BluetoothUuid.HearingAid}).when(mAdapterService)
.getRemoteUuids(any(BluetoothDevice.class));
- HearingAidService.sConnectTimeoutForEachSideMs = 1000;
- HearingAidService.sCheckWhitelistTimeoutMs = 2000;
}
@After
public void tearDown() throws Exception {
- if (!mTargetContext.getResources().getBoolean(R.bool.profile_supported_hearing_aid)) {
+ if (!mTargetContext.getResources().getBoolean(
+ R.bool.config_hearing_aid_profile_supported)) {
return;
}
stopService();
@@ -216,22 +216,30 @@
*/
@Test
public void testGetSetPriority() {
+ when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
+ when(mDatabaseManager.getProfilePriority(mLeftDevice, BluetoothProfile.HEARING_AID))
+ .thenReturn(BluetoothProfile.PRIORITY_UNDEFINED);
Assert.assertEquals("Initial device priority",
BluetoothProfile.PRIORITY_UNDEFINED,
mService.getPriority(mLeftDevice));
- Assert.assertTrue(mService.setPriority(mLeftDevice, BluetoothProfile.PRIORITY_OFF));
+ when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
+ when(mDatabaseManager.getProfilePriority(mLeftDevice, BluetoothProfile.HEARING_AID))
+ .thenReturn(BluetoothProfile.PRIORITY_OFF);
Assert.assertEquals("Setting device priority to PRIORITY_OFF",
BluetoothProfile.PRIORITY_OFF,
mService.getPriority(mLeftDevice));
- Assert.assertTrue(mService.setPriority(mLeftDevice, BluetoothProfile.PRIORITY_ON));
+ when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
+ when(mDatabaseManager.getProfilePriority(mLeftDevice, BluetoothProfile.HEARING_AID))
+ .thenReturn(BluetoothProfile.PRIORITY_ON);
Assert.assertEquals("Setting device priority to PRIORITY_ON",
BluetoothProfile.PRIORITY_ON,
mService.getPriority(mLeftDevice));
- Assert.assertTrue(mService.setPriority(mLeftDevice,
- BluetoothProfile.PRIORITY_AUTO_CONNECT));
+ 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));
@@ -255,13 +263,13 @@
testOkToConnectCase(mSingleDevice,
BluetoothDevice.BOND_NONE, badPriorityValue, false);
testOkToConnectCase(mSingleDevice,
- BluetoothDevice.BOND_BONDING, BluetoothProfile.PRIORITY_UNDEFINED, false);
+ BluetoothDevice.BOND_BONDING, BluetoothProfile.PRIORITY_UNDEFINED, true);
testOkToConnectCase(mSingleDevice,
BluetoothDevice.BOND_BONDING, BluetoothProfile.PRIORITY_OFF, false);
testOkToConnectCase(mSingleDevice,
- BluetoothDevice.BOND_BONDING, BluetoothProfile.PRIORITY_ON, false);
+ BluetoothDevice.BOND_BONDING, BluetoothProfile.PRIORITY_ON, true);
testOkToConnectCase(mSingleDevice,
- BluetoothDevice.BOND_BONDING, BluetoothProfile.PRIORITY_AUTO_CONNECT, false);
+ BluetoothDevice.BOND_BONDING, BluetoothProfile.PRIORITY_AUTO_CONNECT, true);
testOkToConnectCase(mSingleDevice,
BluetoothDevice.BOND_BONDING, badPriorityValue, false);
testOkToConnectCase(mSingleDevice,
@@ -284,8 +292,6 @@
badBondState, BluetoothProfile.PRIORITY_AUTO_CONNECT, false);
testOkToConnectCase(mSingleDevice,
badBondState, badPriorityValue, false);
- // Restore prirority to undefined for this test device
- Assert.assertTrue(mService.setPriority(mSingleDevice, BluetoothProfile.PRIORITY_UNDEFINED));
}
/**
@@ -294,9 +300,13 @@
@Test
public void testOutgoingConnectMissingHearingAidUuid() {
// Update the device priority so okToConnect() returns true
- mService.setPriority(mLeftDevice, BluetoothProfile.PRIORITY_ON);
- mService.setPriority(mRightDevice, BluetoothProfile.PRIORITY_OFF);
- mService.setPriority(mSingleDevice, BluetoothProfile.PRIORITY_OFF);
+ 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);
doReturn(true).when(mNativeInterface).connectHearingAid(any(BluetoothDevice.class));
doReturn(true).when(mNativeInterface).disconnectHearingAid(any(BluetoothDevice.class));
@@ -317,7 +327,9 @@
doReturn(true).when(mNativeInterface).disconnectHearingAid(any(BluetoothDevice.class));
// Set the device priority to PRIORITY_OFF so connect() should fail
- mService.setPriority(mLeftDevice, BluetoothProfile.PRIORITY_OFF);
+ when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
+ when(mDatabaseManager.getProfilePriority(mLeftDevice, BluetoothProfile.HEARING_AID))
+ .thenReturn(BluetoothProfile.PRIORITY_OFF);
// Send a connect request
Assert.assertFalse("Connect expected to fail", mService.connect(mLeftDevice));
@@ -329,9 +341,13 @@
@Test
public void testOutgoingConnectTimeout() {
// Update the device priority so okToConnect() returns true
- mService.setPriority(mLeftDevice, BluetoothProfile.PRIORITY_ON);
- mService.setPriority(mRightDevice, BluetoothProfile.PRIORITY_OFF);
- mService.setPriority(mSingleDevice, BluetoothProfile.PRIORITY_OFF);
+ 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);
doReturn(true).when(mNativeInterface).connectHearingAid(any(BluetoothDevice.class));
doReturn(true).when(mNativeInterface).disconnectHearingAid(any(BluetoothDevice.class));
@@ -345,8 +361,7 @@
mService.getConnectionState(mLeftDevice));
// Verify the connection state broadcast, and that we are in Disconnected state
- verifyConnectionStateIntent(HearingAidService.sConnectTimeoutForEachSideMs * 3,
- mLeftDevice,
+ verifyConnectionStateIntent(HearingAidStateMachine.sConnectTimeoutMs * 2, mLeftDevice,
BluetoothProfile.STATE_DISCONNECTED,
BluetoothProfile.STATE_CONNECTING);
Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED,
@@ -361,9 +376,13 @@
// Update hiSyncId map
getHiSyncIdFromNative();
// Update the device priority so okToConnect() returns true
- mService.setPriority(mLeftDevice, BluetoothProfile.PRIORITY_ON);
- mService.setPriority(mRightDevice, BluetoothProfile.PRIORITY_ON);
- mService.setPriority(mSingleDevice, BluetoothProfile.PRIORITY_OFF);
+ 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);
doReturn(true).when(mNativeInterface).connectHearingAid(any(BluetoothDevice.class));
doReturn(true).when(mNativeInterface).disconnectHearingAid(any(BluetoothDevice.class));
@@ -389,9 +408,13 @@
// Update hiSyncId map
getHiSyncIdFromNative();
// Update the device priority so okToConnect() returns true
- mService.setPriority(mLeftDevice, BluetoothProfile.PRIORITY_ON);
- mService.setPriority(mRightDevice, BluetoothProfile.PRIORITY_ON);
- mService.setPriority(mSingleDevice, BluetoothProfile.PRIORITY_ON);
+ 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);
doReturn(true).when(mNativeInterface).connectHearingAid(any(BluetoothDevice.class));
doReturn(true).when(mNativeInterface).disconnectHearingAid(any(BluetoothDevice.class));
@@ -449,9 +472,13 @@
// Update hiSyncId map
getHiSyncIdFromNative();
// Update the device priority so okToConnect() returns true
- mService.setPriority(mLeftDevice, BluetoothProfile.PRIORITY_ON);
- mService.setPriority(mRightDevice, BluetoothProfile.PRIORITY_ON);
- mService.setPriority(mSingleDevice, BluetoothProfile.PRIORITY_OFF);
+ 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);
doReturn(true).when(mNativeInterface).connectHearingAid(any(BluetoothDevice.class));
doReturn(true).when(mNativeInterface).disconnectHearingAid(any(BluetoothDevice.class));
@@ -562,9 +589,13 @@
@Test
public void testCreateStateMachineStackEvents() {
// Update the device priority so okToConnect() returns true
- mService.setPriority(mLeftDevice, BluetoothProfile.PRIORITY_ON);
- mService.setPriority(mRightDevice, BluetoothProfile.PRIORITY_OFF);
- mService.setPriority(mSingleDevice, BluetoothProfile.PRIORITY_OFF);
+ 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);
doReturn(true).when(mNativeInterface).connectHearingAid(any(BluetoothDevice.class));
doReturn(true).when(mNativeInterface).disconnectHearingAid(any(BluetoothDevice.class));
@@ -623,9 +654,13 @@
@Test
public void testDeleteStateMachineUnbondEvents() {
// Update the device priority so okToConnect() returns true
- mService.setPriority(mLeftDevice, BluetoothProfile.PRIORITY_ON);
- mService.setPriority(mRightDevice, BluetoothProfile.PRIORITY_OFF);
- mService.setPriority(mSingleDevice, BluetoothProfile.PRIORITY_OFF);
+ 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);
doReturn(true).when(mNativeInterface).connectHearingAid(any(BluetoothDevice.class));
doReturn(true).when(mNativeInterface).disconnectHearingAid(any(BluetoothDevice.class));
@@ -680,9 +715,13 @@
@Test
public void testDeleteStateMachineDisconnectEvents() {
// Update the device priority so okToConnect() returns true
- mService.setPriority(mLeftDevice, BluetoothProfile.PRIORITY_ON);
- mService.setPriority(mRightDevice, BluetoothProfile.PRIORITY_OFF);
- mService.setPriority(mSingleDevice, BluetoothProfile.PRIORITY_OFF);
+ 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);
doReturn(true).when(mNativeInterface).connectHearingAid(any(BluetoothDevice.class));
doReturn(true).when(mNativeInterface).disconnectHearingAid(any(BluetoothDevice.class));
@@ -725,9 +764,13 @@
// Update hiSyncId map
getHiSyncIdFromNative();
// Update the device priority so okToConnect() returns true
- mService.setPriority(mLeftDevice, BluetoothProfile.PRIORITY_ON);
- mService.setPriority(mRightDevice, BluetoothProfile.PRIORITY_ON);
- mService.setPriority(mSingleDevice, BluetoothProfile.PRIORITY_OFF);
+ 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);
generateConnectionMessageFromNative(mRightDevice, BluetoothProfile.STATE_CONNECTED,
BluetoothProfile.STATE_DISCONNECTED);
@@ -755,9 +798,13 @@
// Update hiSyncId map
getHiSyncIdFromNative();
// Update the device priority so okToConnect() returns true
- mService.setPriority(mLeftDevice, BluetoothProfile.PRIORITY_ON);
- mService.setPriority(mRightDevice, BluetoothProfile.PRIORITY_ON);
- mService.setPriority(mSingleDevice, BluetoothProfile.PRIORITY_ON);
+ 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);
generateConnectionMessageFromNative(mRightDevice, BluetoothProfile.STATE_CONNECTED,
BluetoothProfile.STATE_DISCONNECTED);
@@ -784,8 +831,11 @@
@Test
public void firstTimeConnection_shouldConnectToBothDevices() {
// Update the device priority so okToConnect() returns true
- mService.setPriority(mLeftDevice, BluetoothProfile.PRIORITY_ON);
- mService.setPriority(mRightDevice, BluetoothProfile.PRIORITY_ON);
+ 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);
doReturn(true).when(mNativeInterface).connectHearingAid(any(BluetoothDevice.class));
doReturn(true).when(mNativeInterface).disconnectHearingAid(any(BluetoothDevice.class));
// Send a connect request for left device
@@ -864,9 +914,13 @@
@Test
public void getHiSyncId_afterFirstDeviceConnected() {
// Update the device priority so okToConnect() returns true
- mService.setPriority(mLeftDevice, BluetoothProfile.PRIORITY_ON);
- mService.setPriority(mRightDevice, BluetoothProfile.PRIORITY_ON);
- mService.setPriority(mSingleDevice, BluetoothProfile.PRIORITY_ON);
+ 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);
doReturn(true).when(mNativeInterface).connectHearingAid(any(BluetoothDevice.class));
doReturn(true).when(mNativeInterface).disconnectHearingAid(any(BluetoothDevice.class));
// Send a connect request
@@ -952,7 +1006,9 @@
List<BluetoothDevice> prevConnectedDevices = mService.getConnectedDevices();
// Update the device priority so okToConnect() returns true
- mService.setPriority(device, BluetoothProfile.PRIORITY_ON);
+ when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
+ when(mDatabaseManager.getProfilePriority(device, BluetoothProfile.HEARING_AID))
+ .thenReturn(BluetoothProfile.PRIORITY_ON);
doReturn(true).when(mNativeInterface).connectHearingAid(device);
doReturn(true).when(mNativeInterface).disconnectHearingAid(device);
@@ -1019,7 +1075,9 @@
private void testOkToConnectCase(BluetoothDevice device, int bondState, int priority,
boolean expected) {
doReturn(bondState).when(mAdapterService).getBondState(device);
- Assert.assertTrue(mService.setPriority(device, priority));
+ when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
+ when(mDatabaseManager.getProfilePriority(device, BluetoothProfile.HEARING_AID))
+ .thenReturn(priority);
Assert.assertEquals(expected, mService.okToConnect(device));
}
diff --git a/tests/unit/src/com/android/bluetooth/hearingaid/HearingAidStateMachineTest.java b/tests/unit/src/com/android/bluetooth/hearingaid/HearingAidStateMachineTest.java
index 474861e..2512c5e 100644
--- a/tests/unit/src/com/android/bluetooth/hearingaid/HearingAidStateMachineTest.java
+++ b/tests/unit/src/com/android/bluetooth/hearingaid/HearingAidStateMachineTest.java
@@ -24,13 +24,14 @@
import android.content.Context;
import android.content.Intent;
import android.os.HandlerThread;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.MediumTest;
-import android.support.test.runner.AndroidJUnit4;
-import com.android.bluetooth.R;
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.MediumTest;
+import androidx.test.runner.AndroidJUnit4;
+
import com.android.bluetooth.TestUtils;
import com.android.bluetooth.btservice.AdapterService;
+import com.android.internal.R;
import org.hamcrest.core.IsInstanceOf;
import org.junit.After;
@@ -61,7 +62,8 @@
public void setUp() throws Exception {
mTargetContext = InstrumentationRegistry.getTargetContext();
Assume.assumeTrue("Ignore test when HearingAidService is not enabled",
- mTargetContext.getResources().getBoolean(R.bool.profile_supported_hearing_aid));
+ mTargetContext.getResources().getBoolean(
+ R.bool.config_hearing_aid_profile_supported));
// Set up mocks and test assets
MockitoAnnotations.initMocks(this);
TestUtils.setAdapterService(mAdapterService);
@@ -77,16 +79,14 @@
mHearingAidStateMachine = new HearingAidStateMachine(mTestDevice, mHearingAidService,
mHearingAidNativeInterface, mHandlerThread.getLooper());
// Override the timeout value to speed up the test
- mHearingAidStateMachine.sConnectTimeoutMs = 1000;
- mHearingAidStateMachine.sDisconnectTimeoutMs = 1000;
- HearingAidService.sConnectTimeoutForEachSideMs = 1000;
- HearingAidService.sCheckWhitelistTimeoutMs = 2000;
+ mHearingAidStateMachine.sConnectTimeoutMs = 1000; // 1s
mHearingAidStateMachine.start();
}
@After
public void tearDown() throws Exception {
- if (!mTargetContext.getResources().getBoolean(R.bool.profile_supported_hearing_aid)) {
+ if (!mTargetContext.getResources().getBoolean(
+ R.bool.config_hearing_aid_profile_supported)) {
return;
}
mHearingAidStateMachine.doQuit();
@@ -186,6 +186,7 @@
BluetoothDevice.class));
doReturn(true).when(mHearingAidNativeInterface).disconnectHearingAid(any(
BluetoothDevice.class));
+ when(mHearingAidService.isConnectedPeerDevices(mTestDevice)).thenReturn(true);
// Send a connect request
mHearingAidStateMachine.sendMessage(HearingAidStateMachine.CONNECT, mTestDevice);
@@ -225,6 +226,7 @@
BluetoothDevice.class));
doReturn(true).when(mHearingAidNativeInterface).disconnectHearingAid(any(
BluetoothDevice.class));
+ when(mHearingAidService.isConnectedPeerDevices(mTestDevice)).thenReturn(true);
// Inject an event for when incoming connection is requested
HearingAidStackEvent connStCh =
@@ -255,5 +257,6 @@
// Check that we are in Disconnected state
Assert.assertThat(mHearingAidStateMachine.getCurrentState(),
IsInstanceOf.instanceOf(HearingAidStateMachine.Disconnected.class));
+ verify(mHearingAidNativeInterface).addToWhiteList(eq(mTestDevice));
}
}
diff --git a/tests/unit/src/com/android/bluetooth/hfp/HeadsetPhoneStateTest.java b/tests/unit/src/com/android/bluetooth/hfp/HeadsetPhoneStateTest.java
index 865a215..f32f96f 100644
--- a/tests/unit/src/com/android/bluetooth/hfp/HeadsetPhoneStateTest.java
+++ b/tests/unit/src/com/android/bluetooth/hfp/HeadsetPhoneStateTest.java
@@ -24,12 +24,13 @@
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.ServiceManager;
-import android.support.test.filters.MediumTest;
-import android.support.test.runner.AndroidJUnit4;
import android.telephony.PhoneStateListener;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
+import androidx.test.filters.MediumTest;
+import androidx.test.runner.AndroidJUnit4;
+
import com.android.bluetooth.TestUtils;
import com.android.internal.telephony.ISub;
@@ -72,6 +73,7 @@
// Stub other methods
when(mHeadsetService.getSystemService(Context.TELEPHONY_SERVICE)).thenReturn(
mTelephonyManager);
+ when(mTelephonyManager.createForSubscriptionId(anyInt())).thenReturn(mTelephonyManager);
when(mHeadsetService.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE)).thenReturn(
mSubscriptionManager);
mHandlerThread = new HandlerThread("HeadsetStateMachineTestHandlerThread");
diff --git a/tests/unit/src/com/android/bluetooth/hfp/HeadsetServiceAndStateMachineTest.java b/tests/unit/src/com/android/bluetooth/hfp/HeadsetServiceAndStateMachineTest.java
index 39249a9..ab8bdd6 100644
--- a/tests/unit/src/com/android/bluetooth/hfp/HeadsetServiceAndStateMachineTest.java
+++ b/tests/unit/src/com/android/bluetooth/hfp/HeadsetServiceAndStateMachineTest.java
@@ -40,17 +40,19 @@
import android.os.ParcelUuid;
import android.os.PowerManager;
import android.os.RemoteException;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.espresso.intent.Intents;
-import android.support.test.espresso.intent.matcher.IntentMatchers;
-import android.support.test.filters.MediumTest;
-import android.support.test.rule.ServiceTestRule;
-import android.support.test.runner.AndroidJUnit4;
import android.telecom.PhoneAccount;
+import androidx.test.InstrumentationRegistry;
+import androidx.test.espresso.intent.Intents;
+import androidx.test.espresso.intent.matcher.IntentMatchers;
+import androidx.test.filters.MediumTest;
+import androidx.test.rule.ServiceTestRule;
+import androidx.test.runner.AndroidJUnit4;
+
import com.android.bluetooth.R;
import com.android.bluetooth.TestUtils;
import com.android.bluetooth.btservice.AdapterService;
+import com.android.bluetooth.btservice.storage.DatabaseManager;
import org.hamcrest.Matchers;
import org.junit.After;
@@ -85,6 +87,7 @@
private static final int MAX_HEADSET_CONNECTIONS = 5;
private static final ParcelUuid[] FAKE_HEADSET_UUID = {BluetoothUuid.Handsfree};
private static final String TEST_PHONE_NUMBER = "1234567890";
+ private static final String TEST_CALLER_ID = "Test Name";
@Rule public final ServiceTestRule mServiceRule = new ServiceTestRule();
@@ -145,6 +148,7 @@
@Spy private HeadsetObjectsFactory mObjectsFactory = HeadsetObjectsFactory.getInstance();
@Mock private AdapterService mAdapterService;
+ @Mock private DatabaseManager mDatabaseManager;
@Mock private HeadsetSystemInterface mSystemInterface;
@Mock private AudioManager mAudioManager;
@Mock private HeadsetPhoneState mPhoneState;
@@ -275,6 +279,9 @@
@Test
public void testConnectFromApi() {
BluetoothDevice device = TestUtils.getTestDevice(mAdapter, 0);
+ when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
+ when(mDatabaseManager.getProfilePriority(device, BluetoothProfile.HEADSET))
+ .thenReturn(BluetoothProfile.PRIORITY_UNDEFINED);
mBondedDevices.add(device);
Assert.assertTrue(mHeadsetService.connect(device));
verify(mObjectsFactory).makeStateMachine(device,
@@ -316,6 +323,9 @@
@Test
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);
mBondedDevices.add(device);
Assert.assertTrue(mHeadsetService.connect(device));
verify(mObjectsFactory).makeStateMachine(device,
@@ -357,6 +367,9 @@
@Test
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);
mBondedDevices.add(device);
Assert.assertTrue(mHeadsetService.connect(device));
verify(mObjectsFactory).makeStateMachine(device,
@@ -473,12 +486,12 @@
verifyVirtualCallStartSequenceInvocations(connectedDevices);
// Virtual call should be preempted by telecom call
mHeadsetServiceBinder.phoneStateChanged(0, 0, HeadsetHalConstants.CALL_STATE_INCOMING,
- TEST_PHONE_NUMBER, 128);
+ TEST_PHONE_NUMBER, 128, "");
Assert.assertFalse(mHeadsetService.isVirtualCallStarted());
verifyVirtualCallStopSequenceInvocations(connectedDevices);
verifyCallStateToNativeInvocation(
new HeadsetCallState(0, 0, HeadsetHalConstants.CALL_STATE_INCOMING,
- TEST_PHONE_NUMBER, 128), connectedDevices);
+ TEST_PHONE_NUMBER, 128, ""), connectedDevices);
}
/**
@@ -575,19 +588,19 @@
IntentMatchers.hasData(dialOutUri)), Intents.times(1));
// Verify that phone state update confirms the dial out event
mHeadsetServiceBinder.phoneStateChanged(0, 0, HeadsetHalConstants.CALL_STATE_DIALING,
- TEST_PHONE_NUMBER, 128);
+ TEST_PHONE_NUMBER, 128, "");
HeadsetCallState dialingCallState =
new HeadsetCallState(0, 0, HeadsetHalConstants.CALL_STATE_DIALING,
- TEST_PHONE_NUMBER, 128);
+ TEST_PHONE_NUMBER, 128, "");
verifyCallStateToNativeInvocation(dialingCallState, connectedDevices);
verify(mNativeInterface).atResponseCode(dialingOutDevice,
HeadsetHalConstants.AT_RESPONSE_OK, 0);
// Verify that IDLE phone state clears the dialing out flag
mHeadsetServiceBinder.phoneStateChanged(1, 0, HeadsetHalConstants.CALL_STATE_IDLE,
- TEST_PHONE_NUMBER, 128);
+ TEST_PHONE_NUMBER, 128, "");
HeadsetCallState activeCallState =
new HeadsetCallState(0, 0, HeadsetHalConstants.CALL_STATE_DIALING,
- TEST_PHONE_NUMBER, 128);
+ TEST_PHONE_NUMBER, 128, "");
verifyCallStateToNativeInvocation(activeCallState, connectedDevices);
Assert.assertFalse(mHeadsetService.hasDeviceInitiatedDialingOut());
}
@@ -1072,6 +1085,32 @@
verifyNoMoreInteractions(mNativeInterface);
}
+ /**
+ * Test to verify the call state and caller information are correctly delivered
+ * {@link BluetoothHeadset#phoneStateChanged(int, int, int, String, int, String, boolean)}
+ */
+ @Test
+ public void testPhoneStateChangedWithIncomingCallState() throws RemoteException {
+ // Connect HF
+ for (int i = 0; i < MAX_HEADSET_CONNECTIONS; ++i) {
+ BluetoothDevice device = TestUtils.getTestDevice(mAdapter, i);
+ connectTestDevice(device);
+ Assert.assertThat(mHeadsetServiceBinder.getConnectedDevices(),
+ Matchers.containsInAnyOrder(mBondedDevices.toArray()));
+ Assert.assertThat(mHeadsetServiceBinder.getDevicesMatchingConnectionStates(
+ new int[]{BluetoothProfile.STATE_CONNECTED}),
+ Matchers.containsInAnyOrder(mBondedDevices.toArray()));
+ }
+ List<BluetoothDevice> connectedDevices = mHeadsetServiceBinder.getConnectedDevices();
+ Assert.assertThat(connectedDevices, Matchers.containsInAnyOrder(mBondedDevices.toArray()));
+ // Incoming call update by telecom
+ mHeadsetServiceBinder.phoneStateChanged(0, 0, HeadsetHalConstants.CALL_STATE_INCOMING,
+ TEST_PHONE_NUMBER, 128, TEST_CALLER_ID);
+ HeadsetCallState incomingCallState = new HeadsetCallState(0, 0,
+ HeadsetHalConstants.CALL_STATE_INCOMING, TEST_PHONE_NUMBER, 128, TEST_CALLER_ID);
+ verifyCallStateToNativeInvocation(incomingCallState, connectedDevices);
+ }
+
private void startVoiceRecognitionFromHf(BluetoothDevice device) {
// Start voice recognition
HeadsetStackEvent startVrEvent =
@@ -1114,6 +1153,9 @@
}
private void connectTestDevice(BluetoothDevice device) {
+ when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
+ when(mDatabaseManager.getProfilePriority(device, BluetoothProfile.HEADSET))
+ .thenReturn(BluetoothProfile.PRIORITY_UNDEFINED);
// Make device bonded
mBondedDevices.add(device);
// Use connecting event to indicate that device is connecting
@@ -1178,19 +1220,19 @@
private void verifyVirtualCallStartSequenceInvocations(List<BluetoothDevice> connectedDevices) {
// Do not verify HeadsetPhoneState changes as it is verified in HeadsetServiceTest
verifyCallStateToNativeInvocation(
- new HeadsetCallState(0, 0, HeadsetHalConstants.CALL_STATE_DIALING, "", 0),
+ new HeadsetCallState(0, 0, HeadsetHalConstants.CALL_STATE_DIALING, "", 0, ""),
connectedDevices);
verifyCallStateToNativeInvocation(
- new HeadsetCallState(0, 0, HeadsetHalConstants.CALL_STATE_ALERTING, "", 0),
+ new HeadsetCallState(0, 0, HeadsetHalConstants.CALL_STATE_ALERTING, "", 0, ""),
connectedDevices);
verifyCallStateToNativeInvocation(
- new HeadsetCallState(1, 0, HeadsetHalConstants.CALL_STATE_IDLE, "", 0),
+ new HeadsetCallState(1, 0, HeadsetHalConstants.CALL_STATE_IDLE, "", 0, ""),
connectedDevices);
}
private void verifyVirtualCallStopSequenceInvocations(List<BluetoothDevice> connectedDevices) {
verifyCallStateToNativeInvocation(
- new HeadsetCallState(0, 0, HeadsetHalConstants.CALL_STATE_IDLE, "", 0),
+ new HeadsetCallState(0, 0, HeadsetHalConstants.CALL_STATE_IDLE, "", 0, ""),
connectedDevices);
}
diff --git a/tests/unit/src/com/android/bluetooth/hfp/HeadsetServiceTest.java b/tests/unit/src/com/android/bluetooth/hfp/HeadsetServiceTest.java
index e5395f2..a73c05c 100644
--- a/tests/unit/src/com/android/bluetooth/hfp/HeadsetServiceTest.java
+++ b/tests/unit/src/com/android/bluetooth/hfp/HeadsetServiceTest.java
@@ -29,14 +29,16 @@
import android.os.ParcelUuid;
import android.os.RemoteException;
import android.os.SystemClock;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.MediumTest;
-import android.support.test.rule.ServiceTestRule;
-import android.support.test.runner.AndroidJUnit4;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.MediumTest;
+import androidx.test.rule.ServiceTestRule;
+import androidx.test.runner.AndroidJUnit4;
import com.android.bluetooth.R;
import com.android.bluetooth.TestUtils;
import com.android.bluetooth.btservice.AdapterService;
+import com.android.bluetooth.btservice.storage.DatabaseManager;
import org.hamcrest.Matchers;
import org.junit.After;
@@ -79,6 +81,7 @@
@Spy private HeadsetObjectsFactory mObjectsFactory = HeadsetObjectsFactory.getInstance();
@Mock private AdapterService mAdapterService;
+ @Mock private DatabaseManager mDatabaseManager;
@Mock private HeadsetSystemInterface mSystemInterface;
@Mock private AudioManager mAudioManager;
@Mock private HeadsetPhoneState mPhoneState;
@@ -147,6 +150,8 @@
mHeadsetServiceBinder = (IBluetoothHeadset.Stub) mHeadsetService.initBinder();
Assert.assertNotNull(mHeadsetServiceBinder);
mHeadsetServiceBinder.setForceScoAudio(true);
+ // Mock database for getPriority()
+ when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
}
@After
@@ -199,13 +204,13 @@
testOkToAcceptConnectionCase(mCurrentDevice, BluetoothDevice.BOND_NONE, badPriorityValue,
false);
testOkToAcceptConnectionCase(mCurrentDevice, BluetoothDevice.BOND_BONDING,
- BluetoothProfile.PRIORITY_UNDEFINED, false);
+ BluetoothProfile.PRIORITY_UNDEFINED, true);
testOkToAcceptConnectionCase(mCurrentDevice, BluetoothDevice.BOND_BONDING,
BluetoothProfile.PRIORITY_OFF, false);
testOkToAcceptConnectionCase(mCurrentDevice, BluetoothDevice.BOND_BONDING,
- BluetoothProfile.PRIORITY_ON, false);
+ BluetoothProfile.PRIORITY_ON, true);
testOkToAcceptConnectionCase(mCurrentDevice, BluetoothDevice.BOND_BONDING,
- BluetoothProfile.PRIORITY_AUTO_CONNECT, false);
+ BluetoothProfile.PRIORITY_AUTO_CONNECT, true);
testOkToAcceptConnectionCase(mCurrentDevice, BluetoothDevice.BOND_BONDING, badPriorityValue,
false);
testOkToAcceptConnectionCase(mCurrentDevice, BluetoothDevice.BOND_BONDED,
@@ -227,9 +232,6 @@
testOkToAcceptConnectionCase(mCurrentDevice, badBondState,
BluetoothProfile.PRIORITY_AUTO_CONNECT, false);
testOkToAcceptConnectionCase(mCurrentDevice, badBondState, badPriorityValue, false);
- // Restore prirority to undefined for this test device
- Assert.assertTrue(
- mHeadsetService.setPriority(mCurrentDevice, BluetoothProfile.PRIORITY_UNDEFINED));
}
/**
@@ -239,6 +241,9 @@
*/
@Test
public void testConnectDevice_connectDeviceBelowLimit() {
+ when(mDatabaseManager.getProfilePriority(any(BluetoothDevice.class),
+ eq(BluetoothProfile.HEADSET)))
+ .thenReturn(BluetoothProfile.PRIORITY_UNDEFINED);
mCurrentDevice = TestUtils.getTestDevice(mAdapter, 0);
Assert.assertTrue(mHeadsetService.connect(mCurrentDevice));
verify(mObjectsFactory).makeStateMachine(mCurrentDevice,
@@ -348,6 +353,9 @@
@Test
public void testConnectDevice_connectDeviceAboveLimit() {
ArrayList<BluetoothDevice> connectedDevices = new ArrayList<>();
+ when(mDatabaseManager.getProfilePriority(any(BluetoothDevice.class),
+ eq(BluetoothProfile.HEADSET)))
+ .thenReturn(BluetoothProfile.PRIORITY_UNDEFINED);
for (int i = 0; i < MAX_HEADSET_CONNECTIONS; ++i) {
mCurrentDevice = TestUtils.getTestDevice(mAdapter, i);
Assert.assertTrue(mHeadsetService.connect(mCurrentDevice));
@@ -397,6 +405,9 @@
*/
@Test
public void testConnectAudio_withOneDevice() {
+ when(mDatabaseManager.getProfilePriority(any(BluetoothDevice.class),
+ eq(BluetoothProfile.HEADSET)))
+ .thenReturn(BluetoothProfile.PRIORITY_UNDEFINED);
mCurrentDevice = TestUtils.getTestDevice(mAdapter, 0);
Assert.assertTrue(mHeadsetService.connect(mCurrentDevice));
verify(mObjectsFactory).makeStateMachine(mCurrentDevice,
@@ -446,6 +457,9 @@
@Test
public void testConnectAudio_withMultipleDevices() {
ArrayList<BluetoothDevice> connectedDevices = new ArrayList<>();
+ when(mDatabaseManager.getProfilePriority(any(BluetoothDevice.class),
+ eq(BluetoothProfile.HEADSET)))
+ .thenReturn(BluetoothProfile.PRIORITY_UNDEFINED);
for (int i = 0; i < MAX_HEADSET_CONNECTIONS; ++i) {
mCurrentDevice = TestUtils.getTestDevice(mAdapter, i);
Assert.assertTrue(mHeadsetService.connect(mCurrentDevice));
@@ -519,6 +533,9 @@
@Test
public void testConnectAudio_connectTwoAudioChannelsShouldFail() {
ArrayList<BluetoothDevice> connectedDevices = new ArrayList<>();
+ when(mDatabaseManager.getProfilePriority(any(BluetoothDevice.class),
+ eq(BluetoothProfile.HEADSET)))
+ .thenReturn(BluetoothProfile.PRIORITY_UNDEFINED);
for (int i = 0; i < MAX_HEADSET_CONNECTIONS; ++i) {
mCurrentDevice = TestUtils.getTestDevice(mAdapter, i);
Assert.assertTrue(mHeadsetService.connect(mCurrentDevice));
@@ -588,6 +605,9 @@
@Test
public void testConnectAudio_firstConnectedAudioDevice() {
ArrayList<BluetoothDevice> connectedDevices = new ArrayList<>();
+ when(mDatabaseManager.getProfilePriority(any(BluetoothDevice.class),
+ eq(BluetoothProfile.HEADSET)))
+ .thenReturn(BluetoothProfile.PRIORITY_UNDEFINED);
doAnswer(invocation -> {
BluetoothDevice[] devicesArray = new BluetoothDevice[connectedDevices.size()];
return connectedDevices.toArray(devicesArray);
@@ -652,10 +672,13 @@
*/
@Test
public void testConnectAudio_deviceDisconnected() {
+ when(mDatabaseManager.getProfilePriority(any(BluetoothDevice.class),
+ eq(BluetoothProfile.HEADSET)))
+ .thenReturn(BluetoothProfile.PRIORITY_UNDEFINED);
mCurrentDevice = TestUtils.getTestDevice(mAdapter, 0);
HeadsetCallState headsetCallState =
new HeadsetCallState(1, 0, HeadsetHalConstants.CALL_STATE_ALERTING,
- TEST_PHONE_NUMBER, 128);
+ TEST_PHONE_NUMBER, 128, "");
Assert.assertTrue(mHeadsetService.connect(mCurrentDevice));
verify(mObjectsFactory).makeStateMachine(mCurrentDevice,
mHeadsetService.getStateMachinesThreadLooper(), mHeadsetService, mAdapterService,
@@ -687,10 +710,10 @@
public void testPhoneStateChange_noDeviceSaveState() throws RemoteException {
HeadsetCallState headsetCallState =
new HeadsetCallState(1, 0, HeadsetHalConstants.CALL_STATE_ALERTING,
- TEST_PHONE_NUMBER, 128);
+ TEST_PHONE_NUMBER, 128, "");
mHeadsetServiceBinder.phoneStateChanged(headsetCallState.mNumActive,
headsetCallState.mNumHeld, headsetCallState.mCallState, headsetCallState.mNumber,
- headsetCallState.mType);
+ headsetCallState.mType, headsetCallState.mName);
HeadsetTestUtils.verifyPhoneStateChangeSetters(mPhoneState, headsetCallState,
ASYNC_CALL_TIMEOUT_MILLIS);
}
@@ -705,7 +728,10 @@
public void testPhoneStateChange_oneDeviceSaveState() throws RemoteException {
HeadsetCallState headsetCallState =
new HeadsetCallState(1, 0, HeadsetHalConstants.CALL_STATE_ALERTING,
- TEST_PHONE_NUMBER, 128);
+ TEST_PHONE_NUMBER, 128, "");
+ when(mDatabaseManager.getProfilePriority(any(BluetoothDevice.class),
+ eq(BluetoothProfile.HEADSET)))
+ .thenReturn(BluetoothProfile.PRIORITY_UNDEFINED);
mCurrentDevice = TestUtils.getTestDevice(mAdapter, 0);
final ArrayList<BluetoothDevice> connectedDevices = new ArrayList<>();
// Connect one device
@@ -741,7 +767,7 @@
// Change phone state
mHeadsetServiceBinder.phoneStateChanged(headsetCallState.mNumActive,
headsetCallState.mNumHeld, headsetCallState.mCallState, headsetCallState.mNumber,
- headsetCallState.mType);
+ headsetCallState.mType, headsetCallState.mName);
// Make sure we notify device about this change
verify(mStateMachines.get(mCurrentDevice)).sendMessage(
HeadsetStateMachine.CALL_STATE_CHANGED, headsetCallState);
@@ -760,8 +786,11 @@
public void testPhoneStateChange_multipleDevicesSaveState() throws RemoteException {
HeadsetCallState headsetCallState =
new HeadsetCallState(1, 0, HeadsetHalConstants.CALL_STATE_ALERTING,
- TEST_PHONE_NUMBER, 128);
+ TEST_PHONE_NUMBER, 128, "");
final ArrayList<BluetoothDevice> connectedDevices = new ArrayList<>();
+ when(mDatabaseManager.getProfilePriority(any(BluetoothDevice.class),
+ eq(BluetoothProfile.HEADSET)))
+ .thenReturn(BluetoothProfile.PRIORITY_UNDEFINED);
for (int i = 0; i < MAX_HEADSET_CONNECTIONS; ++i) {
mCurrentDevice = TestUtils.getTestDevice(mAdapter, i);
Assert.assertTrue(mHeadsetService.connect(mCurrentDevice));
@@ -801,7 +830,7 @@
// Change phone state
mHeadsetServiceBinder.phoneStateChanged(headsetCallState.mNumActive,
headsetCallState.mNumHeld, headsetCallState.mCallState, headsetCallState.mNumber,
- headsetCallState.mType);
+ headsetCallState.mType, headsetCallState.mName);
// Make sure we notify devices about this change
for (BluetoothDevice device : connectedDevices) {
verify(mStateMachines.get(device)).sendMessage(HeadsetStateMachine.CALL_STATE_CHANGED,
@@ -812,6 +841,47 @@
ASYNC_CALL_TIMEOUT_MILLIS);
}
+ /**
+ * Test that whether active device been removed after enable silence mode
+ */
+ @Test
+ public void testSetSilenceMode() {
+ when(mDatabaseManager.getProfilePriority(any(BluetoothDevice.class),
+ eq(BluetoothProfile.HEADSET)))
+ .thenReturn(BluetoothProfile.PRIORITY_UNDEFINED);
+ for (int i = 0; i < 2; i++) {
+ mCurrentDevice = TestUtils.getTestDevice(mAdapter, i);
+ Assert.assertTrue(mHeadsetService.connect(mCurrentDevice));
+ when(mStateMachines.get(mCurrentDevice).getDevice()).thenReturn(mCurrentDevice);
+ when(mStateMachines.get(mCurrentDevice).getConnectionState()).thenReturn(
+ BluetoothProfile.STATE_CONNECTED);
+ when(mStateMachines.get(mCurrentDevice).setSilenceDevice(
+ anyBoolean())).thenReturn(true);
+ }
+ mCurrentDevice = TestUtils.getTestDevice(mAdapter, 0);
+ BluetoothDevice otherDevice = TestUtils.getTestDevice(mAdapter, 1);
+
+ // Test whether active device been removed after enable silence mode.
+ Assert.assertTrue(mHeadsetService.setActiveDevice(mCurrentDevice));
+ Assert.assertEquals(mCurrentDevice, mHeadsetService.getActiveDevice());
+ Assert.assertTrue(mHeadsetService.setSilenceMode(mCurrentDevice, true));
+ Assert.assertNull(mHeadsetService.getActiveDevice());
+
+ // Test whether active device been resumed after disable silence mode.
+ Assert.assertTrue(mHeadsetService.setSilenceMode(mCurrentDevice, false));
+ Assert.assertEquals(mCurrentDevice, mHeadsetService.getActiveDevice());
+
+ // Test that active device should not be changed when silence a non-active device
+ Assert.assertTrue(mHeadsetService.setActiveDevice(mCurrentDevice));
+ Assert.assertEquals(mCurrentDevice, mHeadsetService.getActiveDevice());
+ Assert.assertTrue(mHeadsetService.setSilenceMode(otherDevice, true));
+ Assert.assertEquals(mCurrentDevice, mHeadsetService.getActiveDevice());
+
+ // Test that active device should not be changed when another device exits silence mode
+ Assert.assertTrue(mHeadsetService.setSilenceMode(otherDevice, false));
+ Assert.assertEquals(mCurrentDevice, mHeadsetService.getActiveDevice());
+ }
+
/*
* Helper function to test okToAcceptConnection() method
*
@@ -823,7 +893,8 @@
private void testOkToAcceptConnectionCase(BluetoothDevice device, int bondState, int priority,
boolean expected) {
doReturn(bondState).when(mAdapterService).getBondState(device);
- Assert.assertTrue(mHeadsetService.setPriority(device, priority));
+ when(mDatabaseManager.getProfilePriority(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 80192a6..576d169 100644
--- a/tests/unit/src/com/android/bluetooth/hfp/HeadsetStateMachineTest.java
+++ b/tests/unit/src/com/android/bluetooth/hfp/HeadsetStateMachineTest.java
@@ -31,13 +31,14 @@
import android.os.HandlerThread;
import android.os.UserHandle;
import android.provider.CallLog;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.MediumTest;
-import android.support.test.runner.AndroidJUnit4;
import android.telephony.PhoneStateListener;
import android.test.mock.MockContentProvider;
import android.test.mock.MockContentResolver;
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.MediumTest;
+import androidx.test.runner.AndroidJUnit4;
+
import com.android.bluetooth.R;
import com.android.bluetooth.TestUtils;
import com.android.bluetooth.btservice.AdapterService;
@@ -941,6 +942,87 @@
}
/**
+ * A test to verfiy that we correctly handles AT+BIND event with driver safety case from HF
+ */
+ @Test
+ public void testAtBindWithDriverSafetyEventWhenConnecting() {
+ setUpConnectingState();
+
+ String atString = "1";
+ mHeadsetStateMachine.sendMessage(HeadsetStateMachine.STACK_EVENT,
+ new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_BIND, atString, mTestDevice));
+ ArgumentCaptor<Intent> intentArgument = ArgumentCaptor.forClass(Intent.class);
+ verify(mHeadsetService, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).sendBroadcast(
+ intentArgument.capture(), eq(HeadsetService.BLUETOOTH_PERM));
+ verify(mHeadsetService, times(1)).sendBroadcast(any(), any());
+ Assert.assertEquals(mTestDevice, intentArgument.getValue().getExtra(
+ BluetoothDevice.EXTRA_DEVICE, null));
+ Assert.assertEquals(HeadsetHalConstants.HF_INDICATOR_ENHANCED_DRIVER_SAFETY,
+ intentArgument.getValue().getIntExtra(
+ BluetoothHeadset.EXTRA_HF_INDICATORS_IND_ID, -1));
+ Assert.assertEquals(-1, intentArgument.getValue().getIntExtra(
+ BluetoothHeadset.EXTRA_HF_INDICATORS_IND_VALUE, -2));
+ }
+
+ /**
+ * A test to verfiy that we correctly handles AT+BIND event with battery level case from HF
+ */
+ @Test
+ public void testAtBindEventWithBatteryLevelEventWhenConnecting() {
+ setUpConnectingState();
+
+ String atString = "2";
+ mHeadsetStateMachine.sendMessage(HeadsetStateMachine.STACK_EVENT,
+ new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_BIND, atString, mTestDevice));
+ ArgumentCaptor<Intent> intentArgument = ArgumentCaptor.forClass(Intent.class);
+ verify(mHeadsetService, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).sendBroadcast(
+ intentArgument.capture(), eq(HeadsetService.BLUETOOTH_PERM));
+ verify(mHeadsetService, times(1)).sendBroadcast(any(), any());
+ Assert.assertEquals(mTestDevice, intentArgument.getValue().getExtra(
+ BluetoothDevice.EXTRA_DEVICE, null));
+ Assert.assertEquals(HeadsetHalConstants.HF_INDICATOR_BATTERY_LEVEL_STATUS,
+ intentArgument.getValue().getIntExtra(
+ BluetoothHeadset.EXTRA_HF_INDICATORS_IND_ID, -1));
+ Assert.assertEquals(-1, intentArgument.getValue().getIntExtra(
+ BluetoothHeadset.EXTRA_HF_INDICATORS_IND_VALUE, -2));
+ }
+
+ /**
+ * A test to verfiy that we correctly handles AT+BIND event with error case from HF
+ */
+ @Test
+ public void testAtBindEventWithErrorEventWhenConnecting() {
+ setUpConnectingState();
+
+ String atString = "err,A,123,,1";
+ mHeadsetStateMachine.sendMessage(HeadsetStateMachine.STACK_EVENT,
+ new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_BIND, atString, mTestDevice));
+ ArgumentCaptor<Intent> intentArgument = ArgumentCaptor.forClass(Intent.class);
+ verify(mHeadsetService, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).sendBroadcast(
+ intentArgument.capture(), eq(HeadsetService.BLUETOOTH_PERM));
+ verify(mHeadsetService, times(1)).sendBroadcast(any(), any());
+ Assert.assertEquals(mTestDevice, intentArgument.getValue().getExtra(
+ BluetoothDevice.EXTRA_DEVICE, null));
+ Assert.assertEquals(HeadsetHalConstants.HF_INDICATOR_ENHANCED_DRIVER_SAFETY,
+ intentArgument.getValue().getIntExtra(
+ BluetoothHeadset.EXTRA_HF_INDICATORS_IND_ID, -1));
+ Assert.assertEquals(-1, intentArgument.getValue().getIntExtra(
+ BluetoothHeadset.EXTRA_HF_INDICATORS_IND_VALUE, -2));
+ }
+
+ /**
+ * A test to verify that we correctly set AG indicator mask when enter/exit silence mode
+ */
+ @Test
+ public void testSetSilenceDevice() {
+ doNothing().when(mPhoneState).listenForPhoneState(any(BluetoothDevice.class), anyInt());
+ mHeadsetStateMachine.setSilenceDevice(true);
+ mHeadsetStateMachine.setSilenceDevice(false);
+ verify(mPhoneState, times(2)).listenForPhoneState(mTestDevice,
+ PhoneStateListener.LISTEN_NONE);
+ }
+
+ /**
* Setup Connecting State
* @return number of times mHeadsetService.sendBroadcastAsUser() has been invoked
*/
diff --git a/tests/unit/src/com/android/bluetooth/hfpclient/HeadsetClientServiceTest.java b/tests/unit/src/com/android/bluetooth/hfpclient/HeadsetClientServiceTest.java
index 5826415..17afcc3 100644
--- a/tests/unit/src/com/android/bluetooth/hfpclient/HeadsetClientServiceTest.java
+++ b/tests/unit/src/com/android/bluetooth/hfpclient/HeadsetClientServiceTest.java
@@ -18,10 +18,11 @@
import android.bluetooth.BluetoothAdapter;
import android.content.Context;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.MediumTest;
-import android.support.test.rule.ServiceTestRule;
-import android.support.test.runner.AndroidJUnit4;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.MediumTest;
+import androidx.test.rule.ServiceTestRule;
+import androidx.test.runner.AndroidJUnit4;
import com.android.bluetooth.R;
import com.android.bluetooth.TestUtils;
diff --git a/tests/unit/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachineTest.java b/tests/unit/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachineTest.java
index 334597b..8849d90 100644
--- a/tests/unit/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachineTest.java
+++ b/tests/unit/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachineTest.java
@@ -14,12 +14,13 @@
import android.content.res.Resources;
import android.media.AudioManager;
import android.os.HandlerThread;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.espresso.intent.matcher.IntentMatchers;
-import android.support.test.filters.LargeTest;
-import android.support.test.filters.MediumTest;
-import android.support.test.filters.SmallTest;
-import android.support.test.runner.AndroidJUnit4;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.espresso.intent.matcher.IntentMatchers;
+import androidx.test.filters.LargeTest;
+import androidx.test.filters.MediumTest;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
import com.android.bluetooth.R;
@@ -68,7 +69,7 @@
when(mAudioManager.getStreamVolume(anyInt())).thenReturn(2);
when(mAudioManager.getStreamMaxVolume(anyInt())).thenReturn(10);
when(mAudioManager.getStreamMinVolume(anyInt())).thenReturn(1);
- when(mHeadsetClientService.getSystemService(Context.AUDIO_SERVICE)).thenReturn(
+ when(mHeadsetClientService.getAudioManager()).thenReturn(
mAudioManager);
when(mHeadsetClientService.getResources()).thenReturn(mMockHfpResources);
when(mMockHfpResources.getBoolean(R.bool.hfp_clcc_poll_during_call)).thenReturn(true);
diff --git a/tests/unit/src/com/android/bluetooth/hid/HidDeviceTest.java b/tests/unit/src/com/android/bluetooth/hid/HidDeviceTest.java
index 618d2b4..eae1c1c 100644
--- a/tests/unit/src/com/android/bluetooth/hid/HidDeviceTest.java
+++ b/tests/unit/src/com/android/bluetooth/hid/HidDeviceTest.java
@@ -29,10 +29,11 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Looper;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.MediumTest;
-import android.support.test.rule.ServiceTestRule;
-import android.support.test.runner.AndroidJUnit4;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.MediumTest;
+import androidx.test.rule.ServiceTestRule;
+import androidx.test.runner.AndroidJUnit4;
import com.android.bluetooth.R;
import com.android.bluetooth.TestUtils;
diff --git a/tests/unit/src/com/android/bluetooth/hid/HidHostServiceTest.java b/tests/unit/src/com/android/bluetooth/hid/HidHostServiceTest.java
index d67a51d..499db4a 100644
--- a/tests/unit/src/com/android/bluetooth/hid/HidHostServiceTest.java
+++ b/tests/unit/src/com/android/bluetooth/hid/HidHostServiceTest.java
@@ -15,20 +15,18 @@
*/
package com.android.bluetooth.hid;
-import static org.mockito.Mockito.*;
-
import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothProfile;
import android.content.Context;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.MediumTest;
-import android.support.test.rule.ServiceTestRule;
-import android.support.test.runner.AndroidJUnit4;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.MediumTest;
+import androidx.test.rule.ServiceTestRule;
+import androidx.test.runner.AndroidJUnit4;
import com.android.bluetooth.R;
import com.android.bluetooth.TestUtils;
import com.android.bluetooth.btservice.AdapterService;
+import com.android.bluetooth.btservice.storage.DatabaseManager;
import org.junit.After;
import org.junit.Assert;
@@ -45,7 +43,6 @@
public class HidHostServiceTest {
private HidHostService mService = null;
private BluetoothAdapter mAdapter = null;
- private BluetoothDevice mTestDevice;
private Context mTargetContext;
@Rule public final ServiceTestRule mServiceRule = new ServiceTestRule();
@@ -65,9 +62,6 @@
// Try getting the Bluetooth adapter
mAdapter = BluetoothAdapter.getDefaultAdapter();
Assert.assertNotNull(mAdapter);
-
- // Get a device for testing
- mTestDevice = TestUtils.getTestDevice(mAdapter, 0);
}
@After
@@ -85,79 +79,4 @@
public void testInitialize() {
Assert.assertNotNull(HidHostService.getHidHostService());
}
-
- /**
- * Test okToConnect method using various test cases
- */
- @Test
- public void testOkToConnect() {
- int badPriorityValue = 1024;
- int badBondState = 42;
- testOkToConnectCase(mTestDevice,
- BluetoothDevice.BOND_NONE, BluetoothProfile.PRIORITY_UNDEFINED, false);
- testOkToConnectCase(mTestDevice,
- BluetoothDevice.BOND_NONE, BluetoothProfile.PRIORITY_OFF, false);
- testOkToConnectCase(mTestDevice,
- BluetoothDevice.BOND_NONE, BluetoothProfile.PRIORITY_ON, false);
- testOkToConnectCase(mTestDevice,
- BluetoothDevice.BOND_NONE, BluetoothProfile.PRIORITY_AUTO_CONNECT, false);
- testOkToConnectCase(mTestDevice,
- BluetoothDevice.BOND_NONE, badPriorityValue, false);
- testOkToConnectCase(mTestDevice,
- BluetoothDevice.BOND_BONDING, BluetoothProfile.PRIORITY_UNDEFINED, false);
- testOkToConnectCase(mTestDevice,
- BluetoothDevice.BOND_BONDING, BluetoothProfile.PRIORITY_OFF, false);
- testOkToConnectCase(mTestDevice,
- BluetoothDevice.BOND_BONDING, BluetoothProfile.PRIORITY_ON, false);
- testOkToConnectCase(mTestDevice,
- BluetoothDevice.BOND_BONDING, BluetoothProfile.PRIORITY_AUTO_CONNECT, false);
- testOkToConnectCase(mTestDevice,
- BluetoothDevice.BOND_BONDING, badPriorityValue, false);
- testOkToConnectCase(mTestDevice,
- BluetoothDevice.BOND_BONDED, BluetoothProfile.PRIORITY_UNDEFINED, true);
- testOkToConnectCase(mTestDevice,
- BluetoothDevice.BOND_BONDED, BluetoothProfile.PRIORITY_OFF, false);
- testOkToConnectCase(mTestDevice,
- BluetoothDevice.BOND_BONDED, BluetoothProfile.PRIORITY_ON, true);
- testOkToConnectCase(mTestDevice,
- BluetoothDevice.BOND_BONDED, BluetoothProfile.PRIORITY_AUTO_CONNECT, true);
- testOkToConnectCase(mTestDevice,
- BluetoothDevice.BOND_BONDED, badPriorityValue, false);
- testOkToConnectCase(mTestDevice,
- badBondState, BluetoothProfile.PRIORITY_UNDEFINED, false);
- testOkToConnectCase(mTestDevice,
- badBondState, BluetoothProfile.PRIORITY_OFF, false);
- testOkToConnectCase(mTestDevice,
- badBondState, BluetoothProfile.PRIORITY_ON, false);
- testOkToConnectCase(mTestDevice,
- badBondState, BluetoothProfile.PRIORITY_AUTO_CONNECT, false);
- testOkToConnectCase(mTestDevice,
- badBondState, badPriorityValue, false);
- // Restore prirority to undefined for this test device
- Assert.assertTrue(mService.setPriority(
- mTestDevice, BluetoothProfile.PRIORITY_UNDEFINED));
- }
-
- /**
- * Helper function to test okToConnect() method.
- *
- * @param device test device
- * @param bondState bond state value, could be invalid
- * @param priority value, could be invalid, coudl be invalid
- * @param expected expected result from okToConnect()
- */
- private void testOkToConnectCase(BluetoothDevice device, int bondState, int priority,
- boolean expected) {
- doReturn(bondState).when(mAdapterService).getBondState(device);
- Assert.assertTrue(mService.setPriority(device, priority));
-
- // Test when the AdapterService is in non-quiet mode.
- doReturn(false).when(mAdapterService).isQuietModeEnabled();
- Assert.assertEquals(expected, mService.okToConnect(device));
-
- // Test when the AdapterService is in quiet mode.
- doReturn(true).when(mAdapterService).isQuietModeEnabled();
- Assert.assertEquals(false, mService.okToConnect(device));
- }
-
}
diff --git a/tests/unit/src/com/android/bluetooth/map/BluetoothMapContentObserverTest.java b/tests/unit/src/com/android/bluetooth/map/BluetoothMapContentObserverTest.java
index 47b72d6..cca340e 100644
--- a/tests/unit/src/com/android/bluetooth/map/BluetoothMapContentObserverTest.java
+++ b/tests/unit/src/com/android/bluetooth/map/BluetoothMapContentObserverTest.java
@@ -25,13 +25,14 @@
import android.os.Looper;
import android.os.RemoteException;
import android.os.UserManager;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.MediumTest;
-import android.support.test.runner.AndroidJUnit4;
import android.telephony.TelephonyManager;
import android.test.mock.MockContentProvider;
import android.test.mock.MockContentResolver;
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.MediumTest;
+import androidx.test.runner.AndroidJUnit4;
+
import com.android.bluetooth.R;
import org.junit.Assert;
diff --git a/tests/unit/src/com/android/bluetooth/map/BluetoothMapServiceTest.java b/tests/unit/src/com/android/bluetooth/map/BluetoothMapServiceTest.java
index 34c64c5..0a000a2 100644
--- a/tests/unit/src/com/android/bluetooth/map/BluetoothMapServiceTest.java
+++ b/tests/unit/src/com/android/bluetooth/map/BluetoothMapServiceTest.java
@@ -17,10 +17,11 @@
import android.bluetooth.BluetoothAdapter;
import android.content.Context;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.MediumTest;
-import android.support.test.rule.ServiceTestRule;
-import android.support.test.runner.AndroidJUnit4;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.MediumTest;
+import androidx.test.rule.ServiceTestRule;
+import androidx.test.runner.AndroidJUnit4;
import com.android.bluetooth.R;
import com.android.bluetooth.TestUtils;
diff --git a/tests/unit/src/com/android/bluetooth/mapclient/MapClientStateMachineTest.java b/tests/unit/src/com/android/bluetooth/mapclient/MapClientStateMachineTest.java
index b83cf3d..70854d8 100644
--- a/tests/unit/src/com/android/bluetooth/mapclient/MapClientStateMachineTest.java
+++ b/tests/unit/src/com/android/bluetooth/mapclient/MapClientStateMachineTest.java
@@ -27,13 +27,14 @@
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.MediumTest;
-import android.support.test.filters.Suppress;
-import android.support.test.runner.AndroidJUnit4;
import android.util.Log;
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.MediumTest;
+import androidx.test.runner.AndroidJUnit4;
+
import com.android.bluetooth.R;
+import com.android.bluetooth.btservice.ProfileService;
import org.junit.After;
import org.junit.Assert;
@@ -41,33 +42,32 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-@Suppress // TODO: enable when b/74609188 is debugged
@MediumTest
@RunWith(AndroidJUnit4.class)
public class MapClientStateMachineTest {
private static final String TAG = "MapStateMachineTest";
- private static final Integer TIMEOUT = 3000;
+
+ private static final int ASYNC_CALL_TIMEOUT_MILLIS = 100;
private BluetoothAdapter mAdapter;
private MceStateMachine mMceStateMachine = null;
private BluetoothDevice mTestDevice;
private Context mTargetContext;
- private FakeMapClientService mFakeMapClientService;
- private CountDownLatch mConnectedLatch = null;
- private CountDownLatch mDisconnectedLatch = null;
private Handler mHandler;
+ private ArgumentCaptor<Intent> mIntentArgument = ArgumentCaptor.forClass(Intent.class);
+
+ @Mock
+ private MapClientService mMockMapClientService;
+
@Mock
private MasClient mMockMasClient;
-
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
@@ -75,16 +75,15 @@
Assume.assumeTrue("Ignore test when MapClientService is not enabled",
mTargetContext.getResources().getBoolean(R.bool.profile_supported_mapmce));
+ doReturn(mTargetContext.getResources()).when(mMockMapClientService).getResources();
+
// This line must be called to make sure relevant objects are initialized properly
mAdapter = BluetoothAdapter.getDefaultAdapter();
// Get a device for testing
mTestDevice = mAdapter.getRemoteDevice("00:01:02:03:04:05");
- mConnectedLatch = new CountDownLatch(1);
- mDisconnectedLatch = new CountDownLatch(1);
- mFakeMapClientService = new FakeMapClientService();
when(mMockMasClient.makeRequest(any(Request.class))).thenReturn(true);
- mMceStateMachine = new MceStateMachine(mFakeMapClientService, mTestDevice, mMockMasClient);
+ mMceStateMachine = new MceStateMachine(mMockMapClientService, mTestDevice, mMockMasClient);
Assert.assertNotNull(mMceStateMachine);
if (Looper.myLooper() == null) {
Looper.prepare();
@@ -120,22 +119,15 @@
Log.i(TAG, "in testStateTransitionFromConnectingToDisconnected");
setupSdpRecordReceipt();
Message msg = Message.obtain(mHandler, MceStateMachine.MSG_MAS_DISCONNECTED);
- mMceStateMachine.getCurrentState().processMessage(msg);
+ mMceStateMachine.sendMessage(msg);
// Wait until the message is processed and a broadcast request is sent to
// to MapClientService to change
// state from STATE_CONNECTING to STATE_DISCONNECTED
- boolean result = false;
- try {
- result = mDisconnectedLatch.await(TIMEOUT, TimeUnit.MILLISECONDS);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- // Test that the latch reached zero; i.e., that a broadcast of state-change was received.
- Assert.assertTrue(result);
- // When the state reaches STATE_DISCONNECTED, MceStateMachine object is in the process of
- // being dismantled; i.e., can't rely on getting its current state. That means can't
- // test its current state = STATE_DISCONNECTED.
+ verify(mMockMapClientService,
+ timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(2)).sendBroadcast(
+ mIntentArgument.capture(), eq(ProfileService.BLUETOOTH_PERM));
+ Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED, mMceStateMachine.getState());
}
/**
@@ -147,45 +139,51 @@
setupSdpRecordReceipt();
Message msg = Message.obtain(mHandler, MceStateMachine.MSG_MAS_CONNECTED);
- mMceStateMachine.getCurrentState().processMessage(msg);
+ mMceStateMachine.sendMessage(msg);
// Wait until the message is processed and a broadcast request is sent to
// to MapClientService to change
// state from STATE_CONNECTING to STATE_CONNECTED
- boolean result = false;
- try {
- result = mConnectedLatch.await(TIMEOUT, TimeUnit.MILLISECONDS);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- // Test that the latch reached zero; i.e., that a broadcast of state-change was received.
- Assert.assertTrue(result);
+ verify(mMockMapClientService,
+ timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(2)).sendBroadcast(
+ mIntentArgument.capture(), eq(ProfileService.BLUETOOTH_PERM));
+ Assert.assertEquals(BluetoothProfile.STATE_CONNECTED, mMceStateMachine.getState());
+ }
+
+ /**
+ * Test receiving an empty event report
+ */
+ @Test
+ public void testReceiveEmptyEvent() {
+ setupSdpRecordReceipt();
+ Message msg = Message.obtain(mHandler, MceStateMachine.MSG_MAS_CONNECTED);
+ mMceStateMachine.sendMessage(msg);
+
+ // Wait until the message is processed and a broadcast request is sent to
+ // to MapClientService to change
+ // state from STATE_CONNECTING to STATE_CONNECTED
+ verify(mMockMapClientService,
+ timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(2)).sendBroadcast(
+ mIntentArgument.capture(), eq(ProfileService.BLUETOOTH_PERM));
+ Assert.assertEquals(BluetoothProfile.STATE_CONNECTED, mMceStateMachine.getState());
+
+ // Send an empty notification event, verify the mMceStateMachine is still connected
+ Message notification = Message.obtain(mHandler, MceStateMachine.MSG_NOTIFICATION);
+ mMceStateMachine.getCurrentState().processMessage(msg);
Assert.assertEquals(BluetoothProfile.STATE_CONNECTED, mMceStateMachine.getState());
}
private void setupSdpRecordReceipt() {
+ // Perform first part of MAP connection logic.
+ verify(mMockMapClientService,
+ timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).sendBroadcast(
+ mIntentArgument.capture(), eq(ProfileService.BLUETOOTH_PERM));
+ Assert.assertEquals(BluetoothProfile.STATE_CONNECTING, mMceStateMachine.getState());
+
// Setup receipt of SDP record
- SdpMasRecord record = new SdpMasRecord(1, 1, 1, 1, 1, 1, "blah");
+ SdpMasRecord record = new SdpMasRecord(1, 1, 1, 1, 1, 1, "MasRecord");
Message msg = Message.obtain(mHandler, MceStateMachine.MSG_MAS_SDP_DONE, record);
- mMceStateMachine.getCurrentState().processMessage(msg);
+ mMceStateMachine.sendMessage(msg);
}
- private class FakeMapClientService extends MapClientService {
- @Override
- void cleanupDevice(BluetoothDevice device) {}
- @Override
- public void sendBroadcast(Intent intent, String receiverPermission) {
- int prevState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, -1);
- int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1);
- Log.i(TAG, "received broadcast: prevState = " + prevState
- + ", state = " + state);
- if (prevState == BluetoothProfile.STATE_CONNECTING
- && state == BluetoothProfile.STATE_CONNECTED) {
- mConnectedLatch.countDown();
- } else if (prevState == BluetoothProfile.STATE_CONNECTING
- && state == BluetoothProfile.STATE_DISCONNECTED) {
- mDisconnectedLatch.countDown();
- }
- }
- }
}
diff --git a/tests/unit/src/com/android/bluetooth/mapclient/MapClientTest.java b/tests/unit/src/com/android/bluetooth/mapclient/MapClientTest.java
index a8ac084..bc25a11 100644
--- a/tests/unit/src/com/android/bluetooth/mapclient/MapClientTest.java
+++ b/tests/unit/src/com/android/bluetooth/mapclient/MapClientTest.java
@@ -16,17 +16,22 @@
package com.android.bluetooth.mapclient;
+import static org.mockito.Mockito.*;
+
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
import android.content.Context;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.MediumTest;
-import android.support.test.rule.ServiceTestRule;
-import android.support.test.runner.AndroidJUnit4;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.MediumTest;
+import androidx.test.rule.ServiceTestRule;
+import androidx.test.runner.AndroidJUnit4;
import com.android.bluetooth.R;
import com.android.bluetooth.TestUtils;
import com.android.bluetooth.btservice.AdapterService;
+import com.android.bluetooth.btservice.storage.DatabaseManager;
import org.junit.After;
import org.junit.Assert;
@@ -52,6 +57,7 @@
@Mock private AdapterService mAdapterService;
@Mock private MnsService mMockMnsService;
+ @Mock private DatabaseManager mDatabaseManager;
@Rule public final ServiceTestRule mServiceRule = new ServiceTestRule();
@@ -68,6 +74,7 @@
Assert.assertNotNull(mService);
cleanUpInstanceMap();
mAdapter = BluetoothAdapter.getDefaultAdapter();
+ when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
}
@After
@@ -91,6 +98,17 @@
Assert.assertTrue(mService.getInstanceMap().isEmpty());
}
+ /**
+ * Mock the priority of a bluetooth device
+ *
+ * @param device - The bluetooth device you wish to mock the priority of
+ * @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))
+ .thenReturn(priority);
+ }
+
@Test
public void testInitialize() {
Assert.assertNotNull(MapClientService.getMapClientService());
@@ -106,6 +124,7 @@
Assert.assertNull(mService.getInstanceMap().get(device));
// connect a bluetooth device
+ mockDevicePriority(device, BluetoothProfile.PRIORITY_ON);
Assert.assertTrue(mService.connect(device));
// is the statemachine created
@@ -115,6 +134,25 @@
}
/**
+ * Test that a PRIORITY_OFF device is not connected to
+ */
+ @Test
+ 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);
+ Assert.assertFalse(mService.connect(device));
+
+ // is the statemachine created
+ Map<BluetoothDevice, MceStateMachine> map = mService.getInstanceMap();
+ Assert.assertEquals(0, map.size());
+ Assert.assertNull(map.get(device));
+ }
+
+ /**
* Test connecting MAXIMUM_CONNECTED_DEVICES devices.
*/
@Test
@@ -131,8 +169,9 @@
Assert.assertNull(mService.getInstanceMap().get(d));
}
- // run the test - connect all devices
+ // run the test - connect all devices, set their priorities to on
for (BluetoothDevice d : list) {
+ mockDevicePriority(d, BluetoothProfile.PRIORITY_ON);
Assert.assertTrue(mService.connect(d));
}
diff --git a/tests/unit/src/com/android/bluetooth/newavrcp/BrowserPlayerWrapperTest.java b/tests/unit/src/com/android/bluetooth/newavrcp/BrowserPlayerWrapperTest.java
index 66eac21..38d9c58 100644
--- a/tests/unit/src/com/android/bluetooth/newavrcp/BrowserPlayerWrapperTest.java
+++ b/tests/unit/src/com/android/bluetooth/newavrcp/BrowserPlayerWrapperTest.java
@@ -20,8 +20,9 @@
import android.media.MediaDescription;
import android.media.browse.MediaBrowser.MediaItem;
-import android.support.test.filters.SmallTest;
-import android.support.test.runner.AndroidJUnit4;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
import org.junit.Assert;
import org.junit.Before;
diff --git a/tests/unit/src/com/android/bluetooth/newavrcp/MediaPlayerWrapperTest.java b/tests/unit/src/com/android/bluetooth/newavrcp/MediaPlayerWrapperTest.java
index 85ad147..ee41320 100644
--- a/tests/unit/src/com/android/bluetooth/newavrcp/MediaPlayerWrapperTest.java
+++ b/tests/unit/src/com/android/bluetooth/newavrcp/MediaPlayerWrapperTest.java
@@ -24,11 +24,12 @@
import android.media.session.PlaybackState;
import android.os.HandlerThread;
import android.os.TestLooperManager;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.SmallTest;
-import android.support.test.runner.AndroidJUnit4;
import android.util.Log;
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
@@ -151,23 +152,24 @@
@Test
public void testIsReady() {
MediaPlayerWrapper wrapper = MediaPlayerWrapper.wrap(mMockController, mThread.getLooper());
- Assert.assertTrue(wrapper.isReady());
+ Assert.assertTrue(wrapper.isPlaybackStateReady());
+ Assert.assertTrue(wrapper.isMetadataReady());
- // Test isReady() is false when the playback state is null
+ // Test isPlaybackStateReady() is false when the playback state is null
doReturn(null).when(mMockController).getPlaybackState();
- Assert.assertFalse(wrapper.isReady());
+ Assert.assertFalse(wrapper.isPlaybackStateReady());
// Restore the old playback state
doReturn(mTestState.build()).when(mMockController).getPlaybackState();
- Assert.assertTrue(wrapper.isReady());
+ Assert.assertTrue(wrapper.isPlaybackStateReady());
- // Test isReady() is false when the metadata is null
+ // Test isMetadataReady() is false when the metadata is null
doReturn(null).when(mMockController).getMetadata();
- Assert.assertFalse(wrapper.isReady());
+ Assert.assertFalse(wrapper.isMetadataReady());
// Restore the old metadata
doReturn(mTestMetadata.build()).when(mMockController).getMetadata();
- Assert.assertTrue(wrapper.isReady());
+ Assert.assertTrue(wrapper.isMetadataReady());
}
/*
@@ -178,7 +180,8 @@
public void testControllerUpdate() {
// Create the wrapper object and register the looper with the timeout handler
MediaPlayerWrapper wrapper = MediaPlayerWrapper.wrap(mMockController, mThread.getLooper());
- Assert.assertTrue(wrapper.isReady());
+ Assert.assertTrue(wrapper.isPlaybackStateReady());
+ Assert.assertTrue(wrapper.isMetadataReady());
wrapper.registerCallback(mTestCbs);
// Create a new MediaController that has different metadata than the previous controller
diff --git a/tests/unit/src/com/android/bluetooth/opp/BluetoothOppServiceTest.java b/tests/unit/src/com/android/bluetooth/opp/BluetoothOppServiceTest.java
index 8b40c62..7f479d4 100644
--- a/tests/unit/src/com/android/bluetooth/opp/BluetoothOppServiceTest.java
+++ b/tests/unit/src/com/android/bluetooth/opp/BluetoothOppServiceTest.java
@@ -17,10 +17,11 @@
import android.bluetooth.BluetoothAdapter;
import android.content.Context;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.MediumTest;
-import android.support.test.rule.ServiceTestRule;
-import android.support.test.runner.AndroidJUnit4;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.MediumTest;
+import androidx.test.rule.ServiceTestRule;
+import androidx.test.runner.AndroidJUnit4;
import com.android.bluetooth.R;
import com.android.bluetooth.TestUtils;
diff --git a/tests/unit/src/com/android/bluetooth/pan/PanServiceTest.java b/tests/unit/src/com/android/bluetooth/pan/PanServiceTest.java
index ca96285..6d4574f 100644
--- a/tests/unit/src/com/android/bluetooth/pan/PanServiceTest.java
+++ b/tests/unit/src/com/android/bluetooth/pan/PanServiceTest.java
@@ -17,10 +17,11 @@
import android.bluetooth.BluetoothAdapter;
import android.content.Context;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.MediumTest;
-import android.support.test.rule.ServiceTestRule;
-import android.support.test.runner.AndroidJUnit4;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.MediumTest;
+import androidx.test.rule.ServiceTestRule;
+import androidx.test.runner.AndroidJUnit4;
import com.android.bluetooth.R;
import com.android.bluetooth.TestUtils;
diff --git a/tests/unit/src/com/android/bluetooth/pbap/BluetoothPbapServiceTest.java b/tests/unit/src/com/android/bluetooth/pbap/BluetoothPbapServiceTest.java
index e3dcf8c..8990eb7 100644
--- a/tests/unit/src/com/android/bluetooth/pbap/BluetoothPbapServiceTest.java
+++ b/tests/unit/src/com/android/bluetooth/pbap/BluetoothPbapServiceTest.java
@@ -17,10 +17,11 @@
import android.bluetooth.BluetoothAdapter;
import android.content.Context;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.MediumTest;
-import android.support.test.rule.ServiceTestRule;
-import android.support.test.runner.AndroidJUnit4;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.MediumTest;
+import androidx.test.rule.ServiceTestRule;
+import androidx.test.runner.AndroidJUnit4;
import com.android.bluetooth.R;
import com.android.bluetooth.TestUtils;
diff --git a/tests/unit/src/com/android/bluetooth/pbap/PbapStateMachineTest.java b/tests/unit/src/com/android/bluetooth/pbap/PbapStateMachineTest.java
index 0f085e8..516235f 100644
--- a/tests/unit/src/com/android/bluetooth/pbap/PbapStateMachineTest.java
+++ b/tests/unit/src/com/android/bluetooth/pbap/PbapStateMachineTest.java
@@ -25,9 +25,10 @@
import android.content.Context;
import android.os.Handler;
import android.os.HandlerThread;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.MediumTest;
-import android.support.test.runner.AndroidJUnit4;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.MediumTest;
+import androidx.test.runner.AndroidJUnit4;
import com.android.bluetooth.R;
import com.android.bluetooth.TestUtils;
diff --git a/tests/unit/src/com/android/bluetooth/pbapclient/PbapClientServiceTest.java b/tests/unit/src/com/android/bluetooth/pbapclient/PbapClientServiceTest.java
index dd33d79..5e9b5ae 100644
--- a/tests/unit/src/com/android/bluetooth/pbapclient/PbapClientServiceTest.java
+++ b/tests/unit/src/com/android/bluetooth/pbapclient/PbapClientServiceTest.java
@@ -17,10 +17,11 @@
import android.bluetooth.BluetoothAdapter;
import android.content.Context;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.MediumTest;
-import android.support.test.rule.ServiceTestRule;
-import android.support.test.runner.AndroidJUnit4;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.MediumTest;
+import androidx.test.rule.ServiceTestRule;
+import androidx.test.runner.AndroidJUnit4;
import com.android.bluetooth.R;
import com.android.bluetooth.TestUtils;
diff --git a/tests/unit/src/com/android/bluetooth/pbapclient/PbapParserTest.java b/tests/unit/src/com/android/bluetooth/pbapclient/PbapParserTest.java
index f8ad6bf..667e2b7 100644
--- a/tests/unit/src/com/android/bluetooth/pbapclient/PbapParserTest.java
+++ b/tests/unit/src/com/android/bluetooth/pbapclient/PbapParserTest.java
@@ -24,9 +24,10 @@
import android.net.Uri;
import android.provider.CallLog.Calls;
import android.provider.ContactsContract;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.MediumTest;
-import android.support.test.runner.AndroidJUnit4;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.MediumTest;
+import androidx.test.runner.AndroidJUnit4;
import com.android.bluetooth.R;
diff --git a/tests/unit/src/com/android/bluetooth/sap/SapServiceTest.java b/tests/unit/src/com/android/bluetooth/sap/SapServiceTest.java
index 8505673..bc5bffe 100644
--- a/tests/unit/src/com/android/bluetooth/sap/SapServiceTest.java
+++ b/tests/unit/src/com/android/bluetooth/sap/SapServiceTest.java
@@ -17,10 +17,11 @@
import android.bluetooth.BluetoothAdapter;
import android.content.Context;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.MediumTest;
-import android.support.test.rule.ServiceTestRule;
-import android.support.test.runner.AndroidJUnit4;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.MediumTest;
+import androidx.test.rule.ServiceTestRule;
+import androidx.test.runner.AndroidJUnit4;
import com.android.bluetooth.R;
import com.android.bluetooth.TestUtils;