[automerger skipped] Import translations. DO NOT MERGE
am: 349be32e78 -s ours
am skip reason: subject contains skip directive
Change-Id: I19ee70abea07e977f5fa38325c90d72d83e5ca42
diff --git a/Android.mk b/Android.mk
index 5d3db9a..a34d2a3 100644
--- a/Android.mk
+++ b/Android.mk
@@ -25,11 +25,53 @@
sap-api-java-static \
services.net \
libprotobuf-java-lite \
- bluetooth-protos-lite
+ bluetooth-protos-lite \
-LOCAL_STATIC_ANDROID_LIBRARIES := android-support-v4
+LOCAL_STATIC_ANDROID_LIBRARIES := \
+ 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_REQUIRED_MODULES := libbluetooth
LOCAL_PROGUARD_ENABLED := disabled
include $(BUILD_PACKAGE)
include $(call all-makefiles-under,$(LOCAL_PATH))
+
+include $(CLEAR_VARS)
+
+COMMON_LIBS_PATH := ../../../../../prebuilts/tools/common/m2/repository
+ROOM_LIBS_PATH := ../../lib/room
+
+LOCAL_PREBUILT_STATIC_JAVA_LIBRARIES := \
+ bt-androidx-annotation-nodeps:$(ROOM_LIBS_PATH)/annotation-1.0.0-beta01.jar \
+ bt-androidx-room-common-nodeps:$(ROOM_LIBS_PATH)/room-common-2.0.0-beta01.jar \
+ bt-androidx-room-compiler-nodeps:$(ROOM_LIBS_PATH)/room-compiler-2.0.0-beta01.jar \
+ bt-androidx-room-migration-nodeps:$(ROOM_LIBS_PATH)/room-migration-2.0.0-beta01.jar \
+ bt-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 b24064f..8ce4707 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -69,6 +69,8 @@
<uses-permission android:name="android.permission.UPDATE_DEVICE_STATS" />
<uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" />
+ <uses-sdk android:minSdkVersion="14"/>
+
<!-- For PBAP Owner Vcard Info -->
<uses-permission android:name="android.permission.READ_PROFILE"/>
<application
@@ -79,7 +81,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"
@@ -309,7 +312,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">
@@ -351,14 +354,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 c0fdd80..1262469 100644
--- a/jni/Android.bp
+++ b/jni/Android.bp
@@ -8,13 +8,11 @@
"com_android_bluetooth_hfpclient.cpp",
"com_android_bluetooth_a2dp.cpp",
"com_android_bluetooth_a2dp_sink.cpp",
- "com_android_bluetooth_avrcp.cpp",
"com_android_bluetooth_avrcp_controller.cpp",
"com_android_bluetooth_avrcp_target.cpp",
"com_android_bluetooth_hid_host.cpp",
"com_android_bluetooth_hid_device.cpp",
"com_android_bluetooth_hearing_aid.cpp",
- "com_android_bluetooth_hdp.cpp",
"com_android_bluetooth_pan.cpp",
"com_android_bluetooth_gatt.cpp",
"com_android_bluetooth_sdp.cpp",
@@ -33,10 +31,10 @@
"libchrome",
"libnativehelper",
"liblog",
+ "libutils",
],
static_libs: [
"libbluetooth-types",
- "libutils",
"libcutils",
],
cflags: [
@@ -45,4 +43,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 9f136a0..2076779 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);
diff --git a/jni/com_android_bluetooth_a2dp.cpp b/jni/com_android_bluetooth_a2dp.cpp
index 126405a..af125bd 100644
--- a/jni/com_android_bluetooth_a2dp.cpp
+++ b/jni/com_android_bluetooth_a2dp.cpp
@@ -390,6 +390,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);
@@ -405,6 +432,9 @@
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_active_device(bd_addr);
if (status != BT_STATUS_SUCCESS) {
ALOGE("%s: Failed A2DP set_active_device, status: %d", __func__, status);
@@ -450,6 +480,7 @@
{"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_a2dp_sink.cpp b/jni/com_android_bluetooth_a2dp_sink.cpp
index 50c5087..91de5af 100644
--- a/jni/com_android_bluetooth_a2dp_sink.cpp
+++ b/jni/com_android_bluetooth_a2dp_sink.cpp
@@ -231,7 +231,7 @@
int register_com_android_bluetooth_a2dp_sink(JNIEnv* env) {
return jniRegisterNativeMethods(
- env, "com/android/bluetooth/a2dpsink/A2dpSinkStateMachine", sMethods,
+ env, "com/android/bluetooth/a2dpsink/A2dpSinkService", sMethods,
NELEM(sMethods));
}
}
diff --git a/jni/com_android_bluetooth_avrcp.cpp b/jni/com_android_bluetooth_avrcp.cpp
deleted file mode 100644
index 1eb3553..0000000
--- a/jni/com_android_bluetooth_avrcp.cpp
+++ /dev/null
@@ -1,1529 +0,0 @@
-/*
- * Copyright (C) 2016 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 "BluetoothAvrcpServiceJni"
-
-#define LOG_NDEBUG 0
-
-#include "android_runtime/AndroidRuntime.h"
-#include "com_android_bluetooth.h"
-#include "hardware/bt_rc.h"
-#include "utils/Log.h"
-
-#include <inttypes.h>
-#include <string.h>
-
-namespace android {
-static jmethodID method_getRcFeatures;
-static jmethodID method_getPlayStatus;
-static jmethodID method_getElementAttr;
-static jmethodID method_registerNotification;
-static jmethodID method_volumeChangeCallback;
-static jmethodID method_handlePassthroughCmd;
-static jmethodID method_getFolderItemsCallback;
-static jmethodID method_setAddressedPlayerCallback;
-
-static jmethodID method_setBrowsedPlayerCallback;
-static jmethodID method_changePathCallback;
-static jmethodID method_searchCallback;
-static jmethodID method_playItemCallback;
-static jmethodID method_getItemAttrCallback;
-static jmethodID method_addToPlayListCallback;
-static jmethodID method_getTotalNumOfItemsCallback;
-
-static const btrc_interface_t* sBluetoothAvrcpInterface = NULL;
-static jobject mCallbacksObj = NULL;
-
-/* Function declarations */
-static bool copy_item_attributes(JNIEnv* env, jobject object,
- btrc_folder_items_t* pitem,
- jint* p_attributesIds,
- jobjectArray attributesArray, int item_idx,
- int attribCopiedIndex);
-
-static bool copy_jstring(uint8_t* str, int maxBytes, jstring jstr, JNIEnv* env);
-
-static void cleanup_items(btrc_folder_items_t* p_items, int numItems);
-
-static void btavrcp_remote_features_callback(const RawAddress& bd_addr,
- btrc_remote_features_t features) {
- CallbackEnv sCallbackEnv(__func__);
- if (!sCallbackEnv.valid()) return;
-
- if (!mCallbacksObj) {
- ALOGE("%s: mCallbacksObj is null", __func__);
- return;
- }
-
- ScopedLocalRef<jbyteArray> addr(
- sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
- if (!addr.get()) {
- ALOGE("Unable to allocate byte array for bd_addr");
- return;
- }
-
- sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
- (jbyte*)bd_addr.address);
- sCallbackEnv->CallVoidMethod(mCallbacksObj, method_getRcFeatures, addr.get(),
- (jint)features);
-}
-
-/** Callback for play status request */
-static void btavrcp_get_play_status_callback(const RawAddress& bd_addr) {
- CallbackEnv sCallbackEnv(__func__);
- if (!sCallbackEnv.valid()) return;
-
- if (!mCallbacksObj) {
- ALOGE("%s: mCallbacksObj is null", __func__);
- return;
- }
-
- ScopedLocalRef<jbyteArray> addr(
- sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
- if (!addr.get()) {
- ALOGE("Fail to new jbyteArray bd addr for get_play_status command");
- return;
- }
-
- sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
- (jbyte*)bd_addr.address);
- sCallbackEnv->CallVoidMethod(mCallbacksObj, method_getPlayStatus, addr.get());
-}
-
-static void btavrcp_get_element_attr_callback(uint8_t num_attr,
- btrc_media_attr_t* p_attrs,
- const RawAddress& bd_addr) {
- CallbackEnv sCallbackEnv(__func__);
- if (!sCallbackEnv.valid()) return;
-
- if (!mCallbacksObj) {
- ALOGE("%s: mCallbacksObj is null", __func__);
- return;
- }
-
- ScopedLocalRef<jbyteArray> addr(
- sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
- if (!addr.get()) {
- ALOGE("Fail to new jbyteArray bd addr for get_element_attr command");
- return;
- }
-
- ScopedLocalRef<jintArray> attrs(
- sCallbackEnv.get(), (jintArray)sCallbackEnv->NewIntArray(num_attr));
- if (!attrs.get()) {
- ALOGE("Fail to new jintArray for attrs");
- return;
- }
-
- sCallbackEnv->SetIntArrayRegion(attrs.get(), 0, num_attr, (jint*)p_attrs);
-
- sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
- (jbyte*)bd_addr.address);
- sCallbackEnv->CallVoidMethod(mCallbacksObj, method_getElementAttr, addr.get(),
- (jbyte)num_attr, attrs.get());
-}
-
-static void btavrcp_register_notification_callback(btrc_event_id_t event_id,
- uint32_t param,
- const RawAddress& bd_addr) {
- CallbackEnv sCallbackEnv(__func__);
- if (!sCallbackEnv.valid()) return;
-
- if (!mCallbacksObj) {
- ALOGE("%s: mCallbacksObj is null", __func__);
- return;
- }
-
- ScopedLocalRef<jbyteArray> addr(
- sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
- if (!addr.get()) {
- ALOGE("Fail to new jbyteArray bd addr for register_notification command");
- return;
- }
-
- sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
- (jbyte*)bd_addr.address);
- sCallbackEnv->CallVoidMethod(mCallbacksObj, method_registerNotification,
- addr.get(), (jint)event_id, (jint)param);
-}
-
-static void btavrcp_volume_change_callback(uint8_t volume, uint8_t ctype,
- const RawAddress& bd_addr) {
- CallbackEnv sCallbackEnv(__func__);
- if (!sCallbackEnv.valid()) return;
-
- if (!mCallbacksObj) {
- ALOGE("%s: mCallbacksObj is null", __func__);
- return;
- }
-
- ScopedLocalRef<jbyteArray> addr(
- sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
- if (!addr.get()) {
- ALOGE("Fail to new jbyteArray bd addr for volume_change command");
- return;
- }
-
- sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
- (jbyte*)bd_addr.address);
-
- sCallbackEnv->CallVoidMethod(mCallbacksObj, method_volumeChangeCallback,
- addr.get(), (jint)volume, (jint)ctype);
-}
-
-static void btavrcp_passthrough_command_callback(int id, int pressed,
- const RawAddress& bd_addr) {
- CallbackEnv sCallbackEnv(__func__);
- if (!sCallbackEnv.valid()) return;
-
- if (!mCallbacksObj) {
- ALOGE("%s: mCallbacksObj 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_command command");
- return;
- }
- sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
- (jbyte*)bd_addr.address);
-
- sCallbackEnv->CallVoidMethod(mCallbacksObj, method_handlePassthroughCmd,
- addr.get(), (jint)id, (jint)pressed);
-}
-
-static void btavrcp_set_addressed_player_callback(uint16_t player_id,
- const RawAddress& bd_addr) {
- CallbackEnv sCallbackEnv(__func__);
- if (!sCallbackEnv.valid()) return;
-
- if (!mCallbacksObj) {
- ALOGE("%s: mCallbacksObj is null", __func__);
- return;
- }
-
- ScopedLocalRef<jbyteArray> addr(
- sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
- if (!addr.get()) {
- ALOGE("Fail to new jbyteArray bd addr for set_addressed_player command");
- return;
- }
-
- sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
- (jbyte*)bd_addr.address);
- sCallbackEnv->CallVoidMethod(mCallbacksObj, method_setAddressedPlayerCallback,
- addr.get(), (jint)player_id);
-}
-
-static void btavrcp_set_browsed_player_callback(uint16_t player_id,
- const RawAddress& bd_addr) {
- CallbackEnv sCallbackEnv(__func__);
- if (!sCallbackEnv.valid()) return;
- if (!mCallbacksObj) {
- ALOGE("%s: mCallbacksObj is null", __func__);
- return;
- }
-
- ScopedLocalRef<jbyteArray> addr(
- sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
- if (!addr.get()) {
- ALOGE("Fail to new jbyteArray bd addr for set_browsed_player command");
- return;
- }
- sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
- (jbyte*)bd_addr.address);
-
- sCallbackEnv->CallVoidMethod(mCallbacksObj, method_setBrowsedPlayerCallback,
- addr.get(), (jint)player_id);
-}
-
-static void btavrcp_get_folder_items_callback(
- uint8_t scope, uint32_t start_item, uint32_t end_item, uint8_t num_attr,
- uint32_t* p_attr_ids, const RawAddress& bd_addr) {
- CallbackEnv sCallbackEnv(__func__);
- if (!sCallbackEnv.valid()) return;
-
- if (!mCallbacksObj) {
- ALOGE("%s: mCallbacksObj is null", __func__);
- return;
- }
-
- ScopedLocalRef<jbyteArray> addr(
- sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
- if (!addr.get()) {
- ALOGE("Fail to new jbyteArray bd addr for get_folder_items command");
- return;
- }
-
- uint32_t* puiAttr = (uint32_t*)p_attr_ids;
- ScopedLocalRef<jintArray> attr_ids(sCallbackEnv.get(), NULL);
- sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
- (jbyte*)bd_addr.address);
-
- /* check number of attributes requested by remote device */
- if ((num_attr != BTRC_NUM_ATTR_ALL) && (num_attr != BTRC_NUM_ATTR_NONE)) {
- /* allocate memory for attr_ids only if some attributes passed from below
- * layer */
- attr_ids.reset((jintArray)sCallbackEnv->NewIntArray(num_attr));
- if (!attr_ids.get()) {
- ALOGE("Fail to allocate new jintArray for attrs");
- return;
- }
- sCallbackEnv->SetIntArrayRegion(attr_ids.get(), 0, num_attr,
- (jint*)puiAttr);
- }
-
- sCallbackEnv->CallVoidMethod(
- mCallbacksObj, method_getFolderItemsCallback, addr.get(), (jbyte)scope,
- (jlong)start_item, (jlong)end_item, (jbyte)num_attr, attr_ids.get());
-}
-
-static void btavrcp_change_path_callback(uint8_t direction, uint8_t* folder_uid,
- const RawAddress& bd_addr) {
- CallbackEnv sCallbackEnv(__func__);
- if (!sCallbackEnv.valid()) return;
-
- if (!mCallbacksObj) {
- ALOGE("%s: mCallbacksObj is null", __func__);
- return;
- }
-
- ScopedLocalRef<jbyteArray> attrs(sCallbackEnv.get(),
- sCallbackEnv->NewByteArray(BTRC_UID_SIZE));
- if (!attrs.get()) {
- ALOGE("Fail to new jintArray for attrs");
- return;
- }
-
- ScopedLocalRef<jbyteArray> addr(
- sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
- if (!addr.get()) {
- ALOGE("Fail to new jbyteArray bd addr for change_path command");
- return;
- }
-
- sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
- (jbyte*)bd_addr.address);
- sCallbackEnv->SetByteArrayRegion(
- attrs.get(), 0, sizeof(uint8_t) * BTRC_UID_SIZE, (jbyte*)folder_uid);
- sCallbackEnv->CallVoidMethod(mCallbacksObj, method_changePathCallback,
- addr.get(), (jbyte)direction, attrs.get());
-}
-
-static void btavrcp_get_item_attr_callback(uint8_t scope, uint8_t* uid,
- uint16_t uid_counter,
- uint8_t num_attr,
- btrc_media_attr_t* p_attrs,
- const RawAddress& bd_addr) {
- CallbackEnv sCallbackEnv(__func__);
- if (!sCallbackEnv.valid()) return;
-
- if (!mCallbacksObj) {
- ALOGE("%s: mCallbacksObj is null", __func__);
- return;
- }
-
- ScopedLocalRef<jbyteArray> attr_uid(
- sCallbackEnv.get(), sCallbackEnv->NewByteArray(BTRC_UID_SIZE));
- if (!attr_uid.get()) {
- ALOGE("Fail to new jintArray for attr_uid");
- return;
- }
-
- ScopedLocalRef<jbyteArray> addr(
- sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
- if (!addr.get()) {
- ALOGE("Fail to new jbyteArray bd addr for get_item_attr command");
- return;
- }
-
- ScopedLocalRef<jintArray> attrs(
- sCallbackEnv.get(), (jintArray)sCallbackEnv->NewIntArray(num_attr));
- if (!attrs.get()) {
- ALOGE("Fail to new jintArray for attrs");
- return;
- }
-
- sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
- (jbyte*)bd_addr.address);
- sCallbackEnv->SetIntArrayRegion(attrs.get(), 0, num_attr, (jint*)p_attrs);
- sCallbackEnv->SetByteArrayRegion(
- attr_uid.get(), 0, sizeof(uint8_t) * BTRC_UID_SIZE, (jbyte*)uid);
-
- sCallbackEnv->CallVoidMethod(mCallbacksObj, method_getItemAttrCallback,
- addr.get(), (jbyte)scope, attr_uid.get(),
- (jint)uid_counter, (jbyte)num_attr, attrs.get());
-}
-
-static void btavrcp_play_item_callback(uint8_t scope, uint16_t uid_counter,
- uint8_t* uid,
- const RawAddress& bd_addr) {
- CallbackEnv sCallbackEnv(__func__);
- if (!sCallbackEnv.valid()) return;
- if (!mCallbacksObj) {
- ALOGE("%s: mCallbacksObj is null", __func__);
- return;
- }
-
- ScopedLocalRef<jbyteArray> attrs(sCallbackEnv.get(),
- sCallbackEnv->NewByteArray(BTRC_UID_SIZE));
- if (!attrs.get()) {
- ALOGE("%s: Fail to new jByteArray attrs for play_item command", __func__);
- return;
- }
-
- ScopedLocalRef<jbyteArray> addr(
- sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
- if (!addr.get()) {
- ALOGE("Fail to new jbyteArray bd addr for play_item command");
- return;
- }
-
- sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
- (jbyte*)bd_addr.address);
- sCallbackEnv->SetByteArrayRegion(
- attrs.get(), 0, sizeof(uint8_t) * BTRC_UID_SIZE, (jbyte*)uid);
- sCallbackEnv->CallVoidMethod(mCallbacksObj, method_playItemCallback,
- addr.get(), (jbyte)scope, (jint)uid_counter,
- attrs.get());
-}
-
-static void btavrcp_get_total_num_items_callback(uint8_t scope,
- const RawAddress& bd_addr) {
- CallbackEnv sCallbackEnv(__func__);
- if (!sCallbackEnv.valid()) return;
- if (!mCallbacksObj) {
- ALOGE("%s: mCallbacksObj is null", __func__);
- return;
- }
-
- ScopedLocalRef<jbyteArray> addr(
- sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
- if (!addr.get()) {
- ALOGE("Fail to new jbyteArray bd addr for get_total_num_items command");
- return;
- }
-
- sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
- (jbyte*)bd_addr.address);
- sCallbackEnv->CallVoidMethod(mCallbacksObj, method_getTotalNumOfItemsCallback,
- addr.get(), (jbyte)scope);
-}
-
-static void btavrcp_search_callback(uint16_t charset_id, uint16_t str_len,
- uint8_t* p_str, const RawAddress& bd_addr) {
- CallbackEnv sCallbackEnv(__func__);
- if (!sCallbackEnv.valid()) return;
- if (!mCallbacksObj) {
- ALOGE("%s: mCallbacksObj is null", __func__);
- return;
- }
-
- ScopedLocalRef<jbyteArray> attrs(sCallbackEnv.get(),
- sCallbackEnv->NewByteArray(str_len));
- if (!attrs.get()) {
- ALOGE("Fail to new jintArray for attrs");
- return;
- }
-
- ScopedLocalRef<jbyteArray> addr(
- sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
- if (!addr.get()) {
- ALOGE("Fail to new jbyteArray bd addr for search command");
- return;
- }
-
- sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
- (jbyte*)bd_addr.address);
- sCallbackEnv->SetByteArrayRegion(attrs.get(), 0, str_len * sizeof(uint8_t),
- (jbyte*)p_str);
- sCallbackEnv->CallVoidMethod(mCallbacksObj, method_searchCallback, addr.get(),
- (jint)charset_id, attrs.get());
-}
-
-static void btavrcp_add_to_play_list_callback(uint8_t scope, uint8_t* uid,
- uint16_t uid_counter,
- const RawAddress& bd_addr) {
- CallbackEnv sCallbackEnv(__func__);
- if (!sCallbackEnv.valid()) return;
- if (!mCallbacksObj) {
- ALOGE("%s: mCallbacksObj is null", __func__);
- return;
- }
-
- ScopedLocalRef<jbyteArray> addr(
- sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
- if (!addr.get()) {
- ALOGE("Fail to new jbyteArray bd addr for add_to_play_list command");
- return;
- }
-
- ScopedLocalRef<jbyteArray> attrs(sCallbackEnv.get(),
- sCallbackEnv->NewByteArray(BTRC_UID_SIZE));
- if (!attrs.get()) {
- ALOGE("Fail to new jByteArray for attrs");
- return;
- }
-
- sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
- (jbyte*)bd_addr.address);
- sCallbackEnv->SetByteArrayRegion(
- attrs.get(), 0, sizeof(uint8_t) * BTRC_UID_SIZE, (jbyte*)uid);
- sCallbackEnv->CallVoidMethod(mCallbacksObj, method_addToPlayListCallback,
- addr.get(), (jbyte)scope, attrs.get(),
- (jint)uid_counter);
-}
-
-static btrc_callbacks_t sBluetoothAvrcpCallbacks = {
- sizeof(sBluetoothAvrcpCallbacks),
- btavrcp_remote_features_callback,
- btavrcp_get_play_status_callback,
- NULL,
- NULL,
- NULL,
- NULL,
- NULL,
- NULL,
- btavrcp_get_element_attr_callback,
- btavrcp_register_notification_callback,
- btavrcp_volume_change_callback,
- btavrcp_passthrough_command_callback,
- btavrcp_set_addressed_player_callback,
- btavrcp_set_browsed_player_callback,
- btavrcp_get_folder_items_callback,
- btavrcp_change_path_callback,
- btavrcp_get_item_attr_callback,
- btavrcp_play_item_callback,
- btavrcp_get_total_num_items_callback,
- btavrcp_search_callback,
- btavrcp_add_to_play_list_callback,
-};
-
-static void classInitNative(JNIEnv* env, jclass clazz) {
- method_getRcFeatures =
- env->GetMethodID(clazz, "getRcFeaturesRequestFromNative", "([BI)V");
- method_getPlayStatus =
- env->GetMethodID(clazz, "getPlayStatusRequestFromNative", "([B)V");
-
- method_getElementAttr =
- env->GetMethodID(clazz, "getElementAttrRequestFromNative", "([BB[I)V");
-
- method_registerNotification = env->GetMethodID(
- clazz, "registerNotificationRequestFromNative", "([BII)V");
-
- method_volumeChangeCallback =
- env->GetMethodID(clazz, "volumeChangeRequestFromNative", "([BII)V");
-
- method_handlePassthroughCmd = env->GetMethodID(
- clazz, "handlePassthroughCmdRequestFromNative", "([BII)V");
-
- method_setAddressedPlayerCallback =
- env->GetMethodID(clazz, "setAddressedPlayerRequestFromNative", "([BI)V");
-
- method_setBrowsedPlayerCallback =
- env->GetMethodID(clazz, "setBrowsedPlayerRequestFromNative", "([BI)V");
-
- method_getFolderItemsCallback =
- env->GetMethodID(clazz, "getFolderItemsRequestFromNative", "([BBJJB[I)V");
-
- method_changePathCallback =
- env->GetMethodID(clazz, "changePathRequestFromNative", "([BB[B)V");
-
- method_getItemAttrCallback =
- env->GetMethodID(clazz, "getItemAttrRequestFromNative", "([BB[BIB[I)V");
-
- method_playItemCallback =
- env->GetMethodID(clazz, "playItemRequestFromNative", "([BBI[B)V");
-
- method_getTotalNumOfItemsCallback =
- env->GetMethodID(clazz, "getTotalNumOfItemsRequestFromNative", "([BB)V");
-
- method_searchCallback =
- env->GetMethodID(clazz, "searchRequestFromNative", "([BI[B)V");
-
- method_addToPlayListCallback =
- env->GetMethodID(clazz, "addToPlayListRequestFromNative", "([BB[BI)V");
-
- ALOGI("%s: succeeds", __func__);
-}
-
-static void initNative(JNIEnv* env, jobject object) {
- const bt_interface_t* btInf = getBluetoothInterface();
- if (btInf == NULL) {
- ALOGE("Bluetooth module is not loaded");
- return;
- }
-
- if (sBluetoothAvrcpInterface != NULL) {
- ALOGW("Cleaning up Avrcp Interface before initializing...");
- sBluetoothAvrcpInterface->cleanup();
- sBluetoothAvrcpInterface = NULL;
- }
-
- if (mCallbacksObj != NULL) {
- ALOGW("Cleaning up Avrcp callback object");
- env->DeleteGlobalRef(mCallbacksObj);
- mCallbacksObj = NULL;
- }
-
- sBluetoothAvrcpInterface =
- (btrc_interface_t*)btInf->get_profile_interface(BT_PROFILE_AV_RC_ID);
- if (sBluetoothAvrcpInterface == NULL) {
- ALOGE("Failed to get Bluetooth Avrcp Interface");
- return;
- }
-
- bt_status_t status =
- sBluetoothAvrcpInterface->init(&sBluetoothAvrcpCallbacks);
- if (status != BT_STATUS_SUCCESS) {
- ALOGE("Failed to initialize Bluetooth Avrcp, status: %d", status);
- sBluetoothAvrcpInterface = 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 (sBluetoothAvrcpInterface != NULL) {
- sBluetoothAvrcpInterface->cleanup();
- sBluetoothAvrcpInterface = NULL;
- }
-
- if (mCallbacksObj != NULL) {
- env->DeleteGlobalRef(mCallbacksObj);
- mCallbacksObj = NULL;
- }
-}
-
-static jboolean getPlayStatusRspNative(JNIEnv* env, jobject object,
- jbyteArray address, jint playStatus,
- jint songLen, jint songPos) {
- if (!sBluetoothAvrcpInterface) {
- ALOGE("%s: sBluetoothAvrcpInterface is null", __func__);
- return JNI_FALSE;
- }
-
- jbyte* addr = env->GetByteArrayElements(address, NULL);
- if (!addr) {
- jniThrowIOException(env, EINVAL);
- return JNI_FALSE;
- }
- RawAddress rawAddress;
- rawAddress.FromOctets((uint8_t*)addr);
-
- bt_status_t status = sBluetoothAvrcpInterface->get_play_status_rsp(
- rawAddress, (btrc_play_status_t)playStatus, songLen, songPos);
- if (status != BT_STATUS_SUCCESS) {
- ALOGE("Failed get_play_status_rsp, status: %d", status);
- }
- env->ReleaseByteArrayElements(address, addr, 0);
- return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
-}
-
-static jboolean getElementAttrRspNative(JNIEnv* env, jobject object,
- jbyteArray address, jbyte numAttr,
- jintArray attrIds,
- jobjectArray textArray) {
- if (!sBluetoothAvrcpInterface) {
- ALOGE("%s: sBluetoothAvrcpInterface is null", __func__);
- return JNI_FALSE;
- }
-
- if (numAttr > BTRC_MAX_ELEM_ATTR_SIZE) {
- ALOGE("get_element_attr_rsp: number of attributes exceed maximum");
- return JNI_FALSE;
- }
-
- jbyte* addr = env->GetByteArrayElements(address, NULL);
- if (!addr) {
- jniThrowIOException(env, EINVAL);
- return JNI_FALSE;
- }
-
- btrc_element_attr_val_t* pAttrs = new btrc_element_attr_val_t[numAttr];
- if (!pAttrs) {
- ALOGE("get_element_attr_rsp: not have enough memeory");
- env->ReleaseByteArrayElements(address, addr, 0);
- return JNI_FALSE;
- }
-
- jint* attr = env->GetIntArrayElements(attrIds, NULL);
- if (!attr) {
- delete[] pAttrs;
- jniThrowIOException(env, EINVAL);
- env->ReleaseByteArrayElements(address, addr, 0);
- return JNI_FALSE;
- }
-
- int attr_cnt;
- for (attr_cnt = 0; attr_cnt < numAttr; ++attr_cnt) {
- pAttrs[attr_cnt].attr_id = attr[attr_cnt];
- ScopedLocalRef<jstring> text(
- env, (jstring)env->GetObjectArrayElement(textArray, attr_cnt));
-
- if (!copy_jstring(pAttrs[attr_cnt].text, BTRC_MAX_ATTR_STR_LEN, text.get(),
- env)) {
- break;
- }
- }
-
- if (attr_cnt < numAttr) {
- delete[] pAttrs;
- env->ReleaseIntArrayElements(attrIds, attr, 0);
- ALOGE("%s: Failed to copy attributes", __func__);
- return JNI_FALSE;
- }
-
- RawAddress rawAddress;
- rawAddress.FromOctets((uint8_t*)addr);
- bt_status_t status = sBluetoothAvrcpInterface->get_element_attr_rsp(
- rawAddress, numAttr, pAttrs);
- if (status != BT_STATUS_SUCCESS) {
- ALOGE("Failed get_element_attr_rsp, status: %d", status);
- }
-
- delete[] pAttrs;
- env->ReleaseIntArrayElements(attrIds, attr, 0);
- env->ReleaseByteArrayElements(address, addr, 0);
- return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
-}
-
-static jboolean getItemAttrRspNative(JNIEnv* env, jobject object,
- jbyteArray address, jint rspStatus,
- jbyte numAttr, jintArray attrIds,
- jobjectArray textArray) {
- if (!sBluetoothAvrcpInterface) {
- ALOGE("%s: sBluetoothAvrcpInterface is null", __func__);
- return JNI_FALSE;
- }
-
- jbyte* addr = env->GetByteArrayElements(address, NULL);
- if (!addr) {
- jniThrowIOException(env, EINVAL);
- return JNI_FALSE;
- }
-
- if (numAttr > BTRC_MAX_ELEM_ATTR_SIZE) {
- ALOGE("get_element_attr_rsp: number of attributes exceed maximum");
- return JNI_FALSE;
- }
-
- btrc_element_attr_val_t* pAttrs = new btrc_element_attr_val_t[numAttr];
- if (!pAttrs) {
- ALOGE("%s: not have enough memory", __func__);
- env->ReleaseByteArrayElements(address, addr, 0);
- return JNI_FALSE;
- }
-
- jint* attr = NULL;
- if (attrIds != NULL) {
- attr = env->GetIntArrayElements(attrIds, NULL);
- if (!attr) {
- delete[] pAttrs;
- jniThrowIOException(env, EINVAL);
- env->ReleaseByteArrayElements(address, addr, 0);
- return JNI_FALSE;
- }
- }
-
- for (int attr_cnt = 0; attr_cnt < numAttr; ++attr_cnt) {
- pAttrs[attr_cnt].attr_id = attr[attr_cnt];
- ScopedLocalRef<jstring> text(
- env, (jstring)env->GetObjectArrayElement(textArray, attr_cnt));
-
- if (!copy_jstring(pAttrs[attr_cnt].text, BTRC_MAX_ATTR_STR_LEN, text.get(),
- env)) {
- rspStatus = BTRC_STS_INTERNAL_ERR;
- ALOGE("%s: Failed to copy attributes", __func__);
- break;
- }
- }
- RawAddress rawAddress;
- rawAddress.FromOctets((uint8_t*)addr);
-
- bt_status_t status = sBluetoothAvrcpInterface->get_item_attr_rsp(
- rawAddress, (btrc_status_t)rspStatus, numAttr, pAttrs);
- if (status != BT_STATUS_SUCCESS)
- ALOGE("Failed get_item_attr_rsp, status: %d", status);
-
- if (pAttrs) delete[] pAttrs;
- if (attr) env->ReleaseIntArrayElements(attrIds, attr, 0);
- env->ReleaseByteArrayElements(address, addr, 0);
-
- return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
-}
-
-static jboolean registerNotificationRspPlayStatusNative(JNIEnv* env,
- jobject object,
- jint type,
- jint playStatus) {
- if (!sBluetoothAvrcpInterface) {
- ALOGE("%s: sBluetoothAvrcpInterface is null", __func__);
- return JNI_FALSE;
- }
-
- btrc_register_notification_t param;
- param.play_status = (btrc_play_status_t)playStatus;
-
- bt_status_t status = sBluetoothAvrcpInterface->register_notification_rsp(
- BTRC_EVT_PLAY_STATUS_CHANGED, (btrc_notification_type_t)type, ¶m);
- if (status != BT_STATUS_SUCCESS) {
- ALOGE("Failed register_notification_rsp play status, status: %d", status);
- }
-
- return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
-}
-
-static jboolean registerNotificationRspTrackChangeNative(JNIEnv* env,
- jobject object,
- jint type,
- jbyteArray track) {
- if (!sBluetoothAvrcpInterface) {
- ALOGE("%s: sBluetoothAvrcpInterface is null", __func__);
- return JNI_FALSE;
- }
-
- jbyte* trk = env->GetByteArrayElements(track, NULL);
- if (!trk) {
- jniThrowIOException(env, EINVAL);
- return JNI_FALSE;
- }
-
- btrc_register_notification_t param;
- uint64_t uid = 0;
- for (int uid_idx = 0; uid_idx < BTRC_UID_SIZE; ++uid_idx) {
- param.track[uid_idx] = trk[uid_idx];
- uid = uid + (trk[uid_idx] << (BTRC_UID_SIZE - 1 - uid_idx));
- }
-
- ALOGV("%s: Sending track change notification: %d -> %" PRIu64, __func__, type,
- uid);
-
- bt_status_t status = sBluetoothAvrcpInterface->register_notification_rsp(
- BTRC_EVT_TRACK_CHANGE, (btrc_notification_type_t)type, ¶m);
- if (status != BT_STATUS_SUCCESS) {
- ALOGE("Failed register_notification_rsp track change, status: %d", status);
- }
-
- env->ReleaseByteArrayElements(track, trk, 0);
- return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
-}
-
-static jboolean registerNotificationRspPlayPosNative(JNIEnv* env,
- jobject object, jint type,
- jint playPos) {
- if (!sBluetoothAvrcpInterface) {
- ALOGE("%s: sBluetoothAvrcpInterface is null", __func__);
- return JNI_FALSE;
- }
-
- btrc_register_notification_t param;
- param.song_pos = (uint32_t)playPos;
-
- bt_status_t status = sBluetoothAvrcpInterface->register_notification_rsp(
- BTRC_EVT_PLAY_POS_CHANGED, (btrc_notification_type_t)type, ¶m);
- if (status != BT_STATUS_SUCCESS) {
- ALOGE("Failed register_notification_rsp play position, status: %d", status);
- }
-
- return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
-}
-
-static jboolean registerNotificationRspNowPlayingChangedNative(JNIEnv* env,
- jobject object,
- jint type) {
- if (!sBluetoothAvrcpInterface) {
- ALOGE("%s: sBluetoothAvrcpInterface is null", __func__);
- return JNI_FALSE;
- }
-
- btrc_register_notification_t param;
- bt_status_t status = sBluetoothAvrcpInterface->register_notification_rsp(
- BTRC_EVT_NOW_PLAYING_CONTENT_CHANGED, (btrc_notification_type_t)type,
- ¶m);
- if (status != BT_STATUS_SUCCESS) {
- ALOGE("Failed register_notification_rsp, nowPlaying Content status: %d",
- status);
- }
- return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
-}
-
-static jboolean registerNotificationRspUIDsChangedNative(JNIEnv* env,
- jobject object,
- jint type,
- jint uidCounter) {
- if (!sBluetoothAvrcpInterface) {
- ALOGE("%s: sBluetoothAvrcpInterface is null", __func__);
- return JNI_FALSE;
- }
-
- btrc_register_notification_t param;
- param.uids_changed.uid_counter = (uint16_t)uidCounter;
-
- bt_status_t status = sBluetoothAvrcpInterface->register_notification_rsp(
- BTRC_EVT_UIDS_CHANGED, (btrc_notification_type_t)type, ¶m);
- if (status != BT_STATUS_SUCCESS) {
- ALOGE("Failed register_notification_rsp, uids changed status: %d", status);
- }
-
- return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
-}
-
-static jboolean registerNotificationRspAddrPlayerChangedNative(
- JNIEnv* env, jobject object, jint type, jint playerId, jint uidCounter) {
- if (!sBluetoothAvrcpInterface) {
- ALOGE("%s: sBluetoothAvrcpInterface is null", __func__);
- return JNI_FALSE;
- }
-
- btrc_register_notification_t param;
- param.addr_player_changed.player_id = (uint16_t)playerId;
- param.addr_player_changed.uid_counter = (uint16_t)uidCounter;
-
- bt_status_t status = sBluetoothAvrcpInterface->register_notification_rsp(
- BTRC_EVT_ADDR_PLAYER_CHANGE, (btrc_notification_type_t)type, ¶m);
- if (status != BT_STATUS_SUCCESS) {
- ALOGE("Failed register_notification_rsp address player changed status: %d",
- status);
- }
-
- return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
-}
-
-static jboolean registerNotificationRspAvalPlayerChangedNative(JNIEnv* env,
- jobject object,
- jint type) {
- if (!sBluetoothAvrcpInterface) {
- ALOGE("%s: sBluetoothAvrcpInterface is null", __func__);
- return JNI_FALSE;
- }
-
- btrc_register_notification_t param;
- bt_status_t status = sBluetoothAvrcpInterface->register_notification_rsp(
- BTRC_EVT_AVAL_PLAYER_CHANGE, (btrc_notification_type_t)type, ¶m);
- if (status != BT_STATUS_SUCCESS) {
- ALOGE(
- "Failed register_notification_rsp available player changed status, "
- "status: %d",
- status);
- }
-
- return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
-}
-
-static jboolean setVolumeNative(JNIEnv* env, jobject object, jint volume) {
- if (!sBluetoothAvrcpInterface) {
- ALOGE("%s: sBluetoothAvrcpInterface is null", __func__);
- return JNI_FALSE;
- }
-
- bt_status_t status = sBluetoothAvrcpInterface->set_volume((uint8_t)volume);
- if (status != BT_STATUS_SUCCESS) {
- ALOGE("Failed set_volume, status: %d", status);
- }
-
- return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
-}
-
-/* native response for scope as Media player */
-static jboolean mediaPlayerListRspNative(
- JNIEnv* env, jobject object, jbyteArray address, jint rspStatus,
- jint uidCounter, jbyte itemType, jint numItems, jintArray playerIds,
- jbyteArray playerTypes, jintArray playerSubtypes,
- jbyteArray playStatusValues, jshortArray featureBitmask,
- jobjectArray textArray) {
- if (!sBluetoothAvrcpInterface) {
- ALOGE("%s: sBluetoothAvrcpInterface is null", __func__);
- return JNI_FALSE;
- }
-
- jbyte* addr = env->GetByteArrayElements(address, NULL);
- if (!addr) {
- jniThrowIOException(env, EINVAL);
- return JNI_FALSE;
- }
-
- jbyte *p_playerTypes = NULL, *p_PlayStatusValues = NULL;
- jshort* p_FeatBitMaskValues = NULL;
- jint *p_playerIds = NULL, *p_playerSubTypes = NULL;
- btrc_folder_items_t* p_items = NULL;
- if (rspStatus == BTRC_STS_NO_ERROR) {
- /* allocate memory */
- p_playerIds = env->GetIntArrayElements(playerIds, NULL);
- p_playerTypes = env->GetByteArrayElements(playerTypes, NULL);
- p_playerSubTypes = env->GetIntArrayElements(playerSubtypes, NULL);
- p_PlayStatusValues = env->GetByteArrayElements(playStatusValues, NULL);
- p_FeatBitMaskValues = env->GetShortArrayElements(featureBitmask, NULL);
- p_items = new btrc_folder_items_t[numItems];
- /* deallocate memory and return if allocation failed */
- if (!p_playerIds || !p_playerTypes || !p_playerSubTypes ||
- !p_PlayStatusValues || !p_FeatBitMaskValues || !p_items) {
- if (p_playerIds) env->ReleaseIntArrayElements(playerIds, p_playerIds, 0);
- if (p_playerTypes)
- env->ReleaseByteArrayElements(playerTypes, p_playerTypes, 0);
- if (p_playerSubTypes)
- env->ReleaseIntArrayElements(playerSubtypes, p_playerSubTypes, 0);
- if (p_PlayStatusValues)
- env->ReleaseByteArrayElements(playStatusValues, p_PlayStatusValues, 0);
- if (p_FeatBitMaskValues)
- env->ReleaseShortArrayElements(featureBitmask, p_FeatBitMaskValues, 0);
- if (p_items) delete[] p_items;
-
- jniThrowIOException(env, EINVAL);
- ALOGE("%s: not have enough memory", __func__);
- return JNI_FALSE;
- }
-
- p_items->item_type = (uint8_t)itemType;
-
- /* copy list of media players along with other parameters */
- int itemIdx;
- for (itemIdx = 0; itemIdx < numItems; ++itemIdx) {
- p_items[itemIdx].player.player_id = p_playerIds[itemIdx];
- p_items[itemIdx].player.major_type = p_playerTypes[itemIdx];
- p_items[itemIdx].player.sub_type = p_playerSubTypes[itemIdx];
- p_items[itemIdx].player.play_status = p_PlayStatusValues[itemIdx];
- p_items[itemIdx].player.charset_id = BTRC_CHARSET_ID_UTF8;
-
- ScopedLocalRef<jstring> text(
- env, (jstring)env->GetObjectArrayElement(textArray, itemIdx));
- /* copy player name */
- if (!copy_jstring(p_items[itemIdx].player.name, BTRC_MAX_ATTR_STR_LEN,
- text.get(), env))
- break;
-
- /* Feature bit mask is 128-bit value each */
- for (int InnCnt = 0; InnCnt < 16; InnCnt++) {
- p_items[itemIdx].player.features[InnCnt] =
- (uint8_t)p_FeatBitMaskValues[(itemIdx * 16) + InnCnt];
- }
- }
-
- /* failed to copy list of media players */
- if (itemIdx < numItems) {
- rspStatus = BTRC_STS_INTERNAL_ERR;
- ALOGE("%s: Failed to copy Media player attributes", __func__);
- }
- }
-
- RawAddress* btAddr = (RawAddress*)addr;
- bt_status_t status = sBluetoothAvrcpInterface->get_folder_items_list_rsp(
- *btAddr, (btrc_status_t)rspStatus, uidCounter, numItems, p_items);
- if (status != BT_STATUS_SUCCESS) {
- ALOGE("Failed get_folder_items_list_rsp, status: %d", status);
- }
-
- /* release allocated memory */
- if (p_items) delete[] p_items;
- if (p_playerTypes)
- env->ReleaseByteArrayElements(playerTypes, p_playerTypes, 0);
- if (p_playerSubTypes)
- env->ReleaseIntArrayElements(playerSubtypes, p_playerSubTypes, 0);
- if (p_PlayStatusValues)
- env->ReleaseByteArrayElements(playStatusValues, p_PlayStatusValues, 0);
- if (p_FeatBitMaskValues) {
- env->ReleaseShortArrayElements(featureBitmask, p_FeatBitMaskValues, 0);
- }
- env->ReleaseByteArrayElements(address, addr, 0);
-
- return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
-}
-
-static jboolean getFolderItemsRspNative(
- JNIEnv* env, jobject object, jbyteArray address, jint rspStatus,
- jshort uidCounter, jbyte scope, jint numItems, jbyteArray folderType,
- jbyteArray playable, jbyteArray itemType, jbyteArray itemUidArray,
- jobjectArray displayNameArray, jintArray numAttrs, jintArray attributesIds,
- jobjectArray attributesArray) {
- if (!sBluetoothAvrcpInterface) {
- ALOGE("%s: sBluetoothAvrcpInterface is null", __func__);
- return JNI_FALSE;
- }
-
- jbyte* addr = env->GetByteArrayElements(address, NULL);
- if (!addr) {
- jniThrowIOException(env, EINVAL);
- return JNI_FALSE;
- }
-
- jbyte *p_playable = NULL, *p_item_uid = NULL;
- jbyte* p_item_types = NULL; /* Folder or Media Item */
- jint* p_attributesIds = NULL;
- jbyte* p_folder_types =
- NULL; /* Folder properties like Album/Genre/Artists etc */
- jint* p_num_attrs = NULL;
- btrc_folder_items_t* p_items = NULL;
- /* none of the parameters should be null when no error */
- if (rspStatus == BTRC_STS_NO_ERROR) {
- /* allocate memory to each rsp item */
- if (folderType != NULL)
- p_folder_types = env->GetByteArrayElements(folderType, NULL);
- if (playable != NULL)
- p_playable = env->GetByteArrayElements(playable, NULL);
- if (itemType != NULL)
- p_item_types = env->GetByteArrayElements(itemType, NULL);
- if (NULL != numAttrs)
- p_num_attrs = env->GetIntArrayElements(numAttrs, NULL);
- if (NULL != attributesIds)
- p_attributesIds = env->GetIntArrayElements(attributesIds, NULL);
- if (itemUidArray != NULL)
- p_item_uid = (jbyte*)env->GetByteArrayElements(itemUidArray, NULL);
-
- p_items = new btrc_folder_items_t[numItems];
-
- /* if memory alloc failed, release memory */
- if (p_items && p_folder_types && p_playable && p_item_types && p_item_uid &&
- /* attributes can be null if remote requests 0 attributes */
- ((numAttrs != NULL && p_num_attrs) || (!numAttrs && !p_num_attrs)) &&
- ((attributesIds != NULL && p_attributesIds) ||
- (!attributesIds && !p_attributesIds))) {
- memset(p_items, 0, sizeof(btrc_folder_items_t) * numItems);
- if (scope == BTRC_SCOPE_FILE_SYSTEM || scope == BTRC_SCOPE_SEARCH ||
- scope == BTRC_SCOPE_NOW_PLAYING) {
- int attribCopiedIndex = 0;
- for (int item_idx = 0; item_idx < numItems; item_idx++) {
- if (BTRC_ITEM_FOLDER == p_item_types[item_idx]) {
- btrc_folder_items_t* pitem = &p_items[item_idx];
-
- memcpy(pitem->folder.uid, p_item_uid + item_idx * BTRC_UID_SIZE,
- BTRC_UID_SIZE);
- pitem->item_type = (uint8_t)BTRC_ITEM_FOLDER;
- pitem->folder.charset_id = BTRC_CHARSET_ID_UTF8;
- pitem->folder.type = p_folder_types[item_idx];
- pitem->folder.playable = p_playable[item_idx];
-
- ScopedLocalRef<jstring> text(
- env, (jstring)env->GetObjectArrayElement(displayNameArray,
- item_idx));
- if (!copy_jstring(pitem->folder.name, BTRC_MAX_ATTR_STR_LEN,
- text.get(), env)) {
- rspStatus = BTRC_STS_INTERNAL_ERR;
- ALOGE("%s: failed to copy display name of folder item", __func__);
- break;
- }
- } else if (BTRC_ITEM_MEDIA == p_item_types[item_idx]) {
- btrc_folder_items_t* pitem = &p_items[item_idx];
- memcpy(pitem->media.uid, p_item_uid + item_idx * BTRC_UID_SIZE,
- BTRC_UID_SIZE);
-
- pitem->item_type = (uint8_t)BTRC_ITEM_MEDIA;
- pitem->media.charset_id = BTRC_CHARSET_ID_UTF8;
- pitem->media.type = BTRC_MEDIA_TYPE_AUDIO;
- pitem->media.num_attrs =
- (p_num_attrs != NULL) ? p_num_attrs[item_idx] : 0;
-
- ScopedLocalRef<jstring> text(
- env, (jstring)env->GetObjectArrayElement(displayNameArray,
- item_idx));
- if (!copy_jstring(pitem->media.name, BTRC_MAX_ATTR_STR_LEN,
- text.get(), env)) {
- rspStatus = BTRC_STS_INTERNAL_ERR;
- ALOGE("%s: failed to copy display name of media item", __func__);
- break;
- }
-
- /* copy item attributes */
- if (!copy_item_attributes(env, object, pitem, p_attributesIds,
- attributesArray, item_idx,
- attribCopiedIndex)) {
- ALOGE("%s: error in copying attributes of item = %s", __func__,
- pitem->media.name);
- rspStatus = BTRC_STS_INTERNAL_ERR;
- break;
- }
- attribCopiedIndex += pitem->media.num_attrs;
- }
- }
- }
- } else {
- rspStatus = BTRC_STS_INTERNAL_ERR;
- ALOGE("%s: unable to allocate memory", __func__);
- }
- }
-
- RawAddress* btAddr = (RawAddress*)addr;
- bt_status_t status = sBluetoothAvrcpInterface->get_folder_items_list_rsp(
- *btAddr, (btrc_status_t)rspStatus, uidCounter, numItems, p_items);
- if (status != BT_STATUS_SUCCESS)
- ALOGE("Failed get_folder_items_list_rsp, status: %d", status);
-
- /* Release allocated memory for all attributes in each media item */
- if (p_items) cleanup_items(p_items, numItems);
-
- /* Release allocated memory */
- if (p_folder_types)
- env->ReleaseByteArrayElements(folderType, p_folder_types, 0);
- if (p_playable) env->ReleaseByteArrayElements(playable, p_playable, 0);
- if (p_item_types) env->ReleaseByteArrayElements(itemType, p_item_types, 0);
- if (p_num_attrs) env->ReleaseIntArrayElements(numAttrs, p_num_attrs, 0);
- if (p_attributesIds)
- env->ReleaseIntArrayElements(attributesIds, p_attributesIds, 0);
- if (p_item_uid) env->ReleaseByteArrayElements(itemUidArray, p_item_uid, 0);
- if (p_items) delete[] p_items;
- env->ReleaseByteArrayElements(address, addr, 0);
-
- return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
-}
-
-static jboolean setAddressedPlayerRspNative(JNIEnv* env, jobject object,
- jbyteArray address,
- jint rspStatus) {
- if (!sBluetoothAvrcpInterface) {
- ALOGE("%s: sBluetoothAvrcpInterface is null", __func__);
- return JNI_FALSE;
- }
-
- jbyte* addr = env->GetByteArrayElements(address, NULL);
- if (!addr) {
- jniThrowIOException(env, EINVAL);
- return JNI_FALSE;
- }
-
- RawAddress* btAddr = (RawAddress*)addr;
- bt_status_t status = sBluetoothAvrcpInterface->set_addressed_player_rsp(
- *btAddr, (btrc_status_t)rspStatus);
- if (status != BT_STATUS_SUCCESS) {
- ALOGE("Failed set_addressed_player_rsp, status: %d", status);
- }
- env->ReleaseByteArrayElements(address, addr, 0);
-
- return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
-}
-
-static jboolean setBrowsedPlayerRspNative(JNIEnv* env, jobject object,
- jbyteArray address, jint rspStatus,
- jbyte depth, jint numItems,
- jobjectArray textArray) {
- if (!sBluetoothAvrcpInterface) {
- ALOGE("%s: sBluetoothAvrcpInterface is null", __func__);
- return JNI_FALSE;
- }
-
- jbyte* addr = env->GetByteArrayElements(address, NULL);
- if (!addr) {
- jniThrowIOException(env, EINVAL);
- return JNI_FALSE;
- }
-
- btrc_br_folder_name_t* p_folders = NULL;
- if (rspStatus == BTRC_STS_NO_ERROR) {
- if (depth > 0) {
- p_folders = new btrc_br_folder_name_t[depth];
- }
-
- for (int folder_idx = 0; folder_idx < depth; folder_idx++) {
- /* copy folder names */
- ScopedLocalRef<jstring> text(
- env, (jstring)env->GetObjectArrayElement(textArray, folder_idx));
-
- if (!copy_jstring(p_folders[folder_idx].p_str, BTRC_MAX_ATTR_STR_LEN,
- text.get(), env)) {
- rspStatus = BTRC_STS_INTERNAL_ERR;
- delete[] p_folders;
- env->ReleaseByteArrayElements(address, addr, 0);
- ALOGE("%s: Failed to copy folder name", __func__);
- return JNI_FALSE;
- }
-
- p_folders[folder_idx].str_len =
- strlen((char*)p_folders[folder_idx].p_str);
- }
- }
-
- uint8_t folder_depth =
- depth; /* folder_depth is 0 if current folder is root */
- uint16_t charset_id = BTRC_CHARSET_ID_UTF8;
- RawAddress* btAddr = (RawAddress*)addr;
- bt_status_t status = sBluetoothAvrcpInterface->set_browsed_player_rsp(
- *btAddr, (btrc_status_t)rspStatus, numItems, charset_id, folder_depth,
- p_folders);
- if (status != BT_STATUS_SUCCESS) {
- ALOGE("%s: Failed set_browsed_player_rsp, status: %d", __func__, status);
- }
-
- if (depth > 0) {
- delete[] p_folders;
- }
-
- env->ReleaseByteArrayElements(address, addr, 0);
- return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
-}
-
-static jboolean changePathRspNative(JNIEnv* env, jobject object,
- jbyteArray address, jint rspStatus,
- jint numItems) {
- if (!sBluetoothAvrcpInterface) {
- ALOGE("%s: sBluetoothAvrcpInterface is null", __func__);
- return JNI_FALSE;
- }
-
- jbyte* addr = env->GetByteArrayElements(address, NULL);
- if (!addr) {
- jniThrowIOException(env, EINVAL);
- return JNI_FALSE;
- }
-
- uint32_t nItems = (uint32_t)numItems;
- RawAddress* btAddr = (RawAddress*)addr;
- bt_status_t status = sBluetoothAvrcpInterface->change_path_rsp(
- *btAddr, (btrc_status_t)rspStatus, (uint32_t)nItems);
- if (status != BT_STATUS_SUCCESS) {
- ALOGE("Failed change_path_rsp, status: %d", status);
- }
- env->ReleaseByteArrayElements(address, addr, 0);
-
- return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
-}
-
-static jboolean searchRspNative(JNIEnv* env, jobject object, jbyteArray address,
- jint rspStatus, jint uidCounter,
- jint numItems) {
- if (!sBluetoothAvrcpInterface) {
- ALOGE("%s: sBluetoothAvrcpInterface is null", __func__);
- return JNI_FALSE;
- }
-
- jbyte* addr = env->GetByteArrayElements(address, NULL);
- if (!addr) {
- jniThrowIOException(env, EINVAL);
- return JNI_FALSE;
- }
-
- uint32_t nItems = (uint32_t)numItems;
- RawAddress* btAddr = (RawAddress*)addr;
- bt_status_t status = sBluetoothAvrcpInterface->search_rsp(
- *btAddr, (btrc_status_t)rspStatus, (uint32_t)uidCounter,
- (uint32_t)nItems);
- if (status != BT_STATUS_SUCCESS) {
- ALOGE("Failed search_rsp, status: %d", status);
- }
-
- env->ReleaseByteArrayElements(address, addr, 0);
-
- return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
-}
-
-static jboolean playItemRspNative(JNIEnv* env, jobject object,
- jbyteArray address, jint rspStatus) {
- if (!sBluetoothAvrcpInterface) {
- ALOGE("%s: sBluetoothAvrcpInterface is null", __func__);
- return JNI_FALSE;
- }
-
- jbyte* addr = env->GetByteArrayElements(address, NULL);
- if (!addr) {
- jniThrowIOException(env, EINVAL);
- return JNI_FALSE;
- }
-
- RawAddress* btAddr = (RawAddress*)addr;
- bt_status_t status = sBluetoothAvrcpInterface->play_item_rsp(
- *btAddr, (btrc_status_t)rspStatus);
- if (status != BT_STATUS_SUCCESS) {
- ALOGE("Failed play_item_rsp, status: %d", status);
- }
- env->ReleaseByteArrayElements(address, addr, 0);
-
- return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
-}
-
-static jboolean getTotalNumOfItemsRspNative(JNIEnv* env, jobject object,
- jbyteArray address, jint rspStatus,
- jint uidCounter, jint numItems) {
- if (!sBluetoothAvrcpInterface) {
- ALOGE("%s: sBluetoothAvrcpInterface is null", __func__);
- return JNI_FALSE;
- }
-
- jbyte* addr = env->GetByteArrayElements(address, NULL);
- if (!addr) {
- jniThrowIOException(env, EINVAL);
- return JNI_FALSE;
- }
-
- uint32_t nItems = (uint32_t)numItems;
- RawAddress* btAddr = (RawAddress*)addr;
- bt_status_t status = sBluetoothAvrcpInterface->get_total_num_of_items_rsp(
- *btAddr, (btrc_status_t)rspStatus, (uint32_t)uidCounter,
- (uint32_t)nItems);
- if (status != BT_STATUS_SUCCESS) {
- ALOGE("Failed get_total_num_of_items_rsp, status: %d", status);
- }
- env->ReleaseByteArrayElements(address, addr, 0);
-
- return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
-}
-
-static jboolean addToNowPlayingRspNative(JNIEnv* env, jobject object,
- jbyteArray address, jint rspStatus) {
- if (!sBluetoothAvrcpInterface) {
- ALOGE("%s: sBluetoothAvrcpInterface is null", __func__);
- return JNI_FALSE;
- }
-
- jbyte* addr = env->GetByteArrayElements(address, NULL);
- if (!addr) {
- jniThrowIOException(env, EINVAL);
- return JNI_FALSE;
- }
-
- RawAddress* btAddr = (RawAddress*)addr;
- bt_status_t status = sBluetoothAvrcpInterface->add_to_now_playing_rsp(
- *btAddr, (btrc_status_t)rspStatus);
- if (status != BT_STATUS_SUCCESS) {
- ALOGE("Failed add_to_now_playing_rsp, status: %d", status);
- }
- env->ReleaseByteArrayElements(address, addr, 0);
-
- return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
-}
-
-static JNINativeMethod sMethods[] = {
- {"classInitNative", "()V", (void*)classInitNative},
- {"initNative", "()V", (void*)initNative},
- {"cleanupNative", "()V", (void*)cleanupNative},
- {"getPlayStatusRspNative", "([BIII)Z", (void*)getPlayStatusRspNative},
- {"getElementAttrRspNative", "([BB[I[Ljava/lang/String;)Z",
- (void*)getElementAttrRspNative},
- {"registerNotificationRspPlayStatusNative", "(II)Z",
- (void*)registerNotificationRspPlayStatusNative},
- {"registerNotificationRspTrackChangeNative", "(I[B)Z",
- (void*)registerNotificationRspTrackChangeNative},
- {"registerNotificationRspPlayPosNative", "(II)Z",
- (void*)registerNotificationRspPlayPosNative},
- {"setVolumeNative", "(I)Z", (void*)setVolumeNative},
-
- {"setAddressedPlayerRspNative", "([BI)Z",
- (void*)setAddressedPlayerRspNative},
-
- {"setBrowsedPlayerRspNative", "([BIBI[Ljava/lang/String;)Z",
- (void*)setBrowsedPlayerRspNative},
-
- {"mediaPlayerListRspNative", "([BIIBI[I[B[I[B[S[Ljava/lang/String;)Z",
- (void*)mediaPlayerListRspNative},
-
- {"getFolderItemsRspNative",
- "([BISBI[B[B[B[B[Ljava/lang/String;[I[I[Ljava/lang/String;)Z",
- (void*)getFolderItemsRspNative},
-
- {"changePathRspNative", "([BII)Z", (void*)changePathRspNative},
-
- {"getItemAttrRspNative", "([BIB[I[Ljava/lang/String;)Z",
- (void*)getItemAttrRspNative},
-
- {"playItemRspNative", "([BI)Z", (void*)playItemRspNative},
-
- {"getTotalNumOfItemsRspNative", "([BIII)Z",
- (void*)getTotalNumOfItemsRspNative},
-
- {"searchRspNative", "([BIII)Z", (void*)searchRspNative},
-
- {"addToNowPlayingRspNative", "([BI)Z", (void*)addToNowPlayingRspNative},
-
- {"registerNotificationRspAddrPlayerChangedNative", "(III)Z",
- (void*)registerNotificationRspAddrPlayerChangedNative},
-
- {"registerNotificationRspAvalPlayerChangedNative", "(I)Z",
- (void*)registerNotificationRspAvalPlayerChangedNative},
-
- {"registerNotificationRspUIDsChangedNative", "(II)Z",
- (void*)registerNotificationRspUIDsChangedNative},
-
- {"registerNotificationRspNowPlayingChangedNative", "(I)Z",
- (void*)registerNotificationRspNowPlayingChangedNative}};
-
-int register_com_android_bluetooth_avrcp(JNIEnv* env) {
- return jniRegisterNativeMethods(env, "com/android/bluetooth/avrcp/Avrcp",
- sMethods, NELEM(sMethods));
-}
-
-/* Helper function to copy attributes of item.
- * Assumes that all items in response have same number of attributes
- *
- * returns true on succes, false otherwise.
-*/
-static bool copy_item_attributes(JNIEnv* env, jobject object,
- btrc_folder_items_t* pitem,
- jint* p_attributesIds,
- jobjectArray attributesArray, int item_idx,
- int attribCopiedIndex) {
- bool success = true;
-
- /* copy attributes of the item */
- if (0 < pitem->media.num_attrs) {
- int num_attrs = pitem->media.num_attrs;
- ALOGI("%s num_attr = %d", __func__, num_attrs);
- pitem->media.p_attrs = new btrc_element_attr_val_t[num_attrs];
- if (!pitem->media.p_attrs) {
- return false;
- }
-
- for (int tempAtrCount = 0; tempAtrCount < pitem->media.num_attrs;
- ++tempAtrCount) {
- pitem->media.p_attrs[tempAtrCount].attr_id =
- p_attributesIds[attribCopiedIndex + tempAtrCount];
-
- ScopedLocalRef<jstring> text(
- env, (jstring)env->GetObjectArrayElement(
- attributesArray, attribCopiedIndex + tempAtrCount));
-
- if (!copy_jstring(pitem->media.p_attrs[tempAtrCount].text,
- BTRC_MAX_ATTR_STR_LEN, text.get(), env)) {
- success = false;
- ALOGE("%s: failed to copy attributes", __func__);
- break;
- }
- }
- }
- return success;
-}
-
-/* Helper function to copy String data from java to native
- *
- * returns true on succes, false otherwise
- */
-static bool copy_jstring(uint8_t* str, int maxBytes, jstring jstr,
- JNIEnv* env) {
- if (str == NULL || jstr == NULL || env == NULL) return false;
-
- memset(str, 0, maxBytes);
- const char* p_str = env->GetStringUTFChars(jstr, NULL);
- size_t len = strnlen(p_str, maxBytes - 1);
- memcpy(str, p_str, len);
-
- env->ReleaseStringUTFChars(jstr, p_str);
- return true;
-}
-
-/* Helper function to cleanup items */
-static void cleanup_items(btrc_folder_items_t* p_items, int numItems) {
- for (int item_idx = 0; item_idx < numItems; item_idx++) {
- /* release memory for attributes in case item is media item */
- if ((BTRC_ITEM_MEDIA == p_items[item_idx].item_type) &&
- p_items[item_idx].media.p_attrs != NULL)
- delete[] p_items[item_idx].media.p_attrs;
- }
-}
-}
diff --git a/jni/com_android_bluetooth_avrcp_controller.cpp b/jni/com_android_bluetooth_avrcp_controller.cpp
index 7710a91..2d9e87b 100644
--- a/jni/com_android_bluetooth_avrcp_controller.cpp
+++ b/jni/com_android_bluetooth_avrcp_controller.cpp
@@ -24,6 +24,7 @@
#include "utils/Log.h"
#include <string.h>
+#include <shared_mutex>
namespace android {
static jmethodID method_handlePassthroughRsp;
@@ -46,23 +47,31 @@
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;
static const btrc_ctrl_interface_t* sBluetoothAvrcpInterface = 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;
}
@@ -74,8 +83,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);
@@ -84,13 +98,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;
}
@@ -104,13 +123,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;
}
@@ -123,13 +147,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;
}
@@ -144,13 +173,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),
@@ -168,7 +202,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;
}
@@ -191,13 +225,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),
@@ -229,13 +268,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;
}
@@ -248,13 +292,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;
}
@@ -273,13 +322,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;
}
@@ -323,13 +377,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),
@@ -341,13 +400,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),
@@ -364,8 +428,23 @@
* 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;
+ 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);
// Inspect if the first element is a folder/item or player listing. They are
// always exclusive.
@@ -399,17 +478,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(),
@@ -435,10 +504,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());
}
@@ -446,9 +511,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;
@@ -467,22 +532,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__);
@@ -540,43 +595,129 @@
if (isPlayerListing) {
sCallbackEnv->CallVoidMethod(sCallbacksObj, method_handleGetPlayerItemsRsp,
- itemArray.get());
+ addr.get(), itemArray.get());
} else {
sCallbackEnv->CallVoidMethod(sCallbacksObj, method_handleGetFolderItemsRsp,
- status, itemArray.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;
+ 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(), (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;
+ 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(), (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;
+ 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_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_handleSetAddressedPlayerRsp, (jint)status);
+ sCallbacksObj, method_handleNowPlayingContentChanged, addr.get());
}
static btrc_ctrl_callbacks_t sBluetoothAvrcpCallbacks = {
@@ -596,7 +737,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 void classInitNative(JNIEnv* env, jclass clazz) {
method_handlePassthroughRsp =
@@ -636,32 +779,39 @@
method_handleGetFolderItemsRsp =
env->GetMethodID(clazz, "handleGetFolderItemsRsp",
- "(I[Landroid/media/browse/MediaBrowser$MediaItem;)V");
+ "([BI[Landroid/media/browse/MediaBrowser$MediaItem;)V");
method_handleGetPlayerItemsRsp = env->GetMethodID(
clazz, "handleGetPlayerItemsRsp",
- "([Lcom/android/bluetooth/avrcpcontroller/AvrcpPlayer;)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)V");
+ env->GetMethodID(clazz, "handleChangeFolderRsp", "([BI)V");
method_handleSetBrowsedPlayerRsp =
- env->GetMethodID(clazz, "handleSetBrowsedPlayerRsp", "(II)V");
+ env->GetMethodID(clazz, "handleSetBrowsedPlayerRsp", "([BII)V");
method_handleSetAddressedPlayerRsp =
- env->GetMethodID(clazz, "handleSetAddressedPlayerRsp", "(I)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);
@@ -709,6 +859,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");
@@ -961,7 +1113,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) {
@@ -969,22 +1121,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,
@@ -1029,7 +1181,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) {
@@ -1037,17 +1189,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);
}
@@ -1071,8 +1223,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},
};
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 976e388..0ac85f4 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 {
@@ -685,7 +683,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 =
@@ -699,7 +698,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) {
ALOGE("Error while setting the callbacks: %d\n", ret);
sBluetoothInterface = NULL;
@@ -752,11 +753,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;
}
@@ -1217,12 +1218,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},
@@ -1251,7 +1271,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(
@@ -1306,12 +1327,6 @@
return JNI_ERR;
}
- status = android::register_com_android_bluetooth_avrcp(e);
- if (status < 0) {
- ALOGE("jni avrcp target registration failure: %d", status);
- return JNI_ERR;
- }
-
status = android::register_com_android_bluetooth_avrcp_target(e);
if (status < 0) {
ALOGE("jni new avrcp target registration failure: %d", status);
@@ -1335,12 +1350,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 86e667b..4521515 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>
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 8547aa6..16bf961 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 (!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");
@@ -446,6 +467,7 @@
if (!sBluetoothHfpInterface) {
ALOGW("%s: Failed to get Bluetooth Handsfree Interface", __func__);
jniThrowIOException(env, EINVAL);
+ return;
}
bt_status_t status =
sBluetoothHfpInterface->Init(JniHeadsetCallbacks::GetInstance(),
@@ -804,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__);
@@ -817,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;
}
@@ -908,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/mapapi/com/android/bluetooth/mapapi/BluetoothMapContract.java b/lib/mapapi/com/android/bluetooth/mapapi/BluetoothMapContract.java
index b959858..5040bdc 100644
--- a/lib/mapapi/com/android/bluetooth/mapapi/BluetoothMapContract.java
+++ b/lib/mapapi/com/android/bluetooth/mapapi/BluetoothMapContract.java
@@ -301,12 +301,12 @@
* E.g. as a mapping for them such that the naming will match the underlying
* matching folder ID's.
*/
- public static final String FOLDER_NAME_INBOX = "INBOX";
- public static final String FOLDER_NAME_SENT = "SENT";
- public static final String FOLDER_NAME_OUTBOX = "OUTBOX";
- public static final String FOLDER_NAME_DRAFT = "DRAFT";
- public static final String FOLDER_NAME_DELETED = "DELETED";
- public static final String FOLDER_NAME_OTHER = "OTHER";
+ public static final String FOLDER_NAME_INBOX = "inbox";
+ public static final String FOLDER_NAME_SENT = "sent";
+ public static final String FOLDER_NAME_OUTBOX = "outbox";
+ public static final String FOLDER_NAME_DRAFT = "draft";
+ public static final String FOLDER_NAME_DELETED = "deleted";
+ public static final String FOLDER_NAME_OTHER = "other";
/**
* Folder IDs to be used with Instant Messaging virtual folders
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 cc7d0b6..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,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>
@@ -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 c8b882e..c9e6554 100644
--- a/res/values-de/strings.xml
+++ b/res/values-de/strings.xml
@@ -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-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 76b215d..5d0d886 100644
--- a/res/values-eu/strings.xml
+++ b/res/values-eu/strings.xml
@@ -84,8 +84,8 @@
<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-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 0555642..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,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">"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>
@@ -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 3da1fa7..3d30f17 100644
--- a/res/values-hi/strings.xml
+++ b/res/values-hi/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-hr/strings.xml b/res/values-hr/strings.xml
index 8c7a83b..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,8 +95,8 @@
<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>
@@ -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 a149d94..bf6fbf3 100644
--- a/res/values-hy/strings.xml
+++ b/res/values-hy/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">"Ընտրեք հաշիվները, որոնք ցանկանում եք հասանելի դարձնել Bluetooth-ի միջոցով: Ամեն դեպքում, կապակցվելիս պետք է ընդունեք հաշիվներն օգտագործելու թույլտվությունը:"</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 e4bff90..27ccaac 100644
--- a/res/values-ja/strings.xml
+++ b/res/values-ja/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-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 79405e5..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,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-ky/strings.xml b/res/values-ky/strings.xml
index b6065d9..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,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-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 7a2246f..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>
@@ -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">"Нема СД картичка. Вметнете СД картичка да се зачуваат пренесените датотеки."</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 c90308a..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,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>
@@ -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 d5342de..9a6f652 100644
--- a/res/values-mr/strings.xml
+++ b/res/values-mr/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-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 9d70383..7e7763a 100644
--- a/res/values-my/strings.xml
+++ b/res/values-my/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-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 a927f7e..8819773 100644
--- a/res/values-or/strings.xml
+++ b/res/values-or/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-pa/strings.xml b/res/values-pa/strings.xml
index 508dd3a..07cab6a 100644
--- a/res/values-pa/strings.xml
+++ b/res/values-pa/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-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/strings.xml b/res/values-pt/strings.xml
index a31eddb..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,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 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>
@@ -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-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 8e9b716..54677ac 100644
--- a/res/values-ta/strings.xml
+++ b/res/values-ta/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-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 0fdd7db..8cfec4f 100644
--- a/res/values-th/strings.xml
+++ b/res/values-th/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-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 8440c77..4898537 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 9abb914..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>
@@ -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 4220c84..046dfc6 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 9301e9d..711993e 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>
@@ -32,7 +31,6 @@
<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>
<!-- If true, we will require location to be enabled on the device to
fire Bluetooth LE scan result callbacks in addition to having one
@@ -71,10 +69,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>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 17023e4..1389e0d 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>
diff --git a/src/com/android/bluetooth/Utils.java b/src/com/android/bluetooth/Utils.java
index 457b053..bf68b35 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;
@@ -109,6 +112,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());
@@ -277,50 +298,109 @@
}
/**
- * 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;
}
+ /**
+ * 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;
+ }
+
public static boolean isLegacyForegroundApp(Context context, String pkgName) {
return !isMApp(context, pkgName) && isForegroundApp(context, pkgName);
}
@@ -335,6 +415,15 @@
return true;
}
+ 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;
+ }
/**
* Return true if the specified package name is a foreground app.
*
diff --git a/src/com/android/bluetooth/a2dp/A2dpCodecConfig.java b/src/com/android/bluetooth/a2dp/A2dpCodecConfig.java
index 4de89df..918c2ce 100644
--- a/src/com/android/bluetooth/a2dp/A2dpCodecConfig.java
+++ b/src/com/android/bluetooth/a2dp/A2dpCodecConfig.java
@@ -17,13 +17,17 @@
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.util.Log;
import com.android.bluetooth.R;
+import java.util.Arrays;
+import java.util.Objects;
/*
* A2DP Codec Configuration setup.
*/
@@ -52,13 +56,47 @@
}
void setCodecConfigPreference(BluetoothDevice device,
+ BluetoothCodecStatus codecStatus,
BluetoothCodecConfig codecConfig) {
+ 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, "Cannot find mandatory codec in selectableCodecs.");
+ return;
+ }
+ if (!isCodecConfigSelectable(codecConfig, selectableCodecs)) {
+ Log.w(TAG, "Codec is not selectable: " + codecConfig);
+ return;
+ }
+
+ // Check whether the codecConfig would change current codec config.
+ int prioritizedCodecType = getPrioitizedCodecType(codecConfig, selectableCodecs);
+ BluetoothCodecConfig currentCodecConfig = codecStatus.getCodecConfig();
+ if (prioritizedCodecType == currentCodecConfig.getCodecType()
+ && (currentCodecConfig.getCodecType() != codecConfig.getCodecType()
+ || currentCodecConfig.sameAudioFeedingParameters(codecConfig))) {
+ // Same codec with same parameters, no need to send this request to native.
+ Log.i(TAG, "setCodecConfigPreference: codec not changed.");
+ return;
+ }
+
BluetoothCodecConfig[] codecConfigArray = new BluetoothCodecConfig[1];
codecConfigArray[0] = codecConfig;
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.getCodecType());
+ return;
+ }
+
BluetoothCodecConfig[] codecConfigArray = assignCodecConfigPriorities();
if (codecConfigArray == null) {
return;
@@ -75,7 +113,12 @@
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;
@@ -92,6 +135,35 @@
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();
+ }
+
+ // Check whether the codecConfig is selectable
+ private static boolean isCodecConfigSelectable(BluetoothCodecConfig codecConfig,
+ BluetoothCodecConfig[] selectableCodecs) {
+ for (BluetoothCodecConfig config : selectableCodecs) {
+ if (codecConfig.getCodecType() == config.getCodecType()
+ && (codecConfig.getSampleRate() & config.getSampleRate()) != 0
+ && (codecConfig.getBitsPerSample() & config.getBitsPerSample()) != 0
+ && (codecConfig.getChannelMode() & config.getChannelMode()) != 0) {
+ return true;
+ }
+ }
+ return false;
+ }
+
// Assign the A2DP Source codec config priorities
private BluetoothCodecConfig[] assignCodecConfigPriorities() {
Resources resources = mContext.getResources();
diff --git a/src/com/android/bluetooth/a2dp/A2dpNativeInterface.java b/src/com/android/bluetooth/a2dp/A2dpNativeInterface.java
index e01b325..cbdc28a 100644
--- a/src/com/android/bluetooth/a2dp/A2dpNativeInterface.java
+++ b/src/com/android/bluetooth/a2dp/A2dpNativeInterface.java
@@ -25,11 +25,11 @@
import android.bluetooth.BluetoothCodecConfig;
import android.bluetooth.BluetoothCodecStatus;
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;
/**
* A2DP Native Interface to/from JNI.
@@ -107,6 +107,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
@@ -199,6 +209,7 @@
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 0548cdd..dbec19b 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,23 +29,21 @@
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.util.StatsLog;
import com.android.bluetooth.BluetoothMetricsProto;
import com.android.bluetooth.Utils;
-import com.android.bluetooth.avrcp.Avrcp;
-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.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;
@@ -60,13 +57,13 @@
private static A2dpService sA2dpService;
- private BluetoothAdapter mAdapter;
private AdapterService mAdapterService;
private HandlerThread mStateMachinesThread;
- private Avrcp mAvrcp;
@VisibleForTesting
A2dpNativeInterface mA2dpNativeInterface;
+ @VisibleForTesting
+ ServiceFactory mFactory = new ServiceFactory();
private AudioManager mAudioManager;
private A2dpCodecConfig mA2dpCodecConfig;
@@ -102,10 +99,8 @@
throw new IllegalStateException("start() called twice");
}
- // 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(),
@@ -118,28 +113,25 @@
mMaxConnectedAudioDevices = mAdapterService.getMaxConnectedAudioDevices();
Log.i(TAG, "Max connected audio devices set to " + mMaxConnectedAudioDevices);
- // Step 3: Setup AVRCP
- mAvrcp = Avrcp.make(this);
-
- // Step 4: Start handler thread for state machines
+ // Step 3: Start handler thread for state machines
mStateMachines.clear();
mStateMachinesThread = new HandlerThread("A2dpService.StateMachines");
mStateMachinesThread.start();
- // Step 5: Setup codec config
+ // Step 4: Setup codec config
mA2dpCodecConfig = new A2dpCodecConfig(this, mA2dpNativeInterface);
- // Step 6: Initialize native interface
+ // Step 5: Initialize native interface
mA2dpNativeInterface.init(mMaxConnectedAudioDevices,
mA2dpCodecConfig.codecConfigPriorities());
- // Step 7: Check if A2DP is in offload mode
+ // Step 6: Check if A2DP is in offload mode
mA2dpOffloadEnabled = mAdapterService.isA2dpOffloadEnabled();
if (DBG) {
Log.d(TAG, "A2DP offload flag set to " + mA2dpOffloadEnabled);
}
- // Step 8: Setup broadcast receivers
+ // Step 7: Setup broadcast receivers
IntentFilter filter = new IntentFilter();
filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
mBondStateChangedReceiver = new BondStateChangedReceiver();
@@ -149,10 +141,10 @@
mConnectionStateChangedReceiver = new ConnectionStateChangedReceiver();
registerReceiver(mConnectionStateChangedReceiver, filter);
- // Step 9: Mark service as started
+ // Step 8: Mark service as started
setA2dpService(this);
- // Step 10: Clear active device
+ // Step 9: Clear active device
setActiveDevice(null);
return true;
@@ -166,11 +158,6 @@
return true;
}
- // Step 10: Store volume if there is an active device
- if (mActiveDevice != null && AvrcpTargetService.get() != null) {
- AvrcpTargetService.get().storeVolumeForDevice(mActiveDevice);
- }
-
// Step 9: Clear active device and stop playing audio
removeActiveDevice(true);
@@ -201,19 +188,13 @@
mStateMachinesThread.quitSafely();
mStateMachinesThread = null;
- // Step 3: Cleanup AVRCP
- mAvrcp.doQuit();
- mAvrcp.cleanup();
- mAvrcp = null;
-
// Step 2: Reset maximum number of connected audio devices
mMaxConnectedAudioDevices = 1;
- // Step 1: Clear BluetoothAdapter, AdapterService, A2dpNativeInterface, AudioManager
+ // Step 1: Clear AdapterService, A2dpNativeInterface, AudioManager
mAudioManager = null;
mA2dpNativeInterface = null;
mAdapterService = null;
- mAdapter = null;
return true;
}
@@ -260,8 +241,23 @@
synchronized (mStateMachines) {
if (!connectionAllowedCheckMaxDevices(device)) {
- Log.e(TAG, "Cannot connect to " + device + " : too many connected devices");
- return false;
+ // 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;
+ }
}
A2dpStateMachine smConnect = getOrCreateStateMachine(device);
if (smConnect == null) {
@@ -340,7 +336,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.
@@ -375,7 +371,13 @@
List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
List<BluetoothDevice> devices = new ArrayList<>();
- Set<BluetoothDevice> bondedDevices = mAdapter.getBondedDevices();
+ 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),
@@ -387,9 +389,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;
}
}
}
@@ -424,14 +427,22 @@
}
}
+ private void storeActiveDeviceVolume() {
+ // Make sure volume has been stored before been removed from active.
+ if (mFactory.getAvrcpTargetService() != null && mActiveDevice != null) {
+ mFactory.getAvrcpTargetService().storeVolumeForDevice(mActiveDevice);
+ }
+ }
+
private void removeActiveDevice(boolean forceStopPlayingAudio) {
BluetoothDevice previousActiveDevice = mActiveDevice;
synchronized (mStateMachines) {
- // 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;
@@ -458,6 +469,47 @@
}
/**
+ * 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);
+ }
+ storeActiveDeviceVolume();
+ }
+ }
+
+ /**
* Set the active device.
*
* @param device the active device
@@ -471,10 +523,6 @@
Log.d(TAG, "setActiveDevice(" + device + "): previous is " + previousActiveDevice);
}
- if (previousActiveDevice != null && AvrcpTargetService.get() != null) {
- AvrcpTargetService.get().storeVolumeForDevice(previousActiveDevice);
- }
-
if (device == null) {
// Remove active device and continue playing audio only if necessary.
removeActiveDevice(false);
@@ -500,10 +548,17 @@
codecStatus = sm.getCodecStatus();
boolean deviceChanged = !Objects.equals(device, mActiveDevice);
- 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.
+ updateAndBroadcastActiveDevice(device);
if (deviceChanged) {
// Send an intent with the active device codec config
if (codecStatus != null) {
@@ -516,7 +571,7 @@
if (previousActiveDevice != null) {
if (!mAudioManager.isStreamMute(AudioManager.STREAM_MUSIC)) {
mAudioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC,
- AudioManager.ADJUST_MUTE, 0);
+ AudioManager.ADJUST_MUTE, AudioManager.FLAG_BLUETOOTH_ABS_VOLUME);
wasMuted = true;
}
mAudioManager.setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(
@@ -525,10 +580,8 @@
}
int rememberedVolume = -1;
- if (AvrcpTargetService.get() != null) {
- AvrcpTargetService.get().volumeDeviceSwitched(mActiveDevice);
-
- rememberedVolume = AvrcpTargetService.get()
+ if (mFactory.getAvrcpTargetService() != null) {
+ rememberedVolume = mFactory.getAvrcpTargetService()
.getRememberedVolumeForDevice(mActiveDevice);
}
@@ -542,7 +595,7 @@
mAudioManager.handleBluetoothA2dpDeviceConfigChange(mActiveDevice);
if (wasMuted) {
mAudioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC,
- AudioManager.ADJUST_UNMUTE, 0);
+ AudioManager.ADJUST_UNMUTE, AudioManager.FLAG_BLUETOOTH_ABS_VOLUME);
}
}
}
@@ -569,46 +622,33 @@
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 */
public boolean isAvrcpAbsoluteVolumeSupported() {
- return mAvrcp.isAbsoluteVolumeSupported();
+ // TODO (apanicke): Add a hook here for the AvrcpTargetService.
+ return false;
}
+
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;
}
-
- mAvrcp.setAbsoluteVolume(volume);
- }
-
- public void setAvrcpAudioState(int state) {
- mAvrcp.setA2dpAudioState(state);
- }
-
- public void resetAvrcpBlacklist(BluetoothDevice device) {
- if (mAvrcp != null) {
- mAvrcp.resetBlackList(device.getAddress());
- }
}
boolean isA2dpPlaying(BluetoothDevice device) {
@@ -675,7 +715,17 @@
Log.e(TAG, "Cannot set codec config preference: no active A2DP device");
return;
}
- mA2dpCodecConfig.setCodecConfigPreference(device, codecConfig);
+ if (getSupportsOptionalCodecs(device) != BluetoothA2dp.OPTIONAL_CODECS_SUPPORTED) {
+ Log.e(TAG, "Cannot set codec config preference: not supported");
+ return;
+ }
+
+ BluetoothCodecStatus codecStatus = getCodecStatus(device);
+ if (codecStatus == null) {
+ Log.e(TAG, "Codec status is null on " + device);
+ return;
+ }
+ mA2dpCodecConfig.setCodecConfigPreference(device, codecStatus, codecConfig);
}
/**
@@ -697,7 +747,16 @@
Log.e(TAG, "Cannot enable optional codecs: no active A2DP device");
return;
}
- mA2dpCodecConfig.enableOptionalCodecs(device);
+ if (getSupportsOptionalCodecs(device) != BluetoothA2dp.OPTIONAL_CODECS_SUPPORTED) {
+ Log.e(TAG, "Cannot enable optional codecs: not supported");
+ return;
+ }
+ BluetoothCodecStatus codecStatus = getCodecStatus(device);
+ if (codecStatus == null) {
+ Log.e(TAG, "Cannot enable optional codecs: codec status is null");
+ return;
+ }
+ mA2dpCodecConfig.enableOptionalCodecs(device, codecStatus.getCodecConfig());
}
/**
@@ -719,31 +778,33 @@
Log.e(TAG, "Cannot disable optional codecs: no active A2DP device");
return;
}
- mA2dpCodecConfig.disableOptionalCodecs(device);
+ if (getSupportsOptionalCodecs(device) != BluetoothA2dp.OPTIONAL_CODECS_SUPPORTED) {
+ Log.e(TAG, "Cannot disable optional codecs: not supported");
+ return;
+ }
+ BluetoothCodecStatus codecStatus = getCodecStatus(device);
+ if (codecStatus == null) {
+ Log.e(TAG, "Cannot disable optional codecs: 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) {
@@ -754,9 +815,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
@@ -800,8 +859,27 @@
* @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 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,
@@ -838,11 +916,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
@@ -902,6 +993,10 @@
if (sm.getConnectionState() != BluetoothProfile.STATE_DISCONNECTED) {
return;
}
+ if (mFactory.getAvrcpTargetService() != null) {
+ mFactory.getAvrcpTargetService().removeStoredVolumeForDevice(device);
+ }
+
removeStateMachine(device);
}
}
@@ -921,9 +1016,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 (mStateMachines) {
A2dpStateMachine sm = mStateMachines.get(device);
@@ -933,13 +1036,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)) {
@@ -947,10 +1059,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;
}
}
}
@@ -961,10 +1080,6 @@
}
synchronized (mStateMachines) {
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);
}
// Set the active device if only one connected device is supported and it was connected
@@ -979,6 +1094,10 @@
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);
}
}
@@ -1212,8 +1331,5 @@
for (A2dpStateMachine sm : mStateMachines.values()) {
sm.dump(sb);
}
- if (mAvrcp != null) {
- mAvrcp.dump(sb);
- }
}
}
diff --git a/src/com/android/bluetooth/a2dp/A2dpStateMachine.java b/src/com/android/bluetooth/a2dp/A2dpStateMachine.java
index 3ab26e1..e0df8b2 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;
@@ -88,7 +89,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;
@@ -130,7 +132,6 @@
// Stop if auido is still playing
log("doQuit: stopped playing " + mDevice);
mIsPlaying = false;
- mA2dpService.setAvrcpAudioState(BluetoothA2dp.STATE_NOT_PLAYING);
broadcastAudioState(BluetoothA2dp.STATE_NOT_PLAYING,
BluetoothA2dp.STATE_PLAYING);
}
@@ -158,7 +159,6 @@
if (mIsPlaying) {
Log.i(TAG, "Disconnected: stopped playing: " + mDevice);
mIsPlaying = false;
- mA2dpService.setAvrcpAudioState(BluetoothA2dp.STATE_NOT_PLAYING);
broadcastAudioState(BluetoothA2dp.STATE_NOT_PLAYING,
BluetoothA2dp.STATE_PLAYING);
}
@@ -468,6 +468,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,
@@ -559,7 +563,6 @@
if (!mIsPlaying) {
Log.i(TAG, "Connected: started playing: " + mDevice);
mIsPlaying = true;
- mA2dpService.setAvrcpAudioState(BluetoothA2dp.STATE_PLAYING);
broadcastAudioState(BluetoothA2dp.STATE_PLAYING,
BluetoothA2dp.STATE_NOT_PLAYING);
}
@@ -571,7 +574,6 @@
if (mIsPlaying) {
Log.i(TAG, "Connected: stopped playing: " + mDevice);
mIsPlaying = false;
- mA2dpService.setAvrcpAudioState(BluetoothA2dp.STATE_NOT_PLAYING);
broadcastAudioState(BluetoothA2dp.STATE_NOT_PLAYING,
BluetoothA2dp.STATE_PLAYING);
}
@@ -611,8 +613,11 @@
}
// NOTE: This event is processed in any state
- private void processCodecConfigEvent(BluetoothCodecStatus newCodecStatus) {
+ @VisibleForTesting
+ void processCodecConfigEvent(BluetoothCodecStatus newCodecStatus) {
BluetoothCodecConfig prevCodecConfig = null;
+ BluetoothCodecStatus prevCodecStatus = mCodecStatus;
+
synchronized (this) {
if (mCodecStatus != null) {
prevCodecConfig = mCodecStatus.getCodecConfig();
@@ -633,6 +638,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();
@@ -676,7 +687,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);
@@ -699,6 +710,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:
@@ -752,7 +773,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 17c8885..e41def0 100644
--- a/src/com/android/bluetooth/a2dpsink/A2dpSinkService.java
+++ b/src/com/android/bluetooth/a2dpsink/A2dpSinkService.java
@@ -13,224 +13,94 @@
* 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.IBluetoothA2dpSink;
-import android.content.Intent;
-import android.provider.Settings;
import android.util.Log;
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 java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
+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 = Log.isLoggable(TAG, Log.DEBUG);
+ static final int MAXIMUM_CONNECTED_DEVICES = 1;
- private A2dpSinkStateMachine mStateMachine;
- private static A2dpSinkService sA2dpSinkService;
+ private final BluetoothAdapter mAdapter;
+ protected Map<BluetoothDevice, A2dpSinkStateMachine> mDeviceStateMap =
+ new ConcurrentHashMap<>(1);
- @Override
- protected IProfileServiceBinder initBinder() {
- return new BluetoothA2dpSinkBinder(this);
+ private A2dpSinkStreamHandler mA2dpSinkStreamHandler;
+ private static A2dpSinkService sService;
+
+ static {
+ classInitNative();
}
@Override
protected boolean start() {
- if (DBG) {
- Log.d(TAG, "start()");
- }
- // Start the media browser service.
- Intent startIntent = new Intent(this, A2dpMediaBrowserService.class);
- startService(startIntent);
- mStateMachine = A2dpSinkStateMachine.make(this, this);
- setA2dpSinkService(this);
+ initNative();
+ sService = this;
+ mA2dpSinkStreamHandler = new A2dpSinkStreamHandler(this, this);
return true;
}
@Override
protected boolean stop() {
- if (DBG) {
- Log.d(TAG, "stop()");
+ for (A2dpSinkStateMachine stateMachine : mDeviceStateMap.values()) {
+ stateMachine.quitNow();
}
- setA2dpSinkService(null);
- if (mStateMachine != null) {
- mStateMachine.doQuit();
- }
- 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) {
+ mA2dpSinkStreamHandler.requestAudioFocus(request);
+ }
+
@Override
- protected void cleanup() {
- if (mStateMachine != null) {
- mStateMachine.cleanup();
- }
- }
-
- //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");
-
- int connectionState = mStateMachine.getConnectionState(device);
- if (connectionState == BluetoothProfile.STATE_CONNECTED
- || connectionState == BluetoothProfile.STATE_CONNECTING) {
- return false;
- }
-
- if (getPriority(device) == BluetoothProfile.PRIORITY_OFF) {
- return false;
- }
-
- mStateMachine.sendMessage(A2dpSinkStateMachine.CONNECT, device);
- return true;
- }
-
- boolean disconnect(BluetoothDevice device) {
- enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
- int connectionState = mStateMachine.getConnectionState(device);
- if (connectionState != BluetoothProfile.STATE_CONNECTED
- && connectionState != BluetoothProfile.STATE_CONNECTING) {
- return false;
- }
-
- mStateMachine.sendMessage(A2dpSinkStateMachine.DISCONNECT, device);
- return true;
- }
-
- public List<BluetoothDevice> getConnectedDevices() {
- enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- return mStateMachine.getConnectedDevices();
- }
-
- List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
- enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- return mStateMachine.getDevicesMatchingConnectionStates(states);
- }
-
- int getConnectionState(BluetoothDevice device) {
- enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- return mStateMachine.getConnectionState(device);
- }
-
- 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) {
- 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) {
- if (mStateMachine != null) {
- if (!isPlaying) {
- mStateMachine.sendMessage(A2dpSinkStateMachine.EVENT_AVRCP_TG_PAUSE);
- } else {
- 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) {
- if (mStateMachine != null) {
- mStateMachine.sendMessage(A2dpSinkStateMachine.EVENT_REQUEST_FOCUS);
- }
- }
-
- synchronized boolean isA2dpPlaying(BluetoothDevice device) {
- enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- if (DBG) {
- Log.d(TAG, "isA2dpPlaying(" + device + ")");
- }
- return mStateMachine.isPlaying(device);
- }
-
- BluetoothAudioConfig getAudioConfig(BluetoothDevice device) {
- enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- return mStateMachine.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;
@@ -240,13 +110,13 @@
return null;
}
- if (mService != null && mService.isAvailable()) {
+ if (mService != null) {
return mService;
}
return null;
}
- BluetoothA2dpSinkBinder(A2dpSinkService svc) {
+ A2dpSinkServiceBinder(A2dpSinkService svc) {
mService = svc;
}
@@ -301,15 +171,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) {
@@ -328,6 +189,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) {
@@ -337,13 +207,222 @@
}
}
- ;
+ /* Generic Profile Code */
+
+ /**
+ * 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;
+ }
+ }
+
+ /**
+ * 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() {
+ return getDevicesMatchingConnectionStates(new int[]{BluetoothAdapter.STATE_CONNECTED});
+ }
+
+ protected A2dpSinkStateMachine getOrCreateStateMachine(BluetoothDevice device) {
+ A2dpSinkStateMachine 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) {
+ 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);
- if (mStateMachine != null) {
- mStateMachine.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");
+ 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) {
+ StackEvent event = StackEvent.connectionStateChanged(getDevice(address), state);
+ A2dpSinkStateMachine stateMachine = getOrCreateStateMachine(event.mDevice);
+ stateMachine.sendMessage(A2dpSinkStateMachine.STACK_EVENT, event);
+ }
+
+ private void onAudioStateChanged(byte[] address, int state) {
+ if (state == StackEvent.AUDIO_STATE_STARTED) {
+ mA2dpSinkStreamHandler.obtainMessage(
+ A2dpSinkStreamHandler.SRC_STR_START).sendToTarget();
+ } else if (state == StackEvent.AUDIO_STATE_STOPPED) {
+ mA2dpSinkStreamHandler.obtainMessage(
+ A2dpSinkStreamHandler.SRC_STR_STOP).sendToTarget();
+ }
+ }
+
+ private void onAudioConfigChanged(byte[] address, int sampleRate, int channelCount) {
+ StackEvent event = StackEvent.audioConfigChanged(getDevice(address), sampleRate,
+ channelCount);
+ A2dpSinkStateMachine stateMachine = getOrCreateStateMachine(event.mDevice);
+ stateMachine.sendMessage(A2dpSinkStateMachine.STACK_EVENT, event);
+ }
}
diff --git a/src/com/android/bluetooth/a2dpsink/A2dpSinkStateMachine.java b/src/com/android/bluetooth/a2dpsink/A2dpSinkStateMachine.java
index fb64318..19ed87f 100644
--- a/src/com/android/bluetooth/a2dpsink/A2dpSinkStateMachine.java
+++ b/src/com/android/bluetooth/a2dpsink/A2dpSinkStateMachine.java
@@ -13,874 +13,288 @@
* 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.os.Handler;
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 = Log.isLoggable(TAG, Log.DEBUG);
- static final int CONNECT = 1;
- static final int DISCONNECT = 2;
- private 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;
+ //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;
+ //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 Pending mPending;
- private Connected mConnected;
+ //200->299 Events from Native
+ static final int STACK_EVENT = 200;
- private A2dpSinkService mService;
- private Context mContext;
- private BluetoothAdapter mAdapter;
- private IntentBroadcastHandler mIntentBroadcastHandler;
+ 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;
- // mCurrentDevice is the device connected before the state changes
- // mTargetDevice is the device to be connected
- // mIncomingDevice is the device connecting to us, valid only in Pending state
- // when mIncomingDevice is not null, both mCurrentDevice
- // and mTargetDevice are null
- // when either mCurrentDevice or mTargetDevice is not null,
- // mIncomingDevice is null
- // Stable states
- // No connection, Disconnected state
- // both mCurrentDevice and mTargetDevice are null
- // Connected, Connected state
- // mCurrentDevice is not null, mTargetDevice is null
- // Interim states
- // Connecting to a device, Pending
- // mCurrentDevice is null, mTargetDevice is not null
- // Disconnecting device, Connecting to new device
- // Pending
- // Both mCurrentDevice and mTargetDevice are not null
- // Disconnecting device Pending
- // mCurrentDevice is not null, mTargetDevice is null
- // Incoming connections Pending
- // Both mCurrentDevice and mTargetDevice are null
- private BluetoothDevice mCurrentDevice = null;
- private BluetoothDevice mTargetDevice = null;
- private BluetoothDevice mIncomingDevice = null;
- private BluetoothDevice mPlayingDevice = null;
- private A2dpSinkStreamHandler mStreaming = null;
-
- private final HashMap<BluetoothDevice, BluetoothAudioConfig> mAudioConfigs =
- new HashMap<BluetoothDevice, BluetoothAudioConfig>();
-
- static {
- classInitNative();
- }
-
- private A2dpSinkStateMachine(A2dpSinkService svc, Context context) {
- super("A2dpSinkStateMachine");
- mService = svc;
- mContext = context;
- mAdapter = BluetoothAdapter.getDefaultAdapter();
-
- initNative();
+ A2dpSinkStateMachine(BluetoothDevice device, A2dpSinkService service) {
+ super(TAG);
+ mDevice = device;
+ mDeviceAddress = Utils.getByteAddress(mDevice);
+ mService = service;
+ if (DBG) Log.d(TAG, device.toString());
mDisconnected = new Disconnected();
- mPending = new Pending();
+ mConnecting = new Connecting();
mConnected = new Connected();
+ mDisconnecting = new Disconnecting();
addState(mDisconnected);
- addState(mPending);
+ addState(mConnecting);
addState(mConnected);
+ addState(mDisconnecting);
setInitialState(mDisconnected);
-
- PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
-
- mIntentBroadcastHandler = new IntentBroadcastHandler();
}
- static A2dpSinkStateMachine make(A2dpSinkService svc, Context context) {
- Log.d("A2dpSinkStateMachine", "make");
- A2dpSinkStateMachine a2dpSm = new A2dpSinkStateMachine(svc, context);
- a2dpSm.start();
- return a2dpSm;
+ protected String getConnectionStateChangedIntent() {
+ return BluetoothA2dpSink.ACTION_CONNECTION_STATE_CHANGED;
}
- public void doQuit() {
- if (DBG) {
- Log.d("A2dpSinkStateMachine", "Quit");
- }
- synchronized (A2dpSinkStateMachine.this) {
- mStreaming = null;
- }
- quitNow();
+ /**
+ * Get the current connection state
+ *
+ * @return current State
+ */
+ public int getState() {
+ return mMostRecentState;
}
- public void cleanup() {
- cleanupNative();
- mAudioConfigs.clear();
+ /**
+ * get current audio config
+ */
+ BluetoothAudioConfig getAudioConfig() {
+ return mAudioConfig;
}
+ /**
+ * Get the underlying device tracked by this state machine
+ *
+ * @return device in focus
+ */
+ public synchronized BluetoothDevice getDevice() {
+ return mDevice;
+ }
+
+ /**
+ * send the Connect command asynchronously
+ */
+ public final void connect() {
+ sendMessage(CONNECT);
+ }
+
+ /**
+ * send the Disconnect command asynchronously
+ */
+ public final 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, "mCurrentDevice: " + mCurrentDevice);
- ProfileService.println(sb, "mTargetDevice: " + mTargetDevice);
- ProfileService.println(sb, "mIncomingDevice: " + mIncomingDevice);
- ProfileService.println(sb, "StateMachine: " + this.toString());
+ ProfileService.println(sb, "mDevice: " + mDevice.getAddress() + "("
+ + mDevice.getName() + ") " + this.toString());
}
- private class Disconnected extends State {
+ @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() {
- log("Enter Disconnected: " + getCurrentMessage().what);
+ if (DBG) Log.d(TAG, "Enter Disconnected");
+ if (mMostRecentState != BluetoothProfile.STATE_DISCONNECTED) {
+ sendMessage(CLEANUP);
+ }
+ onConnectionStateChanged(BluetoothProfile.STATE_DISCONNECTED);
}
@Override
public boolean processMessage(Message message) {
- log("Disconnected process message: " + message.what);
- if (mCurrentDevice != null || mTargetDevice != null || mIncomingDevice != null) {
- loge("ERROR: current, target, or mIncomingDevice not null in Disconnected");
- return NOT_HANDLED;
- }
-
- boolean retValue = HANDLED;
switch (message.what) {
- case CONNECT:
- BluetoothDevice device = (BluetoothDevice) message.obj;
- broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTING,
- BluetoothProfile.STATE_DISCONNECTED);
-
- if (!connectA2dpNative(getByteAddress(device))) {
- broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTED,
- BluetoothProfile.STATE_CONNECTING);
- break;
- }
-
- synchronized (A2dpSinkStateMachine.this) {
- mTargetDevice = device;
- transitionTo(mPending);
- }
- // 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: " + getCurrentMessage().what);
- }
-
- // in Disconnected state
- private void processConnectionEvent(BluetoothDevice device, int state) {
- switch (state) {
- case CONNECTION_STATE_DISCONNECTED:
- logw("Ignore A2DP DISCONNECTED event, device: " + device);
- break;
- case CONNECTION_STATE_CONNECTING:
- if (okToConnect(device)) {
- logi("Incoming A2DP accepted");
- broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTING,
- BluetoothProfile.STATE_DISCONNECTED);
- synchronized (A2dpSinkStateMachine.this) {
- mIncomingDevice = device;
- transitionTo(mPending);
- }
- } else {
- //reject the connection and stay in Disconnected state itself
- logi("Incoming A2DP rejected");
- disconnectA2dpNative(getByteAddress(device));
- }
- break;
- case CONNECTION_STATE_CONNECTED:
- logw("A2DP Connected from Disconnected state");
- if (okToConnect(device)) {
- logi("Incoming A2DP accepted");
- broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTED,
- BluetoothProfile.STATE_DISCONNECTED);
- synchronized (A2dpSinkStateMachine.this) {
- mCurrentDevice = device;
- transitionTo(mConnected);
- }
- } else {
- //reject the connection and stay in Disconnected state itself
- logi("Incoming A2DP rejected");
- disconnectA2dpNative(getByteAddress(device));
- }
- break;
- case CONNECTION_STATE_DISCONNECTING:
- logw("Ignore HF DISCONNECTING event, device: " + device);
- break;
- default:
- loge("Incorrect state: " + state);
- break;
- }
- }
- }
-
- private class Pending extends State {
- @Override
- public void enter() {
- log("Enter Pending: " + getCurrentMessage().what);
- }
-
- @Override
- public boolean processMessage(Message message) {
- log("Pending process message: " + message.what);
-
- boolean retValue = HANDLED;
- switch (message.what) {
+ processStackEvent((StackEvent) message.obj);
+ return true;
case CONNECT:
- logd("Disconnect before connecting to another target");
- break;
- case CONNECT_TIMEOUT:
- onConnectionStateChanged(getByteAddress(mTargetDevice),
- CONNECTION_STATE_DISCONNECTED);
- break;
- case DISCONNECT:
- BluetoothDevice device = (BluetoothDevice) message.obj;
- if (mCurrentDevice != null && mTargetDevice != null && mTargetDevice.equals(
- device)) {
- // cancel connection to the mTargetDevice
- broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTED,
- BluetoothProfile.STATE_CONNECTING);
- synchronized (A2dpSinkStateMachine.this) {
- mTargetDevice = null;
- }
- }
- 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;
+ if (DBG) Log.d(TAG, "Connect");
+ transitionTo(mConnecting);
+ return true;
+ case CLEANUP:
+ mService.removeStateMachine(A2dpSinkStateMachine.this);
+ return true;
}
- return retValue;
- }
-
- // in Pending state
- private void processConnectionEvent(BluetoothDevice device, int state) {
- log("processConnectionEvent state " + state);
- log("Devices curr: " + mCurrentDevice + " target: " + mTargetDevice + " incoming: "
- + mIncomingDevice + " device: " + device);
- switch (state) {
- case CONNECTION_STATE_DISCONNECTED:
- mAudioConfigs.remove(device);
- if ((mCurrentDevice != null) && mCurrentDevice.equals(device)) {
- broadcastConnectionState(mCurrentDevice,
- BluetoothProfile.STATE_DISCONNECTED,
- BluetoothProfile.STATE_DISCONNECTING);
- synchronized (A2dpSinkStateMachine.this) {
- mCurrentDevice = null;
- }
-
- if (mTargetDevice != null) {
- if (!connectA2dpNative(getByteAddress(mTargetDevice))) {
- broadcastConnectionState(mTargetDevice,
- BluetoothProfile.STATE_DISCONNECTED,
- BluetoothProfile.STATE_CONNECTING);
- synchronized (A2dpSinkStateMachine.this) {
- mTargetDevice = null;
- transitionTo(mDisconnected);
- }
- }
- } else {
- synchronized (A2dpSinkStateMachine.this) {
- mIncomingDevice = null;
- transitionTo(mDisconnected);
- }
- }
- } else if (mTargetDevice != null && mTargetDevice.equals(device)) {
- // outgoing connection failed
- broadcastConnectionState(mTargetDevice, BluetoothProfile.STATE_DISCONNECTED,
- BluetoothProfile.STATE_CONNECTING);
- synchronized (A2dpSinkStateMachine.this) {
- mTargetDevice = null;
- transitionTo(mDisconnected);
- }
- } else if (mIncomingDevice != null && mIncomingDevice.equals(device)) {
- broadcastConnectionState(mIncomingDevice,
- BluetoothProfile.STATE_DISCONNECTED,
- BluetoothProfile.STATE_CONNECTING);
- synchronized (A2dpSinkStateMachine.this) {
- mIncomingDevice = null;
- transitionTo(mDisconnected);
- }
- } else {
- loge("Unknown device Disconnected: " + device);
- }
- break;
- case CONNECTION_STATE_CONNECTED:
- if ((mCurrentDevice != null) && mCurrentDevice.equals(device)) {
- loge("current device is not null");
- // disconnection failed
- broadcastConnectionState(mCurrentDevice, BluetoothProfile.STATE_CONNECTED,
- BluetoothProfile.STATE_DISCONNECTING);
- if (mTargetDevice != null) {
- broadcastConnectionState(mTargetDevice,
- BluetoothProfile.STATE_DISCONNECTED,
- BluetoothProfile.STATE_CONNECTING);
- }
- synchronized (A2dpSinkStateMachine.this) {
- mTargetDevice = null;
- transitionTo(mConnected);
- }
- } else if (mTargetDevice != null && mTargetDevice.equals(device)) {
- loge("target device is not null");
- broadcastConnectionState(mTargetDevice, BluetoothProfile.STATE_CONNECTED,
- BluetoothProfile.STATE_CONNECTING);
- synchronized (A2dpSinkStateMachine.this) {
- mCurrentDevice = mTargetDevice;
- mTargetDevice = null;
- transitionTo(mConnected);
- }
- } else if (mIncomingDevice != null && mIncomingDevice.equals(device)) {
- loge("incoming device is not null");
- broadcastConnectionState(mIncomingDevice, BluetoothProfile.STATE_CONNECTED,
- BluetoothProfile.STATE_CONNECTING);
- synchronized (A2dpSinkStateMachine.this) {
- mCurrentDevice = mIncomingDevice;
- mIncomingDevice = null;
- transitionTo(mConnected);
- }
- } else {
- loge("Unknown device Connected: " + device);
- // something is wrong here, but sync our state with stack by connecting to
- // the new device and disconnect from previous device.
- broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTED,
- BluetoothProfile.STATE_DISCONNECTED);
- broadcastConnectionState(mCurrentDevice,
- BluetoothProfile.STATE_DISCONNECTED,
- BluetoothProfile.STATE_CONNECTING);
- synchronized (A2dpSinkStateMachine.this) {
- mCurrentDevice = device;
- mTargetDevice = null;
- mIncomingDevice = null;
- transitionTo(mConnected);
- }
- }
- break;
- case CONNECTION_STATE_CONNECTING:
- if ((mCurrentDevice != null) && mCurrentDevice.equals(device)) {
- log("current device tries to connect back");
- // TODO(BT) ignore or reject
- } else if (mTargetDevice != null && mTargetDevice.equals(device)) {
- // The stack is connecting to target device or
- // there is an incoming connection from the target device at the same time
- // we already broadcasted the intent, doing nothing here
- log("Stack and target device are connecting");
- } else if (mIncomingDevice != null && mIncomingDevice.equals(device)) {
- loge("Another connecting event on the incoming device");
- } else {
- // We get an incoming connecting request while Pending
- // TODO(BT) is stack handing this case? let's ignore it for now
- log("Incoming connection while pending, ignore");
- }
- break;
- case CONNECTION_STATE_DISCONNECTING:
- if ((mCurrentDevice != null) && mCurrentDevice.equals(device)) {
- // we already broadcasted the intent, doing nothing here
- if (DBG) {
- log("stack is disconnecting mCurrentDevice");
- }
- } else if (mTargetDevice != null && mTargetDevice.equals(device)) {
- loge("TargetDevice is getting disconnected");
- } else if (mIncomingDevice != null && mIncomingDevice.equals(device)) {
- loge("IncomingDevice is getting disconnected");
- } else {
- loge("Disconnecting unknown device: " + device);
- }
- break;
- default:
- loge("Incorrect state: " + state);
- break;
- }
- }
-
- }
-
- private class Connected extends State {
- @Override
- public void enter() {
- log("Enter Connected: " + getCurrentMessage().what);
- // Upon connected, the audio starts out as stopped
- broadcastAudioState(mCurrentDevice, BluetoothA2dpSink.STATE_NOT_PLAYING,
- BluetoothA2dpSink.STATE_PLAYING);
- synchronized (A2dpSinkStateMachine.this) {
- if (mStreaming == null) {
- if (DBG) {
- log("Creating New A2dpSinkStreamHandler");
- }
- mStreaming = new A2dpSinkStreamHandler(A2dpSinkStateMachine.this, mContext);
- }
- }
- if (mStreaming.getAudioFocus() == AudioManager.AUDIOFOCUS_NONE) {
- informAudioFocusStateNative(0);
- }
- }
-
- @Override
- public boolean processMessage(Message message) {
- log("Connected process message: " + message.what);
- if (mCurrentDevice == null) {
- loge("ERROR: mCurrentDevice is null in Connected");
- return NOT_HANDLED;
- }
-
- switch (message.what) {
- case CONNECT:
- logd("Disconnect before connecting to another target");
- break;
-
- case DISCONNECT: {
- BluetoothDevice device = (BluetoothDevice) message.obj;
- if (!mCurrentDevice.equals(device)) {
- break;
- }
- broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTING,
- BluetoothProfile.STATE_CONNECTED);
- if (!disconnectA2dpNative(getByteAddress(device))) {
- broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTED,
- BluetoothProfile.STATE_DISCONNECTED);
- break;
- }
- mPlayingDevice = null;
- mStreaming.obtainMessage(A2dpSinkStreamHandler.DISCONNECT).sendToTarget();
- transitionTo(mPending);
- }
- 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;
-
- default:
- return NOT_HANDLED;
- }
- return HANDLED;
- }
-
- // in Connected state
- private void processConnectionEvent(BluetoothDevice device, int state) {
- switch (state) {
- case CONNECTION_STATE_DISCONNECTED:
- mAudioConfigs.remove(device);
- if ((mPlayingDevice != null) && (device.equals(mPlayingDevice))) {
- mPlayingDevice = null;
- }
- if (mCurrentDevice.equals(device)) {
- broadcastConnectionState(mCurrentDevice,
- BluetoothProfile.STATE_DISCONNECTED,
- BluetoothProfile.STATE_CONNECTED);
- synchronized (A2dpSinkStateMachine.this) {
- // Take care of existing audio focus in the streaming state machine.
- mStreaming.obtainMessage(A2dpSinkStreamHandler.DISCONNECT)
- .sendToTarget();
- mCurrentDevice = null;
- 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 (!mCurrentDevice.equals(device)) {
- loge("Audio State Device:" + device + "is different from ConnectedDevice:"
- + mCurrentDevice);
- return;
- }
- log(" processAudioStateEvent in state " + state);
- switch (state) {
- case AUDIO_STATE_STARTED:
- mStreaming.obtainMessage(A2dpSinkStreamHandler.SRC_STR_START).sendToTarget();
- break;
- case AUDIO_STATE_REMOTE_SUSPEND:
- case AUDIO_STATE_STOPPED:
- mStreaming.obtainMessage(A2dpSinkStreamHandler.SRC_STR_STOP).sendToTarget();
- 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(BluetoothDevice device) {
- if (getCurrentState() == mDisconnected) {
- return BluetoothProfile.STATE_DISCONNECTED;
- }
-
- synchronized (this) {
- IState currentState = getCurrentState();
- if (currentState == mPending) {
- if ((mTargetDevice != null) && mTargetDevice.equals(device)) {
- return BluetoothProfile.STATE_CONNECTING;
- }
- if ((mCurrentDevice != null) && mCurrentDevice.equals(device)) {
- return BluetoothProfile.STATE_DISCONNECTING;
- }
- if ((mIncomingDevice != null) && mIncomingDevice.equals(device)) {
- return BluetoothProfile.STATE_CONNECTING; // incoming connection
- }
- return BluetoothProfile.STATE_DISCONNECTED;
- }
-
- if (currentState == mConnected) {
- if (mCurrentDevice.equals(device)) {
- return BluetoothProfile.STATE_CONNECTED;
- }
- return BluetoothProfile.STATE_DISCONNECTED;
- } else {
- loge("Bad currentState: " + currentState);
- return BluetoothProfile.STATE_DISCONNECTED;
- }
- }
- }
-
- BluetoothAudioConfig getAudioConfig(BluetoothDevice device) {
- return mAudioConfigs.get(device);
- }
-
- List<BluetoothDevice> getConnectedDevices() {
- List<BluetoothDevice> devices = new ArrayList<BluetoothDevice>();
- synchronized (this) {
- if (getCurrentState() == mConnected) {
- devices.add(mCurrentDevice);
- }
- }
- return devices;
- }
-
- boolean isPlaying(BluetoothDevice device) {
- synchronized (this) {
- if ((mPlayingDevice != null) && (device.equals(mPlayingDevice))) {
- return true;
- }
- }
- return false;
- }
-
- // 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;
- }
-
- synchronized List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
- List<BluetoothDevice> deviceList = new ArrayList<BluetoothDevice>();
- Set<BluetoothDevice> bondedDevices = mAdapter.getBondedDevices();
- int connectionState;
-
- for (BluetoothDevice device : bondedDevices) {
- ParcelUuid[] featureUuids = device.getUuids();
- if (!BluetoothUuid.isUuidPresent(featureUuids, BluetoothUuid.AudioSource)) {
- continue;
- }
- connectionState = getConnectionState(device);
- for (int i = 0; i < states.length; i++) {
- if (connectionState == states[i]) {
- deviceList.add(device);
- }
- }
- }
- return deviceList;
- }
-
-
- // This method does not check for error conditon (newState == prevState)
- private void broadcastConnectionState(BluetoothDevice device, int newState, int prevState) {
-
- int delay = 0;
- mIntentBroadcastHandler.sendMessageDelayed(
- mIntentBroadcastHandler.obtainMessage(MSG_CONNECTION_STATE_CHANGED, prevState,
- newState, device), delay);
- }
-
- 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));
- }
-
- private class StackEvent {
- public int type = EVENT_TYPE_NONE;
- public BluetoothDevice device = null;
- public int valueInt = 0;
- public BluetoothAudioConfig audioConfig = null;
-
- private StackEvent(int type) {
- this.type = type;
- }
- }
-
- /** Handles A2DP connection state change intent broadcasts. */
- private class IntentBroadcastHandler extends Handler {
-
- private void onConnectionStateChanged(BluetoothDevice device, int prevState, int state) {
- if (prevState != state && state == 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, state);
- intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
-//FIXME intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
- mContext.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
- log("Connection state " + device + ": " + prevState + "->" + state);
- }
-
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case MSG_CONNECTION_STATE_CHANGED:
- onConnectionStateChanged((BluetoothDevice) msg.obj, msg.arg1, msg.arg2);
- break;
- }
- }
- }
-
- 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;
}
+
+ 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;
+ }
+ }
+ }
}
- // Event types for STACK_EVENT message
- private static final int EVENT_TYPE_NONE = 0;
- private static final int EVENT_TYPE_CONNECTION_STATE_CHANGED = 1;
- private static final int EVENT_TYPE_AUDIO_STATE_CHANGED = 2;
- private static final int EVENT_TYPE_AUDIO_CONFIG_CHANGED = 3;
+ class Connecting extends State {
+ boolean mIncommingConnection = false;
- // Do not modify without updating the HAL bt_av.h files.
+ @Override
+ public void enter() {
+ if (DBG) Log.d(TAG, "Enter Connecting");
+ onConnectionStateChanged(BluetoothProfile.STATE_CONNECTING);
+ sendMessageDelayed(CONNECT_TIMEOUT, CONNECT_TIMEOUT_MS);
- // 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;
+ if (!mIncommingConnection) {
+ mService.connectA2dpNative(mDeviceAddress);
+ }
- // 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;
+ super.enter();
+ }
- private static native void classInitNative();
+ @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;
+ }
- private native void initNative();
+ 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);
+ }
- private native void cleanupNative();
+ }
- private native boolean connectA2dpNative(byte[] address);
+ class Connected extends State {
+ @Override
+ public void enter() {
+ if (DBG) Log.d(TAG, "Enter Connected");
+ onConnectionStateChanged(BluetoothProfile.STATE_CONNECTED);
+ }
- private native boolean disconnectA2dpNative(byte[] address);
+ @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;
+ }
- public native void informAudioFocusStateNative(int focusGranted);
+ 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;
+ }
+ }
+ }
- public native void informAudioTrackGainNative(float focusGranted);
+ 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 f442c49..c24eca9 100644
--- a/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandler.java
+++ b/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandler.java
@@ -23,12 +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.BluetoothMediaBrowserService;
+import com.android.bluetooth.hfpclient.HeadsetClientService;
import java.util.List;
@@ -51,12 +53,12 @@
* restored.
*/
public class A2dpSinkStreamHandler extends Handler {
- private static final boolean DBG = false;
private static final String TAG = "A2dpSinkStreamHandler";
+ private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
// 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
@@ -68,7 +70,7 @@
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 DELAYED_PAUSE = 9; // If a call just started allow stack time to settle
// Used to indicate focus lost
private static final int STATE_FOCUS_LOST = 0;
@@ -76,7 +78,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
@@ -85,6 +87,16 @@
// Keep track of the relevant audio focus (None, Transient, Gain)
private int mAudioFocus = AudioManager.AUDIOFOCUS_NONE;
+ // 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
@@ -97,12 +109,26 @@
}
};
- public A2dpSinkStreamHandler(A2dpSinkStateMachine a2dpSinkSm, Context context) {
- mA2dpSinkSm = a2dpSinkSm;
+ public A2dpSinkStreamHandler(A2dpSinkService a2dpSinkService, Context context) {
+ mA2dpSinkService = a2dpSinkService;
mContext = context;
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) {
@@ -112,71 +138,45 @@
switch (message.what) {
case SRC_STR_START:
mStreamAvailable = true;
- // Always request audio focus if on TV.
- if (isTvDevice()) {
- if (mAudioFocus == AudioManager.AUDIOFOCUS_NONE) {
- requestAudioFocus();
- }
- }
- // Audio stream has started, stop it if we don't have focus.
- if (mAudioFocus == AudioManager.AUDIOFOCUS_NONE) {
- sendAvrcpPause();
- } else {
- startAvrcpUpdates();
+ if (isTvDevice() || shouldRequestFocus()) {
+ requestAudioFocusIfNone();
}
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;
}
- // Otherwise, pause if we don't have focus
- if (mAudioFocus == AudioManager.AUDIOFOCUS_NONE) {
- sendAvrcpPause();
- } else {
- 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 DISCONNECT:
// Remote device has disconnected, restore everything to default state.
- stopAvrcpUpdates();
mSentPause = false;
break;
@@ -184,11 +184,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;
@@ -210,11 +211,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:
@@ -226,13 +224,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);
}
@@ -241,129 +240,128 @@
/**
* Utility functions.
*/
+ private void requestAudioFocusIfNone() {
+ if (DBG) Log.d(TAG, "requestAudioFocusIfNone()");
+ if (mAudioFocus == AudioManager.AUDIOFOCUS_NONE) {
+ 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() {
+ if (DBG) Log.d(TAG, "requestAudioFocus()");
// Bluetooth A2DP may carry Music, Audio Books, Navigation, or other sounds so mark content
// type unknown.
AudioAttributes streamAttributes =
new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_MEDIA)
.setContentType(AudioAttributes.CONTENT_TYPE_UNKNOWN)
.build();
- // Bluetooth ducking is handled at the native layer so tell the Audio Manger to notify the
- // focus change listener via .setWillPauseWhenDucked().
+ // Bluetooth ducking is handled at the native layer at the request of AudioManager.
AudioFocusRequest focusRequest =
new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN).setAudioAttributes(
streamAttributes)
- .setWillPauseWhenDucked(true)
.setOnAudioFocusChangeListener(mAudioFocusListener, this)
.build();
int focusRequestStatus = mAudioManager.requestAudioFocus(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() {
+ if (DBG) Log.d(TAG, "abandonAudioFocus()");
stopFluorideStreaming();
+ releaseMediaKeyFocus();
mAudioManager.abandonAudioFocus(mAudioFocusListener);
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() {
- mA2dpSinkSm.informAudioFocusStateNative(STATE_FOCUS_GRANTED);
- mA2dpSinkSm.informAudioTrackGainNative(1.0f);
+ mA2dpSinkService.informAudioFocusStateNative(STATE_FOCUS_GRANTED);
+ mA2dpSinkService.informAudioTrackGainNative(1.0f);
}
private void stopFluorideStreaming() {
- mA2dpSinkSm.informAudioFocusStateNative(STATE_FOCUS_LOST);
+ mA2dpSinkService.informAudioFocusStateNative(STATE_FOCUS_LOST);
}
private void setFluorideAudioTrackGain(float gain) {
- mA2dpSinkSm.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();
- } 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();
- } else {
- Log.e(TAG, "stopAvrcpUpdates failed because of connection.");
- }
+ 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() {
@@ -378,4 +376,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 739f17c..0000000
--- a/src/com/android/bluetooth/a2dpsink/mbs/A2dpMediaBrowserService.java
+++ /dev/null
@@ -1,561 +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;
-
- // 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;
-
- 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;
- 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);
- 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();
- }
-
- // 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();
- }
- }
- };
-
- 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();
- }
-
-
- // 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 && !mA2dpDevice.equals(devices.get(0))) {
- Log.w(TAG, "A2dp device : " + mA2dpDevice + " avrcp device " + devices.get(0));
- return;
- }
- mA2dpDevice = devices.get(0);
- 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
deleted file mode 100644
index 1d4810a..0000000
--- a/src/com/android/bluetooth/avrcp/AddressedMediaPlayer.java
+++ /dev/null
@@ -1,601 +0,0 @@
-/*
- * Copyright (C) 2016 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.avrcp;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.media.MediaDescription;
-import android.media.MediaMetadata;
-import android.media.session.MediaSession;
-import android.media.session.MediaSession.QueueItem;
-import android.media.session.PlaybackState;
-import android.os.Bundle;
-import android.util.Log;
-
-import com.android.bluetooth.Utils;
-import com.android.bluetooth.btservice.ProfileService;
-
-import java.nio.ByteBuffer;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-/*************************************************************************************************
- * Provides functionality required for Addressed Media Player, like Now Playing List related
- * browsing commands, control commands to the current addressed player(playItem, play, pause, etc)
- * Acts as an Interface to communicate with media controller APIs for NowPlayingItems.
- ************************************************************************************************/
-
-public class AddressedMediaPlayer {
- private static final String TAG = "AddressedMediaPlayer";
- private static final Boolean DEBUG = false;
-
- private static final long SINGLE_QID = 1;
- private static final String UNKNOWN_TITLE = "(unknown)";
-
- static private final String GPM_BUNDLE_METADATA_KEY =
- "com.google.android.music.mediasession.music_metadata";
-
- private AvrcpMediaRspInterface mMediaInterface;
- @NonNull private List<MediaSession.QueueItem> mNowPlayingList;
-
- private final List<MediaSession.QueueItem> mEmptyNowPlayingList;
-
- private long mLastTrackIdSent;
-
- public AddressedMediaPlayer(AvrcpMediaRspInterface mediaInterface) {
- mEmptyNowPlayingList = new ArrayList<MediaSession.QueueItem>();
- mNowPlayingList = mEmptyNowPlayingList;
- mMediaInterface = mediaInterface;
- mLastTrackIdSent = MediaSession.QueueItem.UNKNOWN_ID;
- }
-
- void cleanup() {
- if (DEBUG) {
- Log.v(TAG, "cleanup");
- }
- mNowPlayingList = mEmptyNowPlayingList;
- mMediaInterface = null;
- mLastTrackIdSent = MediaSession.QueueItem.UNKNOWN_ID;
- }
-
- /* get now playing list from addressed player */
- void getFolderItemsNowPlaying(byte[] bdaddr, AvrcpCmd.FolderItemsCmd reqObj,
- @Nullable MediaController mediaController) {
- if (mediaController == null) {
- // No players (if a player exists, we would have selected it)
- Log.e(TAG, "mediaController = null, sending no available players response");
- mMediaInterface.folderItemsRsp(bdaddr, AvrcpConstants.RSP_NO_AVBL_PLAY, null);
- return;
- }
- List<MediaSession.QueueItem> items = updateNowPlayingList(mediaController);
- getFolderItemsFilterAttr(bdaddr, reqObj, items, AvrcpConstants.BTRC_SCOPE_NOW_PLAYING,
- reqObj.mStartItem, reqObj.mEndItem, mediaController);
- }
-
- /* get item attributes for item in now playing list */
- void getItemAttr(byte[] bdaddr, AvrcpCmd.ItemAttrCmd itemAttr,
- @Nullable MediaController mediaController) {
- int status = AvrcpConstants.RSP_NO_ERROR;
- long mediaId = ByteBuffer.wrap(itemAttr.mUid).getLong();
- List<MediaSession.QueueItem> items = updateNowPlayingList(mediaController);
-
- // NOTE: this is out-of-spec (AVRCP 1.6.1 sec 6.10.4.3, p90) but we answer it anyway
- // because some CTs ask for it.
- if (Arrays.equals(itemAttr.mUid, AvrcpConstants.TRACK_IS_SELECTED)) {
- mediaId = getActiveQueueItemId(mediaController);
- if (DEBUG) {
- Log.d(TAG, "getItemAttr: Remote requests for now playing contents, sending UID: "
- + mediaId);
- }
- }
-
- if (DEBUG) {
- Log.d(TAG, "getItemAttr-UID: 0x" + Utils.byteArrayToString(itemAttr.mUid));
- }
- for (MediaSession.QueueItem item : items) {
- if (item.getQueueId() == mediaId) {
- getItemAttrFilterAttr(bdaddr, itemAttr, item, mediaController);
- return;
- }
- }
-
- // Couldn't find it, so the id is invalid
- mMediaInterface.getItemAttrRsp(bdaddr, AvrcpConstants.RSP_INV_ITEM, null);
- }
-
- /* Refresh and get the queue of now playing.
- */
- @NonNull
- List<MediaSession.QueueItem> updateNowPlayingList(@Nullable MediaController mediaController) {
- if (mediaController == null) {
- return mEmptyNowPlayingList;
- }
- List<MediaSession.QueueItem> items = mediaController.getQueue();
- if (items == null) {
- Log.i(TAG, "null queue from " + mediaController.getPackageName()
- + ", constructing single-item list");
-
- // Because we are database-unaware, we can just number the item here whatever we want
- // because they have to re-poll it every time.
- MediaMetadata metadata = mediaController.getMetadata();
- if (metadata == null) {
- Log.w(TAG, "Controller has no metadata!? Making an empty one");
- metadata = (new MediaMetadata.Builder()).build();
- }
-
- MediaDescription.Builder bob = new MediaDescription.Builder();
- MediaDescription desc = metadata.getDescription();
-
- // set the simple ones that MediaMetadata builds for us
- bob.setMediaId(desc.getMediaId());
- bob.setTitle(desc.getTitle());
- bob.setSubtitle(desc.getSubtitle());
- bob.setDescription(desc.getDescription());
- // fill the ones that we use later
- bob.setExtras(fillBundle(metadata, desc.getExtras()));
-
- // build queue item with the new metadata
- MediaSession.QueueItem current = new QueueItem(bob.build(), SINGLE_QID);
-
- items = new ArrayList<MediaSession.QueueItem>();
- items.add(current);
- }
-
- if (!items.equals(mNowPlayingList)) {
- sendNowPlayingListChanged();
- }
- mNowPlayingList = items;
-
- return mNowPlayingList;
- }
-
- private void sendNowPlayingListChanged() {
- if (mMediaInterface == null) {
- return;
- }
- if (DEBUG) {
- Log.d(TAG, "sendNowPlayingListChanged()");
- }
- mMediaInterface.nowPlayingChangedRsp(AvrcpConstants.NOTIFICATION_TYPE_CHANGED);
- }
-
- private Bundle fillBundle(MediaMetadata metadata, Bundle currentExtras) {
- if (metadata == null) {
- Log.i(TAG, "fillBundle: metadata is null");
- return currentExtras;
- }
-
- Bundle bundle = currentExtras;
- if (bundle == null) {
- bundle = new Bundle();
- }
-
- String[] stringKeys = {
- MediaMetadata.METADATA_KEY_TITLE,
- MediaMetadata.METADATA_KEY_ARTIST,
- MediaMetadata.METADATA_KEY_ALBUM,
- MediaMetadata.METADATA_KEY_GENRE
- };
- for (String key : stringKeys) {
- String current = bundle.getString(key);
- if (current == null) {
- bundle.putString(key, metadata.getString(key));
- }
- }
-
- String[] longKeys = {
- MediaMetadata.METADATA_KEY_TRACK_NUMBER,
- MediaMetadata.METADATA_KEY_NUM_TRACKS,
- MediaMetadata.METADATA_KEY_DURATION
- };
- for (String key : longKeys) {
- if (!bundle.containsKey(key)) {
- bundle.putLong(key, metadata.getLong(key));
- }
- }
- return bundle;
- }
-
- /* Instructs media player to play particular media item */
- void playItem(byte[] bdaddr, byte[] uid, @Nullable MediaController mediaController) {
- long qid = ByteBuffer.wrap(uid).getLong();
- List<MediaSession.QueueItem> items = updateNowPlayingList(mediaController);
-
- if (mediaController == null) {
- Log.e(TAG, "No mediaController when PlayItem " + qid + " requested");
- mMediaInterface.playItemRsp(bdaddr, AvrcpConstants.RSP_INTERNAL_ERR);
- return;
- }
-
- MediaController.TransportControls mediaControllerCntrl =
- mediaController.getTransportControls();
-
- if (items == null) {
- Log.w(TAG, "nowPlayingItems is null");
- mMediaInterface.playItemRsp(bdaddr, AvrcpConstants.RSP_INTERNAL_ERR);
- return;
- }
-
- for (MediaSession.QueueItem item : items) {
- if (qid == item.getQueueId()) {
- if (DEBUG) {
- Log.d(TAG, "Skipping to ID " + qid);
- }
- mediaControllerCntrl.skipToQueueItem(qid);
- mMediaInterface.playItemRsp(bdaddr, AvrcpConstants.RSP_NO_ERROR);
- return;
- }
- }
-
- Log.w(TAG, "Invalid now playing Queue ID " + qid);
- mMediaInterface.playItemRsp(bdaddr, AvrcpConstants.RSP_INV_ITEM);
- }
-
- void getTotalNumOfItems(byte[] bdaddr, @Nullable MediaController mediaController) {
- List<MediaSession.QueueItem> items = updateNowPlayingList(mediaController);
- if (DEBUG) {
- Log.d(TAG, "getTotalNumOfItems: " + items.size() + " items.");
- }
- mMediaInterface.getTotalNumOfItemsRsp(bdaddr, AvrcpConstants.RSP_NO_ERROR, 0, items.size());
- }
-
- void sendTrackChangeWithId(int type, @Nullable MediaController mediaController) {
- Log.d(TAG, "sendTrackChangeWithId (" + type + "): controller " + mediaController);
- long qid = getActiveQueueItemId(mediaController);
- byte[] track = ByteBuffer.allocate(AvrcpConstants.UID_SIZE).putLong(qid).array();
- // The nowPlayingList changed: the new list has the full data for the current item
- mMediaInterface.trackChangedRsp(type, track);
- mLastTrackIdSent = qid;
- }
-
- /*
- * helper method to check if startItem and endItem index is with range of
- * MediaItem list. (Resultset containing all items in current path)
- */
- @Nullable
- private List<MediaSession.QueueItem> getQueueSubset(@NonNull List<MediaSession.QueueItem> items,
- long startItem, long endItem) {
- if (endItem > items.size()) {
- endItem = items.size() - 1;
- }
- if (startItem > Integer.MAX_VALUE) {
- startItem = Integer.MAX_VALUE;
- }
- try {
- List<MediaSession.QueueItem> selected =
- items.subList((int) startItem, (int) Math.min(items.size(), endItem + 1));
- if (selected.isEmpty()) {
- Log.i(TAG, "itemsSubList is empty.");
- return null;
- }
- return selected;
- } catch (IndexOutOfBoundsException ex) {
- Log.i(TAG, "Range (" + startItem + ", " + endItem + ") invalid");
- } catch (IllegalArgumentException ex) {
- Log.i(TAG, "Range start " + startItem + " > size (" + items.size() + ")");
- }
- return null;
- }
-
- /*
- * helper method to filter required attibutes before sending GetFolderItems
- * response
- */
- private void getFolderItemsFilterAttr(byte[] bdaddr, AvrcpCmd.FolderItemsCmd folderItemsReqObj,
- @NonNull List<MediaSession.QueueItem> items, byte scope, long startItem, long endItem,
- @NonNull MediaController mediaController) {
- if (DEBUG) {
- Log.d(TAG,
- "getFolderItemsFilterAttr: startItem =" + startItem + ", endItem = " + endItem);
- }
-
- List<MediaSession.QueueItem> resultItems = getQueueSubset(items, startItem, endItem);
- /* check for index out of bound errors */
- if (resultItems == null) {
- Log.w(TAG, "getFolderItemsFilterAttr: resultItems is empty");
- mMediaInterface.folderItemsRsp(bdaddr, AvrcpConstants.RSP_INV_RANGE, null);
- return;
- }
-
- FolderItemsData folderDataNative = new FolderItemsData(resultItems.size());
-
- /* variables to accumulate attrs */
- ArrayList<String> attrArray = new ArrayList<String>();
- ArrayList<Integer> attrId = new ArrayList<Integer>();
-
- for (int itemIndex = 0; itemIndex < resultItems.size(); itemIndex++) {
- MediaSession.QueueItem item = resultItems.get(itemIndex);
- // get the queue id
- long qid = item.getQueueId();
- byte[] uid = ByteBuffer.allocate(AvrcpConstants.UID_SIZE).putLong(qid).array();
-
- // get the array of uid from 2d to array 1D array
- for (int idx = 0; idx < AvrcpConstants.UID_SIZE; idx++) {
- folderDataNative.mItemUid[itemIndex * AvrcpConstants.UID_SIZE + idx] = uid[idx];
- }
-
- /* Set display name for current item */
- folderDataNative.mDisplayNames[itemIndex] =
- getAttrValue(AvrcpConstants.ATTRID_TITLE, item, mediaController);
-
- int maxAttributesRequested = 0;
- boolean isAllAttribRequested = false;
- /* check if remote requested for attributes */
- if (folderItemsReqObj.mNumAttr != AvrcpConstants.NUM_ATTR_NONE) {
- int attrCnt = 0;
-
- /* add requested attr ids to a temp array */
- if (folderItemsReqObj.mNumAttr == AvrcpConstants.NUM_ATTR_ALL) {
- isAllAttribRequested = true;
- maxAttributesRequested = AvrcpConstants.MAX_NUM_ATTR;
- } else {
- /* get only the requested attribute ids from the request */
- maxAttributesRequested = folderItemsReqObj.mNumAttr;
- }
-
- /* lookup and copy values of attributes for ids requested above */
- for (int idx = 0; idx < maxAttributesRequested; idx++) {
- /* check if media player provided requested attributes */
- String value = null;
-
- int attribId =
- isAllAttribRequested ? (idx + 1) : folderItemsReqObj.mAttrIDs[idx];
- value = getAttrValue(attribId, item, mediaController);
- if (value != null) {
- attrArray.add(value);
- attrId.add(attribId);
- attrCnt++;
- }
- }
- /* add num attr actually received from media player for a particular item */
- folderDataNative.mAttributesNum[itemIndex] = attrCnt;
- }
- }
-
- /* copy filtered attr ids and attr values to response parameters */
- if (folderItemsReqObj.mNumAttr != AvrcpConstants.NUM_ATTR_NONE) {
- folderDataNative.mAttrIds = new int[attrId.size()];
- for (int attrIndex = 0; attrIndex < attrId.size(); attrIndex++) {
- folderDataNative.mAttrIds[attrIndex] = attrId.get(attrIndex);
- }
- folderDataNative.mAttrValues = attrArray.toArray(new String[attrArray.size()]);
- }
- for (int attrIndex = 0; attrIndex < folderDataNative.mAttributesNum.length; attrIndex++) {
- if (DEBUG) {
- Log.d(TAG, "folderDataNative.mAttributesNum"
- + folderDataNative.mAttributesNum[attrIndex] + " attrIndex " + attrIndex);
- }
- }
-
- /* create rsp object and send response to remote device */
- FolderItemsRsp rspObj =
- new FolderItemsRsp(AvrcpConstants.RSP_NO_ERROR, Avrcp.sUIDCounter, scope,
- folderDataNative.mNumItems, folderDataNative.mFolderTypes,
- folderDataNative.mPlayable, folderDataNative.mItemTypes,
- folderDataNative.mItemUid, folderDataNative.mDisplayNames,
- folderDataNative.mAttributesNum, folderDataNative.mAttrIds,
- folderDataNative.mAttrValues);
- mMediaInterface.folderItemsRsp(bdaddr, AvrcpConstants.RSP_NO_ERROR, rspObj);
- }
-
- private String getAttrValue(int attr, MediaSession.QueueItem item,
- @Nullable MediaController mediaController) {
- String attrValue = null;
- if (item == null) {
- if (DEBUG) {
- Log.d(TAG, "getAttrValue received null item");
- }
- return null;
- }
- try {
- MediaDescription desc = item.getDescription();
- Bundle extras = desc.getExtras();
- boolean isCurrentTrack = item.getQueueId() == getActiveQueueItemId(mediaController);
- MediaMetadata data = null;
- if (isCurrentTrack) {
- if (DEBUG) {
- Log.d(TAG, "getAttrValue: item is active, using current data");
- }
- data = mediaController.getMetadata();
- if (data == null) {
- Log.e(TAG, "getMetadata didn't give us any metadata for the current track");
- }
- }
-
- if (data == null) {
- // TODO: This code can be removed when b/63117921 is resolved
- data = (MediaMetadata) extras.get(GPM_BUNDLE_METADATA_KEY);
- extras = null; // We no longer need the data in here
- }
-
- extras = fillBundle(data, extras);
-
- if (DEBUG) {
- Log.d(TAG, "getAttrValue: item " + item + " : " + desc);
- }
- switch (attr) {
- case AvrcpConstants.ATTRID_TITLE:
- /* Title is mandatory attribute */
- if (isCurrentTrack) {
- attrValue = extras.getString(MediaMetadata.METADATA_KEY_TITLE);
- } else {
- attrValue = desc.getTitle().toString();
- }
- break;
-
- case AvrcpConstants.ATTRID_ARTIST:
- attrValue = extras.getString(MediaMetadata.METADATA_KEY_ARTIST);
- break;
-
- case AvrcpConstants.ATTRID_ALBUM:
- attrValue = extras.getString(MediaMetadata.METADATA_KEY_ALBUM);
- break;
-
- case AvrcpConstants.ATTRID_TRACK_NUM:
- attrValue =
- Long.toString(extras.getLong(MediaMetadata.METADATA_KEY_TRACK_NUMBER));
- break;
-
- case AvrcpConstants.ATTRID_NUM_TRACKS:
- attrValue =
- Long.toString(extras.getLong(MediaMetadata.METADATA_KEY_NUM_TRACKS));
- break;
-
- case AvrcpConstants.ATTRID_GENRE:
- attrValue = extras.getString(MediaMetadata.METADATA_KEY_GENRE);
- break;
-
- case AvrcpConstants.ATTRID_PLAY_TIME:
- attrValue = Long.toString(extras.getLong(MediaMetadata.METADATA_KEY_DURATION));
- break;
-
- case AvrcpConstants.ATTRID_COVER_ART:
- Log.e(TAG, "getAttrValue: Cover art attribute not supported");
- return null;
-
- default:
- Log.e(TAG, "getAttrValue: Unknown attribute ID requested: " + attr);
- return null;
- }
- } catch (NullPointerException ex) {
- Log.w(TAG, "getAttrValue: attr id not found in result");
- /* checking if attribute is title, then it is mandatory and cannot send null */
- if (attr == AvrcpConstants.ATTRID_TITLE) {
- attrValue = "<Unknown Title>";
- } else {
- return null;
- }
- }
- if (DEBUG) {
- Log.d(TAG, "getAttrValue: attrvalue = " + attrValue + ", attr id:" + attr);
- }
- return attrValue;
- }
-
- private void getItemAttrFilterAttr(byte[] bdaddr, AvrcpCmd.ItemAttrCmd mItemAttrReqObj,
- MediaSession.QueueItem mediaItem, @Nullable MediaController mediaController) {
- /* Response parameters */
- int[] attrIds = null; /* array of attr ids */
- String[] attrValues = null; /* array of attr values */
-
- /* variables to temperorily add attrs */
- ArrayList<String> attrArray = new ArrayList<String>();
- ArrayList<Integer> attrId = new ArrayList<Integer>();
- ArrayList<Integer> attrTempId = new ArrayList<Integer>();
-
- /* check if remote device has requested for attributes */
- if (mItemAttrReqObj.mNumAttr != AvrcpConstants.NUM_ATTR_NONE) {
- if (mItemAttrReqObj.mNumAttr == AvrcpConstants.NUM_ATTR_ALL) {
- for (int idx = 1; idx < AvrcpConstants.MAX_NUM_ATTR; idx++) {
- attrTempId.add(idx); /* attr id 0x00 is unused */
- }
- } else {
- /* get only the requested attribute ids from the request */
- for (int idx = 0; idx < mItemAttrReqObj.mNumAttr; idx++) {
- if (DEBUG) {
- Log.d(TAG, "getItemAttrFilterAttr: attr id[" + idx + "] :"
- + mItemAttrReqObj.mAttrIDs[idx]);
- }
- attrTempId.add(mItemAttrReqObj.mAttrIDs[idx]);
- }
- }
- }
-
- if (DEBUG) {
- Log.d(TAG, "getItemAttrFilterAttr: attr id list size:" + attrTempId.size());
- }
- /* 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);
- if (value != null) {
- attrArray.add(value);
- attrId.add(attrTempId.get(idx));
- }
- }
-
- /* copy filtered attr ids and attr values to response parameters */
- if (mItemAttrReqObj.mNumAttr != AvrcpConstants.NUM_ATTR_NONE) {
- attrIds = new int[attrId.size()];
-
- for (int attrIndex = 0; attrIndex < attrId.size(); attrIndex++) {
- attrIds[attrIndex] = attrId.get(attrIndex);
- }
-
- attrValues = attrArray.toArray(new String[attrId.size()]);
-
- /* create rsp object and send response */
- ItemAttrRsp rspObj = new ItemAttrRsp(AvrcpConstants.RSP_NO_ERROR, attrIds, attrValues);
- mMediaInterface.getItemAttrRsp(bdaddr, AvrcpConstants.RSP_NO_ERROR, rspObj);
- return;
- }
- }
-
- private long getActiveQueueItemId(@Nullable MediaController controller) {
- if (controller == null) {
- return MediaSession.QueueItem.UNKNOWN_ID;
- }
- PlaybackState state = controller.getPlaybackState();
- if (state == null || state.getState() == PlaybackState.STATE_BUFFERING
- || state.getState() == PlaybackState.STATE_NONE) {
- return MediaSession.QueueItem.UNKNOWN_ID;
- }
- long qid = state.getActiveQueueItemId();
- if (qid != MediaSession.QueueItem.UNKNOWN_ID) {
- return qid;
- }
- // Check if we're presenting a "one item queue"
- if (controller.getMetadata() != null) {
- return SINGLE_QID;
- }
- return MediaSession.QueueItem.UNKNOWN_ID;
- }
-
- String displayMediaItem(MediaSession.QueueItem item) {
- StringBuilder sb = new StringBuilder();
- sb.append("#");
- sb.append(item.getQueueId());
- sb.append(": ");
- sb.append(Utils.ellipsize(getAttrValue(AvrcpConstants.ATTRID_TITLE, item, null)));
- sb.append(" - ");
- sb.append(Utils.ellipsize(getAttrValue(AvrcpConstants.ATTRID_ALBUM, item, null)));
- sb.append(" by ");
- sb.append(Utils.ellipsize(getAttrValue(AvrcpConstants.ATTRID_ARTIST, item, null)));
- sb.append(" (");
- sb.append(getAttrValue(AvrcpConstants.ATTRID_PLAY_TIME, item, null));
- sb.append(" ");
- sb.append(getAttrValue(AvrcpConstants.ATTRID_TRACK_NUM, item, null));
- sb.append("/");
- sb.append(getAttrValue(AvrcpConstants.ATTRID_NUM_TRACKS, item, null));
- sb.append(") ");
- sb.append(getAttrValue(AvrcpConstants.ATTRID_GENRE, item, null));
- return sb.toString();
- }
-
- public void dump(StringBuilder sb, @Nullable MediaController mediaController) {
- ProfileService.println(sb, "AddressedPlayer info:");
- ProfileService.println(sb, "mLastTrackIdSent: " + mLastTrackIdSent);
- ProfileService.println(sb, "mNowPlayingList: " + mNowPlayingList.size() + " elements");
- long currentQueueId = getActiveQueueItemId(mediaController);
- for (MediaSession.QueueItem item : mNowPlayingList) {
- long itemId = item.getQueueId();
- ProfileService.println(sb,
- (itemId == currentQueueId ? "*" : " ") + displayMediaItem(item));
- }
- }
-}
diff --git a/src/com/android/bluetooth/avrcp/Avrcp.java b/src/com/android/bluetooth/avrcp/Avrcp.java
deleted file mode 100644
index e30ad41..0000000
--- a/src/com/android/bluetooth/avrcp/Avrcp.java
+++ /dev/null
@@ -1,3123 +0,0 @@
-/*
- * Copyright (C) 2016 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.avrcp;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.bluetooth.BluetoothA2dp;
-import android.bluetooth.BluetoothAvrcp;
-import android.content.BroadcastReceiver;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.SharedPreferences;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.ResolveInfo;
-import android.content.res.Resources;
-import android.media.AudioManager;
-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.os.Bundle;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Looper;
-import android.os.Message;
-import android.os.SystemClock;
-import android.os.UserManager;
-import android.util.Log;
-import android.view.KeyEvent;
-
-import com.android.bluetooth.R;
-import com.android.bluetooth.Utils;
-import com.android.bluetooth.btservice.ProfileService;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.SortedMap;
-import java.util.TreeMap;
-
-/******************************************************************************
- * support Bluetooth AVRCP profile. support metadata, play status, event
- * notifications, address player selection and browse feature implementation.
- ******************************************************************************/
-
-public final class Avrcp {
- private static final boolean DEBUG = false;
- private static final String TAG = "Avrcp";
- private static final String ABSOLUTE_VOLUME_BLACKLIST = "absolute_volume_blacklist";
-
- private Context mContext;
- private final AudioManager mAudioManager;
- private volatile AvrcpMessageHandler mHandler;
- private Handler mAudioManagerPlaybackHandler;
- private AudioManagerPlaybackListener mAudioManagerPlaybackCb;
- private MediaSessionManager mMediaSessionManager;
- @Nullable private MediaController mMediaController;
- private MediaControllerListener mMediaControllerCb;
- private MediaAttributes mMediaAttributes;
- private long mLastQueueId;
- private PackageManager mPackageManager;
- private int mTransportControlFlags;
- @NonNull private PlaybackState mCurrentPlayState;
- private int mA2dpState;
- private boolean mAudioManagerIsPlaying;
- private int mPlayStatusChangedNT;
- private byte mReportedPlayStatus;
- private int mTrackChangedNT;
- private int mPlayPosChangedNT;
- private int mAddrPlayerChangedNT;
- private int mReportedPlayerID;
- private int mNowPlayingListChangedNT;
- private long mPlaybackIntervalMs;
- private long mLastReportedPosition;
- private long mNextPosMs;
- private long mPrevPosMs;
- private int mFeatures;
- private int mRemoteVolume;
- private int mLastRemoteVolume;
- private int mInitialRemoteVolume;
-
- /* Local volume in audio index 0-15 */
- private int mLocalVolume;
- private int mLastLocalVolume;
- private int mAbsVolThreshold;
-
- private String mAddress;
- private HashMap<Integer, Integer> mVolumeMapping;
-
- private int mLastDirection;
- private final int mVolumeStep;
- private final int mAudioStreamMax;
- private boolean mVolCmdSetInProgress;
- private int mAbsVolRetryTimes;
-
- private static final int NO_PLAYER_ID = 0;
-
- private int mCurrAddrPlayerID;
- private int mCurrBrowsePlayerID;
- private int mLastUsedPlayerID;
- private AvrcpMediaRsp mAvrcpMediaRsp;
-
- /* UID counter to be shared across different files. */
- static short sUIDCounter = AvrcpConstants.DEFAULT_UID_COUNTER;
-
- /* BTRC features */
- public static final int BTRC_FEAT_METADATA = 0x01;
- public static final int BTRC_FEAT_ABSOLUTE_VOLUME = 0x02;
- public static final int BTRC_FEAT_BROWSE = 0x04;
-
- /* AVRC response codes, from avrc_defs */
- private static final int AVRC_RSP_NOT_IMPL = 8;
- private static final int AVRC_RSP_ACCEPT = 9;
- private static final int AVRC_RSP_REJ = 10;
- private static final int AVRC_RSP_IN_TRANS = 11;
- private static final int AVRC_RSP_IMPL_STBL = 12;
- private static final int AVRC_RSP_CHANGED = 13;
- private static final int AVRC_RSP_INTERIM = 15;
-
- /* AVRC request commands from Native */
- private static final int MSG_NATIVE_REQ_GET_RC_FEATURES = 1;
- private static final int MSG_NATIVE_REQ_GET_PLAY_STATUS = 2;
- private static final int MSG_NATIVE_REQ_GET_ELEM_ATTRS = 3;
- private static final int MSG_NATIVE_REQ_REGISTER_NOTIFICATION = 4;
- private static final int MSG_NATIVE_REQ_VOLUME_CHANGE = 5;
- private static final int MSG_NATIVE_REQ_GET_FOLDER_ITEMS = 6;
- private static final int MSG_NATIVE_REQ_SET_ADDR_PLAYER = 7;
- private static final int MSG_NATIVE_REQ_SET_BR_PLAYER = 8;
- private static final int MSG_NATIVE_REQ_CHANGE_PATH = 9;
- private static final int MSG_NATIVE_REQ_PLAY_ITEM = 10;
- private static final int MSG_NATIVE_REQ_GET_ITEM_ATTR = 11;
- private static final int MSG_NATIVE_REQ_GET_TOTAL_NUM_OF_ITEMS = 12;
- private static final int MSG_NATIVE_REQ_PASS_THROUGH = 13;
-
- /* other AVRC messages */
- private static final int MSG_PLAY_INTERVAL_TIMEOUT = 14;
- private static final int MSG_SET_ABSOLUTE_VOLUME = 16;
- private static final int MSG_ABS_VOL_TIMEOUT = 17;
- private static final int MSG_SET_A2DP_AUDIO_STATE = 18;
- private static final int MSG_NOW_PLAYING_CHANGED_RSP = 19;
-
- private static final int CMD_TIMEOUT_DELAY = 2000;
- private static final int MAX_ERROR_RETRY_TIMES = 6;
- private static final int AVRCP_MAX_VOL = 127;
- private static final int AVRCP_BASE_VOLUME_STEP = 1;
-
- /* Communicates with MediaPlayer to fetch media content */
- private BrowsedMediaPlayer mBrowsedMediaPlayer;
-
- /* Addressed player handling */
- private AddressedMediaPlayer mAddressedMediaPlayer;
-
- /* List of Media player instances, useful for retrieving MediaPlayerList or MediaPlayerInfo */
- private SortedMap<Integer, MediaPlayerInfo> mMediaPlayerInfoList;
- private boolean mAvailablePlayerViewChanged;
-
- /* List of media players which supports browse */
- private List<BrowsePlayerInfo> mBrowsePlayerInfoList;
-
- /* Manage browsed players */
- private AvrcpBrowseManager mAvrcpBrowseManager;
-
- /* Broadcast receiver for device connections intent broadcasts */
- private final BroadcastReceiver mAvrcpReceiver = new AvrcpServiceBroadcastReceiver();
- private final BroadcastReceiver mBootReceiver = new AvrcpServiceBootReceiver();
-
- /* Recording passthrough key dispatches */
- private static final int PASSTHROUGH_LOG_MAX_SIZE = DEBUG ? 50 : 10;
- private EvictingQueue<MediaKeyLog> mPassthroughLogs; // Passthorugh keys dispatched
- private List<MediaKeyLog> mPassthroughPending; // Passthrough keys sent not dispatched yet
- private int mPassthroughDispatched; // Number of keys dispatched
-
- private class MediaKeyLog {
- private long mTimeSent;
- private long mTimeProcessed;
- private String mPackage;
- private KeyEvent mEvent;
-
- MediaKeyLog(long time, KeyEvent event) {
- mEvent = event;
- mTimeSent = time;
- }
-
- public boolean addDispatch(long time, KeyEvent event, String packageName) {
- if (mPackage != null) {
- return false;
- }
- if (event.getAction() != mEvent.getAction()) {
- return false;
- }
- if (event.getKeyCode() != mEvent.getKeyCode()) {
- return false;
- }
- mPackage = packageName;
- mTimeProcessed = time;
- return true;
- }
-
- @Override
- public String toString() {
- StringBuilder sb = new StringBuilder();
- sb.append(android.text.format.DateFormat.format("MM-dd HH:mm:ss", mTimeSent));
- sb.append(" " + mEvent.toString());
- if (mPackage == null) {
- sb.append(" (undispatched)");
- } else {
- sb.append(" to " + mPackage);
- sb.append(" in " + (mTimeProcessed - mTimeSent) + "ms");
- }
- return sb.toString();
- }
- }
-
- static {
- classInitNative();
- }
-
- private Avrcp(Context context) {
- mMediaAttributes = new MediaAttributes(null);
- mLastQueueId = MediaSession.QueueItem.UNKNOWN_ID;
- mCurrentPlayState =
- new PlaybackState.Builder().setState(PlaybackState.STATE_NONE, -1L, 0.0f).build();
- mReportedPlayStatus = PLAYSTATUS_ERROR;
- mA2dpState = BluetoothA2dp.STATE_NOT_PLAYING;
- mAudioManagerIsPlaying = false;
- mPlayStatusChangedNT = AvrcpConstants.NOTIFICATION_TYPE_CHANGED;
- mTrackChangedNT = AvrcpConstants.NOTIFICATION_TYPE_CHANGED;
- mPlayPosChangedNT = AvrcpConstants.NOTIFICATION_TYPE_CHANGED;
- mAddrPlayerChangedNT = AvrcpConstants.NOTIFICATION_TYPE_CHANGED;
- mNowPlayingListChangedNT = AvrcpConstants.NOTIFICATION_TYPE_CHANGED;
- mPlaybackIntervalMs = 0L;
- mLastReportedPosition = -1;
- mNextPosMs = -1;
- mPrevPosMs = -1;
- mFeatures = 0;
- mRemoteVolume = -1;
- mInitialRemoteVolume = -1;
- mLastRemoteVolume = -1;
- mLastDirection = 0;
- mVolCmdSetInProgress = false;
- mAbsVolRetryTimes = 0;
- mLocalVolume = -1;
- mLastLocalVolume = -1;
- mAbsVolThreshold = 0;
- mVolumeMapping = new HashMap<Integer, Integer>();
- mCurrAddrPlayerID = NO_PLAYER_ID;
- mReportedPlayerID = mCurrAddrPlayerID;
- mCurrBrowsePlayerID = 0;
- mContext = context;
- mLastUsedPlayerID = 0;
- mAddressedMediaPlayer = null;
-
- initNative();
-
- mMediaSessionManager =
- (MediaSessionManager) context.getSystemService(Context.MEDIA_SESSION_SERVICE);
- mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
- mAudioStreamMax = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
- mVolumeStep = Math.max(AVRCP_BASE_VOLUME_STEP, AVRCP_MAX_VOL / mAudioStreamMax);
-
- Resources resources = context.getResources();
- if (resources != null) {
- mAbsVolThreshold =
- resources.getInteger(R.integer.a2dp_absolute_volume_initial_threshold);
-
- // Update the threshold if the thresholdPercent is valid
- int thresholdPercent =
- resources.getInteger(R.integer.a2dp_absolute_volume_initial_threshold_percent);
- if (thresholdPercent >= 0 && thresholdPercent <= 100) {
- mAbsVolThreshold = (thresholdPercent * mAudioStreamMax) / 100;
- }
- }
-
- // Register for package removal intent broadcasts for media button receiver persistence
- IntentFilter pkgFilter = new IntentFilter();
- pkgFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
- pkgFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
- pkgFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
- pkgFilter.addAction(Intent.ACTION_PACKAGE_DATA_CLEARED);
- pkgFilter.addDataScheme("package");
- context.registerReceiver(mAvrcpReceiver, pkgFilter);
-
- IntentFilter bootFilter = new IntentFilter();
- bootFilter.addAction(Intent.ACTION_USER_UNLOCKED);
- context.registerReceiver(mBootReceiver, bootFilter);
- }
-
- private synchronized void start() {
- HandlerThread thread = new HandlerThread("BluetoothAvrcpHandler");
- thread.start();
- Looper looper = thread.getLooper();
- mHandler = new AvrcpMessageHandler(looper);
- mAudioManagerPlaybackHandler = new Handler(looper);
- mAudioManagerPlaybackCb = new AudioManagerPlaybackListener();
- mMediaControllerCb = new MediaControllerListener();
- mAvrcpMediaRsp = new AvrcpMediaRsp();
- mMediaPlayerInfoList = new TreeMap<Integer, MediaPlayerInfo>();
- mAvailablePlayerViewChanged = false;
- mBrowsePlayerInfoList = Collections.synchronizedList(new ArrayList<BrowsePlayerInfo>());
- mPassthroughDispatched = 0;
- mPassthroughLogs = new EvictingQueue<MediaKeyLog>(PASSTHROUGH_LOG_MAX_SIZE);
- mPassthroughPending = Collections.synchronizedList(new ArrayList<MediaKeyLog>());
- if (mMediaSessionManager != null) {
- mMediaSessionManager.addOnActiveSessionsChangedListener(mActiveSessionListener, null,
- mHandler);
- mMediaSessionManager.setCallback(mButtonDispatchCallback, null);
- }
- mPackageManager = mContext.getApplicationContext().getPackageManager();
-
- /* create object to communicate with addressed player */
- mAddressedMediaPlayer = new AddressedMediaPlayer(mAvrcpMediaRsp);
-
- /* initialize BrowseMananger which manages Browse commands and response */
- mAvrcpBrowseManager = new AvrcpBrowseManager(mContext, mAvrcpMediaRsp);
-
- initMediaPlayersList();
-
- UserManager manager = UserManager.get(mContext);
- if (manager == null || manager.isUserUnlocked()) {
- if (DEBUG) {
- Log.d(TAG, "User already unlocked, initializing player lists");
- }
- // initialize browsable player list and build media player list
- buildBrowsablePlayerList();
- }
-
- mAudioManager.registerAudioPlaybackCallback(mAudioManagerPlaybackCb,
- mAudioManagerPlaybackHandler);
- }
-
- public static Avrcp make(Context context) {
- if (DEBUG) {
- Log.v(TAG, "make");
- }
- Avrcp ar = new Avrcp(context);
- ar.start();
- return ar;
- }
-
- public synchronized void doQuit() {
- if (DEBUG) {
- Log.d(TAG, "doQuit");
- }
- if (mAudioManager != null) {
- mAudioManager.unregisterAudioPlaybackCallback(mAudioManagerPlaybackCb);
- }
- if (mMediaController != null) {
- mMediaController.unregisterCallback(mMediaControllerCb);
- }
- if (mMediaSessionManager != null) {
- mMediaSessionManager.setCallback(null, null);
- mMediaSessionManager.removeOnActiveSessionsChangedListener(mActiveSessionListener);
- }
-
- mAudioManagerPlaybackHandler.removeCallbacksAndMessages(null);
- mHandler.removeCallbacksAndMessages(null);
- Looper looper = mHandler.getLooper();
- mHandler = null;
- if (looper != null) {
- looper.quitSafely();
- }
-
- mAudioManagerPlaybackHandler = null;
- mContext.unregisterReceiver(mAvrcpReceiver);
- mContext.unregisterReceiver(mBootReceiver);
-
- mAddressedMediaPlayer.cleanup();
- mAvrcpBrowseManager.cleanup();
- }
-
- public void cleanup() {
- if (DEBUG) {
- Log.d(TAG, "cleanup");
- }
- cleanupNative();
- if (mVolumeMapping != null) {
- mVolumeMapping.clear();
- }
- }
-
- private class AudioManagerPlaybackListener extends AudioManager.AudioPlaybackCallback {
- @Override
- public void onPlaybackConfigChanged(List<AudioPlaybackConfiguration> configs) {
- super.onPlaybackConfigChanged(configs);
- boolean isPlaying = false;
- for (AudioPlaybackConfiguration config : configs) {
- if (DEBUG) {
- Log.d(TAG, "AudioManager Player: "
- + AudioPlaybackConfiguration.toLogFriendlyString(config));
- }
- if (config.getPlayerState() == AudioPlaybackConfiguration.PLAYER_STATE_STARTED) {
- isPlaying = true;
- break;
- }
- }
- if (DEBUG) {
- Log.d(TAG, "AudioManager isPlaying: " + isPlaying);
- }
- if (mAudioManagerIsPlaying != isPlaying) {
- mAudioManagerIsPlaying = isPlaying;
- updateCurrentMediaState();
- }
- }
- }
-
- private class MediaControllerListener extends MediaController.Callback {
- @Override
- public void onMetadataChanged(MediaMetadata metadata) {
- if (DEBUG) {
- Log.v(TAG, "onMetadataChanged");
- }
- updateCurrentMediaState();
- }
-
- @Override
- public synchronized void onPlaybackStateChanged(PlaybackState state) {
- if (DEBUG) {
- Log.v(TAG, "onPlaybackStateChanged: state " + state.toString());
- }
-
- updateCurrentMediaState();
- }
-
- @Override
- public void onSessionDestroyed() {
- Log.v(TAG, "MediaController session destroyed");
- synchronized (Avrcp.this) {
- if (mMediaController != null) {
- removeMediaController(mMediaController.getWrappedInstance());
- }
- }
- }
-
- @Override
- public void onQueueChanged(List<MediaSession.QueueItem> queue) {
- if (queue == null) {
- Log.v(TAG, "onQueueChanged: received null queue");
- return;
- }
-
- final AvrcpMessageHandler handler = mHandler;
- if (handler == null) {
- if (DEBUG) Log.d(TAG, "onQueueChanged: mHandler is already null");
- return;
- }
-
- Log.v(TAG, "onQueueChanged: NowPlaying list changed, Queue Size = "
- + queue.size());
- handler.sendEmptyMessage(MSG_NOW_PLAYING_CHANGED_RSP);
- }
- }
-
- /** Handles Avrcp messages. */
- private final class AvrcpMessageHandler extends Handler {
- private AvrcpMessageHandler(Looper looper) {
- super(looper);
- }
-
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case MSG_NATIVE_REQ_GET_RC_FEATURES: {
- String address = (String) msg.obj;
- mFeatures = msg.arg1;
- mFeatures = modifyRcFeatureFromBlacklist(mFeatures, address);
- if (DEBUG) {
- Log.v(TAG,
- "MSG_NATIVE_REQ_GET_RC_FEATURES: address=" + address + ", features="
- + msg.arg1 + ", mFeatures=" + mFeatures);
- }
- mAudioManager.avrcpSupportsAbsoluteVolume(address, isAbsoluteVolumeSupported());
- mLastLocalVolume = -1;
- mRemoteVolume = -1;
- mLocalVolume = -1;
- mInitialRemoteVolume = -1;
- mAddress = address;
- if (mVolumeMapping != null) {
- mVolumeMapping.clear();
- }
- break;
- }
-
- case MSG_NATIVE_REQ_GET_PLAY_STATUS: {
- byte[] address = (byte[]) msg.obj;
- int btstate = getBluetoothPlayState(mCurrentPlayState);
- int length = (int) mMediaAttributes.getLength();
- int position = (int) getPlayPosition();
- if (DEBUG) {
- Log.v(TAG,
- "MSG_NATIVE_REQ_GET_PLAY_STATUS, responding with state " + btstate
- + " len " + length + " pos " + position);
- }
- getPlayStatusRspNative(address, btstate, length, position);
- break;
- }
-
- case MSG_NATIVE_REQ_GET_ELEM_ATTRS: {
- String[] textArray;
- AvrcpCmd.ElementAttrCmd elem = (AvrcpCmd.ElementAttrCmd) msg.obj;
- byte numAttr = elem.mNumAttr;
- int[] attrIds = elem.mAttrIDs;
- if (DEBUG) {
- Log.v(TAG, "MSG_NATIVE_REQ_GET_ELEM_ATTRS:numAttr=" + numAttr);
- }
- textArray = new String[numAttr];
- StringBuilder responseDebug = new StringBuilder();
- responseDebug.append("getElementAttr response: ");
- for (int i = 0; i < numAttr; ++i) {
- textArray[i] = mMediaAttributes.getString(attrIds[i]);
- responseDebug.append("[" + attrIds[i] + "=");
- if (attrIds[i] == AvrcpConstants.ATTRID_TITLE
- || attrIds[i] == AvrcpConstants.ATTRID_ARTIST
- || attrIds[i] == AvrcpConstants.ATTRID_ALBUM) {
- responseDebug.append(Utils.ellipsize(textArray[i]) + "] ");
- } else {
- responseDebug.append(textArray[i] + "] ");
- }
- }
- Log.v(TAG, responseDebug.toString());
- byte[] bdaddr = elem.mAddress;
- getElementAttrRspNative(bdaddr, numAttr, attrIds, textArray);
- break;
- }
-
- case MSG_NATIVE_REQ_REGISTER_NOTIFICATION:
- if (DEBUG) {
- Log.v(TAG,
- "MSG_NATIVE_REQ_REGISTER_NOTIFICATION:event=" + msg.arg1 + " param="
- + msg.arg2);
- }
- processRegisterNotification((byte[]) msg.obj, msg.arg1, msg.arg2);
- break;
-
- case MSG_NOW_PLAYING_CHANGED_RSP:
- if (DEBUG) {
- Log.v(TAG, "MSG_NOW_PLAYING_CHANGED_RSP");
- }
- removeMessages(MSG_NOW_PLAYING_CHANGED_RSP);
- updateCurrentMediaState();
- break;
-
- case MSG_PLAY_INTERVAL_TIMEOUT:
- sendPlayPosNotificationRsp(false);
- break;
-
- case MSG_NATIVE_REQ_VOLUME_CHANGE:
- if (!isAbsoluteVolumeSupported()) {
- if (DEBUG) {
- Log.v(TAG, "MSG_NATIVE_REQ_VOLUME_CHANGE ignored, not supported");
- }
- break;
- }
- byte absVol = (byte) ((byte) msg.arg1 & 0x7f); // discard MSB as it is RFD
- if (DEBUG) {
- Log.v(TAG, "MSG_NATIVE_REQ_VOLUME_CHANGE: volume=" + absVol + " ctype="
- + msg.arg2);
- }
-
- if (msg.arg2 == AVRC_RSP_ACCEPT || msg.arg2 == AVRC_RSP_REJ) {
- if (!mVolCmdSetInProgress) {
- Log.e(TAG, "Unsolicited response, ignored");
- break;
- }
- removeMessages(MSG_ABS_VOL_TIMEOUT);
-
- mVolCmdSetInProgress = false;
- mAbsVolRetryTimes = 0;
- }
-
- // convert remote volume to local volume
- int volIndex = convertToAudioStreamVolume(absVol);
- if (mInitialRemoteVolume == -1) {
- mInitialRemoteVolume = absVol;
- if (mAbsVolThreshold > 0 && mAbsVolThreshold < mAudioStreamMax
- && volIndex > mAbsVolThreshold) {
- if (DEBUG) {
- Log.v(TAG, "remote inital volume too high " + volIndex + ">"
- + mAbsVolThreshold);
- }
- Message msg1 = this.obtainMessage(MSG_SET_ABSOLUTE_VOLUME,
- mAbsVolThreshold, 0);
- this.sendMessage(msg1);
- mRemoteVolume = absVol;
- mLocalVolume = volIndex;
- break;
- }
- }
-
- if (mLocalVolume != volIndex && (msg.arg2 == AVRC_RSP_ACCEPT
- || msg.arg2 == AVRC_RSP_CHANGED || msg.arg2 == AVRC_RSP_INTERIM)) {
- /* If the volume has successfully changed */
- mLocalVolume = volIndex;
- if (mLastLocalVolume != -1 && msg.arg2 == AVRC_RSP_ACCEPT) {
- if (mLastLocalVolume != volIndex) {
- /* remote volume changed more than requested due to
- * local and remote has different volume steps */
- if (DEBUG) {
- Log.d(TAG,
- "Remote returned volume does not match desired volume "
- + mLastLocalVolume + " vs " + volIndex);
- }
- mLastLocalVolume = mLocalVolume;
- }
- }
-
- notifyVolumeChanged(mLocalVolume);
- mRemoteVolume = absVol;
- long pecentVolChanged = ((long) absVol * 100) / 0x7f;
- Log.e(TAG, "percent volume changed: " + pecentVolChanged + "%");
- } else if (msg.arg2 == AVRC_RSP_REJ) {
- Log.e(TAG, "setAbsoluteVolume call rejected");
- }
- break;
-
- case MSG_SET_ABSOLUTE_VOLUME:
- if (!isAbsoluteVolumeSupported()) {
- if (DEBUG) {
- Log.v(TAG, "ignore MSG_SET_ABSOLUTE_VOLUME");
- }
- break;
- }
-
- if (DEBUG) {
- Log.v(TAG, "MSG_SET_ABSOLUTE_VOLUME");
- }
-
- if (mVolCmdSetInProgress) {
- if (DEBUG) {
- Log.w(TAG, "There is already a volume command in progress.");
- }
- break;
- }
-
- // Remote device didn't set initial volume. Let's black list it
- if (mInitialRemoteVolume == -1) {
- if (DEBUG) {
- Log.d(TAG, "remote " + mAddress
- + " never tell us initial volume, black list it.");
- }
- blackListCurrentDevice("MSG_SET_ABSOLUTE_VOLUME");
- break;
- }
-
- int avrcpVolume = convertToAvrcpVolume(msg.arg1);
- avrcpVolume = Math.min(AVRCP_MAX_VOL, Math.max(0, avrcpVolume));
- if (DEBUG) {
- Log.d(TAG, "Setting volume to " + msg.arg1 + "-" + avrcpVolume);
- }
- if (setVolumeNative(avrcpVolume)) {
- sendMessageDelayed(obtainMessage(MSG_ABS_VOL_TIMEOUT), CMD_TIMEOUT_DELAY);
- mVolCmdSetInProgress = true;
- mLastRemoteVolume = avrcpVolume;
- mLastLocalVolume = msg.arg1;
- } else {
- if (DEBUG) {
- Log.d(TAG, "setVolumeNative failed");
- }
- }
- break;
-
- case MSG_ABS_VOL_TIMEOUT:
- if (DEBUG) {
- Log.v(TAG, "MSG_ABS_VOL_TIMEOUT: Volume change cmd timed out.");
- }
- mVolCmdSetInProgress = false;
- if (mAbsVolRetryTimes >= MAX_ERROR_RETRY_TIMES) {
- mAbsVolRetryTimes = 0;
- /* too many volume change failures, black list the device */
- blackListCurrentDevice("MSG_ABS_VOL_TIMEOUT");
- } else {
- mAbsVolRetryTimes += 1;
- if (setVolumeNative(mLastRemoteVolume)) {
- sendMessageDelayed(obtainMessage(MSG_ABS_VOL_TIMEOUT),
- CMD_TIMEOUT_DELAY);
- mVolCmdSetInProgress = true;
- }
- }
- break;
-
- case MSG_SET_A2DP_AUDIO_STATE:
- if (DEBUG) {
- Log.v(TAG, "MSG_SET_A2DP_AUDIO_STATE:" + msg.arg1);
- }
- mA2dpState = msg.arg1;
- updateCurrentMediaState();
- break;
-
- case MSG_NATIVE_REQ_GET_FOLDER_ITEMS: {
- AvrcpCmd.FolderItemsCmd folderObj = (AvrcpCmd.FolderItemsCmd) msg.obj;
- if (DEBUG) {
- Log.v(TAG, "MSG_NATIVE_REQ_GET_FOLDER_ITEMS " + folderObj);
- }
- switch (folderObj.mScope) {
- case AvrcpConstants.BTRC_SCOPE_PLAYER_LIST:
- handleMediaPlayerListRsp(folderObj);
- break;
- case AvrcpConstants.BTRC_SCOPE_FILE_SYSTEM:
- case AvrcpConstants.BTRC_SCOPE_NOW_PLAYING:
- handleGetFolderItemBrowseResponse(folderObj, folderObj.mAddress);
- break;
- default:
- Log.e(TAG, "unknown scope for getfolderitems. scope = "
- + folderObj.mScope);
- getFolderItemsRspNative(folderObj.mAddress,
- AvrcpConstants.RSP_INV_SCOPE, (short) 0, (byte) 0, 0, null,
- null, null, null, null, null, null, null);
- }
- break;
- }
-
- case MSG_NATIVE_REQ_SET_ADDR_PLAYER:
- // object is bdaddr, argument 1 is the selected player id
- if (DEBUG) {
- Log.v(TAG, "MSG_NATIVE_REQ_SET_ADDR_PLAYER id=" + msg.arg1);
- }
- setAddressedPlayer((byte[]) msg.obj, msg.arg1);
- break;
-
- case MSG_NATIVE_REQ_GET_ITEM_ATTR:
- // msg object contains the item attribute object
- AvrcpCmd.ItemAttrCmd cmd = (AvrcpCmd.ItemAttrCmd) msg.obj;
- if (DEBUG) {
- Log.v(TAG, "MSG_NATIVE_REQ_GET_ITEM_ATTR " + cmd);
- }
- handleGetItemAttr(cmd);
- break;
-
- case MSG_NATIVE_REQ_SET_BR_PLAYER:
- // argument 1 is the selected player id
- if (DEBUG) {
- Log.v(TAG, "MSG_NATIVE_REQ_SET_BR_PLAYER id=" + msg.arg1);
- }
- setBrowsedPlayer((byte[]) msg.obj, msg.arg1);
- break;
-
- case MSG_NATIVE_REQ_CHANGE_PATH: {
- if (DEBUG) {
- Log.v(TAG, "MSG_NATIVE_REQ_CHANGE_PATH");
- }
- Bundle data = msg.getData();
- byte[] bdaddr = data.getByteArray("BdAddress");
- byte[] folderUid = data.getByteArray("folderUid");
- byte direction = data.getByte("direction");
- if (mAvrcpBrowseManager.getBrowsedMediaPlayer(bdaddr) != null) {
- mAvrcpBrowseManager.getBrowsedMediaPlayer(bdaddr)
- .changePath(folderUid, direction);
- } else {
- Log.e(TAG, "Remote requesting change path before setbrowsedplayer");
- changePathRspNative(bdaddr, AvrcpConstants.RSP_BAD_CMD, 0);
- }
- break;
- }
-
- case MSG_NATIVE_REQ_PLAY_ITEM: {
- Bundle data = msg.getData();
- byte[] bdaddr = data.getByteArray("BdAddress");
- byte[] uid = data.getByteArray("uid");
- byte scope = data.getByte("scope");
- if (DEBUG) {
- Log.v(TAG, "MSG_NATIVE_REQ_PLAY_ITEM scope=" + scope + " id="
- + Utils.byteArrayToString(uid));
- }
- handlePlayItemResponse(bdaddr, uid, scope);
- break;
- }
-
- case MSG_NATIVE_REQ_GET_TOTAL_NUM_OF_ITEMS:
- if (DEBUG) {
- Log.v(TAG, "MSG_NATIVE_REQ_GET_TOTAL_NUM_OF_ITEMS scope=" + msg.arg1);
- }
- // argument 1 is scope, object is bdaddr
- handleGetTotalNumOfItemsResponse((byte[]) msg.obj, (byte) msg.arg1);
- break;
-
- case MSG_NATIVE_REQ_PASS_THROUGH:
- if (DEBUG) {
- Log.v(TAG,
- "MSG_NATIVE_REQ_PASS_THROUGH: id=" + msg.arg1 + " st=" + msg.arg2);
- }
- // argument 1 is id, argument 2 is keyState
- handlePassthroughCmd(msg.arg1, msg.arg2);
- break;
-
- default:
- Log.e(TAG, "unknown message! msg.what=" + msg.what);
- break;
- }
- }
- }
-
- private PlaybackState updatePlaybackState() {
- PlaybackState newState = new PlaybackState.Builder().setState(PlaybackState.STATE_NONE,
- PlaybackState.PLAYBACK_POSITION_UNKNOWN, 0.0f).build();
- synchronized (this) {
- PlaybackState controllerState = null;
- if (mMediaController != null) {
- controllerState = mMediaController.getPlaybackState();
- }
-
- if (controllerState != null) {
- newState = controllerState;
- }
- // Use the AudioManager to update the playback state.
- // NOTE: We cannot use the
- // (mA2dpState == BluetoothA2dp.STATE_PLAYING)
- // check, because after Pause, the A2DP state remains in
- // STATE_PLAYING for 3 more seconds.
- // As a result of that, if we pause the music, on carkits the
- // Play status indicator will continue to display "Playing"
- // for 3 more seconds which can be confusing.
- if ((mAudioManagerIsPlaying && newState.getState() != PlaybackState.STATE_PLAYING) || (
- controllerState == null && mAudioManager != null
- && mAudioManager.isMusicActive())) {
- // Use AudioManager playback state if we don't have the state
- // from MediaControlller
- PlaybackState.Builder builder = new PlaybackState.Builder();
- if (mAudioManagerIsPlaying) {
- builder.setState(PlaybackState.STATE_PLAYING,
- PlaybackState.PLAYBACK_POSITION_UNKNOWN, 1.0f);
- } else {
- builder.setState(PlaybackState.STATE_PAUSED,
- PlaybackState.PLAYBACK_POSITION_UNKNOWN, 0.0f);
- }
- newState = builder.build();
- }
- }
-
- byte newPlayStatus = getBluetoothPlayState(newState);
-
- /* update play status in global media player list */
- MediaPlayerInfo player = getAddressedPlayerInfo();
- if (player != null) {
- player.setPlayStatus(newPlayStatus);
- }
-
- if (DEBUG) {
- Log.v(TAG, "updatePlaybackState (" + mPlayStatusChangedNT + "): " + mReportedPlayStatus
- + "➡" + newPlayStatus + "(" + newState + ")");
- }
-
- if (newState != null) {
- mCurrentPlayState = newState;
- }
-
- return mCurrentPlayState;
- }
-
- private void sendPlaybackStatus(int playStatusChangedNT, byte playbackState) {
- registerNotificationRspPlayStatusNative(playStatusChangedNT, playbackState);
- mPlayStatusChangedNT = playStatusChangedNT;
- mReportedPlayStatus = playbackState;
- }
-
- private void updateTransportControls(int transportControlFlags) {
- mTransportControlFlags = transportControlFlags;
- }
-
- class MediaAttributes {
- private boolean mExists;
- private String mTitle;
- private String mArtistName;
- private String mAlbumName;
- private String mMediaNumber;
- private String mMediaTotalNumber;
- private String mGenre;
- private long mPlayingTimeMs;
-
- private static final int ATTR_TITLE = 1;
- private static final int ATTR_ARTIST_NAME = 2;
- private static final int ATTR_ALBUM_NAME = 3;
- private static final int ATTR_MEDIA_NUMBER = 4;
- private static final int ATTR_MEDIA_TOTAL_NUMBER = 5;
- private static final int ATTR_GENRE = 6;
- private static final int ATTR_PLAYING_TIME_MS = 7;
-
-
- MediaAttributes(MediaMetadata data) {
- mExists = data != null;
- if (!mExists) {
- return;
- }
-
- mArtistName = stringOrBlank(data.getString(MediaMetadata.METADATA_KEY_ARTIST));
- mAlbumName = stringOrBlank(data.getString(MediaMetadata.METADATA_KEY_ALBUM));
- mMediaNumber = longStringOrBlank(data.getLong(MediaMetadata.METADATA_KEY_TRACK_NUMBER));
- mMediaTotalNumber =
- longStringOrBlank(data.getLong(MediaMetadata.METADATA_KEY_NUM_TRACKS));
- mGenre = stringOrBlank(data.getString(MediaMetadata.METADATA_KEY_GENRE));
- mPlayingTimeMs = data.getLong(MediaMetadata.METADATA_KEY_DURATION);
-
- // Try harder for the title.
- mTitle = data.getString(MediaMetadata.METADATA_KEY_TITLE);
-
- if (mTitle == null) {
- MediaDescription desc = data.getDescription();
- if (desc != null) {
- CharSequence val = desc.getDescription();
- if (val != null) {
- mTitle = val.toString();
- }
- }
- }
-
- if (mTitle == null) {
- mTitle = new String();
- }
- }
-
- public long getLength() {
- if (!mExists) {
- return 0L;
- }
- return mPlayingTimeMs;
- }
-
- public boolean equals(MediaAttributes other) {
- if (other == null) {
- return false;
- }
-
- if (mExists != other.mExists) {
- return false;
- }
-
- if (!mExists) {
- return true;
- }
-
- return (mTitle.equals(other.mTitle)) && (mArtistName.equals(other.mArtistName))
- && (mAlbumName.equals(other.mAlbumName)) && (mMediaNumber.equals(
- other.mMediaNumber)) && (mMediaTotalNumber.equals(other.mMediaTotalNumber))
- && (mGenre.equals(other.mGenre)) && (mPlayingTimeMs == other.mPlayingTimeMs);
- }
-
- public String getString(int attrId) {
- if (!mExists) {
- return new String();
- }
-
- switch (attrId) {
- case ATTR_TITLE:
- return mTitle;
- case ATTR_ARTIST_NAME:
- return mArtistName;
- case ATTR_ALBUM_NAME:
- return mAlbumName;
- case ATTR_MEDIA_NUMBER:
- return mMediaNumber;
- case ATTR_MEDIA_TOTAL_NUMBER:
- return mMediaTotalNumber;
- case ATTR_GENRE:
- return mGenre;
- case ATTR_PLAYING_TIME_MS:
- return Long.toString(mPlayingTimeMs);
- default:
- return new String();
- }
- }
-
- private String stringOrBlank(String s) {
- return s == null ? new String() : s;
- }
-
- private String longStringOrBlank(Long s) {
- return s == null ? new String() : s.toString();
- }
-
- @Override
- public String toString() {
- if (!mExists) {
- return "[MediaAttributes: none]";
- }
-
- return "[MediaAttributes: " + mTitle + " - " + mAlbumName + " by " + mArtistName + " ("
- + mPlayingTimeMs + " " + mMediaNumber + "/" + mMediaTotalNumber + ") " + mGenre
- + "]";
- }
-
- public String toRedactedString() {
- if (!mExists) {
- return "[MediaAttributes: none]";
- }
-
- return "[MediaAttributes: " + Utils.ellipsize(mTitle) + " - " + Utils.ellipsize(
- mAlbumName) + " by " + Utils.ellipsize(mArtistName) + " (" + mPlayingTimeMs
- + " " + mMediaNumber + "/" + mMediaTotalNumber + ") " + mGenre + "]";
- }
- }
-
- private void updateCurrentMediaState() {
- // Only do player updates when we aren't registering for track changes.
- MediaAttributes currentAttributes;
- PlaybackState newState = updatePlaybackState();
-
- synchronized (this) {
- if (mMediaController == null) {
- currentAttributes = new MediaAttributes(null);
- } else {
- currentAttributes = new MediaAttributes(mMediaController.getMetadata());
- }
- }
-
- byte newPlayStatus = getBluetoothPlayState(newState);
-
- if (newState.getState() != PlaybackState.STATE_BUFFERING
- && newState.getState() != PlaybackState.STATE_NONE) {
- long newQueueId = MediaSession.QueueItem.UNKNOWN_ID;
- if (newState != null) {
- newQueueId = newState.getActiveQueueItemId();
- }
- if (DEBUG) {
- Log.v(TAG,
- "Media update: id " + mLastQueueId + "➡" + newQueueId + "? " + currentAttributes
- .toRedactedString() + " : " + mMediaAttributes.toRedactedString());
- }
-
- if (mAvailablePlayerViewChanged) {
- registerNotificationRspAvalPlayerChangedNative(
- AvrcpConstants.NOTIFICATION_TYPE_CHANGED);
- mAvailablePlayerViewChanged = false;
- return;
- }
-
- if (mAddrPlayerChangedNT == AvrcpConstants.NOTIFICATION_TYPE_INTERIM
- && mReportedPlayerID != mCurrAddrPlayerID) {
- registerNotificationRspAvalPlayerChangedNative(
- AvrcpConstants.NOTIFICATION_TYPE_CHANGED);
- registerNotificationRspAddrPlayerChangedNative(
- AvrcpConstants.NOTIFICATION_TYPE_CHANGED, mCurrAddrPlayerID, sUIDCounter);
-
- mAvailablePlayerViewChanged = false;
- mAddrPlayerChangedNT = AvrcpConstants.NOTIFICATION_TYPE_CHANGED;
- mReportedPlayerID = mCurrAddrPlayerID;
-
- // Update the now playing list without sending the notification
- mNowPlayingListChangedNT = AvrcpConstants.NOTIFICATION_TYPE_CHANGED;
- mAddressedMediaPlayer.updateNowPlayingList(mMediaController);
- mNowPlayingListChangedNT = AvrcpConstants.NOTIFICATION_TYPE_INTERIM;
- }
-
- // Dont send now playing list changed if the player doesn't support browsing
- MediaPlayerInfo info = getAddressedPlayerInfo();
- if (info != null && info.isBrowseSupported()) {
- if (DEBUG) {
- Log.v(TAG, "Check if NowPlayingList is updated");
- }
- mAddressedMediaPlayer.updateNowPlayingList(mMediaController);
- }
-
- // Notify track changed if:
- // - The CT is registered for the notification
- // - Queue ID is UNKNOWN and MediaMetadata is different
- // - Queue ID is valid and different from last Queue ID sent
- if ((newQueueId == -1 || newQueueId != mLastQueueId)
- && mTrackChangedNT == AvrcpConstants.NOTIFICATION_TYPE_INTERIM
- && !currentAttributes.equals(mMediaAttributes)
- && newPlayStatus == PLAYSTATUS_PLAYING) {
- Log.v(TAG, "Send track changed");
- mMediaAttributes = currentAttributes;
- mLastQueueId = newQueueId;
- sendTrackChangedRsp(false);
- }
- } else {
- Log.i(TAG, "Skipping update due to invalid playback state");
- }
-
- // still send the updated play state if the playback state is none or buffering
- if (DEBUG) {
- Log.v(TAG, "play status change " + mReportedPlayStatus + "➡" + newPlayStatus
- + " mPlayStatusChangedNT: " + mPlayStatusChangedNT);
- }
- if (mPlayStatusChangedNT == AvrcpConstants.NOTIFICATION_TYPE_INTERIM || (mReportedPlayStatus
- != newPlayStatus)) {
- sendPlaybackStatus(AvrcpConstants.NOTIFICATION_TYPE_CHANGED, newPlayStatus);
- }
-
- sendPlayPosNotificationRsp(false);
- }
-
- private void getRcFeaturesRequestFromNative(byte[] address, int features) {
- final AvrcpMessageHandler handler = mHandler;
- if (handler == null) {
- if (DEBUG) Log.d(TAG, "getRcFeaturesRequestFromNative: mHandler is already null");
- return;
- }
-
- Message msg = handler.obtainMessage(MSG_NATIVE_REQ_GET_RC_FEATURES, features, 0,
- Utils.getAddressStringFromByte(address));
- handler.sendMessage(msg);
- }
-
- private void getPlayStatusRequestFromNative(byte[] address) {
- final AvrcpMessageHandler handler = mHandler;
- if (handler == null) {
- if (DEBUG) Log.d(TAG, "getPlayStatusRequestFromNative: mHandler is already null");
- return;
- }
-
- Message msg = handler.obtainMessage(MSG_NATIVE_REQ_GET_PLAY_STATUS);
- msg.obj = address;
- handler.sendMessage(msg);
- }
-
- private void getElementAttrRequestFromNative(byte[] address, byte numAttr, int[] attrs) {
- AvrcpCmd avrcpCmdobj = new AvrcpCmd();
- AvrcpCmd.ElementAttrCmd elemAttr = avrcpCmdobj.new ElementAttrCmd(address, numAttr, attrs);
- Message msg = mHandler.obtainMessage(MSG_NATIVE_REQ_GET_ELEM_ATTRS);
- msg.obj = elemAttr;
- mHandler.sendMessage(msg);
- }
-
- private void registerNotificationRequestFromNative(byte[] address, int eventId, int param) {
- final AvrcpMessageHandler handler = mHandler;
- if (handler == null) {
- if (DEBUG) {
- Log.d(TAG, "registerNotificationRequestFromNative: mHandler is already null");
- }
- return;
- }
- Message msg = handler.obtainMessage(MSG_NATIVE_REQ_REGISTER_NOTIFICATION, eventId, param);
- msg.obj = address;
- handler.sendMessage(msg);
- }
-
- private void processRegisterNotification(byte[] address, int eventId, int param) {
- switch (eventId) {
- case EVT_PLAY_STATUS_CHANGED:
- mPlayStatusChangedNT = AvrcpConstants.NOTIFICATION_TYPE_CHANGED;
- updatePlaybackState();
- sendPlaybackStatus(AvrcpConstants.NOTIFICATION_TYPE_INTERIM, mReportedPlayStatus);
- break;
-
- case EVT_TRACK_CHANGED:
- Log.v(TAG, "Track changed notification enabled");
- mTrackChangedNT = AvrcpConstants.NOTIFICATION_TYPE_INTERIM;
- sendTrackChangedRsp(true);
- break;
-
- case EVT_PLAY_POS_CHANGED:
- mPlayPosChangedNT = AvrcpConstants.NOTIFICATION_TYPE_INTERIM;
- mPlaybackIntervalMs = (long) param * 1000L;
- sendPlayPosNotificationRsp(true);
- break;
-
- case EVT_AVBL_PLAYERS_CHANGED:
- /* Notify remote available players changed */
- if (DEBUG) {
- Log.d(TAG, "Available Players notification enabled");
- }
- registerNotificationRspAvalPlayerChangedNative(
- AvrcpConstants.NOTIFICATION_TYPE_INTERIM);
- break;
-
- case EVT_ADDR_PLAYER_CHANGED:
- /* Notify remote addressed players changed */
- if (DEBUG) {
- Log.d(TAG, "Addressed Player notification enabled");
- }
- registerNotificationRspAddrPlayerChangedNative(
- AvrcpConstants.NOTIFICATION_TYPE_INTERIM, mCurrAddrPlayerID, sUIDCounter);
- mAddrPlayerChangedNT = AvrcpConstants.NOTIFICATION_TYPE_INTERIM;
- mReportedPlayerID = mCurrAddrPlayerID;
- break;
-
- case EVENT_UIDS_CHANGED:
- if (DEBUG) {
- Log.d(TAG, "UIDs changed notification enabled");
- }
- registerNotificationRspUIDsChangedNative(AvrcpConstants.NOTIFICATION_TYPE_INTERIM,
- sUIDCounter);
- break;
-
- case EVENT_NOW_PLAYING_CONTENT_CHANGED:
- if (DEBUG) {
- Log.d(TAG, "Now Playing List changed notification enabled");
- }
- /* send interim response to remote device */
- mNowPlayingListChangedNT = AvrcpConstants.NOTIFICATION_TYPE_INTERIM;
- if (!registerNotificationRspNowPlayingChangedNative(
- AvrcpConstants.NOTIFICATION_TYPE_INTERIM)) {
- Log.e(TAG, "EVENT_NOW_PLAYING_CONTENT_CHANGED: "
- + "registerNotificationRspNowPlayingChangedNative for Interim rsp "
- + "failed!");
- }
- break;
- }
- }
-
- private void handlePassthroughCmdRequestFromNative(byte[] address, int id, int keyState) {
- final AvrcpMessageHandler handler = mHandler;
- if (handler == null) {
- if (DEBUG) {
- Log.d(TAG, "handlePassthroughCmdRequestFromNative: mHandler is already null");
- }
- return;
- }
-
- Message msg = handler.obtainMessage(MSG_NATIVE_REQ_PASS_THROUGH, id, keyState);
- handler.sendMessage(msg);
- }
-
- private void sendTrackChangedRsp(boolean registering) {
- if (!registering && mTrackChangedNT != AvrcpConstants.NOTIFICATION_TYPE_INTERIM) {
- if (DEBUG) {
- Log.d(TAG, "sendTrackChangedRsp: Not registered or registering.");
- }
- return;
- }
-
- mTrackChangedNT = AvrcpConstants.NOTIFICATION_TYPE_CHANGED;
- if (registering) {
- mTrackChangedNT = AvrcpConstants.NOTIFICATION_TYPE_INTERIM;
- }
-
- MediaPlayerInfo info = getAddressedPlayerInfo();
- // for non-browsable players or no player
- if (info != null && !info.isBrowseSupported()) {
- byte[] track = AvrcpConstants.TRACK_IS_SELECTED;
- if (!mMediaAttributes.mExists) {
- track = AvrcpConstants.NO_TRACK_SELECTED;
- }
- registerNotificationRspTrackChangeNative(mTrackChangedNT, track);
- return;
- }
-
- mAddressedMediaPlayer.sendTrackChangeWithId(mTrackChangedNT, mMediaController);
- }
-
- private long getPlayPosition() {
- if (mCurrentPlayState == null) {
- return -1L;
- }
-
- if (mCurrentPlayState.getPosition() == PlaybackState.PLAYBACK_POSITION_UNKNOWN) {
- return -1L;
- }
-
- if (isPlayingState(mCurrentPlayState)) {
- long sinceUpdate =
- (SystemClock.elapsedRealtime() - mCurrentPlayState.getLastPositionUpdateTime());
- return sinceUpdate + mCurrentPlayState.getPosition();
- }
-
- return mCurrentPlayState.getPosition();
- }
-
- private boolean isPlayingState(@Nullable PlaybackState state) {
- if (state == null) {
- return false;
- }
- return (state != null) && (state.getState() == PlaybackState.STATE_PLAYING);
- }
-
- /**
- * Sends a play position notification, or schedules one to be
- * sent later at an appropriate time. If |requested| is true,
- * does both because this was called in reponse to a request from the
- * TG.
- */
- private void sendPlayPosNotificationRsp(boolean requested) {
- if (!requested && mPlayPosChangedNT != AvrcpConstants.NOTIFICATION_TYPE_INTERIM) {
- if (DEBUG) {
- Log.d(TAG, "sendPlayPosNotificationRsp: Not registered or requesting.");
- }
- return;
- }
-
- final AvrcpMessageHandler handler = mHandler;
- if (handler == null) {
- if (DEBUG) Log.d(TAG, "sendPlayPosNotificationRsp: handler is already null");
- return;
- }
-
- long playPositionMs = getPlayPosition();
- String debugLine = "sendPlayPosNotificationRsp: ";
-
- // mNextPosMs is set to -1 when the previous position was invalid
- // so this will be true if the new position is valid & old was invalid.
- // mPlayPositionMs is set to -1 when the new position is invalid,
- // and the old mPrevPosMs is >= 0 so this is true when the new is invalid
- // and the old was valid.
- if (DEBUG) {
- debugLine += "(" + requested + ") " + mPrevPosMs + " <=? " + playPositionMs + " <=? "
- + mNextPosMs;
- if (isPlayingState(mCurrentPlayState)) {
- debugLine += " Playing";
- }
- debugLine += " State: " + mCurrentPlayState.getState();
- }
- if (requested || (
- (mLastReportedPosition != playPositionMs) && (playPositionMs >= mNextPosMs) || (
- playPositionMs <= mPrevPosMs))) {
- if (!requested) {
- mPlayPosChangedNT = AvrcpConstants.NOTIFICATION_TYPE_CHANGED;
- }
- registerNotificationRspPlayPosNative(mPlayPosChangedNT, (int) playPositionMs);
- mLastReportedPosition = playPositionMs;
- if (playPositionMs != PlaybackState.PLAYBACK_POSITION_UNKNOWN) {
- mNextPosMs = playPositionMs + mPlaybackIntervalMs;
- mPrevPosMs = playPositionMs - mPlaybackIntervalMs;
- } else {
- mNextPosMs = -1;
- mPrevPosMs = -1;
- }
- }
-
- handler.removeMessages(MSG_PLAY_INTERVAL_TIMEOUT);
- if (mPlayPosChangedNT == AvrcpConstants.NOTIFICATION_TYPE_INTERIM && isPlayingState(
- mCurrentPlayState)) {
- Message msg = handler.obtainMessage(MSG_PLAY_INTERVAL_TIMEOUT);
- long delay = mPlaybackIntervalMs;
- if (mNextPosMs != -1) {
- delay = mNextPosMs - (playPositionMs > 0 ? playPositionMs : 0);
- }
- if (DEBUG) {
- debugLine += " Timeout " + delay + "ms";
- }
- handler.sendMessageDelayed(msg, delay);
- }
- if (DEBUG) {
- Log.d(TAG, debugLine);
- }
- }
-
- /**
- * This is called from AudioService. It will return whether this device supports abs volume.
- * NOT USED AT THE MOMENT.
- */
- public boolean isAbsoluteVolumeSupported() {
- return ((mFeatures & BTRC_FEAT_ABSOLUTE_VOLUME) != 0);
- }
-
- /**
- * We get this call from AudioService. This will send a message to our handler object,
- * requesting our handler to call setVolumeNative()
- */
- public void setAbsoluteVolume(int volume) {
- if (volume == mLocalVolume) {
- if (DEBUG) {
- Log.v(TAG, "setAbsoluteVolume is setting same index, ignore " + volume);
- }
- return;
- }
-
- final AvrcpMessageHandler handler = mHandler;
- if (handler == null) {
- if (DEBUG) Log.d(TAG, "setAbsoluteVolume: mHandler is already null");
- return;
- }
-
- Message msg = handler.obtainMessage(MSG_SET_ABSOLUTE_VOLUME, volume, 0);
- handler.sendMessage(msg);
- }
-
- /* Called in the native layer as a btrc_callback to return the volume set on the carkit in the
- * case when the volume is change locally on the carkit. This notification is not called when
- * the volume is changed from the phone.
- *
- * This method will send a message to our handler to change the local stored volume and notify
- * AudioService to update the UI
- */
- private void volumeChangeRequestFromNative(byte[] address, int volume, int ctype) {
- final AvrcpMessageHandler handler = mHandler;
- if (handler == null) {
- if (DEBUG) Log.d(TAG, "volumeChangeRequestFromNative: mHandler is already null");
- return;
- }
-
- Message msg = handler.obtainMessage(MSG_NATIVE_REQ_VOLUME_CHANGE, volume, ctype);
- Bundle data = new Bundle();
- data.putByteArray("BdAddress", address);
- msg.setData(data);
- handler.sendMessage(msg);
- }
-
- private void getFolderItemsRequestFromNative(byte[] address, byte scope, long startItem,
- long endItem, byte numAttr, int[] attrIds) {
- final AvrcpMessageHandler handler = mHandler;
- if (handler == null) {
- if (DEBUG) Log.d(TAG, "getFolderItemsRequestFromNative: mHandler is already null");
- return;
- }
- AvrcpCmd avrcpCmdobj = new AvrcpCmd();
- AvrcpCmd.FolderItemsCmd folderObj =
- avrcpCmdobj.new FolderItemsCmd(address, scope, startItem, endItem, numAttr,
- attrIds);
- Message msg = handler.obtainMessage(MSG_NATIVE_REQ_GET_FOLDER_ITEMS, 0, 0);
- msg.obj = folderObj;
- handler.sendMessage(msg);
- }
-
- private void setAddressedPlayerRequestFromNative(byte[] address, int playerId) {
- final AvrcpMessageHandler handler = mHandler;
- if (handler == null) {
- if (DEBUG) Log.d(TAG, "setAddressedPlayerRequestFromNative: mHandler is already null");
- return;
- }
-
- Message msg = handler.obtainMessage(MSG_NATIVE_REQ_SET_ADDR_PLAYER, playerId, 0);
- msg.obj = address;
- handler.sendMessage(msg);
- }
-
- private void setBrowsedPlayerRequestFromNative(byte[] address, int playerId) {
- final AvrcpMessageHandler handler = mHandler;
- if (handler == null) {
- if (DEBUG) Log.d(TAG, "setBrowsedPlayerRequestFromNative: mHandler is already null");
- return;
- }
-
- Message msg = handler.obtainMessage(MSG_NATIVE_REQ_SET_BR_PLAYER, playerId, 0);
- msg.obj = address;
- handler.sendMessage(msg);
- }
-
- private void changePathRequestFromNative(byte[] address, byte direction, byte[] folderUid) {
- final AvrcpMessageHandler handler = mHandler;
- if (handler == null) {
- if (DEBUG) Log.d(TAG, "changePathRequestFromNative: mHandler is already null");
- return;
- }
-
- Bundle data = new Bundle();
- Message msg = handler.obtainMessage(MSG_NATIVE_REQ_CHANGE_PATH);
- data.putByteArray("BdAddress", address);
- data.putByteArray("folderUid", folderUid);
- data.putByte("direction", direction);
- msg.setData(data);
- handler.sendMessage(msg);
- }
-
- private void getItemAttrRequestFromNative(byte[] address, byte scope, byte[] itemUid,
- int uidCounter, byte numAttr, int[] attrs) {
- final AvrcpMessageHandler handler = mHandler;
- if (handler == null) {
- if (DEBUG) Log.d(TAG, "getItemAttrRequestFromNative: mHandler is already null");
- return;
- }
- AvrcpCmd avrcpCmdobj = new AvrcpCmd();
- AvrcpCmd.ItemAttrCmd itemAttr =
- avrcpCmdobj.new ItemAttrCmd(address, scope, itemUid, uidCounter, numAttr, attrs);
- Message msg = handler.obtainMessage(MSG_NATIVE_REQ_GET_ITEM_ATTR);
- msg.obj = itemAttr;
- handler.sendMessage(msg);
- }
-
- private void searchRequestFromNative(byte[] address, int charsetId, byte[] searchStr) {
- /* Search is not supported */
- Log.w(TAG, "searchRequestFromNative: search is not supported");
- searchRspNative(address, AvrcpConstants.RSP_SRCH_NOT_SPRTD, 0, 0);
- }
-
- private void playItemRequestFromNative(byte[] address, byte scope, int uidCounter, byte[] uid) {
- final AvrcpMessageHandler handler = mHandler;
- if (handler == null) {
- if (DEBUG) Log.d(TAG, "playItemRequestFromNative: mHandler is already null");
- return;
- }
-
- Bundle data = new Bundle();
- Message msg = handler.obtainMessage(MSG_NATIVE_REQ_PLAY_ITEM);
- data.putByteArray("BdAddress", address);
- data.putByteArray("uid", uid);
- data.putInt("uidCounter", uidCounter);
- data.putByte("scope", scope);
-
- msg.setData(data);
- handler.sendMessage(msg);
- }
-
- private void addToPlayListRequestFromNative(byte[] address, byte scope, byte[] uid,
- int uidCounter) {
- /* add to NowPlaying not supported */
- Log.w(TAG, "addToPlayListRequestFromNative: not supported! scope=" + scope);
- addToNowPlayingRspNative(address, AvrcpConstants.RSP_INTERNAL_ERR);
- }
-
- private void getTotalNumOfItemsRequestFromNative(byte[] address, byte scope) {
- final AvrcpMessageHandler handler = mHandler;
- if (handler == null) {
- if (DEBUG) Log.d(TAG, "getTotalNumOfItemsRequestFromNative: mHandler is already null");
- return;
- }
-
- Bundle data = new Bundle();
- Message msg = handler.obtainMessage(MSG_NATIVE_REQ_GET_TOTAL_NUM_OF_ITEMS);
- msg.arg1 = scope;
- msg.obj = address;
- handler.sendMessage(msg);
- }
-
- private void notifyVolumeChanged(int volume) {
- mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, volume,
- AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_BLUETOOTH_ABS_VOLUME);
- }
-
- private int convertToAudioStreamVolume(int volume) {
- // Rescale volume to match AudioSystem's volume
- return (int) Math.floor((double) volume * mAudioStreamMax / AVRCP_MAX_VOL);
- }
-
- private int convertToAvrcpVolume(int volume) {
- return (int) Math.ceil((double) volume * AVRCP_MAX_VOL / mAudioStreamMax);
- }
-
- private void blackListCurrentDevice(String reason) {
- mFeatures &= ~BTRC_FEAT_ABSOLUTE_VOLUME;
- mAudioManager.avrcpSupportsAbsoluteVolume(mAddress, isAbsoluteVolumeSupported());
-
- SharedPreferences pref =
- mContext.getSharedPreferences(ABSOLUTE_VOLUME_BLACKLIST, Context.MODE_PRIVATE);
- SharedPreferences.Editor editor = pref.edit();
-
- StringBuilder sb = new StringBuilder();
- sb.append("Time: ");
- sb.append(android.text.format.DateFormat.format("yyyy/MM/dd HH:mm:ss",
- System.currentTimeMillis()));
- sb.append(" Reason: ");
- sb.append(reason);
- editor.putString(mAddress, sb.toString());
- editor.apply();
- }
-
- private int modifyRcFeatureFromBlacklist(int feature, String address) {
- SharedPreferences pref =
- mContext.getSharedPreferences(ABSOLUTE_VOLUME_BLACKLIST, Context.MODE_PRIVATE);
- if (!pref.contains(address)) {
- return feature;
- }
- return feature & ~BTRC_FEAT_ABSOLUTE_VOLUME;
- }
-
- public void resetBlackList(String address) {
- SharedPreferences pref =
- mContext.getSharedPreferences(ABSOLUTE_VOLUME_BLACKLIST, Context.MODE_PRIVATE);
- SharedPreferences.Editor editor = pref.edit();
- editor.remove(address);
- editor.apply();
- }
-
- /**
- * This is called from A2dpStateMachine to set A2dp audio state.
- */
- public void setA2dpAudioState(int state) {
- final AvrcpMessageHandler handler = mHandler;
- if (handler == null) {
- if (DEBUG) Log.d(TAG, "setA2dpAudioState: mHandler is already null");
- return;
- }
-
- Message msg = handler.obtainMessage(MSG_SET_A2DP_AUDIO_STATE, state, 0);
- handler.sendMessage(msg);
- }
-
- private class AvrcpServiceBootReceiver extends BroadcastReceiver {
- @Override
- public void onReceive(Context context, Intent intent) {
- String action = intent.getAction();
- if (action.equals(Intent.ACTION_USER_UNLOCKED)) {
- if (DEBUG) {
- Log.d(TAG, "User unlocked, initializing player lists");
- }
- /* initializing media player's list */
- buildBrowsablePlayerList();
- }
- }
- }
-
- private class AvrcpServiceBroadcastReceiver extends BroadcastReceiver {
- @Override
- public void onReceive(Context context, Intent intent) {
- String action = intent.getAction();
- if (DEBUG) {
- Log.d(TAG, "AvrcpServiceBroadcastReceiver-> Action: " + action);
- }
-
- if (action.equals(Intent.ACTION_PACKAGE_REMOVED) || action.equals(
- Intent.ACTION_PACKAGE_DATA_CLEARED)) {
- if (!intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
- // a package is being removed, not replaced
- String packageName = intent.getData().getSchemeSpecificPart();
- if (packageName != null) {
- handlePackageModified(packageName, true);
- }
- }
-
- } else if (action.equals(Intent.ACTION_PACKAGE_ADDED) || action.equals(
- Intent.ACTION_PACKAGE_CHANGED)) {
- String packageName = intent.getData().getSchemeSpecificPart();
- if (DEBUG) {
- Log.d(TAG, "AvrcpServiceBroadcastReceiver-> packageName: " + packageName);
- }
- if (packageName != null) {
- handlePackageModified(packageName, false);
- }
- }
- }
- }
-
- private void handlePackageModified(String packageName, boolean removed) {
- if (DEBUG) {
- Log.d(TAG, "packageName: " + packageName + " removed: " + removed);
- }
-
- if (removed) {
- removeMediaPlayerInfo(packageName);
- // old package is removed, updating local browsable player's list
- if (isBrowseSupported(packageName)) {
- removePackageFromBrowseList(packageName);
- }
- } else {
- // new package has been added.
- if (isBrowsableListUpdated(packageName)) {
- // Rebuilding browsable players list
- buildBrowsablePlayerList();
- }
- }
- }
-
- private boolean isBrowsableListUpdated(String newPackageName) {
- // getting the browsable media players list from package manager
- Intent intent = new Intent("android.media.browse.MediaBrowserService");
- List<ResolveInfo> resInfos =
- mPackageManager.queryIntentServices(intent, PackageManager.MATCH_ALL);
- for (ResolveInfo resolveInfo : resInfos) {
- if (resolveInfo.serviceInfo.packageName.equals(newPackageName)) {
- if (DEBUG) {
- Log.d(TAG,
- "isBrowsableListUpdated: package includes MediaBrowserService, true");
- }
- return true;
- }
- }
-
- // if list has different size
- if (resInfos.size() != mBrowsePlayerInfoList.size()) {
- if (DEBUG) {
- Log.d(TAG, "isBrowsableListUpdated: browsable list size mismatch, true");
- }
- return true;
- }
-
- Log.d(TAG, "isBrowsableListUpdated: false");
- return false;
- }
-
- private void removePackageFromBrowseList(String packageName) {
- if (DEBUG) {
- Log.d(TAG, "removePackageFromBrowseList: " + packageName);
- }
- synchronized (mBrowsePlayerInfoList) {
- int browseInfoID = getBrowseId(packageName);
- if (browseInfoID != -1) {
- mBrowsePlayerInfoList.remove(browseInfoID);
- }
- }
- }
-
- /*
- * utility function to get the browse player index from global browsable
- * list. It may return -1 if specified package name is not in the list.
- */
- private int getBrowseId(String packageName) {
- boolean response = false;
- int browseInfoID = 0;
- synchronized (mBrowsePlayerInfoList) {
- for (BrowsePlayerInfo info : mBrowsePlayerInfoList) {
- if (info.packageName.equals(packageName)) {
- response = true;
- break;
- }
- browseInfoID++;
- }
- }
-
- if (!response) {
- browseInfoID = -1;
- }
-
- if (DEBUG) {
- Log.d(TAG, "getBrowseId for packageName: " + packageName + " , browseInfoID: "
- + browseInfoID);
- }
- return browseInfoID;
- }
-
- private void setAddressedPlayer(byte[] bdaddr, int selectedId) {
- String functionTag = "setAddressedPlayer(" + selectedId + "): ";
-
- synchronized (mMediaPlayerInfoList) {
- if (mMediaPlayerInfoList.isEmpty()) {
- Log.w(TAG, functionTag + "no players, send no available players");
- setAddressedPlayerRspNative(bdaddr, AvrcpConstants.RSP_NO_AVBL_PLAY);
- return;
- }
- if (!mMediaPlayerInfoList.containsKey(selectedId)) {
- Log.w(TAG, functionTag + "invalid id, sending response back ");
- setAddressedPlayerRspNative(bdaddr, AvrcpConstants.RSP_INV_PLAYER);
- return;
- }
-
- if (isPlayerAlreadyAddressed(selectedId)) {
- MediaPlayerInfo info = getAddressedPlayerInfo();
- Log.i(TAG, functionTag + "player already addressed: " + info);
- setAddressedPlayerRspNative(bdaddr, AvrcpConstants.RSP_NO_ERROR);
- return;
- }
- // register new Media Controller Callback and update the current IDs
- if (!updateCurrentController(selectedId, mCurrBrowsePlayerID)) {
- Log.e(TAG, functionTag + "updateCurrentController failed!");
- setAddressedPlayerRspNative(bdaddr, AvrcpConstants.RSP_INTERNAL_ERR);
- return;
- }
- // If we don't have a controller, try to launch the player
- MediaPlayerInfo info = getAddressedPlayerInfo();
- if (info.getMediaController() == null) {
- Intent launch = mPackageManager.getLaunchIntentForPackage(info.getPackageName());
- Log.i(TAG, functionTag + "launching player " + launch);
- mContext.startActivity(launch);
- }
- }
- setAddressedPlayerRspNative(bdaddr, AvrcpConstants.RSP_NO_ERROR);
- }
-
- private void setBrowsedPlayer(byte[] bdaddr, int selectedId) {
- int status = AvrcpConstants.RSP_NO_ERROR;
-
- // checking for error cases
- if (mMediaPlayerInfoList.isEmpty()) {
- status = AvrcpConstants.RSP_NO_AVBL_PLAY;
- Log.w(TAG, "setBrowsedPlayer: No available players! ");
- } else {
- // Workaround for broken controllers selecting ID 0
- // Seen at least on Ford, Chevrolet MyLink
- if (selectedId == 0) {
- Log.w(TAG, "setBrowsedPlayer: workaround invalid id 0");
- selectedId = mCurrAddrPlayerID;
- }
-
- // update current browse player id and start browsing service
- updateNewIds(mCurrAddrPlayerID, selectedId);
- String browsedPackage = getPackageName(selectedId);
-
- if (!isPackageNameValid(browsedPackage)) {
- Log.w(TAG, " Invalid package for id:" + mCurrBrowsePlayerID);
- status = AvrcpConstants.RSP_INV_PLAYER;
- } else if (!isBrowseSupported(browsedPackage)) {
- Log.w(TAG, "Browse unsupported for id:" + mCurrBrowsePlayerID + ", packagename : "
- + browsedPackage);
- status = AvrcpConstants.RSP_PLAY_NOT_BROW;
- } else if (!startBrowseService(bdaddr, browsedPackage)) {
- Log.e(TAG, "service cannot be started for browse player id:" + mCurrBrowsePlayerID
- + ", packagename : " + browsedPackage);
- status = AvrcpConstants.RSP_INTERNAL_ERR;
- }
- }
-
- if (status != AvrcpConstants.RSP_NO_ERROR) {
- setBrowsedPlayerRspNative(bdaddr, status, (byte) 0x00, 0, null);
- }
-
- if (DEBUG) {
- Log.d(TAG, "setBrowsedPlayer for selectedId: " + selectedId + " , status: " + status);
- }
- }
-
- private MediaSessionManager.OnActiveSessionsChangedListener mActiveSessionListener =
- new MediaSessionManager.OnActiveSessionsChangedListener() {
-
- @Override
- public void onActiveSessionsChanged(
- List<android.media.session.MediaController> newControllers) {
- Set<String> updatedPackages = new HashSet<String>();
- // Update the current players
- for (android.media.session.MediaController controller : newControllers) {
- String packageName = controller.getPackageName();
- if (DEBUG) {
- Log.v(TAG, "ActiveSession: " + MediaControllerFactory.wrap(controller));
- }
- // Only use the first (highest priority) controller from each package
- if (updatedPackages.contains(packageName)) {
- continue;
- }
- addMediaPlayerController(controller);
- updatedPackages.add(packageName);
- }
-
- if (newControllers.size() > 0 && getAddressedPlayerInfo() == null) {
- if (DEBUG) {
- Log.v(TAG, "No addressed player but active sessions, taking first.");
- }
- setAddressedMediaSessionPackage(newControllers.get(0).getPackageName());
- }
- updateCurrentMediaState();
- }
- };
-
- private void setAddressedMediaSessionPackage(@Nullable String packageName) {
- if (packageName == null) {
- // Should only happen when there's no media players, reset to no available player.
- updateCurrentController(0, mCurrBrowsePlayerID);
- return;
- }
- if (packageName.equals("com.android.server.telecom")) {
- Log.d(TAG, "Ignore addressed media session change to telecom");
- return;
- }
- // No change.
- if (getPackageName(mCurrAddrPlayerID).equals(packageName)) {
- return;
- }
- if (DEBUG) {
- Log.v(TAG, "Changing addressed media session to " + packageName);
- }
- // If the player doesn't exist, we need to add it.
- if (getMediaPlayerInfo(packageName) == null) {
- addMediaPlayerPackage(packageName);
- updateCurrentMediaState();
- }
- synchronized (mMediaPlayerInfoList) {
- for (Map.Entry<Integer, MediaPlayerInfo> entry : mMediaPlayerInfoList.entrySet()) {
- if (entry.getValue().getPackageName().equals(packageName)) {
- int newAddrID = entry.getKey();
- if (DEBUG) {
- Log.v(TAG, "Set addressed #" + newAddrID + " " + entry.getValue());
- }
- updateCurrentController(newAddrID, mCurrBrowsePlayerID);
- updateCurrentMediaState();
- return;
- }
- }
- }
- // We shouldn't ever get here.
- Log.e(TAG, "Player info for " + packageName + " doesn't exist!");
- }
-
- private void setActiveMediaSession(MediaSession.Token token) {
- android.media.session.MediaController activeController =
- new android.media.session.MediaController(mContext, token);
- if (activeController.getPackageName().equals("com.android.server.telecom")) {
- Log.d(TAG, "Ignore active media session change to telecom");
- return;
- }
- if (DEBUG) {
- Log.v(TAG, "Set active media session " + activeController.getPackageName());
- }
- addMediaPlayerController(activeController);
- setAddressedMediaSessionPackage(activeController.getPackageName());
- }
-
- private boolean startBrowseService(byte[] bdaddr, String packageName) {
- boolean status = true;
-
- /* creating new instance for Browse Media Player */
- String browseService = getBrowseServiceName(packageName);
- if (!browseService.isEmpty()) {
- mAvrcpBrowseManager.getBrowsedMediaPlayer(bdaddr)
- .setBrowsed(packageName, browseService);
- } else {
- Log.w(TAG, "No Browser service available for " + packageName);
- status = false;
- }
-
- if (DEBUG) {
- Log.d(TAG,
- "startBrowseService for packageName: " + packageName + ", status = " + status);
- }
- return status;
- }
-
- private String getBrowseServiceName(String packageName) {
- String browseServiceName = "";
-
- // getting the browse service name from browse player info
- synchronized (mBrowsePlayerInfoList) {
- int browseInfoID = getBrowseId(packageName);
- if (browseInfoID != -1) {
- browseServiceName = mBrowsePlayerInfoList.get(browseInfoID).serviceClass;
- }
- }
-
- if (DEBUG) {
- Log.d(TAG, "getBrowseServiceName for packageName: " + packageName
- + ", browseServiceName = " + browseServiceName);
- }
- return browseServiceName;
- }
-
- void buildBrowsablePlayerList() {
- synchronized (mBrowsePlayerInfoList) {
- mBrowsePlayerInfoList.clear();
- Intent intent = new Intent(android.service.media.MediaBrowserService.SERVICE_INTERFACE);
- List<ResolveInfo> playerList =
- mPackageManager.queryIntentServices(intent, PackageManager.MATCH_ALL);
-
- for (ResolveInfo info : playerList) {
- String displayableName = info.loadLabel(mPackageManager).toString();
- String serviceName = info.serviceInfo.name;
- String packageName = info.serviceInfo.packageName;
-
- if (DEBUG) {
- Log.d(TAG, "Adding " + serviceName + " to list of browsable players");
- }
- BrowsePlayerInfo currentPlayer =
- new BrowsePlayerInfo(packageName, displayableName, serviceName);
- mBrowsePlayerInfoList.add(currentPlayer);
- MediaPlayerInfo playerInfo = getMediaPlayerInfo(packageName);
- MediaController controller =
- (playerInfo == null) ? null : playerInfo.getMediaController();
- // Refresh the media player entry so it notices we can browse
- if (controller != null) {
- addMediaPlayerController(controller.getWrappedInstance());
- } else {
- addMediaPlayerPackage(packageName);
- }
- }
- updateCurrentMediaState();
- }
- }
-
- /* Initializes list of media players identified from session manager active sessions */
- private void initMediaPlayersList() {
- synchronized (mMediaPlayerInfoList) {
- // Clearing old browsable player's list
- mMediaPlayerInfoList.clear();
-
- if (mMediaSessionManager == null) {
- if (DEBUG) {
- Log.w(TAG, "initMediaPlayersList: no media session manager!");
- }
- return;
- }
-
- List<android.media.session.MediaController> controllers =
- mMediaSessionManager.getActiveSessions(null);
- if (DEBUG) {
- Log.v(TAG, "initMediaPlayerInfoList: " + controllers.size() + " controllers");
- }
- /* Initializing all media players */
- for (android.media.session.MediaController controller : controllers) {
- addMediaPlayerController(controller);
- }
-
- updateCurrentMediaState();
-
- if (mMediaPlayerInfoList.size() > 0) {
- // Set the first one as the Addressed Player
- updateCurrentController(mMediaPlayerInfoList.firstKey(), -1);
- }
- }
- }
-
- private List<android.media.session.MediaController> getMediaControllers() {
- List<android.media.session.MediaController> controllers =
- new ArrayList<android.media.session.MediaController>();
- synchronized (mMediaPlayerInfoList) {
- for (MediaPlayerInfo info : mMediaPlayerInfoList.values()) {
- MediaController controller = info.getMediaController();
- if (controller != null) {
- controllers.add(controller.getWrappedInstance());
- }
- }
- }
- return controllers;
- }
-
- /** Add (or update) a player to the media player list without a controller */
- private boolean addMediaPlayerPackage(String packageName) {
- MediaPlayerInfo info = new MediaPlayerInfo(null, AvrcpConstants.PLAYER_TYPE_AUDIO,
- AvrcpConstants.PLAYER_SUBTYPE_NONE, PLAYSTATUS_STOPPED,
- getFeatureBitMask(packageName), packageName, getAppLabel(packageName));
- return addMediaPlayerInfo(info);
- }
-
- /** Add (or update) a player to the media player list given an active controller */
- private boolean addMediaPlayerController(android.media.session.MediaController controller) {
- String packageName = controller.getPackageName();
- MediaPlayerInfo info = new MediaPlayerInfo(MediaControllerFactory.wrap(controller),
- AvrcpConstants.PLAYER_TYPE_AUDIO, AvrcpConstants.PLAYER_SUBTYPE_NONE,
- getBluetoothPlayState(controller.getPlaybackState()),
- getFeatureBitMask(packageName), controller.getPackageName(),
- getAppLabel(packageName));
- return addMediaPlayerInfo(info);
- }
-
- /** Add or update a player to the media player list given the MediaPlayerInfo object.
- * @return true if an item was updated, false if it was added instead
- */
- private boolean addMediaPlayerInfo(MediaPlayerInfo info) {
- int updateId = -1;
- boolean updated = false;
- boolean currentRemoved = false;
- if (info.getPackageName().equals("com.android.server.telecom")) {
- Log.d(TAG, "Skip adding telecom to the media player info list");
- return updated;
- }
- synchronized (mMediaPlayerInfoList) {
- for (Map.Entry<Integer, MediaPlayerInfo> entry : mMediaPlayerInfoList.entrySet()) {
- MediaPlayerInfo current = entry.getValue();
- int id = entry.getKey();
- if (info.getPackageName().equals(current.getPackageName())) {
- if (!current.equalView(info)) {
- // If we would present a different player, make it a new player
- // so that controllers know whether a player is browsable or not.
- mMediaPlayerInfoList.remove(id);
- currentRemoved = (mCurrAddrPlayerID == id);
- break;
- }
- updateId = id;
- updated = true;
- break;
- }
- }
- if (updateId == -1) {
- // New player
- mLastUsedPlayerID++;
- updateId = mLastUsedPlayerID;
- mAvailablePlayerViewChanged = true;
- }
- mMediaPlayerInfoList.put(updateId, info);
- }
- if (DEBUG) {
- Log.d(TAG, (updated ? "update #" : "add #") + updateId + ":" + info.toString());
- }
- if (currentRemoved || updateId == mCurrAddrPlayerID) {
- updateCurrentController(updateId, mCurrBrowsePlayerID);
- }
- return updated;
- }
-
- /** Remove all players related to |packageName| from the media player info list */
- private MediaPlayerInfo removeMediaPlayerInfo(String packageName) {
- synchronized (mMediaPlayerInfoList) {
- int removeKey = -1;
- for (Map.Entry<Integer, MediaPlayerInfo> entry : mMediaPlayerInfoList.entrySet()) {
- if (entry.getValue().getPackageName().equals(packageName)) {
- removeKey = entry.getKey();
- break;
- }
- }
- if (removeKey != -1) {
- if (DEBUG) {
- Log.d(TAG, "remove #" + removeKey + ":" + mMediaPlayerInfoList.get(removeKey));
- }
- mAvailablePlayerViewChanged = true;
- return mMediaPlayerInfoList.remove(removeKey);
- }
-
- return null;
- }
- }
-
- /** Remove the controller referenced by |controller| from any player in the list */
- private void removeMediaController(@Nullable android.media.session.MediaController controller) {
- if (controller == null) {
- return;
- }
- synchronized (mMediaPlayerInfoList) {
- for (Map.Entry<Integer, MediaPlayerInfo> entry : mMediaPlayerInfoList.entrySet()) {
- MediaPlayerInfo info = entry.getValue();
- MediaController c = info.getMediaController();
- if (c != null && c.equals(controller)) {
- info.setMediaController(null);
- if (entry.getKey() == mCurrAddrPlayerID) {
- updateCurrentController(mCurrAddrPlayerID, mCurrBrowsePlayerID);
- }
- }
- }
- }
- }
-
- /*
- * utility function to get the playback state of any media player through
- * media controller APIs.
- */
- private byte getBluetoothPlayState(PlaybackState pbState) {
- if (pbState == null) {
- Log.w(TAG, "playState object null, sending STOPPED");
- return PLAYSTATUS_STOPPED;
- }
-
- switch (pbState.getState()) {
- case PlaybackState.STATE_PLAYING:
- return PLAYSTATUS_PLAYING;
-
- case PlaybackState.STATE_BUFFERING:
- case PlaybackState.STATE_STOPPED:
- case PlaybackState.STATE_NONE:
- case PlaybackState.STATE_CONNECTING:
- return PLAYSTATUS_STOPPED;
-
- case PlaybackState.STATE_PAUSED:
- return PLAYSTATUS_PAUSED;
-
- case PlaybackState.STATE_FAST_FORWARDING:
- case PlaybackState.STATE_SKIPPING_TO_NEXT:
- case PlaybackState.STATE_SKIPPING_TO_QUEUE_ITEM:
- return PLAYSTATUS_FWD_SEEK;
-
- case PlaybackState.STATE_REWINDING:
- case PlaybackState.STATE_SKIPPING_TO_PREVIOUS:
- return PLAYSTATUS_REV_SEEK;
-
- case PlaybackState.STATE_ERROR:
- default:
- return PLAYSTATUS_ERROR;
- }
- }
-
- /*
- * utility function to get the feature bit mask of any media player through
- * package name
- */
- private short[] getFeatureBitMask(String packageName) {
-
- ArrayList<Short> featureBitsList = new ArrayList<Short>();
-
- /* adding default feature bits */
- featureBitsList.add(AvrcpConstants.AVRC_PF_PLAY_BIT_NO);
- featureBitsList.add(AvrcpConstants.AVRC_PF_STOP_BIT_NO);
- featureBitsList.add(AvrcpConstants.AVRC_PF_PAUSE_BIT_NO);
- featureBitsList.add(AvrcpConstants.AVRC_PF_REWIND_BIT_NO);
- featureBitsList.add(AvrcpConstants.AVRC_PF_FAST_FWD_BIT_NO);
- featureBitsList.add(AvrcpConstants.AVRC_PF_FORWARD_BIT_NO);
- featureBitsList.add(AvrcpConstants.AVRC_PF_BACKWARD_BIT_NO);
- featureBitsList.add(AvrcpConstants.AVRC_PF_ADV_CTRL_BIT_NO);
-
- /* Add/Modify browse player supported features. */
- if (isBrowseSupported(packageName)) {
- featureBitsList.add(AvrcpConstants.AVRC_PF_BROWSE_BIT_NO);
- 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);
- }
-
- // converting arraylist to array for response
- short[] featureBitsArray = new short[featureBitsList.size()];
-
- for (int i = 0; i < featureBitsList.size(); i++) {
- featureBitsArray[i] = featureBitsList.get(i).shortValue();
- }
-
- return featureBitsArray;
- }
-
- /**
- * Checks the Package name if it supports Browsing or not.
- *
- * @param packageName - name of the package to get the Id.
- * @return true if it supports browsing, else false.
- */
- private boolean isBrowseSupported(String packageName) {
- synchronized (mBrowsePlayerInfoList) {
- /* check if Browsable Player's list contains this package name */
- for (BrowsePlayerInfo info : mBrowsePlayerInfoList) {
- if (info.packageName.equals(packageName)) {
- if (DEBUG) {
- Log.v(TAG, "isBrowseSupported for " + packageName + ": true");
- }
- return true;
- }
- }
- }
-
- if (DEBUG) {
- Log.v(TAG, "isBrowseSupported for " + packageName + ": false");
- }
- return false;
- }
-
- private String getPackageName(int id) {
- MediaPlayerInfo player = null;
- synchronized (mMediaPlayerInfoList) {
- player = mMediaPlayerInfoList.getOrDefault(id, null);
- }
-
- if (player == null) {
- Log.w(TAG, "No package name for player (" + id + " not valid)");
- return "";
- }
-
- String packageName = player.getPackageName();
- if (DEBUG) {
- Log.v(TAG, "Player " + id + " package: " + packageName);
- }
- return packageName;
- }
-
- /* from the global object, getting the current browsed player's package name */
- private String getCurrentBrowsedPlayer(byte[] bdaddr) {
- String browsedPlayerPackage = "";
-
- Map<String, BrowsedMediaPlayer> connList = mAvrcpBrowseManager.getConnList();
- String bdaddrStr = new String(bdaddr);
- if (connList.containsKey(bdaddrStr)) {
- browsedPlayerPackage = connList.get(bdaddrStr).getPackageName();
- }
- if (DEBUG) {
- Log.v(TAG, "getCurrentBrowsedPlayerPackage: " + browsedPlayerPackage);
- }
- return browsedPlayerPackage;
- }
-
- /* Returns the MediaPlayerInfo for the currently addressed media player */
- private MediaPlayerInfo getAddressedPlayerInfo() {
- synchronized (mMediaPlayerInfoList) {
- return mMediaPlayerInfoList.getOrDefault(mCurrAddrPlayerID, null);
- }
- }
-
- /*
- * Utility function to get the Media player info from package name returns
- * null if package name not found in media players list
- */
- private MediaPlayerInfo getMediaPlayerInfo(String packageName) {
- synchronized (mMediaPlayerInfoList) {
- if (mMediaPlayerInfoList.isEmpty()) {
- if (DEBUG) {
- Log.v(TAG, "getMediaPlayerInfo: Media players list empty");
- }
- return null;
- }
-
- for (MediaPlayerInfo info : mMediaPlayerInfoList.values()) {
- if (packageName.equals(info.getPackageName())) {
- if (DEBUG) {
- Log.v(TAG, "getMediaPlayerInfo: Found " + packageName);
- }
- return info;
- }
- }
- if (DEBUG) {
- Log.w(TAG, "getMediaPlayerInfo: " + packageName + " not found");
- }
- return null;
- }
- }
-
- /* prepare media list & return the media player list response object */
- private MediaPlayerListRsp prepareMediaPlayerRspObj() {
- synchronized (mMediaPlayerInfoList) {
- // TODO(apanicke): This hack will go away as soon as a developer
- // option to enable or disable player selection is created. Right
- // now this is needed to fix BMW i3 carkits and any other carkits
- // that might try to connect to a player that isnt the current
- // player based on this list
- int numPlayers = 1;
-
- int[] playerIds = new int[numPlayers];
- byte[] playerTypes = new byte[numPlayers];
- int[] playerSubTypes = new int[numPlayers];
- String[] displayableNameArray = new String[numPlayers];
- byte[] playStatusValues = new byte[numPlayers];
- short[] featureBitMaskValues =
- new short[numPlayers * AvrcpConstants.AVRC_FEATURE_MASK_SIZE];
-
- // Reserve the first spot for the currently addressed player if
- // we have one
- int players = mMediaPlayerInfoList.containsKey(mCurrAddrPlayerID) ? 1 : 0;
- for (Map.Entry<Integer, MediaPlayerInfo> entry : mMediaPlayerInfoList.entrySet()) {
- int idx = players;
- if (entry.getKey() == mCurrAddrPlayerID) {
- idx = 0;
- } else {
- continue; // TODO(apanicke): Remove, see above note
- }
- MediaPlayerInfo info = entry.getValue();
- playerIds[idx] = entry.getKey();
- playerTypes[idx] = info.getMajorType();
- playerSubTypes[idx] = info.getSubType();
- displayableNameArray[idx] = info.getDisplayableName();
- playStatusValues[idx] = info.getPlayStatus();
-
- short[] featureBits = info.getFeatureBitMask();
- for (int numBit = 0; numBit < featureBits.length; numBit++) {
- /* gives which octet this belongs to */
- byte octet = (byte) (featureBits[numBit] / 8);
- /* gives the bit position within the octet */
- byte bit = (byte) (featureBits[numBit] % 8);
- featureBitMaskValues[(idx * AvrcpConstants.AVRC_FEATURE_MASK_SIZE) + octet] |=
- (1 << bit);
- }
-
- /* printLogs */
- if (DEBUG) {
- Log.d(TAG, "Player " + playerIds[idx] + ": " + displayableNameArray[idx]
- + " type: " + playerTypes[idx] + ", " + playerSubTypes[idx]
- + " status: " + playStatusValues[idx]);
- }
-
- if (idx != 0) {
- players++;
- }
- }
-
- if (DEBUG) {
- Log.d(TAG, "prepareMediaPlayerRspObj: numPlayers = " + numPlayers);
- }
-
- return new MediaPlayerListRsp(AvrcpConstants.RSP_NO_ERROR, sUIDCounter, numPlayers,
- AvrcpConstants.BTRC_ITEM_PLAYER, playerIds, playerTypes, playerSubTypes,
- playStatusValues, featureBitMaskValues, displayableNameArray);
- }
- }
-
- /* build media player list and send it to remote. */
- private void handleMediaPlayerListRsp(AvrcpCmd.FolderItemsCmd folderObj) {
- MediaPlayerListRsp rspObj = null;
- synchronized (mMediaPlayerInfoList) {
- int numPlayers = mMediaPlayerInfoList.size();
- if (numPlayers == 0) {
- mediaPlayerListRspNative(folderObj.mAddress, AvrcpConstants.RSP_NO_AVBL_PLAY,
- (short) 0, (byte) 0, 0, null, null, null, null, null, null);
- return;
- }
- if (folderObj.mStartItem >= numPlayers) {
- Log.i(TAG, "handleMediaPlayerListRsp: start = " + folderObj.mStartItem
- + " > num of items = " + numPlayers);
- mediaPlayerListRspNative(folderObj.mAddress, AvrcpConstants.RSP_INV_RANGE,
- (short) 0, (byte) 0, 0, null, null, null, null, null, null);
- return;
- }
- rspObj = prepareMediaPlayerRspObj();
- }
- if (DEBUG) {
- Log.d(TAG, "handleMediaPlayerListRsp: sending " + rspObj.mNumItems + " players");
- }
- mediaPlayerListRspNative(folderObj.mAddress, rspObj.mStatus, rspObj.mUIDCounter,
- rspObj.mItemType, rspObj.mNumItems, rspObj.mPlayerIds, rspObj.mPlayerTypes,
- rspObj.mPlayerSubTypes, rspObj.mPlayStatusValues, rspObj.mFeatureBitMaskValues,
- rspObj.mPlayerNameList);
- }
-
- /* unregister to the old controller, update new IDs and register to the new controller */
- private boolean updateCurrentController(int addrId, int browseId) {
- boolean registerRsp = true;
-
- updateNewIds(addrId, browseId);
-
- MediaController newController = null;
- MediaPlayerInfo info = getAddressedPlayerInfo();
- if (info != null) {
- newController = info.getMediaController();
- }
-
- if (DEBUG) {
- Log.d(TAG, "updateCurrentController: " + mMediaController + " to " + newController);
- }
- synchronized (this) {
- if (mMediaController == null || (!mMediaController.equals(newController))) {
- if (mMediaController != null) {
- mMediaController.unregisterCallback(mMediaControllerCb);
- }
- mMediaController = newController;
- if (mMediaController != null) {
- mMediaController.registerCallback(mMediaControllerCb, mHandler);
- } else {
- registerRsp = false;
- }
- }
- }
- updateCurrentMediaState();
- return registerRsp;
- }
-
- /* Handle getfolderitems for scope = VFS, Search, NowPlayingList */
- private void handleGetFolderItemBrowseResponse(AvrcpCmd.FolderItemsCmd folderObj,
- byte[] bdaddr) {
- int status = AvrcpConstants.RSP_NO_ERROR;
-
- /* Browsed player is already set */
- if (folderObj.mScope == AvrcpConstants.BTRC_SCOPE_FILE_SYSTEM) {
- if (mAvrcpBrowseManager.getBrowsedMediaPlayer(bdaddr) == null) {
- Log.e(TAG, "handleGetFolderItemBrowseResponse: no browsed player set for "
- + Utils.getAddressStringFromByte(bdaddr));
- getFolderItemsRspNative(bdaddr, AvrcpConstants.RSP_INTERNAL_ERR, (short) 0,
- (byte) 0x00, 0, null, null, null, null, null, null, null, null);
- return;
- }
- mAvrcpBrowseManager.getBrowsedMediaPlayer(bdaddr).getFolderItemsVFS(folderObj);
- return;
- }
- if (folderObj.mScope == AvrcpConstants.BTRC_SCOPE_NOW_PLAYING) {
- mAddressedMediaPlayer.getFolderItemsNowPlaying(bdaddr, folderObj, mMediaController);
- return;
- }
-
- /* invalid scope */
- Log.e(TAG, "handleGetFolderItemBrowseResponse: unknown scope " + folderObj.mScope);
- getFolderItemsRspNative(bdaddr, AvrcpConstants.RSP_INV_SCOPE, (short) 0, (byte) 0x00, 0,
- null, null, null, null, null, null, null, null);
- }
-
- /* utility function to update the global values of current Addressed and browsed player */
- private void updateNewIds(int addrId, int browseId) {
- if (DEBUG) {
- Log.v(TAG,
- "updateNewIds: Addressed:" + mCurrAddrPlayerID + " to " + addrId + ", Browse:"
- + mCurrBrowsePlayerID + " to " + browseId);
- }
- mCurrAddrPlayerID = addrId;
- mCurrBrowsePlayerID = browseId;
- }
-
- /* Getting the application's displayable name from package name */
- private String getAppLabel(String packageName) {
- ApplicationInfo appInfo = null;
- try {
- appInfo = mPackageManager.getApplicationInfo(packageName, 0);
- } catch (NameNotFoundException e) {
- e.printStackTrace();
- }
-
- return (String) (appInfo != null ? mPackageManager.getApplicationLabel(appInfo)
- : "Unknown");
- }
-
- private void handlePlayItemResponse(byte[] bdaddr, byte[] uid, byte scope) {
- if (scope == AvrcpConstants.BTRC_SCOPE_NOW_PLAYING) {
- mAddressedMediaPlayer.playItem(bdaddr, uid, mMediaController);
- } else {
- if (!isAddrPlayerSameAsBrowsed(bdaddr)) {
- Log.w(TAG, "Remote requesting play item on uid which may not be recognized by"
- + "current addressed player");
- playItemRspNative(bdaddr, AvrcpConstants.RSP_INV_ITEM);
- }
-
- if (mAvrcpBrowseManager.getBrowsedMediaPlayer(bdaddr) != null) {
- mAvrcpBrowseManager.getBrowsedMediaPlayer(bdaddr).playItem(uid, scope);
- } else {
- Log.e(TAG, "handlePlayItemResponse: Remote requested playitem "
- + "before setbrowsedplayer");
- playItemRspNative(bdaddr, AvrcpConstants.RSP_INTERNAL_ERR);
- }
- }
- }
-
- private void handleGetItemAttr(AvrcpCmd.ItemAttrCmd itemAttr) {
- if (itemAttr.mUidCounter != sUIDCounter) {
- Log.e(TAG, "handleGetItemAttr: invaild uid counter.");
- getItemAttrRspNative(itemAttr.mAddress, AvrcpConstants.RSP_UID_CHANGED, (byte) 0, null,
- null);
- return;
- }
- if (itemAttr.mScope == AvrcpConstants.BTRC_SCOPE_NOW_PLAYING) {
- if (mCurrAddrPlayerID == NO_PLAYER_ID) {
- getItemAttrRspNative(itemAttr.mAddress, AvrcpConstants.RSP_NO_AVBL_PLAY, (byte) 0,
- null, null);
- return;
- }
- mAddressedMediaPlayer.getItemAttr(itemAttr.mAddress, itemAttr, mMediaController);
- return;
- }
- // All other scopes use browsed player
- if (mAvrcpBrowseManager.getBrowsedMediaPlayer(itemAttr.mAddress) != null) {
- mAvrcpBrowseManager.getBrowsedMediaPlayer(itemAttr.mAddress).getItemAttr(itemAttr);
- } else {
- Log.e(TAG, "Could not get attributes. mBrowsedMediaPlayer is null");
- getItemAttrRspNative(itemAttr.mAddress, AvrcpConstants.RSP_INTERNAL_ERR, (byte) 0, null,
- null);
- }
- }
-
- private void handleGetTotalNumOfItemsResponse(byte[] bdaddr, byte scope) {
- // for scope as media player list
- if (scope == AvrcpConstants.BTRC_SCOPE_PLAYER_LIST) {
- int numPlayers = 0;
- synchronized (mMediaPlayerInfoList) {
- numPlayers = mMediaPlayerInfoList.size();
- }
- if (DEBUG) {
- Log.d(TAG, "handleGetTotalNumOfItemsResponse: " + numPlayers + " players.");
- }
- getTotalNumOfItemsRspNative(bdaddr, AvrcpConstants.RSP_NO_ERROR, 0, numPlayers);
- } else if (scope == AvrcpConstants.BTRC_SCOPE_NOW_PLAYING) {
- mAddressedMediaPlayer.getTotalNumOfItems(bdaddr, mMediaController);
- } else {
- // for FileSystem browsing scopes as VFS, Now Playing
- if (mAvrcpBrowseManager.getBrowsedMediaPlayer(bdaddr) != null) {
- mAvrcpBrowseManager.getBrowsedMediaPlayer(bdaddr).getTotalNumOfItems(scope);
- } else {
- Log.e(TAG, "Could not get Total NumOfItems. mBrowsedMediaPlayer is null");
- getTotalNumOfItemsRspNative(bdaddr, AvrcpConstants.RSP_INTERNAL_ERR, 0, 0);
- }
- }
-
- }
-
- /* check if browsed player and addressed player are same */
- private boolean isAddrPlayerSameAsBrowsed(byte[] bdaddr) {
- String browsedPlayer = getCurrentBrowsedPlayer(bdaddr);
-
- if (!isPackageNameValid(browsedPlayer)) {
- Log.w(TAG, "Browsed player name empty");
- return false;
- }
-
- MediaPlayerInfo info = getAddressedPlayerInfo();
- String packageName = (info == null) ? "<none>" : info.getPackageName();
- if (info == null || !packageName.equals(browsedPlayer)) {
- if (DEBUG) {
- Log.d(TAG, browsedPlayer + " is not addressed player " + packageName);
- }
- return false;
- }
- return true;
- }
-
- /* checks if package name is not null or empty */
- private boolean isPackageNameValid(String browsedPackage) {
- boolean isValid = (browsedPackage != null && browsedPackage.length() > 0);
- if (DEBUG) {
- Log.d(TAG, "isPackageNameValid: browsedPackage = " + browsedPackage + "isValid = "
- + isValid);
- }
- return isValid;
- }
-
- /* checks if selected addressed player is already addressed */
- private boolean isPlayerAlreadyAddressed(int selectedId) {
- // checking if selected ID is same as the current addressed player id
- boolean isAddressed = (mCurrAddrPlayerID == selectedId);
- if (DEBUG) {
- Log.d(TAG, "isPlayerAlreadyAddressed: isAddressed = " + isAddressed);
- }
- return isAddressed;
- }
-
- public void dump(StringBuilder sb) {
- sb.append("AVRCP:\n");
- ProfileService.println(sb, "mMediaAttributes: " + mMediaAttributes.toRedactedString());
- ProfileService.println(sb, "mTransportControlFlags: " + mTransportControlFlags);
- ProfileService.println(sb, "mCurrentPlayState: " + mCurrentPlayState);
- ProfileService.println(sb, "mPlayStatusChangedNT: " + mPlayStatusChangedNT);
- ProfileService.println(sb, "mTrackChangedNT: " + mTrackChangedNT);
- ProfileService.println(sb, "mPlaybackIntervalMs: " + mPlaybackIntervalMs);
- ProfileService.println(sb, "mPlayPosChangedNT: " + mPlayPosChangedNT);
- ProfileService.println(sb, "mNextPosMs: " + mNextPosMs);
- ProfileService.println(sb, "mPrevPosMs: " + mPrevPosMs);
- ProfileService.println(sb, "mFeatures: " + mFeatures);
- ProfileService.println(sb, "mRemoteVolume: " + mRemoteVolume);
- ProfileService.println(sb, "mLastRemoteVolume: " + mLastRemoteVolume);
- ProfileService.println(sb, "mLastDirection: " + mLastDirection);
- ProfileService.println(sb, "mVolumeStep: " + mVolumeStep);
- ProfileService.println(sb, "mAudioStreamMax: " + mAudioStreamMax);
- ProfileService.println(sb, "mVolCmdSetInProgress: " + mVolCmdSetInProgress);
- ProfileService.println(sb, "mAbsVolRetryTimes: " + mAbsVolRetryTimes);
- ProfileService.println(sb, "mVolumeMapping: " + mVolumeMapping.toString());
- synchronized (this) {
- if (mMediaController != null) {
- ProfileService.println(sb,
- "mMediaController: " + mMediaController.getWrappedInstance() + " pkg "
- + mMediaController.getPackageName());
- }
- }
- ProfileService.println(sb, "");
- ProfileService.println(sb, "Media Players:");
- synchronized (mMediaPlayerInfoList) {
- for (Map.Entry<Integer, MediaPlayerInfo> entry : mMediaPlayerInfoList.entrySet()) {
- int key = entry.getKey();
- ProfileService.println(sb,
- ((mCurrAddrPlayerID == key) ? " *#" : " #") + entry.getKey() + ": " + entry
- .getValue());
- }
- }
-
- ProfileService.println(sb, "");
- mAddressedMediaPlayer.dump(sb, mMediaController);
-
- ProfileService.println(sb, "");
- ProfileService.println(sb, mPassthroughDispatched + " passthrough operations: ");
- if (mPassthroughDispatched > mPassthroughLogs.size()) {
- ProfileService.println(sb, " (last " + mPassthroughLogs.size() + ")");
- }
- synchronized (mPassthroughLogs) {
- for (MediaKeyLog log : mPassthroughLogs) {
- ProfileService.println(sb, " " + log);
- }
- }
- synchronized (mPassthroughPending) {
- for (MediaKeyLog log : mPassthroughPending) {
- ProfileService.println(sb, " " + log);
- }
- }
-
- // Print the blacklisted devices (for absolute volume control)
- SharedPreferences pref =
- mContext.getSharedPreferences(ABSOLUTE_VOLUME_BLACKLIST, Context.MODE_PRIVATE);
- Map<String, ?> allKeys = pref.getAll();
- ProfileService.println(sb, "");
- ProfileService.println(sb, "Runtime Blacklisted Devices (absolute volume):");
- if (allKeys.isEmpty()) {
- ProfileService.println(sb, " None");
- } else {
- for (Map.Entry<String, ?> entry : allKeys.entrySet()) {
- String key = entry.getKey();
- Object value = entry.getValue();
- if (value instanceof String) {
- ProfileService.println(sb, " " + key + " " + value);
- } else {
- ProfileService.println(sb, " " + key + " Reason: Unknown");
- }
- }
- }
- }
-
- public class AvrcpBrowseManager {
- public Map<String, BrowsedMediaPlayer> connList = new HashMap<String, BrowsedMediaPlayer>();
- private AvrcpMediaRspInterface mMediaInterface;
- private Context mContext;
-
- public AvrcpBrowseManager(Context context, AvrcpMediaRspInterface mediaInterface) {
- mContext = context;
- mMediaInterface = mediaInterface;
- }
-
- public void cleanup() {
- Iterator entries = connList.entrySet().iterator();
- while (entries.hasNext()) {
- Map.Entry entry = (Map.Entry) entries.next();
- BrowsedMediaPlayer browsedMediaPlayer = (BrowsedMediaPlayer) entry.getValue();
- if (browsedMediaPlayer != null) {
- browsedMediaPlayer.cleanup();
- }
- }
- // clean up the map
- connList.clear();
- }
-
- // get the a free media player interface based on the passed bd address
- // if the no items is found for the passed media player then it assignes a
- // available media player interface
- public BrowsedMediaPlayer getBrowsedMediaPlayer(byte[] bdaddr) {
- BrowsedMediaPlayer mediaPlayer;
- String bdaddrStr = new String(bdaddr);
- if (connList.containsKey(bdaddrStr)) {
- mediaPlayer = connList.get(bdaddrStr);
- } else {
- mediaPlayer = new BrowsedMediaPlayer(bdaddr, mContext, mMediaInterface);
- connList.put(bdaddrStr, mediaPlayer);
- }
- return mediaPlayer;
- }
-
- // clears the details pertaining to passed bdaddres
- public boolean clearBrowsedMediaPlayer(byte[] bdaddr) {
- String bdaddrStr = new String(bdaddr);
- if (connList.containsKey(bdaddrStr)) {
- connList.remove(bdaddrStr);
- return true;
- }
- return false;
- }
-
- public Map<String, BrowsedMediaPlayer> getConnList() {
- return connList;
- }
-
- /* Helper function to convert colon separated bdaddr to byte string */
- private byte[] hexStringToByteArray(String s) {
- int len = s.length();
- byte[] data = new byte[len / 2];
- for (int i = 0; i < len; i += 2) {
- data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character.digit(
- s.charAt(i + 1), 16));
- }
- return data;
- }
- }
-
- /*
- * private class which handles responses from AvrcpMediaManager. Maps responses to native
- * responses. This class implements the AvrcpMediaRspInterface interface.
- */
- private class AvrcpMediaRsp implements AvrcpMediaRspInterface {
- private static final String TAG = "AvrcpMediaRsp";
-
- @Override
- public void setAddrPlayerRsp(byte[] address, int rspStatus) {
- if (!setAddressedPlayerRspNative(address, rspStatus)) {
- Log.e(TAG, "setAddrPlayerRsp failed!");
- }
- }
-
- @Override
- public void setBrowsedPlayerRsp(byte[] address, int rspStatus, byte depth, int numItems,
- String[] textArray) {
- if (!setBrowsedPlayerRspNative(address, rspStatus, depth, numItems, textArray)) {
- Log.e(TAG, "setBrowsedPlayerRsp failed!");
- }
- }
-
- @Override
- public void mediaPlayerListRsp(byte[] address, int rspStatus, MediaPlayerListRsp rspObj) {
- if (rspObj != null && rspStatus == AvrcpConstants.RSP_NO_ERROR) {
- if (!mediaPlayerListRspNative(address, rspStatus, sUIDCounter, rspObj.mItemType,
- rspObj.mNumItems, rspObj.mPlayerIds, rspObj.mPlayerTypes,
- rspObj.mPlayerSubTypes, rspObj.mPlayStatusValues,
- rspObj.mFeatureBitMaskValues, rspObj.mPlayerNameList)) {
- Log.e(TAG, "mediaPlayerListRsp failed!");
- }
- } else {
- Log.e(TAG, "mediaPlayerListRsp: rspObj is null");
- if (!mediaPlayerListRspNative(address, rspStatus, sUIDCounter, (byte) 0x00, 0, null,
- null, null, null, null, null)) {
- Log.e(TAG, "mediaPlayerListRsp failed!");
- }
- }
- }
-
- @Override
- public void folderItemsRsp(byte[] address, int rspStatus, FolderItemsRsp rspObj) {
- if (rspObj != null && rspStatus == AvrcpConstants.RSP_NO_ERROR) {
- if (!getFolderItemsRspNative(address, rspStatus, sUIDCounter, rspObj.mScope,
- rspObj.mNumItems, rspObj.mFolderTypes, rspObj.mPlayable, rspObj.mItemTypes,
- rspObj.mItemUid, rspObj.mDisplayNames, rspObj.mAttributesNum,
- rspObj.mAttrIds, rspObj.mAttrValues)) {
- Log.e(TAG, "getFolderItemsRspNative failed!");
- }
- } else {
- Log.e(TAG, "folderItemsRsp: rspObj is null or rspStatus is error:" + rspStatus);
- if (!getFolderItemsRspNative(address, rspStatus, sUIDCounter, (byte) 0x00, 0, null,
- null, null, null, null, null, null, null)) {
- Log.e(TAG, "getFolderItemsRspNative failed!");
- }
- }
-
- }
-
- @Override
- public void changePathRsp(byte[] address, int rspStatus, int numItems) {
- if (!changePathRspNative(address, rspStatus, numItems)) {
- Log.e(TAG, "changePathRspNative failed!");
- }
- }
-
- @Override
- public void getItemAttrRsp(byte[] address, int rspStatus, ItemAttrRsp rspObj) {
- if (rspObj != null && rspStatus == AvrcpConstants.RSP_NO_ERROR) {
- if (!getItemAttrRspNative(address, rspStatus, rspObj.mNumAttr,
- rspObj.mAttributesIds, rspObj.mAttributesArray)) {
- Log.e(TAG, "getItemAttrRspNative failed!");
- }
- } else {
- Log.e(TAG, "getItemAttrRsp: rspObj is null or rspStatus is error:" + rspStatus);
- if (!getItemAttrRspNative(address, rspStatus, (byte) 0x00, null, null)) {
- Log.e(TAG, "getItemAttrRspNative failed!");
- }
- }
- }
-
- @Override
- public void playItemRsp(byte[] address, int rspStatus) {
- if (!playItemRspNative(address, rspStatus)) {
- Log.e(TAG, "playItemRspNative failed!");
- }
- }
-
- @Override
- public void getTotalNumOfItemsRsp(byte[] address, int rspStatus, int uidCounter,
- int numItems) {
- if (!getTotalNumOfItemsRspNative(address, rspStatus, sUIDCounter, numItems)) {
- Log.e(TAG, "getTotalNumOfItemsRspNative failed!");
- }
- }
-
- @Override
- public void addrPlayerChangedRsp(int type, int playerId, int uidCounter) {
- if (!registerNotificationRspAddrPlayerChangedNative(type, playerId, sUIDCounter)) {
- Log.e(TAG, "registerNotificationRspAddrPlayerChangedNative failed!");
- }
- }
-
- @Override
- public void avalPlayerChangedRsp(byte[] address, int type) {
- if (!registerNotificationRspAvalPlayerChangedNative(type)) {
- Log.e(TAG, "registerNotificationRspAvalPlayerChangedNative failed!");
- }
- }
-
- @Override
- public void uidsChangedRsp(int type) {
- if (!registerNotificationRspUIDsChangedNative(type, sUIDCounter)) {
- Log.e(TAG, "registerNotificationRspUIDsChangedNative failed!");
- }
- }
-
- @Override
- public void nowPlayingChangedRsp(int type) {
- if (mNowPlayingListChangedNT != AvrcpConstants.NOTIFICATION_TYPE_INTERIM) {
- if (DEBUG) {
- Log.d(TAG, "NowPlayingListChanged: Not registered or requesting.");
- }
- return;
- }
-
- if (!registerNotificationRspNowPlayingChangedNative(type)) {
- Log.e(TAG, "registerNotificationRspNowPlayingChangedNative failed!");
- }
- mNowPlayingListChangedNT = AvrcpConstants.NOTIFICATION_TYPE_CHANGED;
- }
-
- @Override
- public void trackChangedRsp(int type, byte[] uid) {
- if (!registerNotificationRspTrackChangeNative(type, uid)) {
- Log.e(TAG, "registerNotificationRspTrackChangeNative failed!");
- }
- }
- }
-
- /* getters for some private variables */
- public AvrcpBrowseManager getAvrcpBrowseManager() {
- return mAvrcpBrowseManager;
- }
-
- /* PASSTHROUGH COMMAND MANAGEMENT */
-
- void handlePassthroughCmd(int op, int state) {
- int code = avrcpPassthroughToKeyCode(op);
- if (code == KeyEvent.KEYCODE_UNKNOWN) {
- Log.w(TAG, "Ignoring passthrough of unknown key " + op + " state " + state);
- return;
- }
- int action = KeyEvent.ACTION_DOWN;
- if (state == AvrcpConstants.KEY_STATE_RELEASE) {
- action = KeyEvent.ACTION_UP;
- }
- KeyEvent event = new KeyEvent(action, code);
- if (!KeyEvent.isMediaKey(code)) {
- Log.w(TAG, "Passthrough non-media key " + op + " (code " + code + ") state " + state);
- }
-
- mMediaSessionManager.dispatchMediaKeyEvent(event);
- addKeyPending(event);
- }
-
- private int avrcpPassthroughToKeyCode(int operation) {
- switch (operation) {
- case BluetoothAvrcp.PASSTHROUGH_ID_UP:
- return KeyEvent.KEYCODE_DPAD_UP;
- case BluetoothAvrcp.PASSTHROUGH_ID_DOWN:
- return KeyEvent.KEYCODE_DPAD_DOWN;
- case BluetoothAvrcp.PASSTHROUGH_ID_LEFT:
- return KeyEvent.KEYCODE_DPAD_LEFT;
- case BluetoothAvrcp.PASSTHROUGH_ID_RIGHT:
- return KeyEvent.KEYCODE_DPAD_RIGHT;
- case BluetoothAvrcp.PASSTHROUGH_ID_RIGHT_UP:
- return KeyEvent.KEYCODE_DPAD_UP_RIGHT;
- case BluetoothAvrcp.PASSTHROUGH_ID_RIGHT_DOWN:
- return KeyEvent.KEYCODE_DPAD_DOWN_RIGHT;
- case BluetoothAvrcp.PASSTHROUGH_ID_LEFT_UP:
- return KeyEvent.KEYCODE_DPAD_UP_LEFT;
- case BluetoothAvrcp.PASSTHROUGH_ID_LEFT_DOWN:
- return KeyEvent.KEYCODE_DPAD_DOWN_LEFT;
- case BluetoothAvrcp.PASSTHROUGH_ID_0:
- return KeyEvent.KEYCODE_NUMPAD_0;
- case BluetoothAvrcp.PASSTHROUGH_ID_1:
- return KeyEvent.KEYCODE_NUMPAD_1;
- case BluetoothAvrcp.PASSTHROUGH_ID_2:
- return KeyEvent.KEYCODE_NUMPAD_2;
- case BluetoothAvrcp.PASSTHROUGH_ID_3:
- return KeyEvent.KEYCODE_NUMPAD_3;
- case BluetoothAvrcp.PASSTHROUGH_ID_4:
- return KeyEvent.KEYCODE_NUMPAD_4;
- case BluetoothAvrcp.PASSTHROUGH_ID_5:
- return KeyEvent.KEYCODE_NUMPAD_5;
- case BluetoothAvrcp.PASSTHROUGH_ID_6:
- return KeyEvent.KEYCODE_NUMPAD_6;
- case BluetoothAvrcp.PASSTHROUGH_ID_7:
- return KeyEvent.KEYCODE_NUMPAD_7;
- case BluetoothAvrcp.PASSTHROUGH_ID_8:
- return KeyEvent.KEYCODE_NUMPAD_8;
- case BluetoothAvrcp.PASSTHROUGH_ID_9:
- return KeyEvent.KEYCODE_NUMPAD_9;
- case BluetoothAvrcp.PASSTHROUGH_ID_DOT:
- return KeyEvent.KEYCODE_NUMPAD_DOT;
- case BluetoothAvrcp.PASSTHROUGH_ID_ENTER:
- return KeyEvent.KEYCODE_NUMPAD_ENTER;
- case BluetoothAvrcp.PASSTHROUGH_ID_CLEAR:
- return KeyEvent.KEYCODE_CLEAR;
- case BluetoothAvrcp.PASSTHROUGH_ID_CHAN_UP:
- return KeyEvent.KEYCODE_CHANNEL_UP;
- case BluetoothAvrcp.PASSTHROUGH_ID_CHAN_DOWN:
- return KeyEvent.KEYCODE_CHANNEL_DOWN;
- case BluetoothAvrcp.PASSTHROUGH_ID_PREV_CHAN:
- return KeyEvent.KEYCODE_LAST_CHANNEL;
- case BluetoothAvrcp.PASSTHROUGH_ID_INPUT_SEL:
- return KeyEvent.KEYCODE_TV_INPUT;
- case BluetoothAvrcp.PASSTHROUGH_ID_DISP_INFO:
- return KeyEvent.KEYCODE_INFO;
- case BluetoothAvrcp.PASSTHROUGH_ID_HELP:
- return KeyEvent.KEYCODE_HELP;
- case BluetoothAvrcp.PASSTHROUGH_ID_PAGE_UP:
- return KeyEvent.KEYCODE_PAGE_UP;
- case BluetoothAvrcp.PASSTHROUGH_ID_PAGE_DOWN:
- return KeyEvent.KEYCODE_PAGE_DOWN;
- case BluetoothAvrcp.PASSTHROUGH_ID_POWER:
- return KeyEvent.KEYCODE_POWER;
- case BluetoothAvrcp.PASSTHROUGH_ID_VOL_UP:
- return KeyEvent.KEYCODE_VOLUME_UP;
- case BluetoothAvrcp.PASSTHROUGH_ID_VOL_DOWN:
- return KeyEvent.KEYCODE_VOLUME_DOWN;
- case BluetoothAvrcp.PASSTHROUGH_ID_MUTE:
- return KeyEvent.KEYCODE_MUTE;
- case BluetoothAvrcp.PASSTHROUGH_ID_PLAY:
- return KeyEvent.KEYCODE_MEDIA_PLAY;
- case BluetoothAvrcp.PASSTHROUGH_ID_STOP:
- return KeyEvent.KEYCODE_MEDIA_STOP;
- case BluetoothAvrcp.PASSTHROUGH_ID_PAUSE:
- return KeyEvent.KEYCODE_MEDIA_PAUSE;
- case BluetoothAvrcp.PASSTHROUGH_ID_RECORD:
- return KeyEvent.KEYCODE_MEDIA_RECORD;
- case BluetoothAvrcp.PASSTHROUGH_ID_REWIND:
- return KeyEvent.KEYCODE_MEDIA_REWIND;
- case BluetoothAvrcp.PASSTHROUGH_ID_FAST_FOR:
- return KeyEvent.KEYCODE_MEDIA_FAST_FORWARD;
- case BluetoothAvrcp.PASSTHROUGH_ID_EJECT:
- return KeyEvent.KEYCODE_MEDIA_EJECT;
- case BluetoothAvrcp.PASSTHROUGH_ID_FORWARD:
- return KeyEvent.KEYCODE_MEDIA_NEXT;
- case BluetoothAvrcp.PASSTHROUGH_ID_BACKWARD:
- return KeyEvent.KEYCODE_MEDIA_PREVIOUS;
- case BluetoothAvrcp.PASSTHROUGH_ID_F1:
- return KeyEvent.KEYCODE_F1;
- case BluetoothAvrcp.PASSTHROUGH_ID_F2:
- return KeyEvent.KEYCODE_F2;
- case BluetoothAvrcp.PASSTHROUGH_ID_F3:
- return KeyEvent.KEYCODE_F3;
- case BluetoothAvrcp.PASSTHROUGH_ID_F4:
- return KeyEvent.KEYCODE_F4;
- case BluetoothAvrcp.PASSTHROUGH_ID_F5:
- return KeyEvent.KEYCODE_F5;
- // Fallthrough for all unknown key mappings
- case BluetoothAvrcp.PASSTHROUGH_ID_SELECT:
- case BluetoothAvrcp.PASSTHROUGH_ID_ROOT_MENU:
- case BluetoothAvrcp.PASSTHROUGH_ID_SETUP_MENU:
- case BluetoothAvrcp.PASSTHROUGH_ID_CONT_MENU:
- case BluetoothAvrcp.PASSTHROUGH_ID_FAV_MENU:
- case BluetoothAvrcp.PASSTHROUGH_ID_EXIT:
- case BluetoothAvrcp.PASSTHROUGH_ID_SOUND_SEL:
- case BluetoothAvrcp.PASSTHROUGH_ID_ANGLE:
- case BluetoothAvrcp.PASSTHROUGH_ID_SUBPICT:
- case BluetoothAvrcp.PASSTHROUGH_ID_VENDOR:
- default:
- return KeyEvent.KEYCODE_UNKNOWN;
- }
- }
-
- private void addKeyPending(KeyEvent event) {
- mPassthroughPending.add(new MediaKeyLog(System.currentTimeMillis(), event));
- }
-
- private void recordKeyDispatched(KeyEvent event, String packageName) {
- long time = System.currentTimeMillis();
- Log.v(TAG, "recordKeyDispatched: " + event + " dispatched to " + packageName);
- setAddressedMediaSessionPackage(packageName);
- synchronized (mPassthroughPending) {
- Iterator<MediaKeyLog> pending = mPassthroughPending.iterator();
- while (pending.hasNext()) {
- MediaKeyLog log = pending.next();
- if (log.addDispatch(time, event, packageName)) {
- mPassthroughDispatched++;
- mPassthroughLogs.add(log);
- pending.remove();
- return;
- }
- }
- Log.w(TAG, "recordKeyDispatch: can't find matching log!");
- }
- }
-
- private final MediaSessionManager.Callback mButtonDispatchCallback =
- new MediaSessionManager.Callback() {
- @Override
- public void onMediaKeyEventDispatched(KeyEvent event, MediaSession.Token token) {
- // Get the package name
- android.media.session.MediaController controller =
- new android.media.session.MediaController(mContext, token);
- String targetPackage = controller.getPackageName();
- recordKeyDispatched(event, targetPackage);
- }
-
- @Override
- public void onMediaKeyEventDispatched(KeyEvent event, ComponentName receiver) {
- recordKeyDispatched(event, receiver.getPackageName());
- }
-
- @Override
- public void onAddressedPlayerChanged(MediaSession.Token token) {
- setActiveMediaSession(token);
- }
-
- @Override
- public void onAddressedPlayerChanged(ComponentName receiver) {
- if (receiver == null) {
- // No active sessions, and no session to revive, give up.
- setAddressedMediaSessionPackage(null);
- return;
- }
- // We can still get a passthrough which will revive this player.
- setAddressedMediaSessionPackage(receiver.getPackageName());
- }
- };
-
- // Do not modify without updating the HAL bt_rc.h files.
-
- // match up with btrc_play_status_t enum of bt_rc.h
- static final byte PLAYSTATUS_STOPPED = 0;
- static final byte PLAYSTATUS_PLAYING = 1;
- static final byte PLAYSTATUS_PAUSED = 2;
- static final byte PLAYSTATUS_FWD_SEEK = 3;
- static final byte PLAYSTATUS_REV_SEEK = 4;
- static final byte PLAYSTATUS_ERROR = (byte) 255;
-
- // match up with btrc_media_attr_t enum of bt_rc.h
- static final int MEDIA_ATTR_TITLE = 1;
- static final int MEDIA_ATTR_ARTIST = 2;
- static final int MEDIA_ATTR_ALBUM = 3;
- static final int MEDIA_ATTR_TRACK_NUM = 4;
- static final int MEDIA_ATTR_NUM_TRACKS = 5;
- static final int MEDIA_ATTR_GENRE = 6;
- static final int MEDIA_ATTR_PLAYING_TIME = 7;
-
- // match up with btrc_event_id_t enum of bt_rc.h
- static final int EVT_PLAY_STATUS_CHANGED = 1;
- static final int EVT_TRACK_CHANGED = 2;
- static final int EVT_TRACK_REACHED_END = 3;
- static final int EVT_TRACK_REACHED_START = 4;
- static final int EVT_PLAY_POS_CHANGED = 5;
- static final int EVT_BATT_STATUS_CHANGED = 6;
- static final int EVT_SYSTEM_STATUS_CHANGED = 7;
- static final int EVT_APP_SETTINGS_CHANGED = 8;
- static final int EVENT_NOW_PLAYING_CONTENT_CHANGED = 9;
- static final int EVT_AVBL_PLAYERS_CHANGED = 0xa;
- static final int EVT_ADDR_PLAYER_CHANGED = 0xb;
- static final int EVENT_UIDS_CHANGED = 0x0c;
-
- private static native void classInitNative();
-
- private native void initNative();
-
- private native void cleanupNative();
-
- private native boolean getPlayStatusRspNative(byte[] address, int playStatus, int songLen,
- int songPos);
-
- private native boolean getElementAttrRspNative(byte[] address, byte numAttr, int[] attrIds,
- String[] textArray);
-
- private native boolean registerNotificationRspPlayStatusNative(int type, int playStatus);
-
- private native boolean registerNotificationRspTrackChangeNative(int type, byte[] track);
-
- private native boolean registerNotificationRspPlayPosNative(int type, int playPos);
-
- private native boolean setVolumeNative(int volume);
-
- private native boolean sendPassThroughCommandNative(int keyCode, int keyState);
-
- private native boolean setAddressedPlayerRspNative(byte[] address, int rspStatus);
-
- private native boolean setBrowsedPlayerRspNative(byte[] address, int rspStatus, byte depth,
- int numItems, String[] textArray);
-
- private native boolean mediaPlayerListRspNative(byte[] address, int rsStatus, int uidCounter,
- byte itemType, int numItems, int[] playerIds, byte[] playerTypes, int[] playerSubTypes,
- byte[] playStatusValues, short[] featureBitMaskValues, String[] textArray);
-
- private native boolean getFolderItemsRspNative(byte[] address, int rspStatus, short uidCounter,
- byte scope, int numItems, byte[] folderTypes, byte[] playable, byte[] itemTypes,
- byte[] itemUidArray, String[] textArray, int[] attributesNum, int[] attributesIds,
- String[] attributesArray);
-
- private native boolean changePathRspNative(byte[] address, int rspStatus, int numItems);
-
- private native boolean getItemAttrRspNative(byte[] address, int rspStatus, byte numAttr,
- int[] attrIds, String[] textArray);
-
- private native boolean playItemRspNative(byte[] address, int rspStatus);
-
- private native boolean getTotalNumOfItemsRspNative(byte[] address, int rspStatus,
- int uidCounter, int numItems);
-
- private native boolean searchRspNative(byte[] address, int rspStatus, int uidCounter,
- int numItems);
-
- private native boolean addToNowPlayingRspNative(byte[] address, int rspStatus);
-
- private native boolean registerNotificationRspAddrPlayerChangedNative(int type, int playerId,
- int uidCounter);
-
- private native boolean registerNotificationRspAvalPlayerChangedNative(int type);
-
- private native boolean registerNotificationRspUIDsChangedNative(int type, int uidCounter);
-
- private native boolean registerNotificationRspNowPlayingChangedNative(int type);
-
-}
diff --git a/src/com/android/bluetooth/avrcp/AvrcpConstants.java b/src/com/android/bluetooth/avrcp/AvrcpConstants.java
deleted file mode 100644
index 50a2eeb..0000000
--- a/src/com/android/bluetooth/avrcp/AvrcpConstants.java
+++ /dev/null
@@ -1,165 +0,0 @@
-/*
- * Copyright (C) 2016 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.avrcp;
-
-/*************************************************************************************************
- * Grouped all HAL constants into a file to be consistent with the stack.
- * Moved the constants used in Avrcp to this new file to be used across multiple files.
- * Helps in easier modifications and future enhancements in the constants.
- ************************************************************************************************/
-
-/*
- * @hide
- */
-final class AvrcpConstants {
-
- /* Do not modify without upating the HAL bt_rc.h file */
- /** Response Error codes **/
- static final byte RSP_BAD_CMD = 0x00; /* Invalid command */
- static final byte RSP_BAD_PARAM = 0x01; /* Invalid parameter */
- static final byte RSP_NOT_FOUND = 0x02; /* Specified parameter is
- * wrong or not found */
- static final byte RSP_INTERNAL_ERR = 0x03; /* Internal Error */
- static final byte RSP_NO_ERROR = 0x04; /* Operation Success */
- static final byte RSP_UID_CHANGED = 0x05; /* UIDs changed */
- static final byte RSP_RESERVED = 0x06; /* Reserved */
- static final byte RSP_INV_DIRN = 0x07; /* Invalid direction */
- static final byte RSP_INV_DIRECTORY = 0x08; /* Invalid directory */
- static final byte RSP_INV_ITEM = 0x09; /* Invalid Item */
- static final byte RSP_INV_SCOPE = 0x0a; /* Invalid scope */
- static final byte RSP_INV_RANGE = 0x0b; /* Invalid range */
- static final byte RSP_DIRECTORY = 0x0c; /* UID is a directory */
- static final byte RSP_MEDIA_IN_USE = 0x0d; /* Media in use */
- static final byte RSP_PLAY_LIST_FULL = 0x0e; /* Playing list full */
- static final byte RSP_SRCH_NOT_SPRTD = 0x0f; /* Search not supported */
- static final byte RSP_SRCH_IN_PROG = 0x10; /* Search in progress */
- static final byte RSP_INV_PLAYER = 0x11; /* Invalid player */
- static final byte RSP_PLAY_NOT_BROW = 0x12; /* Player not browsable */
- static final byte RSP_PLAY_NOT_ADDR = 0x13; /* Player not addressed */
- static final byte RSP_INV_RESULTS = 0x14; /* Invalid results */
- static final byte RSP_NO_AVBL_PLAY = 0x15; /* No available players */
- static final byte RSP_ADDR_PLAY_CHGD = 0x16; /* Addressed player changed */
-
- /* valid scopes for get_folder_items */
- static final byte BTRC_SCOPE_PLAYER_LIST = 0x00; /* Media Player List */
- static final byte BTRC_SCOPE_FILE_SYSTEM = 0x01; /* Virtual File System */
- static final byte BTRC_SCOPE_SEARCH = 0x02; /* Search */
- static final byte BTRC_SCOPE_NOW_PLAYING = 0x03; /* Now Playing */
-
- /* valid directions for change path */
- static final byte DIR_UP = 0x00;
- static final byte DIR_DOWN = 0x01;
-
- /* item type to browse */
- static final byte BTRC_ITEM_PLAYER = 0x01;
- static final byte BTRC_ITEM_FOLDER = 0x02;
- static final byte BTRC_ITEM_MEDIA = 0x03;
-
- /* valid folder types */
- static final byte FOLDER_TYPE_MIXED = 0x00;
- static final byte FOLDER_TYPE_TITLES = 0x01;
- static final byte FOLDER_TYPE_ALBUMS = 0x02;
- static final byte FOLDER_TYPE_ARTISTS = 0x03;
- static final byte FOLDER_TYPE_GENRES = 0x04;
- static final byte FOLDER_TYPE_PLAYLISTS = 0x05;
- static final byte FOLDER_TYPE_YEARS = 0x06;
-
- /* valid playable flags */
- static final byte ITEM_NOT_PLAYABLE = 0x00;
- static final byte ITEM_PLAYABLE = 0x01;
-
- /* valid Attribute ids for media elements */
- static final int ATTRID_TITLE = 0x01;
- static final int ATTRID_ARTIST = 0x02;
- static final int ATTRID_ALBUM = 0x03;
- static final int ATTRID_TRACK_NUM = 0x04;
- static final int ATTRID_NUM_TRACKS = 0x05;
- static final int ATTRID_GENRE = 0x06;
- static final int ATTRID_PLAY_TIME = 0x07;
- static final int ATTRID_COVER_ART = 0x08;
-
- /* constants to send in Track change response */
- static final byte[] NO_TRACK_SELECTED = {
- (byte) 0xFF,
- (byte) 0xFF,
- (byte) 0xFF,
- (byte) 0xFF,
- (byte) 0xFF,
- (byte) 0xFF,
- (byte) 0xFF,
- (byte) 0xFF
- };
- static final byte[] TRACK_IS_SELECTED = {
- (byte) 0x00,
- (byte) 0x00,
- (byte) 0x00,
- (byte) 0x00,
- (byte) 0x00,
- (byte) 0x00,
- (byte) 0x00,
- (byte) 0x00
- };
-
- /* UID size */
- static final int UID_SIZE = 8;
-
- static final short DEFAULT_UID_COUNTER = 0x0000;
-
- /* Bitmask size for Media Players */
- static final int AVRC_FEATURE_MASK_SIZE = 16;
-
- /* Maximum attributes for media item */
- static final int MAX_NUM_ATTR = 8;
-
- /* notification types for remote device */
- static final int NOTIFICATION_TYPE_INTERIM = 0;
- static final int NOTIFICATION_TYPE_CHANGED = 1;
-
- static final int TRACK_ID_SIZE = 8;
-
- /* player feature bit mask constants */
- static final short AVRC_PF_PLAY_BIT_NO = 40;
- static final short AVRC_PF_STOP_BIT_NO = 41;
- static final short AVRC_PF_PAUSE_BIT_NO = 42;
- static final short AVRC_PF_REWIND_BIT_NO = 44;
- static final short AVRC_PF_FAST_FWD_BIT_NO = 45;
- static final short AVRC_PF_FORWARD_BIT_NO = 47;
- static final short AVRC_PF_BACKWARD_BIT_NO = 48;
- static final short AVRC_PF_ADV_CTRL_BIT_NO = 58;
- static final short AVRC_PF_BROWSE_BIT_NO = 59;
- static final short AVRC_PF_ADD2NOWPLAY_BIT_NO = 61;
- static final short AVRC_PF_UID_UNIQUE_BIT_NO = 62;
- static final short AVRC_PF_NOW_PLAY_BIT_NO = 65;
- static final short AVRC_PF_GET_NUM_OF_ITEMS_BIT_NO = 67;
-
- static final byte PLAYER_TYPE_AUDIO = 1;
- static final int PLAYER_SUBTYPE_NONE = 0;
-
- // match up with btrc_play_status_t enum of bt_rc.h
- static final int PLAYSTATUS_STOPPED = 0;
- static final int PLAYSTATUS_PLAYING = 1;
- static final int PLAYSTATUS_PAUSED = 2;
- static final int PLAYSTATUS_FWD_SEEK = 3;
- static final int PLAYSTATUS_REV_SEEK = 4;
- static final int PLAYSTATUS_ERROR = 255;
-
- static final byte NUM_ATTR_ALL = (byte) 0x00;
- static final byte NUM_ATTR_NONE = (byte) 0xFF;
-
- static final int KEY_STATE_PRESS = 1;
- static final int KEY_STATE_RELEASE = 0;
-}
diff --git a/src/com/android/bluetooth/avrcp/AvrcpHelperClasses.java b/src/com/android/bluetooth/avrcp/AvrcpHelperClasses.java
deleted file mode 100644
index ea6d7b8..0000000
--- a/src/com/android/bluetooth/avrcp/AvrcpHelperClasses.java
+++ /dev/null
@@ -1,451 +0,0 @@
-/*
- * Copyright (C) 2016 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.avrcp;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-
-import com.android.bluetooth.Utils;
-
-import java.util.ArrayDeque;
-import java.util.Arrays;
-import java.util.Collection;
-
-/*************************************************************************************************
- * Helper classes used for callback/response of browsing commands:-
- * 1) To bundle parameters for native callbacks/response.
- * 2) Stores information of Addressed and Browsed Media Players.
- ************************************************************************************************/
-
-class AvrcpCmd {
-
- AvrcpCmd() {}
-
- /* Helper classes to pass parameters from callbacks to Avrcp handler */
- class FolderItemsCmd {
- byte mScope;
- long mStartItem;
- long mEndItem;
- byte mNumAttr;
- int[] mAttrIDs;
- public byte[] mAddress;
-
- FolderItemsCmd(byte[] address, byte scope, long startItem, long endItem, byte numAttr,
- int[] attrIds) {
- mAddress = address;
- this.mScope = scope;
- this.mStartItem = startItem;
- this.mEndItem = endItem;
- this.mNumAttr = numAttr;
- this.mAttrIDs = attrIds;
- }
-
- @Override
- public String toString() {
- StringBuilder sb = new StringBuilder();
- sb.append("[FolderItemCmd: scope " + mScope);
- sb.append(" start " + mStartItem);
- sb.append(" end " + mEndItem);
- sb.append(" numAttr " + mNumAttr);
- sb.append(" attrs: ");
- for (int i = 0; i < mNumAttr; i++) {
- sb.append(mAttrIDs[i] + " ");
- }
- return sb.toString();
- }
- }
-
- class ItemAttrCmd {
- byte mScope;
- byte[] mUid;
- int mUidCounter;
- byte mNumAttr;
- int[] mAttrIDs;
- public byte[] mAddress;
-
- ItemAttrCmd(byte[] address, byte scope, byte[] uid, int uidCounter, byte numAttr,
- int[] attrIDs) {
- mAddress = address;
- mScope = scope;
- mUid = uid;
- mUidCounter = uidCounter;
- mNumAttr = numAttr;
- mAttrIDs = attrIDs;
- }
-
- @Override
- public String toString() {
- StringBuilder sb = new StringBuilder();
- sb.append("[ItemAttrCmd: scope " + mScope);
- sb.append(" uid " + Utils.byteArrayToString(mUid));
- sb.append(" numAttr " + mNumAttr);
- sb.append(" attrs: ");
- for (int i = 0; i < mNumAttr; i++) {
- sb.append(mAttrIDs[i] + " ");
- }
- return sb.toString();
- }
- }
-
- class ElementAttrCmd {
- byte mNumAttr;
- int[] mAttrIDs;
- public byte[] mAddress;
-
- ElementAttrCmd(byte[] address, byte numAttr, int[] attrIDs) {
- mAddress = address;
- mNumAttr = numAttr;
- mAttrIDs = attrIDs;
- }
- }
-}
-
-/* Helper classes to pass parameters to native response */
-class MediaPlayerListRsp {
- byte mStatus;
- short mUIDCounter;
- byte mItemType;
- int[] mPlayerIds;
- byte[] mPlayerTypes;
- int[] mPlayerSubTypes;
- byte[] mPlayStatusValues;
- short[] mFeatureBitMaskValues;
- String[] mPlayerNameList;
- int mNumItems;
-
- MediaPlayerListRsp(byte status, short uidCounter, int numItems, byte itemType, int[] playerIds,
- byte[] playerTypes, int[] playerSubTypes, byte[] playStatusValues,
- short[] featureBitMaskValues, String[] playerNameList) {
- this.mStatus = status;
- this.mUIDCounter = uidCounter;
- this.mNumItems = numItems;
- this.mItemType = itemType;
- this.mPlayerIds = playerIds;
- this.mPlayerTypes = playerTypes;
- this.mPlayerSubTypes = new int[numItems];
- this.mPlayerSubTypes = playerSubTypes;
- this.mPlayStatusValues = new byte[numItems];
- this.mPlayStatusValues = playStatusValues;
- int bitMaskSize = AvrcpConstants.AVRC_FEATURE_MASK_SIZE;
- this.mFeatureBitMaskValues = new short[numItems * bitMaskSize];
- for (int bitMaskIndex = 0; bitMaskIndex < (numItems * bitMaskSize); bitMaskIndex++) {
- this.mFeatureBitMaskValues[bitMaskIndex] = featureBitMaskValues[bitMaskIndex];
- }
- this.mPlayerNameList = playerNameList;
- }
-}
-
-class FolderItemsRsp {
- byte mStatus;
- short mUIDCounter;
- byte mScope;
- int mNumItems;
- byte[] mFolderTypes;
- byte[] mPlayable;
- byte[] mItemTypes;
- byte[] mItemUid;
- String[] mDisplayNames; /* display name of the item. Eg: Folder name or song name */
- int[] mAttributesNum;
- int[] mAttrIds;
- String[] mAttrValues;
-
- FolderItemsRsp(byte status, short uidCounter, byte scope, int numItems, byte[] folderTypes,
- byte[] playable, byte[] itemTypes, byte[] itemsUid, String[] displayNameArray,
- int[] attributesNum, int[] attrIds, String[] attrValues) {
- this.mStatus = status;
- this.mUIDCounter = uidCounter;
- this.mScope = scope;
- this.mNumItems = numItems;
- this.mFolderTypes = folderTypes;
- this.mPlayable = playable;
- this.mItemTypes = itemTypes;
- this.mItemUid = itemsUid;
- this.mDisplayNames = displayNameArray;
- this.mAttributesNum = attributesNum;
- this.mAttrIds = attrIds;
- this.mAttrValues = attrValues;
- }
-}
-
-class ItemAttrRsp {
- byte mStatus;
- byte mNumAttr;
- int[] mAttributesIds;
- String[] mAttributesArray;
-
- ItemAttrRsp(byte status, int[] attributesIds, String[] attributesArray) {
- mStatus = status;
- mNumAttr = (byte) attributesIds.length;
- mAttributesIds = attributesIds;
- mAttributesArray = attributesArray;
- }
-}
-
-/* stores information of Media Players in the system */
-class MediaPlayerInfo {
-
- private byte mMajorType;
- private int mSubType;
- private byte mPlayStatus;
- private short[] mFeatureBitMask;
- @NonNull private String mPackageName;
- @NonNull private String mDisplayableName;
- @Nullable private MediaController mMediaController;
-
- MediaPlayerInfo(@Nullable MediaController controller, byte majorType, int subType,
- byte playStatus, short[] featureBitMask, @NonNull String packageName,
- @Nullable String displayableName) {
- this.setMajorType(majorType);
- this.setSubType(subType);
- this.mPlayStatus = playStatus;
- // store a copy the FeatureBitMask array
- this.mFeatureBitMask = Arrays.copyOf(featureBitMask, featureBitMask.length);
- Arrays.sort(this.mFeatureBitMask);
- this.setPackageName(packageName);
- this.setDisplayableName(displayableName);
- this.setMediaController(controller);
- }
-
- /* getters and setters */
- byte getPlayStatus() {
- return mPlayStatus;
- }
-
- void setPlayStatus(byte playStatus) {
- this.mPlayStatus = playStatus;
- }
-
- MediaController getMediaController() {
- return mMediaController;
- }
-
- void setMediaController(MediaController mediaController) {
- if (mediaController != null) {
- this.mPackageName = mediaController.getPackageName();
- }
- this.mMediaController = mediaController;
- }
-
- void setPackageName(@NonNull String name) {
- // Controller determines package name when it is set.
- if (mMediaController != null) {
- return;
- }
- this.mPackageName = name;
- }
-
- String getPackageName() {
- if (mMediaController != null) {
- return mMediaController.getPackageName();
- } else if (mPackageName != null) {
- return mPackageName;
- }
- return null;
- }
-
- byte getMajorType() {
- return mMajorType;
- }
-
- void setMajorType(byte majorType) {
- this.mMajorType = majorType;
- }
-
- int getSubType() {
- return mSubType;
- }
-
- void setSubType(int subType) {
- this.mSubType = subType;
- }
-
- String getDisplayableName() {
- return mDisplayableName;
- }
-
- void setDisplayableName(@Nullable String displayableName) {
- if (displayableName == null) {
- displayableName = "";
- }
- this.mDisplayableName = displayableName;
- }
-
- short[] getFeatureBitMask() {
- return mFeatureBitMask;
- }
-
- void setFeatureBitMask(short[] featureBitMask) {
- synchronized (this) {
- this.mFeatureBitMask = Arrays.copyOf(featureBitMask, featureBitMask.length);
- Arrays.sort(this.mFeatureBitMask);
- }
- }
-
- boolean isBrowseSupported() {
- synchronized (this) {
- if (this.mFeatureBitMask == null) {
- return false;
- }
- for (short bit : this.mFeatureBitMask) {
- if (bit == AvrcpConstants.AVRC_PF_BROWSE_BIT_NO) {
- return true;
- }
- }
- }
- return false;
- }
-
- /** Tests if the view of this player presented to the controller is different enough to
- * justify sending an Available Players Changed update */
- public boolean equalView(MediaPlayerInfo other) {
- return (this.mMajorType == other.getMajorType()) && (this.mSubType == other.getSubType())
- && Arrays.equals(this.mFeatureBitMask, other.getFeatureBitMask())
- && this.mDisplayableName.equals(other.getDisplayableName());
- }
-
- @Override
- public String toString() {
- StringBuilder sb = new StringBuilder();
- sb.append("MediaPlayerInfo ");
- sb.append(getPackageName());
- sb.append(" (as '" + getDisplayableName() + "')");
- sb.append(" Type = " + getMajorType());
- sb.append(", SubType = " + getSubType());
- sb.append(", Status = " + mPlayStatus);
- sb.append(" Feature Bits [");
- short[] bits = getFeatureBitMask();
- for (int i = 0; i < bits.length; i++) {
- if (i != 0) {
- sb.append(" ");
- }
- sb.append(bits[i]);
- }
- sb.append("] Controller: ");
- sb.append(getMediaController());
- return sb.toString();
- }
-}
-
-/* stores information for browsable Media Players available in the system */
-class BrowsePlayerInfo {
- public String packageName;
- public String displayableName;
- public String serviceClass;
-
- BrowsePlayerInfo(String packageName, String displayableName, String serviceClass) {
- this.packageName = packageName;
- this.displayableName = displayableName;
- this.serviceClass = serviceClass;
- }
-
- @Override
- public String toString() {
- StringBuilder sb = new StringBuilder();
- sb.append("BrowsePlayerInfo ");
- sb.append(packageName);
- sb.append(" ( as '" + displayableName + "')");
- sb.append(" service " + serviceClass);
- return sb.toString();
- }
-}
-
-class FolderItemsData {
- /* initialize sizes for rsp parameters */ int mNumItems;
- int[] mAttributesNum;
- byte[] mFolderTypes;
- byte[] mItemTypes;
- byte[] mPlayable;
- byte[] mItemUid;
- String[] mDisplayNames;
- int[] mAttrIds;
- String[] mAttrValues;
- int mAttrCounter;
-
- FolderItemsData(int size) {
- mNumItems = size;
- mAttributesNum = new int[size];
-
- mFolderTypes = new byte[size]; /* folderTypes */
- mItemTypes = new byte[size]; /* folder or media item */
- mPlayable = new byte[size];
- Arrays.fill(mFolderTypes, AvrcpConstants.FOLDER_TYPE_MIXED);
- Arrays.fill(mItemTypes, AvrcpConstants.BTRC_ITEM_MEDIA);
- Arrays.fill(mPlayable, AvrcpConstants.ITEM_PLAYABLE);
-
- mItemUid = new byte[size * AvrcpConstants.UID_SIZE];
- mDisplayNames = new String[size];
-
- mAttrIds = null; /* array of attr ids */
- mAttrValues = null; /* array of attr values */
- }
-}
-
-/** A queue that evicts the first element when you add an element to the end when it reaches a
- * maximum size.
- * This is useful for keeping a FIFO queue of items where the items drop off the front, i.e. a log
- * with a maximum size.
- */
-class EvictingQueue<E> extends ArrayDeque<E> {
- private int mMaxSize;
-
- EvictingQueue(int maxSize) {
- super();
- mMaxSize = maxSize;
- }
-
- EvictingQueue(int maxSize, int initialElements) {
- super(initialElements);
- mMaxSize = maxSize;
- }
-
- EvictingQueue(int maxSize, Collection<? extends E> c) {
- super(c);
- mMaxSize = maxSize;
- }
-
- @Override
- public void addFirst(E e) {
- if (super.size() == mMaxSize) {
- return;
- }
- super.addFirst(e);
- }
-
- @Override
- public void addLast(E e) {
- if (super.size() == mMaxSize) {
- super.remove();
- }
- super.addLast(e);
- }
-
- @Override
- public boolean offerFirst(E e) {
- if (super.size() == mMaxSize) {
- return false;
- }
- return super.offerFirst(e);
- }
-
- @Override
- public boolean offerLast(E e) {
- if (super.size() == mMaxSize) {
- super.remove();
- }
- return super.offerLast(e);
- }
-}
diff --git a/src/com/android/bluetooth/avrcp/AvrcpMediaRspInterface.java b/src/com/android/bluetooth/avrcp/AvrcpMediaRspInterface.java
deleted file mode 100644
index 8cab68e..0000000
--- a/src/com/android/bluetooth/avrcp/AvrcpMediaRspInterface.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright (C) 2016 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.avrcp;
-
-
-/*************************************************************************************************
- * Interface for classes which handle callbacks from AvrcpMediaManager.
- * These callbacks should map to native responses and used to communicate with the native layer.
- ************************************************************************************************/
-
-public interface AvrcpMediaRspInterface {
- void setAddrPlayerRsp(byte[] address, int rspStatus);
-
- void setBrowsedPlayerRsp(byte[] address, int rspStatus, byte depth, int numItems,
- String[] textArray);
-
- void mediaPlayerListRsp(byte[] address, int rspStatus, MediaPlayerListRsp rspObj);
-
- void folderItemsRsp(byte[] address, int rspStatus, FolderItemsRsp rspObj);
-
- void changePathRsp(byte[] address, int rspStatus, int numItems);
-
- void getItemAttrRsp(byte[] address, int rspStatus, ItemAttrRsp rspObj);
-
- void playItemRsp(byte[] address, int rspStatus);
-
- void getTotalNumOfItemsRsp(byte[] address, int rspStatus, int uidCounter, int numItems);
-
- void addrPlayerChangedRsp(int type, int playerId, int uidCounter);
-
- void avalPlayerChangedRsp(byte[] address, int type);
-
- void uidsChangedRsp(int type);
-
- void nowPlayingChangedRsp(int type);
-
- void trackChangedRsp(int type, byte[] uid);
-}
-
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 c7029ce..2f6563d 100644
--- a/src/com/android/bluetooth/newavrcp/AvrcpTargetService.java
+++ b/src/com/android/bluetooth/avrcp/AvrcpTargetService.java
@@ -34,6 +34,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;
@@ -43,7 +44,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";
@@ -55,6 +56,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;
@@ -140,11 +142,6 @@
Log.i(TAG, "Starting the AVRCP Target Service");
mCurrentData = new MediaData(null, null, null);
- mReceiver = new AvrcpBroadcastReceiver();
- IntentFilter filter = new IntentFilter();
- filter.addAction(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED);
- registerReceiver(mReceiver, filter);
-
if (!SystemProperties.getBoolean(AVRCP_ENABLE_PROPERTY, true)) {
Log.w(TAG, "Skipping initialization of the new AVRCP Target Service");
sInstance = null;
@@ -156,15 +153,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;
@@ -227,17 +229,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
deleted file mode 100644
index 0889d90..0000000
--- a/src/com/android/bluetooth/avrcp/BrowsedMediaPlayer.java
+++ /dev/null
@@ -1,842 +0,0 @@
-/*
- * Copyright (C) 2016 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.avrcp;
-
-import android.annotation.NonNull;
-import android.content.ComponentName;
-import android.content.Context;
-import android.media.MediaDescription;
-import android.media.MediaMetadata;
-import android.media.browse.MediaBrowser;
-import android.media.browse.MediaBrowser.MediaItem;
-import android.media.session.MediaSession;
-import android.os.Bundle;
-import android.util.Log;
-
-import java.math.BigInteger;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Stack;
-
-/*************************************************************************************************
- * Provides functionality required for Browsed Media Player like browsing Virtual File System, get
- * Item Attributes, play item from the file system, etc.
- * Acts as an Interface to communicate with Media Browsing APIs for browsing FileSystem.
- ************************************************************************************************/
-
-class BrowsedMediaPlayer {
- private static final boolean DEBUG = false;
- private static final String TAG = "BrowsedMediaPlayer";
-
- /* connection state with MediaBrowseService */
- private static final int DISCONNECTED = 0;
- private static final int CONNECTED = 1;
- private static final int SUSPENDED = 2;
-
- private static final String[] ROOT_FOLDER = {"root"};
-
- /* package and service name of target Media Player which is set for browsing */
- private String mPackageName;
- private String mConnectingPackageName;
- private String mClassName;
- private Context mContext;
- private AvrcpMediaRspInterface mMediaInterface;
- private byte[] mBDAddr;
-
- /* Object used to connect to MediaBrowseService of Media Player */
- private MediaBrowser mMediaBrowser = null;
- private MediaController mMediaController = null;
-
- /* The mediaId to be used for subscribing for children using the MediaBrowser */
- private String mMediaId = null;
- private String mRootFolderUid = null;
- private int mConnState = DISCONNECTED;
-
- /* stores the path trail during changePath */
- private Stack<String> mPathStack = null;
-
- /* Number of items in current folder */
- private int mCurrFolderNumItems = 0;
-
- /* store mapping between uid(Avrcp) and mediaId(Media Player). */
- private HashMap<Integer, String> mHmap = new HashMap<Integer, String>();
-
- /* command objects from avrcp handler */
- private AvrcpCmd.FolderItemsCmd mFolderItemsReqObj;
-
- /* store result of getfolderitems with scope="vfs" */
- private List<MediaBrowser.MediaItem> mFolderItems = null;
-
- /* Connection state callback handler */
- class MediaConnectionCallback extends MediaBrowser.ConnectionCallback {
- private String mCallbackPackageName;
- private MediaBrowser mBrowser;
-
- MediaConnectionCallback(String packageName) {
- this.mCallbackPackageName = packageName;
- }
-
- public void setBrowser(MediaBrowser b) {
- mBrowser = b;
- }
-
- @Override
- public void onConnected() {
- mConnState = CONNECTED;
- if (DEBUG) {
- Log.d(TAG, "mediaBrowser CONNECTED to " + mPackageName);
- }
- /* perform init tasks and set player as browsed player on successful connection */
- onBrowseConnect(mCallbackPackageName, mBrowser);
-
- // Remove what could be a circular dependency causing GC to never happen on this object
- mBrowser = null;
- }
-
- @Override
- public void onConnectionFailed() {
- 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_INTERNAL_ERR,
- (byte) 0x00, 0, null);
- }
-
- @Override
- public void onConnectionSuspended() {
- mBrowser = null;
- mConnState = SUSPENDED;
- Log.e(TAG, "mediaBrowser SUSPENDED connection with " + mPackageName);
- }
- }
-
- /* Subscription callback handler. Subscribe to a folder to get its contents */
- private MediaBrowser.SubscriptionCallback mFolderItemsCb =
- new MediaBrowser.SubscriptionCallback() {
-
- @Override
- public void onChildrenLoaded(String parentId,
- List<MediaBrowser.MediaItem> children) {
- if (DEBUG) {
- Log.d(TAG, "OnChildren Loaded folder items: childrens= " + children.size());
- }
-
- /*
- * cache current folder items and send as rsp when remote requests
- * get_folder_items (scope = vfs)
- */
- if (mFolderItems == null) {
- if (DEBUG) {
- Log.d(TAG, "sending setbrowsed player rsp");
- }
- mFolderItems = children;
- mMediaInterface.setBrowsedPlayerRsp(mBDAddr, AvrcpConstants.RSP_NO_ERROR,
- (byte) 0x00, children.size(), ROOT_FOLDER);
- } else {
- mFolderItems = children;
- mCurrFolderNumItems = mFolderItems.size();
- mMediaInterface.changePathRsp(mBDAddr, AvrcpConstants.RSP_NO_ERROR,
- mCurrFolderNumItems);
- }
- mMediaBrowser.unsubscribe(parentId);
- }
-
- /* UID is invalid */
- @Override
- public void onError(String id) {
- Log.e(TAG, "set browsed player rsp. Could not get root folder items");
- mMediaInterface.setBrowsedPlayerRsp(mBDAddr, AvrcpConstants.RSP_INTERNAL_ERR,
- (byte) 0x00, 0, null);
- }
- };
-
- /* callback from media player in response to getitemAttr request */
- private class ItemAttribSubscriber extends MediaBrowser.SubscriptionCallback {
- private String mMediaId;
- private AvrcpCmd.ItemAttrCmd mAttrReq;
-
- ItemAttribSubscriber(@NonNull AvrcpCmd.ItemAttrCmd attrReq, @NonNull String mediaId) {
- mAttrReq = attrReq;
- mMediaId = mediaId;
- }
-
- @Override
- public void onChildrenLoaded(String parentId, List<MediaBrowser.MediaItem> children) {
- String logprefix = "ItemAttribSubscriber(" + mMediaId + "): ";
- if (DEBUG) {
- Log.d(TAG, logprefix + "OnChildren Loaded");
- }
- int status = AvrcpConstants.RSP_INV_ITEM;
-
- if (children == null) {
- Log.w(TAG, logprefix + "children list is null parentId: " + parentId);
- } else {
- /* find the item in the folder */
- for (MediaBrowser.MediaItem item : children) {
- if (item.getMediaId().equals(mMediaId)) {
- if (DEBUG) {
- Log.d(TAG, logprefix + "found item");
- }
- getItemAttrFilterAttr(item);
- status = AvrcpConstants.RSP_NO_ERROR;
- break;
- }
- }
- }
- /* Send only error from here, in case of success, getItemAttrFilterAttr sends */
- if (status != AvrcpConstants.RSP_NO_ERROR) {
- Log.e(TAG, logprefix + "not able to find item from " + parentId);
- mMediaInterface.getItemAttrRsp(mBDAddr, status, null);
- }
- mMediaBrowser.unsubscribe(parentId);
- }
-
- @Override
- public void onError(String id) {
- Log.e(TAG, "Could not get attributes from media player id: " + id);
- mMediaInterface.getItemAttrRsp(mBDAddr, AvrcpConstants.RSP_INTERNAL_ERR, null);
- }
-
- /* helper method to filter required attibuteand send GetItemAttr response */
- private void getItemAttrFilterAttr(@NonNull MediaBrowser.MediaItem mediaItem) {
- /* Response parameters */
- int[] attrIds = null; /* array of attr ids */
- String[] attrValues = null; /* array of attr values */
-
- /* variables to temperorily add attrs */
- ArrayList<Integer> attrIdArray = new ArrayList<Integer>();
- ArrayList<String> attrValueArray = new ArrayList<String>();
- ArrayList<Integer> attrReqIds = new ArrayList<Integer>();
-
- if (mAttrReq.mNumAttr == AvrcpConstants.NUM_ATTR_NONE) {
- // Note(jamuraa): the stack should never send this, remove?
- Log.i(TAG, "getItemAttrFilterAttr: No attributes requested");
- mMediaInterface.getItemAttrRsp(mBDAddr, AvrcpConstants.RSP_BAD_PARAM, null);
- return;
- }
-
- /* check if remote device has requested all attributes */
- if (mAttrReq.mNumAttr == AvrcpConstants.NUM_ATTR_ALL
- || mAttrReq.mNumAttr == AvrcpConstants.MAX_NUM_ATTR) {
- for (int idx = 1; idx <= AvrcpConstants.MAX_NUM_ATTR; idx++) {
- attrReqIds.add(idx); /* attr id 0x00 is unused */
- }
- } else {
- /* get only the requested attribute ids from the request */
- for (int idx = 0; idx < mAttrReq.mNumAttr; idx++) {
- attrReqIds.add(mAttrReq.mAttrIDs[idx]);
- }
- }
-
- /* 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);
- if (value != null) {
- attrIdArray.add(attrId);
- attrValueArray.add(value);
- }
- }
-
- /* copy filtered attr ids and attr values to response parameters */
- attrIds = new int[attrIdArray.size()];
- for (int i = 0; i < attrIdArray.size(); i++) {
- attrIds[i] = attrIdArray.get(i);
- }
-
- attrValues = attrValueArray.toArray(new String[attrIdArray.size()]);
-
- /* create rsp object and send response */
- ItemAttrRsp rspObj = new ItemAttrRsp(AvrcpConstants.RSP_NO_ERROR, attrIds, attrValues);
- mMediaInterface.getItemAttrRsp(mBDAddr, AvrcpConstants.RSP_NO_ERROR, rspObj);
- }
- }
-
- /* Constructor */
- BrowsedMediaPlayer(byte[] address, Context context,
- AvrcpMediaRspInterface mAvrcpMediaRspInterface) {
- mContext = context;
- mMediaInterface = mAvrcpMediaRspInterface;
- mBDAddr = address;
- }
-
- /* initialize mediacontroller in order to communicate with media player. */
- private void onBrowseConnect(String connectedPackage, MediaBrowser browser) {
- if (!connectedPackage.equals(mConnectingPackageName)) {
- Log.w(TAG, "onBrowseConnect: recieved callback for package we aren't connecting to "
- + connectedPackage);
- return;
- }
- mConnectingPackageName = null;
-
- if (browser == null) {
- Log.e(TAG, "onBrowseConnect: received a null browser for " + connectedPackage);
- mMediaInterface.setBrowsedPlayerRsp(mBDAddr, AvrcpConstants.RSP_INTERNAL_ERR,
- (byte) 0x00, 0, null);
- return;
- }
-
- MediaSession.Token token = null;
- try {
- if (!browser.isConnected()) {
- Log.e(TAG, "setBrowsedPlayer: " + mPackageName + "not connected");
- } else if ((token = browser.getSessionToken()) == null) {
- Log.e(TAG, "setBrowsedPlayer: " + mPackageName + "no Session token");
- } else {
- /* update to the new MediaBrowser */
- if (mMediaBrowser != null) {
- mMediaBrowser.disconnect();
- }
- mMediaBrowser = browser;
- mPackageName = connectedPackage;
-
- /* get rootfolder uid from media player */
- if (mMediaId == null) {
- mMediaId = mMediaBrowser.getRoot();
- /*
- * assuming that root folder uid will not change on uids changed
- */
- mRootFolderUid = mMediaId;
- /* store root folder uid to stack */
- mPathStack.push(mMediaId);
- }
-
- mMediaController = MediaControllerFactory.make(mContext, token);
- /* get root folder items */
- mMediaBrowser.subscribe(mRootFolderUid, mFolderItemsCb);
- return;
- }
- } catch (NullPointerException ex) {
- Log.e(TAG, "setBrowsedPlayer : Null pointer during init");
- ex.printStackTrace();
- }
-
- mMediaInterface.setBrowsedPlayerRsp(mBDAddr, AvrcpConstants.RSP_INTERNAL_ERR, (byte) 0x00,
- 0, null);
- }
-
- public void setBrowsed(String packageName, String cls) {
- mConnectingPackageName = packageName;
- mClassName = cls;
- /* cleanup variables from previous browsed calls */
- mFolderItems = null;
- mMediaId = null;
- mRootFolderUid = null;
- /*
- * create stack to store the navigation trail (current folder ID). This
- * will be required while navigating up the folder
- */
- mPathStack = new Stack<String>();
-
- /* Bind to MediaBrowseService of MediaPlayer */
- MediaConnectionCallback callback = new MediaConnectionCallback(packageName);
- MediaBrowser tempBrowser =
- new MediaBrowser(mContext, new ComponentName(packageName, mClassName), callback,
- null);
- callback.setBrowser(tempBrowser);
-
- tempBrowser.connect();
- }
-
- /* called when connection to media player is closed */
- public void cleanup() {
- if (DEBUG) {
- Log.d(TAG, "cleanup");
- }
-
- if (mConnState != DISCONNECTED) {
- mMediaBrowser.disconnect();
- }
-
- mHmap = null;
- mMediaController = null;
- mMediaBrowser = null;
- mPathStack = null;
- }
-
- public boolean isPlayerConnected() {
- if (mMediaBrowser == null) {
- if (DEBUG) {
- Log.d(TAG, "isPlayerConnected: mMediaBrowser = null!");
- }
- return false;
- }
-
- return mMediaBrowser.isConnected();
- }
-
- /* returns number of items in new path as reponse */
- public void changePath(byte[] folderUid, byte direction) {
- if (DEBUG) {
- Log.d(TAG, "changePath.direction = " + direction);
- }
- String newPath = "";
-
- if (!isPlayerConnected()) {
- Log.w(TAG, "changePath: disconnected from player service, sending internal error");
- mMediaInterface.changePathRsp(mBDAddr, AvrcpConstants.RSP_INTERNAL_ERR, 0);
- return;
- }
-
- if (mMediaBrowser == null) {
- Log.e(TAG, "Media browser is null, sending internal error");
- mMediaInterface.changePathRsp(mBDAddr, AvrcpConstants.RSP_INTERNAL_ERR, 0);
- return;
- }
-
- /* check direction and change the path */
- if (direction == AvrcpConstants.DIR_DOWN) { /* move down */
- if ((newPath = byteToString(folderUid)) == null) {
- Log.e(TAG, "Could not get media item from folder Uid, sending err response");
- mMediaInterface.changePathRsp(mBDAddr, AvrcpConstants.RSP_INV_ITEM, 0);
- } else if (!isBrowsableFolderDn(newPath)) {
- /* new path is not browsable */
- Log.e(TAG, "ItemUid received from changePath cmd is not browsable");
- mMediaInterface.changePathRsp(mBDAddr, AvrcpConstants.RSP_INV_DIRECTORY, 0);
- } else if (mPathStack.peek().equals(newPath)) {
- /* new_folder is same as current folder */
- Log.e(TAG, "new_folder is same as current folder, Invalid direction!");
- mMediaInterface.changePathRsp(mBDAddr, AvrcpConstants.RSP_INV_DIRN, 0);
- } else {
- mMediaBrowser.subscribe(newPath, mFolderItemsCb);
- /* assume that call is success and update stack with new folder path */
- mPathStack.push(newPath);
- }
- } else if (direction == AvrcpConstants.DIR_UP) { /* move up */
- if (!isBrowsableFolderUp()) {
- /* Already on the root, cannot allow up: PTS: test case TC_TG_MCN_CB_BI_02_C
- * This is required, otherwise some CT will keep on sending change path up
- * until they receive error */
- Log.w(TAG, "Cannot go up from now, already in the root, Invalid direction!");
- mMediaInterface.changePathRsp(mBDAddr, AvrcpConstants.RSP_INV_DIRN, 0);
- } else {
- /* move folder up */
- mPathStack.pop();
- newPath = mPathStack.peek();
- mMediaBrowser.subscribe(newPath, mFolderItemsCb);
- }
- } else { /* invalid direction */
- Log.w(TAG, "changePath : Invalid direction " + direction);
- mMediaInterface.changePathRsp(mBDAddr, AvrcpConstants.RSP_INV_DIRN, 0);
- }
- }
-
- public void getItemAttr(AvrcpCmd.ItemAttrCmd itemAttr) {
- String mediaID;
- if (DEBUG) {
- Log.d(TAG, "getItemAttr");
- }
-
- /* check if uid is valid by doing a lookup in hashmap */
- mediaID = byteToString(itemAttr.mUid);
- if (mediaID == null) {
- Log.e(TAG, "uid is invalid");
- mMediaInterface.getItemAttrRsp(mBDAddr, AvrcpConstants.RSP_INV_ITEM, null);
- return;
- }
-
- /* check scope */
- if (itemAttr.mScope != AvrcpConstants.BTRC_SCOPE_FILE_SYSTEM) {
- Log.e(TAG, "invalid scope");
- mMediaInterface.getItemAttrRsp(mBDAddr, AvrcpConstants.RSP_INV_SCOPE, null);
- return;
- }
-
- if (mMediaBrowser == null) {
- Log.e(TAG, "mMediaBrowser is null");
- mMediaInterface.getItemAttrRsp(mBDAddr, AvrcpConstants.RSP_INTERNAL_ERR, null);
- return;
- }
-
- /* Subscribe to the parent to list items and retrieve the right one */
- mMediaBrowser.subscribe(mPathStack.peek(), new ItemAttribSubscriber(itemAttr, mediaID));
- }
-
- public void getTotalNumOfItems(byte scope) {
- if (DEBUG) {
- Log.d(TAG, "getTotalNumOfItems scope = " + scope);
- }
- if (scope != AvrcpConstants.BTRC_SCOPE_FILE_SYSTEM) {
- Log.e(TAG, "getTotalNumOfItems error" + scope);
- mMediaInterface.getTotalNumOfItemsRsp(mBDAddr, AvrcpConstants.RSP_INV_SCOPE, 0, 0);
- return;
- }
-
- if (mFolderItems == null) {
- Log.e(TAG, "mFolderItems is null, sending internal error");
- /* folderitems were not fetched during change path */
- mMediaInterface.getTotalNumOfItemsRsp(mBDAddr, AvrcpConstants.RSP_INTERNAL_ERR, 0, 0);
- return;
- }
-
- /* find num items using size of already cached folder items */
- mMediaInterface.getTotalNumOfItemsRsp(mBDAddr, AvrcpConstants.RSP_NO_ERROR, 0,
- mFolderItems.size());
- }
-
- public void getFolderItemsVFS(AvrcpCmd.FolderItemsCmd reqObj) {
- if (!isPlayerConnected()) {
- Log.e(TAG, "unable to connect to media player, sending internal error");
- /* unable to connect to media player. Send error response to remote device */
- mMediaInterface.folderItemsRsp(mBDAddr, AvrcpConstants.RSP_INTERNAL_ERR, null);
- return;
- }
-
- if (DEBUG) {
- Log.d(TAG, "getFolderItemsVFS");
- }
- mFolderItemsReqObj = reqObj;
-
- if (mFolderItems == null) {
- /* Failed to fetch folder items from media player. Send error to remote device */
- Log.e(TAG, "Failed to fetch folder items during getFolderItemsVFS");
- mMediaInterface.folderItemsRsp(mBDAddr, AvrcpConstants.RSP_INTERNAL_ERR, null);
- return;
- }
-
- /* Filter attributes based on the request and send response to remote device */
- getFolderItemsFilterAttr(mBDAddr, reqObj, mFolderItems,
- AvrcpConstants.BTRC_SCOPE_FILE_SYSTEM, mFolderItemsReqObj.mStartItem,
- mFolderItemsReqObj.mEndItem);
- }
-
- /* Instructs media player to play particular media item */
- public void playItem(byte[] uid, byte scope) {
- String folderUid;
-
- if (isPlayerConnected()) {
- /* check if uid is valid */
- if ((folderUid = byteToString(uid)) == null) {
- Log.e(TAG, "uid is invalid!");
- mMediaInterface.playItemRsp(mBDAddr, AvrcpConstants.RSP_INV_ITEM);
- return;
- }
-
- if (mMediaController != null) {
- MediaController.TransportControls mediaControllerCntrl =
- mMediaController.getTransportControls();
- if (DEBUG) {
- Log.d(TAG, "Sending playID: " + folderUid);
- }
-
- if (scope == AvrcpConstants.BTRC_SCOPE_FILE_SYSTEM) {
- mediaControllerCntrl.playFromMediaId(folderUid, null);
- mMediaInterface.playItemRsp(mBDAddr, AvrcpConstants.RSP_NO_ERROR);
- } else {
- Log.e(TAG, "playItem received for invalid scope!");
- mMediaInterface.playItemRsp(mBDAddr, AvrcpConstants.RSP_INV_SCOPE);
- }
- } else {
- Log.e(TAG, "mediaController is null");
- mMediaInterface.playItemRsp(mBDAddr, AvrcpConstants.RSP_INTERNAL_ERR);
- }
- } else {
- Log.e(TAG, "playItem: Not connected to media player");
- mMediaInterface.playItemRsp(mBDAddr, AvrcpConstants.RSP_INTERNAL_ERR);
- }
- }
-
- /*
- * helper method to check if startItem and endItem index is with range of
- * MediaItem list. (Resultset containing all items in current path)
- */
- private List<MediaBrowser.MediaItem> checkIndexOutofBounds(byte[] bdaddr,
- List<MediaBrowser.MediaItem> children, long startItem, long endItem) {
- if (endItem >= children.size()) {
- endItem = children.size() - 1;
- }
- if (startItem >= Integer.MAX_VALUE) {
- startItem = Integer.MAX_VALUE;
- }
- try {
- List<MediaBrowser.MediaItem> childrenSubList =
- children.subList((int) startItem, (int) endItem + 1);
- if (childrenSubList.isEmpty()) {
- Log.i(TAG, "childrenSubList is empty.");
- throw new IndexOutOfBoundsException();
- }
- return childrenSubList;
- } catch (IndexOutOfBoundsException ex) {
- Log.w(TAG, "Index out of bounds start item =" + startItem + " end item = " + Math.min(
- children.size(), endItem + 1));
- return null;
- } catch (IllegalArgumentException ex) {
- Log.i(TAG, "Index out of bounds start item =" + startItem + " > size");
- return null;
- }
- }
-
-
- /*
- * helper method to filter required attibutes before sending GetFolderItems response
- */
- public void getFolderItemsFilterAttr(byte[] bdaddr, AvrcpCmd.FolderItemsCmd mFolderItemsReqObj,
- List<MediaBrowser.MediaItem> children, byte scope, long startItem, long endItem) {
- if (DEBUG) {
- Log.d(TAG,
- "getFolderItemsFilterAttr: startItem =" + startItem + ", endItem = " + endItem);
- }
-
- List<MediaBrowser.MediaItem> resultItems = new ArrayList<MediaBrowser.MediaItem>();
-
- if (children == null) {
- Log.e(TAG, "Error: children are null in getFolderItemsFilterAttr");
- mMediaInterface.folderItemsRsp(bdaddr, AvrcpConstants.RSP_INV_RANGE, null);
- return;
- }
-
- /* check for index out of bound errors */
- resultItems = checkIndexOutofBounds(bdaddr, children, startItem, endItem);
- if (resultItems == null) {
- Log.w(TAG, "resultItems is null.");
- mMediaInterface.folderItemsRsp(bdaddr, AvrcpConstants.RSP_INV_RANGE, null);
- return;
- }
- FolderItemsData folderDataNative = new FolderItemsData(resultItems.size());
-
- /* variables to temperorily add attrs */
- ArrayList<String> attrArray = new ArrayList<String>();
- ArrayList<Integer> attrId = new ArrayList<Integer>();
-
- for (int itemIndex = 0; itemIndex < resultItems.size(); itemIndex++) {
- /* item type. Needs to be set by media player */
- MediaBrowser.MediaItem item = resultItems.get(itemIndex);
- int flags = item.getFlags();
- if ((flags & MediaBrowser.MediaItem.FLAG_BROWSABLE) != 0) {
- folderDataNative.mItemTypes[itemIndex] = AvrcpConstants.BTRC_ITEM_FOLDER;
- } else {
- folderDataNative.mItemTypes[itemIndex] = AvrcpConstants.BTRC_ITEM_MEDIA;
- }
-
- /* set playable */
- if ((flags & MediaBrowser.MediaItem.FLAG_PLAYABLE) != 0) {
- folderDataNative.mPlayable[itemIndex] = AvrcpConstants.ITEM_PLAYABLE;
- } else {
- folderDataNative.mPlayable[itemIndex] = AvrcpConstants.ITEM_NOT_PLAYABLE;
- }
- /* set uid for current item */
- byte[] uid = stringToByte(item.getDescription().getMediaId());
- for (int idx = 0; idx < AvrcpConstants.UID_SIZE; idx++) {
- folderDataNative.mItemUid[itemIndex * AvrcpConstants.UID_SIZE + idx] = uid[idx];
- }
-
- /* Set display name for current item */
- folderDataNative.mDisplayNames[itemIndex] =
- getAttrValue(AvrcpConstants.ATTRID_TITLE, item);
-
- int maxAttributesRequested = 0;
- boolean isAllAttribRequested = false;
- /* check if remote requested for attributes */
- if (mFolderItemsReqObj.mNumAttr != AvrcpConstants.NUM_ATTR_NONE) {
- int attrCnt = 0;
-
- /* add requested attr ids to a temp array */
- if (mFolderItemsReqObj.mNumAttr == AvrcpConstants.NUM_ATTR_ALL) {
- isAllAttribRequested = true;
- maxAttributesRequested = AvrcpConstants.MAX_NUM_ATTR;
- } else {
- /* get only the requested attribute ids from the request */
- maxAttributesRequested = mFolderItemsReqObj.mNumAttr;
- }
-
- /* lookup and copy values of attributes for ids requested above */
- for (int idx = 0; idx < maxAttributesRequested; idx++) {
- /* check if media player provided requested attributes */
- String value = null;
-
- int attribId =
- isAllAttribRequested ? (idx + 1) : mFolderItemsReqObj.mAttrIDs[idx];
- value = getAttrValue(attribId, resultItems.get(itemIndex));
- if (value != null) {
- attrArray.add(value);
- attrId.add(attribId);
- attrCnt++;
- }
- }
- /* add num attr actually received from media player for a particular item */
- folderDataNative.mAttributesNum[itemIndex] = attrCnt;
- }
- }
-
- /* copy filtered attr ids and attr values to response parameters */
- if (attrId.size() > 0) {
- folderDataNative.mAttrIds = new int[attrId.size()];
- for (int attrIndex = 0; attrIndex < attrId.size(); attrIndex++) {
- folderDataNative.mAttrIds[attrIndex] = attrId.get(attrIndex);
- }
- folderDataNative.mAttrValues = attrArray.toArray(new String[attrArray.size()]);
- }
-
- /* create rsp object and send response to remote device */
- FolderItemsRsp rspObj =
- new FolderItemsRsp(AvrcpConstants.RSP_NO_ERROR, Avrcp.sUIDCounter, scope,
- folderDataNative.mNumItems, folderDataNative.mFolderTypes,
- folderDataNative.mPlayable, folderDataNative.mItemTypes,
- folderDataNative.mItemUid, folderDataNative.mDisplayNames,
- folderDataNative.mAttributesNum, folderDataNative.mAttrIds,
- folderDataNative.mAttrValues);
- mMediaInterface.folderItemsRsp(bdaddr, AvrcpConstants.RSP_NO_ERROR, rspObj);
- }
-
- public static String getAttrValue(int attr, MediaBrowser.MediaItem item) {
- String attrValue = null;
- try {
- MediaDescription desc = item.getDescription();
- Bundle extras = desc.getExtras();
- switch (attr) {
- /* Title is mandatory attribute */
- case AvrcpConstants.ATTRID_TITLE:
- attrValue = desc.getTitle().toString();
- break;
-
- case AvrcpConstants.ATTRID_ARTIST:
- attrValue = extras.getString(MediaMetadata.METADATA_KEY_ARTIST);
- break;
-
- case AvrcpConstants.ATTRID_ALBUM:
- attrValue = extras.getString(MediaMetadata.METADATA_KEY_ALBUM);
- break;
-
- case AvrcpConstants.ATTRID_TRACK_NUM:
- attrValue = extras.getString(MediaMetadata.METADATA_KEY_TRACK_NUMBER);
- break;
-
- case AvrcpConstants.ATTRID_NUM_TRACKS:
- attrValue = extras.getString(MediaMetadata.METADATA_KEY_NUM_TRACKS);
- break;
-
- case AvrcpConstants.ATTRID_GENRE:
- attrValue = extras.getString(MediaMetadata.METADATA_KEY_GENRE);
- break;
-
- case AvrcpConstants.ATTRID_PLAY_TIME:
- attrValue = extras.getString(MediaMetadata.METADATA_KEY_DURATION);
- break;
-
- case AvrcpConstants.ATTRID_COVER_ART:
- Log.e(TAG, "getAttrValue: Cover art attribute not supported");
- return null;
-
- default:
- Log.e(TAG, "getAttrValue: Unknown attribute ID requested: " + attr);
- return null;
- }
- } catch (NullPointerException ex) {
- Log.w(TAG, "getAttrValue: attr id not found in result");
- /* checking if attribute is title, then it is mandatory and cannot send null */
- if (attr == AvrcpConstants.ATTRID_TITLE) {
- attrValue = "<Unknown Title>";
- } else {
- return null;
- }
- }
- if (DEBUG) {
- Log.d(TAG, "getAttrValue: attrvalue = " + attrValue + "attr id:" + attr);
- }
- return attrValue;
- }
-
-
- public String getPackageName() {
- return mPackageName;
- }
-
- /* Helper methods */
-
- /* check if item is browsable Down*/
- private boolean isBrowsableFolderDn(String uid) {
- for (MediaBrowser.MediaItem item : mFolderItems) {
- if (item.getMediaId().equals(uid) && (
- (item.getFlags() & MediaBrowser.MediaItem.FLAG_BROWSABLE)
- == MediaBrowser.MediaItem.FLAG_BROWSABLE)) {
- return true;
- }
- }
- return false;
- }
-
- /* check if browsable Up*/
- private boolean isBrowsableFolderUp() {
- if (mPathStack.peek().equals(mRootFolderUid)) {
- /* Already on the root, cannot go up */
- return false;
- }
- return true;
- }
-
- /* convert uid to mediaId */
- private String byteToString(byte[] byteArray) {
- int uid = new BigInteger(byteArray).intValue();
- String mediaId = mHmap.get(uid);
- return mediaId;
- }
-
- /* convert mediaId to uid */
- private byte[] stringToByte(String mediaId) {
- /* check if this mediaId already exists in hashmap */
- if (!mHmap.containsValue(mediaId)) { /* add to hashmap */
- // Offset by one as uid 0 is reserved
- int uid = mHmap.size() + 1;
- mHmap.put(uid, mediaId);
- return intToByteArray(uid);
- } else { /* search key for give mediaId */
- for (int uid : mHmap.keySet()) {
- if (mHmap.get(uid).equals(mediaId)) {
- return intToByteArray(uid);
- }
- }
- }
- return null;
- }
-
- /* converts queue item received from getQueue call, to MediaItem used by FilterAttr method */
- private List<MediaBrowser.MediaItem> queueItem2MediaItem(
- List<MediaSession.QueueItem> tempItems) {
-
- List<MediaBrowser.MediaItem> tempMedia = new ArrayList<MediaBrowser.MediaItem>();
- for (int itemCount = 0; itemCount < tempItems.size(); itemCount++) {
- MediaDescription.Builder build = new MediaDescription.Builder();
- build.setMediaId(Long.toString(tempItems.get(itemCount).getQueueId()));
- build.setTitle(tempItems.get(itemCount).getDescription().getTitle());
- build.setExtras(tempItems.get(itemCount).getDescription().getExtras());
- MediaDescription des = build.build();
- MediaItem item = new MediaItem((des), MediaItem.FLAG_PLAYABLE);
- tempMedia.add(item);
- }
- return tempMedia;
- }
-
- /* convert integer to byte array of size 8 bytes */
- public byte[] intToByteArray(int value) {
- int index = 0;
- byte[] encodedValue = new byte[AvrcpConstants.UID_SIZE];
-
- encodedValue[index++] = (byte) 0x00;
- encodedValue[index++] = (byte) 0x00;
- encodedValue[index++] = (byte) 0x00;
- encodedValue[index++] = (byte) 0x00;
- encodedValue[index++] = (byte) (value >> 24);
- encodedValue[index++] = (byte) (value >> 16);
- encodedValue[index++] = (byte) (value >> 8);
- encodedValue[index++] = (byte) value;
-
- return encodedValue;
- }
-}
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/avrcp/mockable/MediaController.java b/src/com/android/bluetooth/avrcp/mockable/MediaController.java
index 934a454..5c7b73f 100644
--- a/src/com/android/bluetooth/avrcp/mockable/MediaController.java
+++ b/src/com/android/bluetooth/avrcp/mockable/MediaController.java
@@ -44,13 +44,13 @@
public android.media.session.MediaController.TransportControls mTransportDelegate;
public TransportControls mTransportControls;
- public MediaController(@NonNull android.media.session.MediaController delegate) {
+ MediaController(@NonNull android.media.session.MediaController delegate) {
mDelegate = delegate;
mTransportDelegate = delegate.getTransportControls();
mTransportControls = new TransportControls();
}
- public MediaController(Context context, MediaSession.Token token) {
+ MediaController(Context context, MediaSession.Token token) {
mDelegate = new android.media.session.MediaController(context, token);
mTransportDelegate = mDelegate.getTransportControls();
mTransportControls = new TransportControls();
@@ -65,101 +65,169 @@
return mTransportControls;
}
+ /**
+ * Wrapper for MediaController.dispatchMediaButtonEvent(KeyEvent keyEvent)
+ */
public boolean dispatchMediaButtonEvent(@NonNull KeyEvent keyEvent) {
return mDelegate.dispatchMediaButtonEvent(keyEvent);
}
+ /**
+ * Wrapper for MediaController.getPlaybackState()
+ */
@Nullable
public PlaybackState getPlaybackState() {
return mDelegate.getPlaybackState();
}
+
+ /**
+ * Wrapper for MediaController.getMetadata()
+ */
@Nullable
public MediaMetadata getMetadata() {
return mDelegate.getMetadata();
}
+ /**
+ * Wrapper for MediaController.getQueue()
+ */
@Nullable
public List<MediaSession.QueueItem> getQueue() {
return mDelegate.getQueue();
}
+ /**
+ * Wrapper for MediaController.getQueueTitle()
+ */
@Nullable
public CharSequence getQueueTitle() {
return mDelegate.getQueueTitle();
}
+ /**
+ * Wrapper for MediaController.getExtras()
+ */
@Nullable
public Bundle getExtras() {
return mDelegate.getExtras();
}
+ /**
+ * Wrapper for MediaController.getRatingType()
+ */
public int getRatingType() {
return mDelegate.getRatingType();
}
+ /**
+ * Wrapper for MediaController.getFlags()
+ */
public long getFlags() {
return mDelegate.getFlags();
}
+ /**
+ * Wrapper for MediaController.getPlaybackInfo()
+ */
@Nullable
public android.media.session.MediaController.PlaybackInfo getPlaybackInfo() {
return mDelegate.getPlaybackInfo();
}
+
+ /**
+ * Wrapper for MediaController.getSessionActivity()
+ */
@Nullable
public PendingIntent getSessionActivity() {
return mDelegate.getSessionActivity();
}
+ /**
+ * Wrapper for MediaController.getSessionToken()
+ */
@NonNull
public MediaSession.Token getSessionToken() {
return mDelegate.getSessionToken();
}
+ /**
+ * Wrapper for MediaController.setVolumeTo(int value, int flags)
+ */
public void setVolumeTo(int value, int flags) {
mDelegate.setVolumeTo(value, flags);
}
+ /**
+ * Wrapper for MediaController.adjustVolume(int direction, int flags)
+ */
public void adjustVolume(int direction, int flags) {
mDelegate.adjustVolume(direction, flags);
}
+ /**
+ * Wrapper for MediaController.registerCallback(Callback callback)
+ */
public void registerCallback(@NonNull Callback callback) {
//TODO(apanicke): Add custom callback struct to be able to analyze and
// delegate callbacks
mDelegate.registerCallback(callback);
}
+ /**
+ * Wrapper for MediaController.registerCallback(Callback callback, Handler handler)
+ */
public void registerCallback(@NonNull Callback callback, @Nullable Handler handler) {
mDelegate.registerCallback(callback, handler);
}
+ /**
+ * Wrapper for MediaController.unregisterCallback(Callback callback)
+ */
public void unregisterCallback(@NonNull Callback callback) {
mDelegate.unregisterCallback(callback);
}
+ /**
+ * Wrapper for MediaController.sendCommand(String command, Bundle args, ResultReceiver cb)
+ */
public void sendCommand(@NonNull String command, @Nullable Bundle args,
@Nullable ResultReceiver cb) {
mDelegate.sendCommand(command, args, cb);
}
+ /**
+ * Wrapper for MediaController.getPackageName()
+ */
public String getPackageName() {
return mDelegate.getPackageName();
}
+ /**
+ * Wrapper for MediaController.getTag()
+ */
public String getTag() {
return mDelegate.getTag();
}
+ /**
+ * Wrapper for MediaController.controlsSameSession(MediaController other)
+ */
public boolean controlsSameSession(MediaController other) {
return mDelegate.controlsSameSession(other.getWrappedInstance());
}
+ /**
+ * Wrapper for MediaController.controlsSameSession(MediaController other)
+ */
public boolean controlsSameSession(android.media.session.MediaController other) {
return mDelegate.controlsSameSession(other);
}
+ /**
+ * Wrapper for MediaController.equals(Object other)
+ */
@Override
public boolean equals(Object o) {
if (o instanceof android.media.session.MediaController) {
@@ -171,6 +239,9 @@
return false;
}
+ /**
+ * Wrapper for MediaController.toString()
+ */
@Override
public String toString() {
MediaMetadata data = getMetadata();
@@ -179,83 +250,146 @@
mDelegate.hashCode()) + ") " + desc;
}
+ /**
+ * Wrapper for MediaController.Callback
+ */
public abstract static class Callback extends android.media.session.MediaController.Callback {}
+ /**
+ * Wrapper for MediaController.TransportControls
+ */
public class TransportControls {
+ /**
+ * Wrapper for MediaController.TransportControls.prepare()
+ */
public void prepare() {
mTransportDelegate.prepare();
}
+ /**
+ * Wrapper for MediaController.TransportControls.prepareFromMediaId()
+ */
public void prepareFromMediaId(String mediaId, Bundle extras) {
mTransportDelegate.prepareFromMediaId(mediaId, extras);
}
+ /**
+ * Wrapper for MediaController.TransportControls.prepareFromSearch()
+ */
public void prepareFromSearch(String query, Bundle extras) {
mTransportDelegate.prepareFromSearch(query, extras);
}
+ /**
+ * Wrapper for MediaController.TransportControls.prepareFromUri()
+ */
public void prepareFromUri(Uri uri, Bundle extras) {
mTransportDelegate.prepareFromUri(uri, extras);
}
+ /**
+ * Wrapper for MediaController.TransportControls.play()
+ */
public void play() {
mTransportDelegate.play();
}
+ /**
+ * Wrapper for MediaController.TransportControls.playFromMediaId()
+ */
public void playFromMediaId(String mediaId, Bundle extras) {
mTransportDelegate.playFromMediaId(mediaId, extras);
}
+ /**
+ * Wrapper for MediaController.TransportControls.playFromSearch()
+ */
public void playFromSearch(String query, Bundle extras) {
mTransportDelegate.playFromSearch(query, extras);
}
+ /**
+ * Wrapper for MediaController.TransportControls.playFromUri()
+ */
public void playFromUri(Uri uri, Bundle extras) {
mTransportDelegate.playFromUri(uri, extras);
}
+ /**
+ * Wrapper for MediaController.TransportControls.skipToQueueItem()
+ */
public void skipToQueueItem(long id) {
mTransportDelegate.skipToQueueItem(id);
}
+ /**
+ * Wrapper for MediaController.TransportControls.pause()
+ */
public void pause() {
mTransportDelegate.pause();
}
+ /**
+ * Wrapper for MediaController.TransportControls.stop()
+ */
public void stop() {
mTransportDelegate.stop();
}
+ /**
+ * Wrapper for MediaController.TransportControls.seekTo()
+ */
public void seekTo(long pos) {
mTransportDelegate.seekTo(pos);
}
+ /**
+ * Wrapper for MediaController.TransportControls.fastForward()
+ */
public void fastForward() {
mTransportDelegate.fastForward();
}
+ /**
+ * Wrapper for MediaController.TransportControls.skipToNext()
+ */
public void skipToNext() {
mTransportDelegate.skipToNext();
}
+ /**
+ * Wrapper for MediaController.TransportControls.rewind()
+ */
public void rewind() {
mTransportDelegate.rewind();
}
+ /**
+ * Wrapper for MediaController.TransportControls.skipToPrevious()
+ */
public void skipToPrevious() {
mTransportDelegate.skipToPrevious();
}
+ /**
+ * Wrapper for MediaController.TransportControls.setRating()
+ */
public void setRating(Rating rating) {
mTransportDelegate.setRating(rating);
}
+ /**
+ * Wrapper for MediaController.TransportControls.sendCustomAction()
+ */
public void sendCustomAction(@NonNull PlaybackState.CustomAction customAction,
@Nullable Bundle args) {
mTransportDelegate.sendCustomAction(customAction, args);
}
+ /**
+ * Wrapper for MediaController.TransportControls.sendCustomAction()
+ */
public void sendCustomAction(@NonNull String action, @Nullable Bundle args) {
mTransportDelegate.sendCustomAction(action, args);
}
diff --git a/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerService.java b/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerService.java
index 6111ec4..64e63df 100644
--- a/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerService.java
+++ b/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerService.java
@@ -21,14 +21,11 @@
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
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.HandlerThread;
-import android.os.Message;
import android.util.Log;
import com.android.bluetooth.Utils;
@@ -37,15 +34,21 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import java.util.Map;
+import java.util.Set;
import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
/**
* Provides Bluetooth AVRCP Controller profile, as a service in the Bluetooth application.
*/
public class AvrcpControllerService extends ProfileService {
static final String TAG = "AvrcpControllerService";
- static final boolean DBG = false;
- static final boolean VDBG = false;
+ static final int MAXIMUM_CONNECTED_DEVICES = 5;
+ static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
+ static final boolean VDBG = Log.isLoggable(TAG, Log.VERBOSE);
+
+ public static final String MEDIA_ITEM_UID_KEY = "media-item-uid-key";
/*
* Play State Values from JNI
*/
@@ -56,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
@@ -165,461 +90,109 @@
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;
+ static BrowseTree sBrowseTree;
+ private static AvrcpControllerService sService;
+ private final BluetoothAdapter mAdapter;
- /* 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 AvrcpControllerStateMachine mAvrcpCtSm;
- private static AvrcpControllerService sAvrcpControllerService;
- // UID size is 8 bytes (AVRCP 1.6 spec)
- private static final byte[] EMPTY_UID = {0, 0, 0, 0, 0, 0, 0, 0};
-
- // 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;
+ 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() {
- HandlerThread thread = new HandlerThread("BluetoothAvrcpHandler");
- thread.start();
- mAvrcpCtSm = new AvrcpControllerStateMachine(this);
- mAvrcpCtSm.start();
+ initNative();
+ sBrowseTree = new BrowseTree(null);
+ sService = this;
- setAvrcpControllerService(this);
+ // Start the media browser service.
+ Intent startIntent = new Intent(this, BluetoothMediaBrowserService.class);
+ startService(startIntent);
return true;
}
@Override
protected boolean stop() {
- setAvrcpControllerService(null);
- if (mAvrcpCtSm != null) {
- mAvrcpCtSm.doQuit();
+ Intent stopIntent = new Intent(this, BluetoothMediaBrowserService.class);
+ stopService(stopIntent);
+ for (AvrcpControllerStateMachine stateMachine : mDeviceStateMap.values()) {
+ 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>();
- if (mConnectedDevice != null) {
- devices.add(mConnectedDevice);
+ 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<BluetoothDevice>();
- for (int i = 0; i < states.length; i++) {
- if (states[i] == BluetoothProfile.STATE_CONNECTED && mConnectedDevice != null) {
- devices.add(mConnectedDevice);
+ 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 (mConnectedDevice != 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);
- if (device == null) {
- Log.e(TAG, "sendGroupNavigationCmd device is null");
- }
-
- if (!(device.equals(mConnectedDevice))) {
- Log.e(TAG, " Device does not match " + device + " connected " + mConnectedDevice);
- return;
- }
- enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- 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);
- if (device == null) {
- Log.e(TAG, "sendPassThroughCmd Device is null");
- return;
- }
-
- if (!device.equals(mConnectedDevice)) {
- Log.w(TAG, " Device does not match device " + device + " conn " + mConnectedDevice);
- return;
- }
-
- enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- Message msg =
- mAvrcpCtSm.obtainMessage(AvrcpControllerStateMachine.MESSAGE_SEND_PASS_THROUGH_CMD,
- keyCode, keyState, device);
- mAvrcpCtSm.sendMessage(msg);
- }
-
- public void startAvrcpUpdates() {
- mAvrcpCtSm.obtainMessage(AvrcpControllerStateMachine.MESSAGE_START_METADATA_BROADCASTS)
- .sendToTarget();
- }
-
- public void stopAvrcpUpdates() {
- 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;
+ } else {
+ if (!requestedNode.isCached()) {
+ if (DBG) Log.d(TAG, "node is not cached");
+ refreshContents(requestedNode);
+ }
+ if (DBG) Log.d(TAG, "Returning contents");
+ return requestedNode.getContents();
}
-
- if (!device.equals(mConnectedDevice)) {
- 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 (!device.equals(mConnectedDevice)) {
- Log.e(TAG, "Device " + device + " does not match connected deivce " + mConnectedDevice);
- return null;
-
- }
- enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- 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 (!device.equals(mConnectedDevice)) {
- Log.e(TAG, "device " + device + " does not match connected device " + mConnectedDevice);
- 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 (!device.equals(mConnectedDevice)) {
- Log.e(TAG, "getChildren device " + device + " does not match " + mConnectedDevice);
- return false;
- }
-
- if (!mBrowseConnected) {
- Log.e(TAG, "getChildren browse not yet connected");
- 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 (!device.equals(mConnectedDevice)) {
- Log.e(TAG,
- "getNowPlayingList device " + device + " does not match " + mConnectedDevice);
- return false;
- }
-
- if (!mBrowseConnected) {
- Log.e(TAG, "getNowPlayingList browse not yet connected");
- return false;
- }
-
- enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-
- 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 (!device.equals(mConnectedDevice)) {
- Log.e(TAG, "getFolderListing device " + device + " does not match " + mConnectedDevice);
- return false;
- }
-
- if (!mBrowseConnected) {
- Log.e(TAG, "getFolderListing browse not yet connected");
- return false;
- }
-
- enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-
- 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 (!device.equals(mConnectedDevice)) {
- Log.e(TAG, "getPlayerList device " + device + " does not match " + mConnectedDevice);
- return false;
- }
-
- if (!mBrowseConnected) {
- Log.e(TAG, "getPlayerList browse not yet connected");
- return false;
- }
-
- enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-
- 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 (!device.equals(mConnectedDevice)) {
- Log.e(TAG, "changeFolderPath device " + device + " does not match " + mConnectedDevice);
- return false;
- }
-
- if (!mBrowseConnected) {
- Log.e(TAG, "changeFolderPath browse not yet connected");
- return false;
- }
-
- enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-
- 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 (!device.equals(mConnectedDevice)) {
- Log.e(TAG, "changeFolderPath device " + device + " does not match " + mConnectedDevice);
- return false;
- }
-
- if (!mBrowseConnected) {
- Log.e(TAG, "setBrowsedPlayer browse not yet connected");
- return false;
- }
-
- enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-
- 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 (!device.equals(mConnectedDevice)) {
- Log.e(TAG, "fetchAttrAndPlayItem device " + device + " does not match "
- + mConnectedDevice);
- return;
- }
-
- if (!mBrowseConnected) {
- Log.e(TAG, "fetchAttrAndPlayItem browse not yet connected");
- return;
- }
- mAvrcpCtSm.fetchAttrAndPlayItem(uid);
+ @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() {
@@ -628,14 +201,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
@@ -667,153 +240,102 @@
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 " + rcConnected + " " + brConnected + device
- + " conn device " + mConnectedDevice);
+ 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 = (device.equals(mConnectedDevice) ? BluetoothProfile.STATE_CONNECTED
- : BluetoothProfile.STATE_DISCONNECTED);
- int newState = (rcConnected ? BluetoothProfile.STATE_CONNECTED
- : BluetoothProfile.STATE_DISCONNECTED);
-
- if (rcConnected && oldState == BluetoothProfile.STATE_DISCONNECTED) {
- /* AVRCPControllerService supports single connection */
- if (mConnectedDevice != null) {
- Log.d(TAG, "A Connection already exists, returning");
- return;
- }
- mConnectedDevice = device;
- Message msg = mAvrcpCtSm.obtainMessage(
- AvrcpControllerStateMachine.MESSAGE_PROCESS_CONNECTION_CHANGE, newState,
- oldState, device);
- mAvrcpCtSm.sendMessage(msg);
- } else if (!rcConnected && oldState == BluetoothProfile.STATE_CONNECTED) {
- mConnectedDevice = null;
- 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) {
- BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
- Message msg =
- mAvrcpCtSm.obtainMessage(AvrcpControllerStateMachine.MESSAGE_PROCESS_RC_FEATURES,
- features, 0, device);
- mAvrcpCtSm.sendMessage(msg);
+ /* Do Nothing. */
}
// 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 && !device.equals(mConnectedDevice)) {
- Log.e(TAG, "handleRegisterNotificationAbsVol device not found " + address);
- return;
+ if (DBG) {
+ Log.d(TAG, "handleRegisterNotificationAbsVol");
}
- 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 && !device.equals(mConnectedDevice)) {
- Log.e(TAG, "handleSetAbsVolume device not found " + address);
- return;
+ if (DBG) {
+ Log.d(TAG, "handleSetAbsVolume ");
}
- 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);
+ }
}
// Called by JNI when a track changes and local AvrcpController is registered for updates.
@@ -822,24 +344,13 @@
if (DBG) {
Log.d(TAG, "onTrackChanged");
}
- BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
- if (device != null && !device.equals(mConnectedDevice)) {
- Log.e(TAG, "onTrackChanged device not found " + address);
- return;
- }
- List<Integer> attrList = new ArrayList<>();
- for (int attr : attributes) {
- attrList.add(attr);
+ BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
+ AvrcpControllerStateMachine stateMachine = getStateMachine(device);
+ if (stateMachine != null) {
+ stateMachine.sendMessage(AvrcpControllerStateMachine.MESSAGE_PROCESS_TRACK_CHANGED,
+ TrackInfo.getMetadata(attributes, attribVals));
}
- List<String> attrValList = Arrays.asList(attribVals);
- TrackInfo trackInfo = new TrackInfo(attrList, attrValList);
- if (VDBG) {
- Log.d(TAG, "onTrackChanged " + trackInfo);
- }
- Message msg = mAvrcpCtSm.obtainMessage(
- AvrcpControllerStateMachine.MESSAGE_PROCESS_TRACK_CHANGED, trackInfo);
- mAvrcpCtSm.sendMessage(msg);
}
// Called by JNI periodically based upon timer to update play position
@@ -849,14 +360,12 @@
Log.d(TAG, "onPlayPositionChanged pos " + currSongPosition);
}
BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
- if (device != null && !device.equals(mConnectedDevice)) {
- 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);
}
- Message msg = mAvrcpCtSm.obtainMessage(
- AvrcpControllerStateMachine.MESSAGE_PROCESS_PLAY_POS_CHANGED,
- songLen, currSongPosition);
- mAvrcpCtSm.sendMessage(msg);
}
// Called by JNI on changes of play status
@@ -864,11 +373,6 @@
if (DBG) {
Log.d(TAG, "onPlayStatusChanged " + playStatus);
}
- BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
- if (device != null && !device.equals(mConnectedDevice)) {
- Log.e(TAG, "onPlayStatusChanged not found device not found " + address);
- return;
- }
int playbackState = PlaybackState.STATE_NONE;
switch (playStatus) {
case JNI_PLAY_STATUS_STOPPED:
@@ -884,14 +388,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;
}
- 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
@@ -901,13 +408,13 @@
Log.d(TAG, "handlePlayerAppSetting rspLen = " + rspLen);
}
BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
- if (device != null && !device.equals(mConnectedDevice)) {
- 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,
@@ -916,50 +423,48 @@
Log.d(TAG, "onPlayerAppSettingChanged ");
}
BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
- if (device != null && !device.equals(mConnectedDevice)) {
- Log.e(TAG, "onPlayerAppSettingChanged not found 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) {
+ void handleGetFolderItemsRsp(byte[] address, int status, MediaItem[] items) {
if (DBG) {
Log.d(TAG, "handleGetFolderItemsRsp called with status " + status + " items "
+ items.length + " items.");
}
-
- 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) {
+
+ void handleGetPlayerItemsRsp(byte[] address, AvrcpPlayer[] items) {
if (DBG) {
Log.d(TAG, "handleGetFolderItemsRsp called with " + items.length + " items.");
}
+
for (AvrcpPlayer item : items) {
if (VDBG) {
Log.d(TAG, "bt player item: " + item);
@@ -970,28 +475,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);
@@ -1001,23 +509,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);
@@ -1028,112 +533,285 @@
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) {
+ private void handleChangeFolderRsp(byte[] address, int count) {
if (DBG) {
Log.d(TAG, "handleChangeFolderRsp count: " + count);
}
- Message msg =
- mAvrcpCtSm.obtainMessage(AvrcpControllerStateMachine.MESSAGE_PROCESS_FOLDER_PATH,
- count);
- mAvrcpCtSm.sendMessage(msg);
+ BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
+ AvrcpControllerStateMachine stateMachine = getStateMachine(device);
+ if (stateMachine != null) {
+ stateMachine.sendMessage(AvrcpControllerStateMachine.MESSAGE_PROCESS_FOLDER_PATH,
+ count);
+ }
}
- private void handleSetBrowsedPlayerRsp(int items, int depth) {
+ private void handleSetBrowsedPlayerRsp(byte[] address, int items, int depth) {
if (DBG) {
Log.d(TAG, "handleSetBrowsedPlayerRsp depth: " + depth);
}
- Message msg = mAvrcpCtSm.obtainMessage(
- AvrcpControllerStateMachine.MESSAGE_PROCESS_SET_BROWSED_PLAYER, items, depth);
- mAvrcpCtSm.sendMessage(msg);
+ BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
+
+ AvrcpControllerStateMachine stateMachine = getStateMachine(device);
+ if (stateMachine != null) {
+ stateMachine.sendMessage(AvrcpControllerStateMachine.MESSAGE_PROCESS_SET_BROWSED_PLAYER,
+ items, depth);
+ }
}
- private void handleSetAddressedPlayerRsp(int status) {
+ private void handleSetAddressedPlayerRsp(byte[] address, int status) {
if (DBG) {
Log.d(TAG, "handleSetAddressedPlayerRsp status: " + status);
}
- Message msg = mAvrcpCtSm.obtainMessage(
- AvrcpControllerStateMachine.MESSAGE_PROCESS_SET_ADDRESSED_PLAYER);
- mAvrcpCtSm.sendMessage(msg);
+ BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
+
+ AvrcpControllerStateMachine stateMachine = getStateMachine(device);
+ if (stateMachine != null) {
+ stateMachine.sendMessage(
+ AvrcpControllerStateMachine.MESSAGE_PROCESS_SET_ADDRESSED_PLAYER);
+ }
+ }
+
+ 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;
+ }
+
+ /**
+ * Remove state machine from device map once it is no longer needed.
+ */
+ public 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();
}
@Override
public void dump(StringBuilder sb) {
super.dump(sb);
- 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);
+ /**
+ * Send button press commands to addressed device
+ *
+ * @param keyCode key code as defined in AVRCP specification
+ * @param keyState 0 = key pressed, 1 = key released
+ * @return command was sent
+ */
+ public native boolean sendPassThroughCommandNative(byte[] address, int keyCode, int keyState);
- static native boolean sendGroupNavigationCommandNative(byte[] address, int keyCode,
+ /**
+ * Send group navigation commands
+ *
+ * @param keyCode next/previous
+ * @param keyState state
+ * @return command was sent
+ */
+ public native boolean sendGroupNavigationCommandNative(byte[] address, int keyCode,
int keyState);
- static native void setPlayerApplicationSettingValuesNative(byte[] address, byte numAttrib,
- byte[] atttibIds, byte[] attribVal);
+ /**
+ * Change player specific settings such as shuffle
+ *
+ * @param numAttrib number of settings being sent
+ * @param attribIds list of settings to be changed
+ * @param attribVal list of settings values
+ */
+ public native void setPlayerApplicationSettingValuesNative(byte[] address, byte numAttrib,
+ byte[] attribIds, byte[] attribVal);
- /* This api is used to send response to SET_ABS_VOL_CMD */
- static native void sendAbsVolRspNative(byte[] address, int absVol, int label);
+ /**
+ * Send response to set absolute volume
+ *
+ * @param absVol new volume
+ * @param label label
+ */
+ public native void sendAbsVolRspNative(byte[] address, int absVol, int label);
- /* This api is used to inform remote for any volume level changes */
- static native void sendRegisterAbsVolRspNative(byte[] address, byte rspType, int absVol,
+ /**
+ * Register for any volume level changes
+ *
+ * @param rspType type of response
+ * @param absVol current volume
+ * @param label label
+ */
+ public native void sendRegisterAbsVolRspNative(byte[] address, byte rspType, int absVol,
int label);
- /* API used to fetch the playback state */
- static native void getPlaybackStateNative(byte[] address);
+ /**
+ * Fetch the playback state
+ */
+ public native void getPlaybackStateNative(byte[] address);
- /* API used to fetch the current now playing list */
- static native void getNowPlayingListNative(byte[] address, int start, int end);
+ /**
+ * Fetch the current now playing list
+ *
+ * @param start first index to retrieve
+ * @param end last index to retrieve
+ */
+ public native void getNowPlayingListNative(byte[] address, int start, int end);
- /* API used to fetch the current folder's listing */
- static native void getFolderListNative(byte[] address, int start, int end);
+ /**
+ * Fetch the current folder's listing
+ *
+ * @param start first index to retrieve
+ * @param end last index to retrieve
+ */
+ public native void getFolderListNative(byte[] address, int start, int end);
- /* API used to fetch the listing of players */
- static native void getPlayerListNative(byte[] address, int start, int end);
+ /**
+ * Fetch the listing of players
+ *
+ * @param start first index to retrieve
+ * @param end last index to retrieve
+ */
+ public 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);
+ /**
+ * Change the current browsed folder
+ *
+ * @param direction up/down
+ * @param uid folder unique id
+ */
+ public native void changeFolderPathNative(byte[] address, byte direction, long uid);
- static native void playItemNative(byte[] address, byte scope, byte[] uid, int uidCounter);
+ /**
+ * Play item with provided uid
+ *
+ * @param scope scope of item to played
+ * @param uid song unique id
+ * @param uidCounter counter
+ */
+ public native void playItemNative(byte[] address, byte scope, long uid, int uidCounter);
- static native void setBrowsedPlayerNative(byte[] address, int playerId);
+ /**
+ * Set a specific player for browsing
+ *
+ * @param playerId player number
+ */
+ public native void setBrowsedPlayerNative(byte[] address, int playerId);
- static native void setAddressedPlayerNative(byte[] address, int playerId);
+ /**
+ * Set a specific player for handling playback commands
+ *
+ * @param playerId player number
+ */
+ public native void setAddressedPlayerNative(byte[] address, int playerId);
}
diff --git a/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java b/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java
index 3077664..fa18045 100644
--- a/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java
+++ b/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java
@@ -17,23 +17,22 @@
package com.android.bluetooth.avrcpcontroller;
import android.bluetooth.BluetoothAvrcpController;
-import android.bluetooth.BluetoothAvrcpPlayerSettings;
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.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,595 +42,488 @@
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 = Log.isLoggable(TAG, Log.DEBUG);
- // 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;
- // 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
*/
private static final int ABS_VOL_BASE = 127;
- /*
- * Notification types for Avrcp protocol JNI.
- */
- 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 = false;
-
- private final Context mContext;
private final AudioManager mAudioManager;
- 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;
+ 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 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;
- private RemoteDevice mRemoteDevice;
- private AvrcpPlayer mAddressedPlayer;
-
- // Only accessed from State Machine processMessage
+ boolean mRemoteControlConnected = false;
+ boolean mBrowsingConnected = false;
+ BrowseTree mBrowseTree = null;
+ private AvrcpPlayer mAddressedPlayer = new AvrcpPlayer();
+ private int mAddressedPlayerId = -1;
+ private SparseArray<AvrcpPlayer> mAvailablePlayerList = new SparseArray<AvrcpPlayer>();
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
- AvrcpControllerStateMachine(Context context) {
+ AvrcpControllerStateMachine(BluetoothDevice device, AvrcpControllerService service) {
super(TAG);
- mContext = context;
-
- mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
- IntentFilter filter = new IntentFilter(AudioManager.VOLUME_CHANGED_ACTION);
- mContext.registerReceiver(mBroadcastReceiver, filter);
+ mDevice = device;
+ mDeviceAddress = Utils.getByteAddress(mDevice);
+ mService = service;
+ logD(device.toString());
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);
+ mAudioManager = (AudioManager) service.getSystemService(Context.AUDIO_SERVICE);
setInitialState(mDisconnected);
}
- 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;
+ mBrowseTree = new BrowseTree(mDevice);
+ 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);
+ 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());
+ 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() {
+ logD("Enter Disconnected");
+ if (mMostRecentState != BluetoothProfile.STATE_DISCONNECTED) {
+ sendMessage(CLEANUP);
+ }
+ broadcastConnectionStateChanged(BluetoothProfile.STATE_DISCONNECTED);
+ }
@Override
- public boolean processMessage(Message msg) {
- if (DBG) Log.d(TAG, " HandleMessage: " + dumpMessageString(msg.what));
- 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);
+ break;
}
return true;
}
}
+ protected class Connecting extends State {
+ @Override
+ 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) {
- 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);
- } 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;
- } 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);
- 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);
- 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;
-
- default:
- return false;
- }
- }
- return true;
- }
- }
-
- // 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) {
+ 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;
+ mAddressedPlayer.setPlayStatus(msg.arg1);
+ BluetoothMediaBrowserService.notifyChanged(mAddressedPlayer.getPlaybackState());
+ if (mAddressedPlayer.getPlaybackState().getState()
+ == PlaybackState.STATE_PLAYING
+ && A2dpSinkService.getFocusState() == AudioManager.AUDIOFOCUS_NONE
+ && !shouldRequestFocus()) {
+ sendMessage(MSG_AVRCP_PASSTHRU,
+ AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE);
+ }
+ 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;
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 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;
@@ -639,651 +531,267 @@
// 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:
+ mService.getPlayerListNative(mDeviceAddress,
+ start, end);
+ break;
case AvrcpControllerService.BROWSE_SCOPE_NOW_PLAYING:
- AvrcpControllerService.getNowPlayingListNative(
- mRemoteDevice.getBluetoothAddress(), start, end);
+ mService.getNowPlayingListNative(
+ mDeviceAddress, start, end);
break;
case AvrcpControllerService.BROWSE_SCOPE_VFS:
- AvrcpControllerService.getFolderListNative(mRemoteDevice.getBluetoothAddress(),
+ mService.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()) {
+ mService.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;
+ mService.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());
+ mService.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.
- }
- quit();
- }
-
- 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);
- // 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);
+ 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);
}
- AvrcpControllerService.sendAbsVolRspNative(mRemoteDevice.getBluetoothAddress(), absVol,
- label);
+ mService.sendAbsVolRspNative(mDeviceAddress, 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() {
+ MediaSession.Callback mSessionCallbacks = new MediaSession.Callback() {
@Override
- public void onReceive(Context context, Intent intent) {
- String action = intent.getAction();
- if (action.equals(AudioManager.VOLUME_CHANGED_ACTION)) {
- int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
- if (streamType == AudioManager.STREAM_MUSIC) {
- sendMessage(MESSAGE_PROCESS_VOLUME_CHANGED_NOTIFICATION);
- }
+ 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);
+ }
};
- 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;
- default:
- str = Integer.toString(message);
- break;
+ protected void broadcastConnectionStateChanged(int currentState) {
+ if (mMostRecentState == currentState) {
+ return;
}
- return str;
+ 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);
}
- public static String displayBluetoothAvrcpSettings(BluetoothAvrcpPlayerSettings mSett) {
- StringBuffer sb = new StringBuffer();
- int supportedSetting = mSett.getSettings();
- if (VDBG) {
- Log.d(TAG, " setting: " + supportedSetting);
- }
- if ((supportedSetting & BluetoothAvrcpPlayerSettings.SETTING_EQUALIZER) != 0) {
- sb.append(" EQ : ");
- sb.append(Integer.toString(mSett.getSettingValue(
- BluetoothAvrcpPlayerSettings.SETTING_EQUALIZER)));
- }
- 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();
+ 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..bed38d9 100644
--- a/src/com/android/bluetooth/avrcpcontroller/AvrcpPlayer.java
+++ b/src/com/android/bluetooth/avrcpcontroller/AvrcpPlayer.java
@@ -16,33 +16,64 @@
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
*/
class AvrcpPlayer {
private static final String TAG = "AvrcpPlayer";
- private static final boolean DBG = true;
+ private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
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..3504cd4
--- /dev/null
+++ b/src/com/android/bluetooth/avrcpcontroller/BluetoothMediaBrowserService.java
@@ -0,0 +1,201 @@
+/*
+ * 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.MediaController;
+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 = Log.isLoggable(TAG, Log.DEBUG);
+
+ 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");
+ }
+ }
+
+ /**
+ * Get object for controlling playback
+ */
+ public static synchronized MediaController.TransportControls getTransportControls() {
+ if (sBluetoothMediaBrowserService != null) {
+ return sBluetoothMediaBrowserService.mSession.getController().getTransportControls();
+ } else {
+ Log.w(TAG, "transportControls Unavailable");
+ return null;
+ }
+ }
+
+ /**
+ * 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 4482bd7..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 = false;
-
- 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/RemoteDevice.java b/src/com/android/bluetooth/avrcpcontroller/RemoteDevice.java
deleted file mode 100644
index 7946747..0000000
--- a/src/com/android/bluetooth/avrcpcontroller/RemoteDevice.java
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * Copyright (C) 2016 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.bluetooth.BluetoothDevice;
-
-import com.android.bluetooth.Utils;
-
-/*
- * Contains information about remote device specifically the player and features enabled on it along
- * with an encapsulation of the current track and playlist information.
- */
-class RemoteDevice {
-
- /*
- * Remote features from JNI
- */
- private static final int FEAT_NONE = 0;
- private static final int FEAT_METADATA = 1;
- private static final int FEAT_ABSOLUTE_VOLUME = 2;
- private static final int FEAT_BROWSE = 4;
-
- private static final int VOLUME_LABEL_UNDEFINED = -1;
-
- final BluetoothDevice mBTDevice;
- private int mRemoteFeatures;
- private boolean mAbsVolNotificationRequested;
- private boolean mFirstAbsVolCmdRecvd;
- private int mNotificationLabel;
-
- RemoteDevice(BluetoothDevice mDevice) {
- mBTDevice = mDevice;
- mRemoteFeatures = FEAT_NONE;
- mAbsVolNotificationRequested = false;
- mNotificationLabel = VOLUME_LABEL_UNDEFINED;
- mFirstAbsVolCmdRecvd = false;
- }
-
- synchronized void setRemoteFeatures(int remoteFeatures) {
- mRemoteFeatures = remoteFeatures;
- }
-
- public synchronized byte[] getBluetoothAddress() {
- return Utils.getByteAddress(mBTDevice);
- }
-
- public synchronized void setNotificationLabel(int label) {
- mNotificationLabel = label;
- }
-
- public synchronized int getNotificationLabel() {
- return mNotificationLabel;
- }
-
- public synchronized void setAbsVolNotificationRequested(boolean request) {
- mAbsVolNotificationRequested = request;
- }
-
- public synchronized boolean getAbsVolNotificationRequested() {
- return mAbsVolNotificationRequested;
- }
-
- public synchronized void setFirstAbsVolCmdRecvd() {
- mFirstAbsVolCmdRecvd = true;
- }
-
- public synchronized boolean getFirstAbsVolCmdRecvd() {
- return mFirstAbsVolCmdRecvd;
- }
-}
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 e89fb4c..fd1b784 100644
--- a/src/com/android/bluetooth/avrcpcontroller/TrackInfo.java
+++ b/src/com/android/bluetooth/avrcpcontroller/TrackInfo.java
@@ -14,34 +14,11 @@
* limitations under the License.
*/
-
package com.android.bluetooth.avrcpcontroller;
import android.media.MediaMetadata;
-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 = false;
-
- /*
- * 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
*/
@@ -53,95 +30,49 @@
private static final int MEDIA_ATTRIBUTE_GENRE = 0x06;
private static final int MEDIA_ATTRIBUTE_PLAYING_TIME = 0x07;
-
- 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.
-
- TrackInfo() {
- this(new ArrayList<Integer>(), new ArrayList<String>());
- }
-
- 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));
+ 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;
+ }
}
-
- 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;
- }
-
- @Override
- 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) + "]";
- }
-
- 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);
- 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();
+ return metaDataBuilder.build();
}
}
diff --git a/src/com/android/bluetooth/btservice/AbstractionLayer.java b/src/com/android/bluetooth/btservice/AbstractionLayer.java
index 6a2e636..e15104d 100644
--- a/src/com/android/bluetooth/btservice/AbstractionLayer.java
+++ b/src/com/android/bluetooth/btservice/AbstractionLayer.java
@@ -52,6 +52,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;
diff --git a/src/com/android/bluetooth/btservice/ActiveDeviceManager.java b/src/com/android/bluetooth/btservice/ActiveDeviceManager.java
index e1f999d..69381c5 100644
--- a/src/com/android/bluetooth/btservice/ActiveDeviceManager.java
+++ b/src/com/android/bluetooth/btservice/ActiveDeviceManager.java
@@ -33,12 +33,12 @@
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.internal.annotations.VisibleForTesting;
import java.util.LinkedList;
import java.util.List;
diff --git a/src/com/android/bluetooth/btservice/AdapterProperties.java b/src/com/android/bluetooth/btservice/AdapterProperties.java
index 981a45a..4b4b4df 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,12 +41,12 @@
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;
+import androidx.annotation.VisibleForTesting;
+
import com.android.bluetooth.Utils;
import com.android.bluetooth.btservice.RemoteDevices.DeviceProperties;
@@ -77,6 +78,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>();
@@ -130,6 +134,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;
@@ -203,6 +210,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);
@@ -287,6 +295,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
*/
@@ -533,11 +580,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
@@ -794,6 +839,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);
}
@@ -876,6 +931,7 @@
Intent intent;
if (state == AbstractionLayer.BT_DISCOVERY_STOPPED) {
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 9d8cde9..508eacf 100644
--- a/src/com/android/bluetooth/btservice/AdapterService.java
+++ b/src/com/android/bluetooth/btservice/AdapterService.java
@@ -18,6 +18,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;
@@ -25,8 +26,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;
@@ -66,6 +69,8 @@
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.internal.R;
@@ -111,8 +116,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 =
@@ -129,6 +137,8 @@
private static final int CONTROLLER_ENERGY_UPDATE_TIMEOUT_MILLIS = 30;
+ private final ArrayList<DiscoveringPackage> mDiscoveringPackages = new ArrayList<>();
+
static {
classInitNative();
}
@@ -165,6 +175,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;
@@ -182,6 +194,9 @@
private ProfileObserver mProfileObserver;
private PhonePolicy mPhonePolicy;
private ActiveDeviceManager mActiveDeviceManager;
+ private DatabaseManager mDatabaseManager;
+ private SilenceDeviceManager mSilenceDeviceManager;
+ private AppOpsManager mAppOps;
/**
* Register a {@link ProfileService} with AdapterService.
@@ -224,19 +239,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;
}
@@ -252,7 +267,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);
@@ -271,12 +286,14 @@
}
mRunningProfiles.add(profile);
if (GattService.class.getSimpleName().equals(profile.getName())) {
- enableNativeWithGuestFlag();
+ enableNative();
} else if (mRegisteredProfiles.size() == Config.getSupportedProfiles().length
&& mRegisteredProfiles.size() == mRunningProfiles.size()) {
mAdapterProperties.onBluetoothReady();
updateUuids();
setBluetoothClassFromConfig();
+ getAdapterPropertyNative(AbstractionLayer.BT_PROPERTY_LOCAL_IO_CAPS);
+ getAdapterPropertyNative(AbstractionLayer.BT_PROPERTY_LOCAL_IO_CAPS_BLE);
mAdapterStateMachine.sendMessage(AdapterState.BREDR_STARTED);
}
break;
@@ -296,7 +313,6 @@
mAdapterStateMachine.sendMessage(AdapterState.BREDR_STOPPED);
} else if (mRunningProfiles.size() == 0) {
disableNative();
- mAdapterStateMachine.sendMessage(AdapterState.BLE_STOPPED);
}
break;
default:
@@ -372,13 +388,15 @@
debugLog("onCreate()");
mRemoteDevices = new RemoteDevices(this, Looper.getMainLooper());
mRemoteDevices.init();
+ clearDiscoveringPackages();
mBinder = new AdapterServiceBinder(this);
mAdapterProperties = new AdapterProperties(this);
mAdapterStateMachine = AdapterState.make(this);
mJniCallbacks = new JniCallbacks(this, mAdapterProperties);
- 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);
@@ -407,6 +425,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
@@ -522,6 +547,7 @@
void stateChangeCallback(int status) {
if (status == AbstractionLayer.BT_STATE_OFF) {
debugLog("stateChangeCallback: disableNative() completed");
+ mAdapterStateMachine.sendMessage(AdapterState.BLE_STOPPED);
} else if (status == AbstractionLayer.BT_STATE_ON) {
mAdapterStateMachine.sendMessage(AdapterState.BLE_STARTED);
} else {
@@ -605,15 +631,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);
}
}
@@ -648,6 +686,10 @@
}
}
+ if (mDatabaseManager != null) {
+ mDatabaseManager.cleanup();
+ }
+
if (mAdapterStateMachine != null) {
mAdapterStateMachine.doQuit();
}
@@ -683,6 +725,10 @@
mPhonePolicy.cleanup();
}
+ if (mSilenceDeviceManager != null) {
+ mSilenceDeviceManager.cleanup();
+ }
+
if (mActiveDeviceManager != null) {
mActiveDeviceManager.cleanup();
}
@@ -873,6 +919,7 @@
return service.setName(name);
}
+ @Override
public BluetoothClass getBluetoothClass() {
if (!Utils.checkCaller()) {
Log.w(TAG, "getBluetoothClass() - Not allowed for non-active user");
@@ -884,6 +931,7 @@
return service.getBluetoothClass();
}
+ @Override
public boolean setBluetoothClass(BluetoothClass bluetoothClass) {
if (!Utils.checkCaller()) {
Log.w(TAG, "setBluetoothClass() - Not allowed for non-active user");
@@ -898,6 +946,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");
@@ -954,7 +1050,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;
@@ -964,7 +1060,7 @@
if (service == null) {
return false;
}
- return service.startDiscovery();
+ return service.startDiscovery(callingPackage);
}
@Override
@@ -1293,6 +1389,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");
@@ -1551,6 +1675,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());
@@ -1690,6 +1851,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);
+ }
+
int getScanMode() {
enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
@@ -1717,10 +1919,42 @@
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;
+ }
+ synchronized (mDiscoveringPackages) {
+ mDiscoveringPackages.add(new DiscoveringPackage(callingPackage, permission));
+ }
return startDiscoveryNative();
}
@@ -1753,6 +1987,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();
@@ -1989,6 +2233,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);
}
@@ -2000,6 +2249,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));
@@ -2013,6 +2267,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);
@@ -2029,9 +2288,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();
@@ -2118,6 +2388,9 @@
boolean factoryReset() {
enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED, "Need BLUETOOTH permission");
+ if (mDatabaseManager != null) {
+ mDatabaseManager.factoryReset();
+ }
return factoryResetNative();
}
@@ -2417,6 +2690,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);
}
@@ -2452,6 +2783,7 @@
writer.println();
mAdapterProperties.dump(fd, writer, args);
writer.println("mSnoopLogSettingAtEnable = " + mSnoopLogSettingAtEnable);
+ writer.println("mDefaultSnoopLogSettingAtEnable = " + mDefaultSnoopLogSettingAtEnable);
writer.println();
mAdapterStateMachine.dump(fd, writer, args);
@@ -2460,6 +2792,7 @@
for (ProfileService profile : mRegisteredProfiles) {
profile.dump(sb);
}
+ mSilenceDeviceManager.dump(fd, writer, args);
writer.write(sb.toString());
writer.flush();
@@ -2520,21 +2853,36 @@
}
};
- 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));
}
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();
@@ -2610,6 +2958,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/BondStateMachine.java b/src/com/android/bluetooth/btservice/BondStateMachine.java
index 81e5aae..037753e 100644
--- a/src/com/android/bluetooth/btservice/BondStateMachine.java
+++ b/src/com/android/bluetooth/btservice/BondStateMachine.java
@@ -20,11 +20,13 @@
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.util.StatsLog;
import com.android.bluetooth.Utils;
import com.android.bluetooth.a2dp.A2dpService;
@@ -40,6 +42,7 @@
import java.util.ArrayList;
import java.util.HashSet;
+import java.util.Objects;
import java.util.Set;
/**
@@ -204,6 +207,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) {
/* this is either none/bonded, remove and transition */
@@ -315,8 +323,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) {
@@ -368,7 +386,13 @@
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 != null && ((devProp.getDeviceType() == BluetoothDevice.DEVICE_TYPE_CLASSIC
@@ -464,9 +488,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) {
@@ -482,7 +511,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);
@@ -518,11 +554,6 @@
if (pbapClientService != null) {
pbapClientService.setPriority(device, BluetoothProfile.PRIORITY_UNDEFINED);
}
-
- // Clear Absolute Volume black list
- if (a2dpService != null) {
- a2dpService.resetAvrcpBlacklist(device);
- }
}
private String state2str(int state) {
diff --git a/src/com/android/bluetooth/btservice/Config.java b/src/com/android/bluetooth/btservice/Config.java
index 8a9c0a1..2b1f46c 100644
--- a/src/com/android/bluetooth/btservice/Config.java
+++ b/src/com/android/bluetooth/btservice/Config.java
@@ -30,7 +30,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;
@@ -73,8 +72,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,
@@ -100,7 +97,8 @@
(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))
};
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 a0ad490..39b1252 100644
--- a/src/com/android/bluetooth/btservice/PhonePolicy.java
+++ b/src/com/android/bluetooth/btservice/PhonePolicy.java
@@ -31,7 +31,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;
@@ -40,6 +39,7 @@
import com.android.bluetooth.hid.HidHostService;
import com.android.bluetooth.pan.PanService;
import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
import java.util.HashSet;
import java.util.List;
@@ -284,20 +284,23 @@
}
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) {
- 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) {
+ removeAutoConnectFromA2dpSink(device);
+ removeAutoConnectFromHeadset(device);
+ }
}
}
}
@@ -326,6 +329,45 @@
}
}
+ private boolean handleAllProfilesDisconnected(BluetoothDevice device) {
+ boolean atLeastOneProfileConnectedForDevice = false;
+ boolean allProfilesEmpty = true;
+ HeadsetService hsService = mFactory.getHeadsetService();
+ A2dpService a2dpService = mFactory.getA2dpService();
+ PanService panService = mFactory.getPanService();
+
+ 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 (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();
@@ -410,45 +452,15 @@
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();
- boolean atLeastOneProfileConnectedForDevice = false;
- boolean allProfilesEmpty = true;
- List<BluetoothDevice> a2dpConnDevList = 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 (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;
- }
-
if (hsService != null) {
if (!mHeadsetRetrySet.contains(device) && (hsService.getPriority(device)
>= BluetoothProfile.PRIORITY_ON) && (hsService.getConnectionState(device)
@@ -468,6 +480,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 011177e..46e28a5 100644
--- a/src/com/android/bluetooth/btservice/RemoteDevices.java
+++ b/src/com/android/bluetooth/btservice/RemoteDevices.java
@@ -30,12 +30,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;
@@ -79,7 +80,8 @@
case MESSAGE_UUID_INTENT:
BluetoothDevice device = (BluetoothDevice) msg.obj;
if (device != null) {
- sendUuidIntent(device);
+ DeviceProperties prop = getDeviceProperties(device);
+ sendUuidIntent(device, prop);
}
break;
}
@@ -273,7 +275,6 @@
return mRssi;
}
}
-
/**
* @return mDeviceType
*/
@@ -366,8 +367,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);
@@ -455,6 +455,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);
}
@@ -503,7 +504,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;
@@ -525,7 +526,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);
@@ -541,13 +542,13 @@
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) {
sAdapterService.deviceUuidUpdated(bdDevice);
- sendUuidIntent(bdDevice);
+ sendUuidIntent(bdDevice, device);
}
break;
case AbstractionLayer.BT_PROPERTY_TYPE_OF_DEVICE:
@@ -583,9 +584,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) {
@@ -632,6 +639,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
@@ -831,13 +847,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 2bd7ab5..fe79a5c 100644
--- a/src/com/android/bluetooth/btservice/ServiceFactory.java
+++ b/src/com/android/bluetooth/btservice/ServiceFactory.java
@@ -17,6 +17,7 @@
package com.android.bluetooth.btservice;
import com.android.bluetooth.a2dp.A2dpService;
+import com.android.bluetooth.avrcp.AvrcpTargetService;
import com.android.bluetooth.hearingaid.HearingAidService;
import com.android.bluetooth.hfp.HeadsetService;
import com.android.bluetooth.hid.HidDeviceService;
@@ -48,4 +49,8 @@
public HearingAidService getHearingAidService() {
return HearingAidService.getHearingAidService();
}
+
+ 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 af59262..d54a25a 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;
@@ -86,10 +87,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 fd8551a..8a3a1aa 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;
@@ -98,6 +99,9 @@
private static final int ET_LEGACY_MASK = 0x10;
+ 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"),
UUID.fromString("00002A4B-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;
@@ -274,36 +284,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
@@ -1000,8 +1008,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;
}
@@ -1088,15 +1095,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.
@@ -1312,9 +1314,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) {
@@ -1326,6 +1332,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:
@@ -1335,6 +1347,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:
@@ -1343,6 +1359,9 @@
}
currChar.addDescriptor(new BluetoothGattDescriptor(el.uuid, el.id, 0));
+ if (isRestrictedChar) {
+ restrictedIds.add(el.id);
+ }
break;
case GattDbElement.TYPE_INCLUDED_SERVICE:
@@ -1361,8 +1380,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 */);
}
@@ -1383,13 +1404,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);
}
}
@@ -1917,16 +1937,28 @@
if (DBG) {
Log.d(TAG, "start scan with filters");
}
+ 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) {
@@ -1958,19 +1990,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);
}
@@ -1978,9 +2016,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) {
@@ -2457,7 +2497,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:
@@ -2978,15 +3018,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;
@@ -2995,13 +3031,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) {
@@ -3153,7 +3188,7 @@
void addScanEvent(BluetoothMetricsProto.ScanEvent event) {
synchronized (mScanEvents) {
if (mScanEvents.size() == NUM_SCAN_EVENTS_KEPT) {
- mScanEvents.remove(0);
+ mScanEvents.remove();
}
mScanEvents.add(event);
}
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 15233e4..6c47711 100644
--- a/src/com/android/bluetooth/gatt/ScanFilterQueue.java
+++ b/src/com/android/bluetooth/gatt/ScanFilterQueue.java
@@ -95,6 +95,15 @@
Entry entry = new Entry();
entry.type = TYPE_SOLICIT_UUID;
entry.uuid = uuid;
+ entry.uuid_mask = new UUID(0, 0);
+ mEntries.add(entry);
+ }
+
+ void addSolicitUuid(UUID uuid, UUID uuidMask) {
+ Entry entry = new Entry();
+ entry.type = TYPE_SOLICIT_UUID;
+ entry.uuid = uuid;
+ entry.uuid_mask = uuidMask;
mEntries.add(entry);
}
@@ -179,6 +188,14 @@
addUuid(filter.getServiceUuid().getUuid(), filter.getServiceUuidMask().getUuid());
}
}
+ if (filter.getServiceSolicitationUuid() != null) {
+ if (filter.getServiceSolicitationUuidMask() == null) {
+ addSolicitUuid(filter.getServiceSolicitationUuid().getUuid());
+ } else {
+ addSolicitUuid(filter.getServiceSolicitationUuid().getUuid(),
+ filter.getServiceSolicitationUuidMask().getUuid());
+ }
+ }
if (filter.getManufacturerData() != null) {
if (filter.getManufacturerDataMask() == null) {
addManufacturerData(filter.getManufacturerId(), filter.getManufacturerData());
diff --git a/src/com/android/bluetooth/gatt/ScanManager.java b/src/com/android/bluetooth/gatt/ScanManager.java
index 5207c69..0736978 100644
--- a/src/com/android/bluetooth/gatt/ScanManager.java
+++ b/src/com/android/bluetooth/gatt/ScanManager.java
@@ -323,7 +323,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);
@@ -416,7 +416,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);
diff --git a/src/com/android/bluetooth/hdp/HealthService.java b/src/com/android/bluetooth/hdp/HealthService.java
deleted file mode 100644
index 6a4af04..0000000
--- a/src/com/android/bluetooth/hdp/HealthService.java
+++ /dev/null
@@ -1,1014 +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;
- int appId = (mApps.get(appConfig)).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);
- int appId = (mApps.get(chan.mConfig)).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);
- 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);
- }
- IBluetoothHealthCallback callback = (mApps.get(config)).mCallback;
- if (callback == null) {
- Log.e(TAG, "Callback object null");
- }
-
- 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);
- }
- }
-
- IBluetoothHealthCallback callback = (mApps.get(config)).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 704a3d5..f29d9b9 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;
@@ -50,16 +52,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;
@@ -74,11 +72,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);
@@ -115,9 +116,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();
@@ -170,9 +172,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();
@@ -247,6 +250,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;
@@ -259,27 +270,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;
}
@@ -329,13 +327,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()) {
@@ -438,20 +451,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) {
@@ -494,6 +505,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);
}
@@ -508,7 +528,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");
}
@@ -611,6 +631,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
@@ -731,10 +754,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..5b8d798 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);
}
}
@@ -172,13 +169,8 @@
}
break;
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);
- }
+ Log.d(TAG, "Disconnected: DISCONNECT: call native disconnect for " + mDevice);
+ mNativeInterface.disconnectHearingAid(mDevice);
break;
case STACK_EVENT:
HearingAidStackEvent event = (HearingAidStackEvent) message.obj;
@@ -246,11 +238,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 +260,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 +328,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 +426,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
@@ -489,7 +481,7 @@
private void processConnectionEvent(int state) {
switch (state) {
case HearingAidStackEvent.CONNECTION_STATE_DISCONNECTED:
- Log.i(TAG, "Disconnected from " + mDevice);
+ Log.i(TAG, "Disconnected from " + mDevice + " but still in Whitelist");
transitionTo(mDisconnected);
break;
case HearingAidStackEvent.CONNECTION_STATE_DISCONNECTING:
@@ -504,7 +496,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/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 bcf123c..941ec14 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;
@@ -160,7 +160,7 @@
return;
}
Log.i(TAG, "startListenForPhoneState(), subId=" + subId + ", enabled_events=" + events);
- mPhoneStateListener = new HeadsetPhoneStateListener(subId,
+ mPhoneStateListener = new HeadsetPhoneStateListener(
mHeadsetService.getStateMachinesThreadLooper());
mTelephonyManager.listen(mPhoneStateListener, events);
if ((events & PhoneStateListener.LISTEN_SIGNAL_STRENGTHS) != 0) {
@@ -192,7 +192,7 @@
return mNumActive;
}
- @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
public void setNumActiveCall(int numActive) {
mNumActive = numActive;
}
@@ -201,7 +201,7 @@
return mCallState;
}
- @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
public void setCallState(int callState) {
mCallState = callState;
}
@@ -210,7 +210,7 @@
return mNumHeld;
}
- @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
public void setNumHeldCall(int numHeldCall) {
mNumHeld = numHeldCall;
}
@@ -228,7 +228,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;
@@ -274,8 +274,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 6d16a6a..1dbafd0 100644
--- a/src/com/android/bluetooth/hfp/HeadsetService.java
+++ b/src/com/android/bluetooth/hfp/HeadsetService.java
@@ -39,9 +39,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;
@@ -61,7 +61,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)}
*
@@ -219,7 +220,9 @@
mStateMachinesThread.quitSafely();
mStateMachinesThread = null;
// Step 1: Clear
- mAdapterService = null;
+ synchronized (mStateMachines) {
+ mAdapterService = null;
+ }
return true;
}
@@ -333,8 +336,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: {
@@ -600,12 +603,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
@@ -770,14 +773,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) {
final ParcelUuid[] featureUuids = mAdapterService.getRemoteUuids(device);
if (!BluetoothUuid.containsAnyUuid(featureUuids, HEADSET_UUIDS)) {
@@ -808,18 +811,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) {
@@ -1007,6 +1009,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
@@ -1221,9 +1253,9 @@
}
mVirtualCallStarted = true;
// 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;
}
}
@@ -1239,7 +1271,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;
}
@@ -1452,7 +1484,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) {
// Should stop all other audio mode in this case
@@ -1498,7 +1530,7 @@
});
doForEachConnectedStateMachine(
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 (callState == HeadsetHalConstants.CALL_STATE_IDLE
&& mSystemInterface.isCallIdle() && !isAudioOn()) {
@@ -1668,6 +1700,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
diff --git a/src/com/android/bluetooth/hfp/HeadsetStackEvent.java b/src/com/android/bluetooth/hfp/HeadsetStackEvent.java
index 200fb92..328ac08 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;
@@ -153,8 +153,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:
diff --git a/src/com/android/bluetooth/hfp/HeadsetStateMachine.java b/src/com/android/bluetooth/hfp/HeadsetStateMachine.java
index d25d9ed..c5485f9 100644
--- a/src/com/android/bluetooth/hfp/HeadsetStateMachine.java
+++ b/src/com/android/bluetooth/hfp/HeadsetStateMachine.java
@@ -16,23 +16,28 @@
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;
import android.os.Message;
import android.os.SystemClock;
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.util.StatsLog;
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;
@@ -128,6 +133,7 @@
// Runtime states
private int mSpeakerVolume;
private int mMicVolume;
+ private boolean mDeviceSilenced;
private HeadsetAgIndicatorEnableState mAgIndicatorEnableState;
// The timestamp when the device entered connecting/connected state
private long mConnectingTimestampMs = Long.MIN_VALUE;
@@ -170,6 +176,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);
// Initialize state machine
@@ -299,6 +306,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);
@@ -527,8 +540,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() {
@@ -585,9 +599,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;
@@ -830,6 +841,8 @@
break;
}
case CALL_STATE_CHANGED: {
+ if (mDeviceSilenced) break;
+
HeadsetCallState callState = (HeadsetCallState) message.obj;
if (!mNativeInterface.phoneStateChange(mDevice, callState)) {
stateLogW("processCallState: failed to update call state " + callState);
@@ -921,7 +934,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:
@@ -1011,9 +1024,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);
@@ -1427,6 +1437,27 @@
return mConnectingTimestampMs;
}
+ /**
+ * 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;
+ }
+ if (silence) {
+ mSystemInterface.getHeadsetPhoneState().listenForPhoneState(mDevice,
+ PhoneStateListener.LISTEN_NONE);
+ } else {
+ updateAgIndicatorEnableState(mAgIndicatorEnableState);
+ }
+ mDeviceSilenced = silence;
+ return true;
+ }
+
/*
* Put the AT command, company ID, arguments, and device in an Intent and broadcast it.
*/
@@ -1803,6 +1834,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));
}
@@ -1880,19 +1923,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) {
@@ -1908,8 +1947,6 @@
log("Invalid HF Indicator Received");
break;
}
-
- iter = iter1 + 1; // move past comma
}
}
@@ -1947,7 +1984,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;
@@ -2024,6 +2062,18 @@
}
}
+ 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/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 63f5206..c378f8e 100644
--- a/src/com/android/bluetooth/hid/HidHostService.java
+++ b/src/com/android/bluetooth/hid/HidHostService.java
@@ -25,10 +25,10 @@
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 androidx.annotation.VisibleForTesting;
+
import com.android.bluetooth.BluetoothMetricsProto;
import com.android.bluetooth.Utils;
import com.android.bluetooth.btservice.AdapterService;
@@ -181,7 +181,7 @@
if (DBG) {
Log.d(TAG, "Incoming HID connection rejected");
}
- disconnectHidNative(Utils.getByteAddress(device));
+ virtualUnPlugNative(Utils.getByteAddress(device));
} else {
broadcastConnectionState(device, convertHalState(halState));
}
@@ -523,11 +523,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 +536,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 */
diff --git a/src/com/android/bluetooth/map/BluetoothMapService.java b/src/com/android/bluetooth/map/BluetoothMapService.java
index bfb1d3f..cc42b60 100644
--- a/src/com/android/bluetooth/map/BluetoothMapService.java
+++ b/src/com/android/bluetooth/map/BluetoothMapService.java
@@ -36,8 +36,6 @@
import android.os.ParcelUuid;
import android.os.PowerManager;
import android.os.RemoteException;
-import android.provider.Settings;
-import android.support.annotation.VisibleForTesting;
import android.text.TextUtils;
import android.util.Log;
import android.util.SparseArray;
@@ -45,8 +43,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;
@@ -577,14 +577,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
diff --git a/src/com/android/bluetooth/map/BluetoothMnsObexClient.java b/src/com/android/bluetooth/map/BluetoothMnsObexClient.java
index eba6485..2f2af1d 100644
--- a/src/com/android/bluetooth/map/BluetoothMnsObexClient.java
+++ b/src/com/android/bluetooth/map/BluetoothMnsObexClient.java
@@ -222,7 +222,6 @@
if (looper != null) {
looper.quit();
}
- mHandler = null;
}
/* Disconnect if connected */
diff --git a/src/com/android/bluetooth/mapclient/MapClientService.java b/src/com/android/bluetooth/mapclient/MapClientService.java
index 0d77c8e..9989a98 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,
@@ -346,10 +348,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 +502,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 +519,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..11b634c 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);
@@ -492,15 +566,20 @@
case MSG_NOTIFICATION:
EventReport ev = (EventReport) msg.obj;
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());
}
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 +595,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 +608,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 +671,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 +701,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 +721,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 +805,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 99e3e69..03db259 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppLauncherActivity.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppLauncherActivity.java
@@ -75,6 +75,17 @@
Intent intent = getIntent();
String action = intent.getAction();
+ if (action == null) {
+ Log.w(TAG, " Received " + intent + " with null action");
+ finish();
+ 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
diff --git a/src/com/android/bluetooth/opp/BluetoothOppService.java b/src/com/android/bluetooth/opp/BluetoothOppService.java
index 383a497..914b9b6 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;
@@ -115,6 +115,8 @@
private UpdateThread mUpdateThread;
+ private boolean mUpdateThreadRunning;
+
private ArrayList<BluetoothOppShareInfo> mShares;
private ArrayList<BluetoothOppBatch> mBatches;
@@ -241,6 +243,10 @@
@Override
public boolean stop() {
+ if (sBluetoothOppService == null) {
+ Log.w(TAG, "stop() called before start()");
+ return true;
+ }
setBluetoothOppService(null);
mHandler.sendMessage(mHandler.obtainMessage(STOP_LISTENER));
return true;
@@ -330,8 +336,19 @@
unregisterReceivers();
synchronized (BluetoothOppService.this) {
if (mUpdateThread != null) {
+ mUpdateThread.interrupt();
+ }
+ }
+ while (mUpdateThread != null && mUpdateThreadRunning) {
+ try {
+ Thread.sleep(50);
+ } catch (Exception e) {
+ Log.e(TAG, "Thread sleep", e);
+ }
+ }
+ synchronized (BluetoothOppService.this) {
+ if (mUpdateThread != null) {
try {
- mUpdateThread.interrupt();
mUpdateThread.join();
} catch (InterruptedException e) {
Log.e(TAG, "Interrupted", e);
@@ -339,6 +356,7 @@
mUpdateThread = null;
}
}
+
mNotifier.cancelNotifications();
break;
case START_LISTENER:
@@ -551,6 +569,7 @@
if (mUpdateThread == null) {
mUpdateThread = new UpdateThread();
mUpdateThread.start();
+ mUpdateThreadRunning = true;
}
}
}
@@ -580,6 +599,7 @@
while (!mIsInterrupted) {
synchronized (BluetoothOppService.this) {
if (mUpdateThread != this) {
+ mUpdateThreadRunning = false;
throw new IllegalStateException(
"multiple UpdateThreads in BluetoothOppService");
}
@@ -589,6 +609,7 @@
}
if (!mPendingUpdate) {
mUpdateThread = null;
+ mUpdateThreadRunning = false;
return;
}
mPendingUpdate = false;
@@ -598,6 +619,7 @@
BluetoothShare._ID);
if (cursor == null) {
+ mUpdateThreadRunning = false;
return;
}
@@ -634,9 +656,6 @@
}
}
- if (shouldScanFile(arrayPos)) {
- scanFile(arrayPos);
- }
deleteShare(arrayPos); // this advances in the array
} else {
int id = cursor.getInt(idColumn);
@@ -660,14 +679,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);
-
+ scanFileIfNeeded(arrayPos);
++arrayPos;
cursor.moveToNext();
isAfterLast = cursor.isAfterLast();
@@ -691,6 +707,8 @@
cursor.close();
}
+
+ mUpdateThreadRunning = false;
}
}
@@ -1031,8 +1049,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);
@@ -1040,20 +1064,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..fc45d3f 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);
diff --git a/src/com/android/bluetooth/opp/BluetoothOppUtility.java b/src/com/android/bluetooth/opp/BluetoothOppUtility.java
index 1b5cd59..7533d0d 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppUtility.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppUtility.java
@@ -45,6 +45,7 @@
import android.database.Cursor;
import android.net.Uri;
import android.os.Environment;
+import android.os.SystemProperties;
import android.util.Log;
import com.android.bluetooth.R;
@@ -56,6 +57,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;
@@ -66,6 +68,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>();
@@ -292,6 +296,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) {
@@ -311,11 +326,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 700b395..8c52df7 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;
@@ -398,11 +398,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;
}
@@ -411,9 +411,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/BluetoothPbapActivity.java b/src/com/android/bluetooth/pbap/BluetoothPbapActivity.java
index f709cd5..5468a76 100644
--- a/src/com/android/bluetooth/pbap/BluetoothPbapActivity.java
+++ b/src/com/android/bluetooth/pbap/BluetoothPbapActivity.java
@@ -109,7 +109,7 @@
Intent i = getIntent();
String action = i.getAction();
mDevice = i.getParcelableExtra(BluetoothPbapService.EXTRA_DEVICE);
- if (action.equals(BluetoothPbapService.AUTH_CHALL_ACTION)) {
+ if (action != null && action.equals(BluetoothPbapService.AUTH_CHALL_ACTION)) {
showPbapDialog(DIALOG_YES_NO_AUTH);
mCurrentDialog = DIALOG_YES_NO_AUTH;
} else {
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 247a76d..979becd
--- a/src/com/android/bluetooth/pbap/BluetoothPbapObexServer.java
+++ b/src/com/android/bluetooth/pbap/BluetoothPbapObexServer.java
@@ -817,50 +817,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 374b5a1..e7dba2a 100644
--- a/src/com/android/bluetooth/pbap/BluetoothPbapService.java
+++ b/src/com/android/bluetooth/pbap/BluetoothPbapService.java
@@ -49,7 +49,6 @@
import android.os.Message;
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;
@@ -61,6 +60,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;
@@ -127,6 +127,7 @@
static final int CONTACTS_LOADED = 5;
static final int CHECK_SECONDARY_VERSION_COUNTER = 6;
static final int ROLLOVER_COUNTERS = 7;
+ static final int GET_LOCAL_TELEPHONY_DETAILS = 8;
static final int USER_CONFIRM_TIMEOUT_VALUE = 30000;
static final int RELEASE_WAKE_LOCK_DELAY = 10000;
@@ -408,6 +409,8 @@
mPbapStateMachineMap.remove(remoteDevice);
}
break;
+ case GET_LOCAL_TELEPHONY_DETAILS:
+ getLocalTelephonyDetails();
default:
break;
}
@@ -507,18 +510,12 @@
Log.e(TAG, "Illegal state exception, content observer is already registered");
}
- TelephonyManager tm = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
- if (tm != null) {
- sLocalPhoneNum = tm.getLine1Number();
- sLocalPhoneName = tm.getLine1AlphaTag();
- if (TextUtils.isEmpty(sLocalPhoneName)) {
- sLocalPhoneName = this.getString(R.string.localPhoneName);
- }
- }
+ setBluetoothPbapService(this);
+ mSessionStatusHandler.sendMessage(
+ mSessionStatusHandler.obtainMessage(GET_LOCAL_TELEPHONY_DETAILS));
mSessionStatusHandler.sendMessage(mSessionStatusHandler.obtainMessage(LOAD_CONTACTS));
mSessionStatusHandler.sendMessage(mSessionStatusHandler.obtainMessage(START_LISTENER));
- setBluetoothPbapService(this);
return true;
}
@@ -686,7 +683,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();
@@ -775,4 +772,18 @@
mThreadUpdateSecVersionCounter.start();
}
}
+
+ private void getLocalTelephonyDetails() {
+ TelephonyManager tm = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
+ if (tm != null) {
+ sLocalPhoneNum = tm.getLine1Number();
+ sLocalPhoneName = tm.getLine1AlphaTag();
+ if (TextUtils.isEmpty(sLocalPhoneName)) {
+ sLocalPhoneName = this.getString(R.string.localPhoneName);
+ }
+ }
+ if (VERBOSE)
+ Log.v(TAG, "Local Phone Details- Number:" + sLocalPhoneNum
+ + ", Name:" + sLocalPhoneName);
+ }
}
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 e58fa2e..5ba2b4b
--- a/src/com/android/bluetooth/pbap/BluetoothPbapVcardManager.java
+++ b/src/com/android/bluetooth/pbap/BluetoothPbapVcardManager.java
@@ -332,12 +332,18 @@
contactCursor = mResolver.query(myUri, PHONES_CONTACTS_PROJECTION, null, null,
Phone.CONTACT_ID);
+ ArrayList<String> contactNameIdList = new ArrayList<String>();
+ appendDistinctNameIdList(contactNameIdList,
+ mContext.getString(android.R.string.unknownName), contactCursor);
+
if (contactCursor != null) {
if (!composer.initWithCallback(contactCursor,
new EnterpriseRawContactEntitlesInfoCallback())) {
return nameList;
}
+ int i = 0;
+ contactCursor.moveToFirst();
while (!composer.isAfterLast()) {
String vcard = composer.createOneEntry();
if (vcard == null) {
@@ -362,8 +368,9 @@
if (TextUtils.isEmpty(name)) {
name = mContext.getString(android.R.string.unknownName);
}
- nameList.add(name);
+ nameList.add(contactNameIdList.get(i));
}
+ i++;
}
if (orderByWhat == BluetoothPbapObexServer.ORDER_BY_INDEXED) {
if (V) {
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..d8281fe 100644
--- a/src/com/android/bluetooth/sap/SapServer.java
+++ b/src/com/android/bluetooth/sap/SapServer.java
@@ -491,7 +491,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 66e0b19..a9b1994 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;
@@ -539,7 +539,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();
@@ -596,19 +596,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 d0cca25..d5e3770 100644
--- a/src/com/android/bluetooth/sdp/SdpManager.java
+++ b/src/com/android/bluetooth/sdp/SdpManager.java
@@ -85,6 +85,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);
@@ -537,6 +540,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..4e6b7ba 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,42 @@
}
/**
+ * 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.length == 1 ? "" : 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..c121cf7
--- /dev/null
+++ b/tests/unit/src/com/android/bluetooth/a2dp/A2dpCodecConfigTest.java
@@ -0,0 +1,415 @@
+/*
+ * 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 androidx.test.InstrumentationRegistry;
+import androidx.test.filters.MediumTest;
+import androidx.test.runner.AndroidJUnit4;
+
+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 A2dpCodecConfig mA2dpCodecConfig;
+ private BluetoothAdapter mAdapter;
+ private BluetoothCodecConfig mCodecConfigSbc;
+ private BluetoothCodecConfig mCodecConfigAac;
+ private BluetoothCodecConfig mCodecConfigAptx;
+ private BluetoothCodecConfig mCodecConfigAptxHd;
+ private BluetoothCodecConfig mCodecConfigLdac;
+ private BluetoothDevice mTestDevice;
+
+ @Mock private A2dpNativeInterface mA2dpNativeInterface;
+
+ static final int SBC_PRIORITY_DEFAULT = 1001;
+ static final int AAC_PRIORITY_DEFAULT = 2001;
+ static final int APTX_PRIORITY_DEFAULT = 3001;
+ static final int APTX_HD_PRIORITY_DEFAULT = 4001;
+ static final int LDAC_PRIORITY_DEFAULT = 5001;
+ static final int PRIORITY_HIGH = 1000000;
+
+
+ @Before
+ public void setUp() throws Exception {
+ mTargetContext = InstrumentationRegistry.getTargetContext();
+ // Set up mocks and test assets
+ MockitoAnnotations.initMocks(this);
+
+ mA2dpCodecConfig = new A2dpCodecConfig(mTargetContext, mA2dpNativeInterface);
+ mTestDevice = BluetoothAdapter.getDefaultAdapter().getRemoteDevice("00:01:02:03:04:05");
+
+ doReturn(true).when(mA2dpNativeInterface).setCodecConfigPreference(
+ any(BluetoothDevice.class),
+ any(BluetoothCodecConfig[].class));
+
+ // Create sample codec configs
+ mCodecConfigSbc = 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
+ mCodecConfigAac = 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
+ mCodecConfigAptx = new BluetoothCodecConfig(
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX,
+ APTX_PRIORITY_DEFAULT,
+ BluetoothCodecConfig.SAMPLE_RATE_44100,
+ BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+ BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+ 0, 0, 0, 0); // Codec-specific fields
+ mCodecConfigAptxHd = new BluetoothCodecConfig(
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD,
+ APTX_HD_PRIORITY_DEFAULT,
+ BluetoothCodecConfig.SAMPLE_RATE_44100,
+ BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+ BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+ 0, 0, 0, 0); // Codec-specific fields
+ mCodecConfigLdac = new BluetoothCodecConfig(
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+ LDAC_PRIORITY_DEFAULT,
+ BluetoothCodecConfig.SAMPLE_RATE_44100,
+ BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+ BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+ 0, 0, 0, 0); // Codec-specific fields
+
+ }
+
+ @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
+ public void testSetCodecPreference_priorityHighToDefault() {
+ testSetCodecPreference_codecPriorityChangeCase(
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, SBC_PRIORITY_DEFAULT,
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, PRIORITY_HIGH,
+ true);
+ testSetCodecPreference_codecPriorityChangeCase(
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC, AAC_PRIORITY_DEFAULT,
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC, PRIORITY_HIGH,
+ true);
+ testSetCodecPreference_codecPriorityChangeCase(
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX, APTX_PRIORITY_DEFAULT,
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX, PRIORITY_HIGH,
+ true);
+ testSetCodecPreference_codecPriorityChangeCase(
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD, APTX_HD_PRIORITY_DEFAULT,
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD, PRIORITY_HIGH,
+ true);
+ testSetCodecPreference_codecPriorityChangeCase(
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC, LDAC_PRIORITY_DEFAULT,
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC, PRIORITY_HIGH,
+ false);
+ }
+
+ @Test
+ public void testSetCodecPreference_priorityDefaultToHigh() {
+ testSetCodecPreference_codecPriorityChangeCase(
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, PRIORITY_HIGH,
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC, LDAC_PRIORITY_DEFAULT,
+ true);
+ testSetCodecPreference_codecPriorityChangeCase(
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC, PRIORITY_HIGH,
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC, LDAC_PRIORITY_DEFAULT,
+ true);
+ testSetCodecPreference_codecPriorityChangeCase(
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX, PRIORITY_HIGH,
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC, LDAC_PRIORITY_DEFAULT,
+ true);
+ testSetCodecPreference_codecPriorityChangeCase(
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD, PRIORITY_HIGH,
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC, LDAC_PRIORITY_DEFAULT,
+ true);
+ testSetCodecPreference_codecPriorityChangeCase(
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC, PRIORITY_HIGH,
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC, LDAC_PRIORITY_DEFAULT,
+ false);
+ }
+
+ @Test
+ public void testSetCodecPreference_priorityHighToHigh() {
+ testSetCodecPreference_codecPriorityChangeCase(
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, PRIORITY_HIGH,
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, PRIORITY_HIGH,
+ false);
+ testSetCodecPreference_codecPriorityChangeCase(
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC, PRIORITY_HIGH,
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, PRIORITY_HIGH,
+ true);
+ testSetCodecPreference_codecPriorityChangeCase(
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX, PRIORITY_HIGH,
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, PRIORITY_HIGH,
+ true);
+ testSetCodecPreference_codecPriorityChangeCase(
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD, PRIORITY_HIGH,
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, PRIORITY_HIGH,
+ true);
+ testSetCodecPreference_codecPriorityChangeCase(
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC, PRIORITY_HIGH,
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, PRIORITY_HIGH,
+ true);
+ }
+
+ @Test
+ public void testSetCodecPreference_parametersChange() {
+ int unSupportedParameter = 200;
+
+ testSetCodecPreference_parametersChangeCase(
+ BluetoothCodecConfig.SAMPLE_RATE_44100,
+ BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+ false);
+ testSetCodecPreference_parametersChangeCase(
+ BluetoothCodecConfig.SAMPLE_RATE_44100,
+ BluetoothCodecConfig.BITS_PER_SAMPLE_24,
+ true);
+ testSetCodecPreference_parametersChangeCase(
+ BluetoothCodecConfig.SAMPLE_RATE_44100,
+ unSupportedParameter,
+ false);
+
+ testSetCodecPreference_parametersChangeCase(
+ BluetoothCodecConfig.SAMPLE_RATE_48000,
+ BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+ true);
+ testSetCodecPreference_parametersChangeCase(
+ BluetoothCodecConfig.SAMPLE_RATE_48000,
+ BluetoothCodecConfig.BITS_PER_SAMPLE_24,
+ true);
+ testSetCodecPreference_parametersChangeCase(
+ BluetoothCodecConfig.SAMPLE_RATE_48000,
+ unSupportedParameter,
+ false);
+
+ testSetCodecPreference_parametersChangeCase(
+ unSupportedParameter,
+ BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+ false);
+ testSetCodecPreference_parametersChangeCase(
+ unSupportedParameter,
+ BluetoothCodecConfig.BITS_PER_SAMPLE_24,
+ false);
+ testSetCodecPreference_parametersChangeCase(
+ unSupportedParameter,
+ unSupportedParameter,
+ false);
+ }
+
+ @Test
+ public void testDisableOptionalCodecs() {
+ int verifyCount = 0;
+ BluetoothCodecConfig[] codecConfigArray =
+ new BluetoothCodecConfig[BluetoothCodecConfig.SOURCE_CODEC_TYPE_MAX];
+ codecConfigArray[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
+
+ // Test don't invoke to native when current codec is SBC
+ mA2dpCodecConfig.disableOptionalCodecs(mTestDevice, mCodecConfigSbc);
+ verify(mA2dpNativeInterface, times(verifyCount))
+ .setCodecConfigPreference(mTestDevice, codecConfigArray);
+
+ // Test invoke to native when current codec is not SBC
+ mA2dpCodecConfig.disableOptionalCodecs(mTestDevice, mCodecConfigAac);
+ verify(mA2dpNativeInterface, times(++verifyCount))
+ .setCodecConfigPreference(mTestDevice, codecConfigArray);
+ mA2dpCodecConfig.disableOptionalCodecs(mTestDevice, mCodecConfigAptx);
+ verify(mA2dpNativeInterface, times(++verifyCount))
+ .setCodecConfigPreference(mTestDevice, codecConfigArray);
+ mA2dpCodecConfig.disableOptionalCodecs(mTestDevice, mCodecConfigAptxHd);
+ verify(mA2dpNativeInterface, times(++verifyCount))
+ .setCodecConfigPreference(mTestDevice, codecConfigArray);
+ mA2dpCodecConfig.disableOptionalCodecs(mTestDevice, mCodecConfigLdac);
+ verify(mA2dpNativeInterface, times(++verifyCount))
+ .setCodecConfigPreference(mTestDevice, codecConfigArray);
+ }
+
+ @Test
+ public void testEnableOptionalCodecs() {
+ int verifyCount = 0;
+ BluetoothCodecConfig[] codecConfigArray =
+ new BluetoothCodecConfig[BluetoothCodecConfig.SOURCE_CODEC_TYPE_MAX];
+ codecConfigArray[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
+
+ // Test invoke to native when current codec is SBC
+ mA2dpCodecConfig.enableOptionalCodecs(mTestDevice, mCodecConfigSbc);
+ verify(mA2dpNativeInterface, times(++verifyCount))
+ .setCodecConfigPreference(mTestDevice, codecConfigArray);
+
+ // Test don't invoke to native when current codec is not SBC
+ mA2dpCodecConfig.enableOptionalCodecs(mTestDevice, mCodecConfigAac);
+ verify(mA2dpNativeInterface, times(verifyCount))
+ .setCodecConfigPreference(mTestDevice, codecConfigArray);
+ mA2dpCodecConfig.enableOptionalCodecs(mTestDevice, mCodecConfigAptx);
+ verify(mA2dpNativeInterface, times(verifyCount))
+ .setCodecConfigPreference(mTestDevice, codecConfigArray);
+ mA2dpCodecConfig.enableOptionalCodecs(mTestDevice, mCodecConfigAptxHd);
+ verify(mA2dpNativeInterface, times(verifyCount))
+ .setCodecConfigPreference(mTestDevice, codecConfigArray);
+ mA2dpCodecConfig.enableOptionalCodecs(mTestDevice, mCodecConfigLdac);
+ verify(mA2dpNativeInterface, times(verifyCount))
+ .setCodecConfigPreference(mTestDevice, codecConfigArray);
+ }
+
+ private void testSetCodecPreference_parametersChangeCase(int sampleRate, int bitPerSample,
+ boolean invokeNative) {
+ BluetoothCodecConfig[] invalidSelectableCodecs = new BluetoothCodecConfig[1];
+ invalidSelectableCodecs[0] = new BluetoothCodecConfig(
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+ LDAC_PRIORITY_DEFAULT,
+ (BluetoothCodecConfig.SAMPLE_RATE_44100 | BluetoothCodecConfig.SAMPLE_RATE_48000),
+ (BluetoothCodecConfig.BITS_PER_SAMPLE_16 | BluetoothCodecConfig.BITS_PER_SAMPLE_24),
+ BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+ 0, 0, 0, 0); // Codec-specific fields
+
+
+ BluetoothCodecConfig[] selectableCodecs = new BluetoothCodecConfig[2];
+ selectableCodecs[0] = mCodecConfigSbc;
+ selectableCodecs[1] = new BluetoothCodecConfig(
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+ LDAC_PRIORITY_DEFAULT,
+ (BluetoothCodecConfig.SAMPLE_RATE_44100 | BluetoothCodecConfig.SAMPLE_RATE_48000),
+ (BluetoothCodecConfig.BITS_PER_SAMPLE_16 | BluetoothCodecConfig.BITS_PER_SAMPLE_24),
+ BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+ 0, 0, 0, 0); // Codec-specific fields
+
+ BluetoothCodecConfig[] codecConfigArray = new BluetoothCodecConfig[1];
+ codecConfigArray[0] = new BluetoothCodecConfig(
+ BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+ LDAC_PRIORITY_DEFAULT,
+ sampleRate,
+ bitPerSample,
+ BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+ 0, 0, 0, 0); // Codec-specific fields
+
+ BluetoothCodecStatus codecStatus = new BluetoothCodecStatus(mCodecConfigLdac,
+ invalidSelectableCodecs,
+ invalidSelectableCodecs);
+ mA2dpCodecConfig.setCodecConfigPreference(mTestDevice, codecStatus, codecConfigArray[0]);
+
+ codecStatus = new BluetoothCodecStatus(mCodecConfigLdac,
+ selectableCodecs,
+ selectableCodecs);
+
+ mA2dpCodecConfig.setCodecConfigPreference(mTestDevice, codecStatus, codecConfigArray[0]);
+ verify(mA2dpNativeInterface, times(invokeNative ? 1 : 0))
+ .setCodecConfigPreference(mTestDevice, codecConfigArray);
+
+ }
+
+ private void testSetCodecPreference_codecPriorityChangeCase(int newCodecType,
+ int newCodecPriority, int oldCodecType, int oldCodecPriority, boolean invokeNative) {
+ BluetoothCodecConfig[] selectableCodecs = new BluetoothCodecConfig[5];
+ selectableCodecs[0] = mCodecConfigSbc;
+ selectableCodecs[1] = mCodecConfigAac;
+ selectableCodecs[2] = mCodecConfigAptx;
+ selectableCodecs[3] = mCodecConfigAptxHd;
+ selectableCodecs[4] = mCodecConfigLdac;
+
+ BluetoothCodecConfig[] invalidSelectableCodecs = new BluetoothCodecConfig[4];
+ invalidSelectableCodecs[0] = mCodecConfigAac;
+ invalidSelectableCodecs[1] = mCodecConfigAptx;
+ invalidSelectableCodecs[2] = mCodecConfigAptxHd;
+ invalidSelectableCodecs[3] = mCodecConfigLdac;
+
+ BluetoothCodecConfig oldCodecConfig = new BluetoothCodecConfig(
+ oldCodecType,
+ oldCodecPriority,
+ BluetoothCodecConfig.SAMPLE_RATE_44100,
+ BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+ BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+ 0, 0, 0, 0); // Codec-specific fields
+
+ BluetoothCodecConfig[] codecConfigArray = new BluetoothCodecConfig[1];
+ codecConfigArray[0] = new BluetoothCodecConfig(
+ newCodecType,
+ newCodecPriority,
+ BluetoothCodecConfig.SAMPLE_RATE_44100,
+ BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+ BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+ 0, 0, 0, 0); // Codec-specific fields
+
+ BluetoothCodecStatus codecStatus = new BluetoothCodecStatus(oldCodecConfig,
+ invalidSelectableCodecs,
+ invalidSelectableCodecs);
+ mA2dpCodecConfig.setCodecConfigPreference(mTestDevice, codecStatus, codecConfigArray[0]);
+
+ codecStatus = new BluetoothCodecStatus(oldCodecConfig,
+ selectableCodecs,
+ selectableCodecs);
+
+ mA2dpCodecConfig.setCodecConfigPreference(mTestDevice, codecStatus, codecConfigArray[0]);
+ verify(mA2dpNativeInterface, times(invokeNative ? 1 : 0))
+ .setCodecConfigPreference(mTestDevice, codecConfigArray);
+ }
+}
diff --git a/tests/unit/src/com/android/bluetooth/a2dp/A2dpServiceTest.java b/tests/unit/src/com/android/bluetooth/a2dp/A2dpServiceTest.java
index bef3bdb..78f4ad0 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));
@@ -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
deleted file mode 100644
index 5af45e1..0000000
--- a/tests/unit/src/com/android/bluetooth/avrcp/AvrcpTest.java
+++ /dev/null
@@ -1,83 +0,0 @@
-package com.android.bluetooth.avrcp;
-
-import static org.mockito.Mockito.*;
-
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-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 org.junit.Assert;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.ArrayList;
-import java.util.List;
-
-
-/**
- * Unit tests for {@link Avrcp}
- */
-@MediumTest
-@RunWith(AndroidJUnit4.class)
-public class AvrcpTest {
- @Test
- public void testCanStart() {
- if (Looper.myLooper() == null) {
- Looper.prepare();
- }
-
- Avrcp a = Avrcp.make(InstrumentationRegistry.getTargetContext());
- }
-
- @Test
- public void testFailedBrowseStart() {
- if (Looper.myLooper() == null) {
- Looper.prepare();
- }
-
- Context mockContext = mock(Context.class);
- AudioManager mockAudioManager = mock(AudioManager.class);
- PackageManager mockPackageManager = mock(PackageManager.class);
-
- when(mockAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC)).thenReturn(100);
-
- when(mockContext.getSystemService(Context.AUDIO_SERVICE)).thenReturn(mockAudioManager);
-
- when(mockContext.getApplicationContext()).thenReturn(mockContext);
- when(mockContext.getPackageManager()).thenReturn(mockPackageManager);
-
-
- // Call to get the BrowsableMediaPlayers
- // We must return at least one to try to startService
- List<ResolveInfo> resInfos = new ArrayList<ResolveInfo>();
-
- ServiceInfo fakeService = new ServiceInfo();
- fakeService.name = ".browse.MediaBrowserService";
- fakeService.packageName = "com.test.android.fake";
-
- ResolveInfo fakePackage = new ResolveInfo();
- fakePackage.serviceInfo = fakeService;
- fakePackage.nonLocalizedLabel = "Fake Package";
- resInfos.add(fakePackage);
- when(mockPackageManager.queryIntentServices(isA(Intent.class), anyInt())).thenReturn(
- resInfos);
-
- when(mockContext.startService(isA(Intent.class))).thenThrow(new SecurityException("test"));
-
- // Make calls start() which calls buildMediaPlayersList() which should
- // try to start the service?
- try {
- Avrcp a = Avrcp.make(mockContext);
- } catch (SecurityException e) {
- Assert.fail(
- "Threw SecurityException instead of protecting against it: " + e.toString());
- }
- }
-}
diff --git a/tests/unit/src/com/android/bluetooth/avrcp/EvictingQueueTest.java b/tests/unit/src/com/android/bluetooth/avrcp/EvictingQueueTest.java
deleted file mode 100644
index ff44f06..0000000
--- a/tests/unit/src/com/android/bluetooth/avrcp/EvictingQueueTest.java
+++ /dev/null
@@ -1,58 +0,0 @@
-package com.android.bluetooth.avrcp;
-
-import android.support.test.filters.MediumTest;
-import android.support.test.runner.AndroidJUnit4;
-
-import org.junit.Assert;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/**
- * Unit tests for {@link EvictingQueue}.
- */
-@MediumTest
-@RunWith(AndroidJUnit4.class)
-public class EvictingQueueTest {
- @Test
- public void testEvictingQueue_canAddItems() {
- EvictingQueue<Integer> e = new EvictingQueue<Integer>(10);
-
- e.add(1);
-
- Assert.assertEquals((long) e.size(), (long) 1);
- }
-
- @Test
- public void testEvictingQueue_maxItems() {
- EvictingQueue<Integer> e = new EvictingQueue<Integer>(5);
-
- e.add(1);
- e.add(2);
- e.add(3);
- e.add(4);
- e.add(5);
- e.add(6);
-
- Assert.assertEquals((long) e.size(), (long) 5);
- // Items drop off the front
- Assert.assertEquals((long) e.peek(), (long) 2);
- }
-
- @Test
- public void testEvictingQueue_frontDrop() {
- EvictingQueue<Integer> e = new EvictingQueue<Integer>(5);
-
- e.add(1);
- e.add(2);
- e.add(3);
- e.add(4);
- e.add(5);
-
- Assert.assertEquals((long) e.size(), (long) 5);
-
- e.addFirst(6);
-
- Assert.assertEquals((long) e.size(), (long) 5);
- Assert.assertEquals((long) e.peek(), (long) 1);
- }
-}
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/avrcpcontroller/AvrcpControllerStateMachineTest.java b/tests/unit/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachineTest.java
new file mode 100644
index 0000000..b1d1743
--- /dev/null
+++ b/tests/unit/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachineTest.java
@@ -0,0 +1,510 @@
+/*
+ * 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.avrcpcontroller;
+
+import static org.mockito.Mockito.*;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothAvrcpController;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
+import android.content.Context;
+import android.content.Intent;
+import android.media.session.MediaController;
+import android.os.Looper;
+
+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.ProfileService;
+
+import org.hamcrest.core.IsInstanceOf;
+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.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class AvrcpControllerStateMachineTest {
+ private static final int ASYNC_CALL_TIMEOUT_MILLIS = 100;
+ private static final int CONNECT_TIMEOUT_TEST_MILLIS = 1000;
+ private static final int KEY_DOWN = 0;
+ private static final int KEY_UP = 1;
+ private BluetoothAdapter mAdapter;
+ private AvrcpControllerStateMachine mAvrcpControllerStateMachine;
+ private Context mTargetContext;
+ private BluetoothDevice mTestDevice;
+ private ArgumentCaptor<Intent> mIntentArgument = ArgumentCaptor.forClass(Intent.class);
+ private byte[] mTestAddress = new byte[]{00, 01, 02, 03, 04, 05};
+
+ @Rule public final ServiceTestRule mServiceRule = new ServiceTestRule();
+
+ @Mock
+ private AdapterService mAdapterService;
+ @Mock
+ private AvrcpControllerService mAvrcpControllerService;
+
+ AvrcpControllerStateMachine mAvrcpStateMachine;
+
+ @Before
+ public void setUp() throws Exception {
+ mTargetContext = InstrumentationRegistry.getTargetContext();
+ Assume.assumeTrue("Ignore test when AVRCP Controller is not enabled",
+ mTargetContext.getResources().getBoolean(
+ R.bool.profile_supported_avrcp_controller));
+ if (Looper.myLooper() == null) {
+ Looper.prepare();
+ }
+ Assert.assertNotNull(Looper.myLooper());
+
+ // Setup mocks and test assets
+ MockitoAnnotations.initMocks(this);
+ TestUtils.setAdapterService(mAdapterService);
+ TestUtils.startService(mServiceRule, AvrcpControllerService.class);
+ doReturn(mTargetContext.getResources()).when(mAvrcpControllerService).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(mTestAddress);
+ mAvrcpControllerService.start();
+ mAvrcpControllerService.sBrowseTree = new BrowseTree(null);
+ mAvrcpStateMachine = new AvrcpControllerStateMachine(mTestDevice, mAvrcpControllerService);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ if (!mTargetContext.getResources().getBoolean(R.bool.profile_supported_avrcp_controller)) {
+ return;
+ }
+ TestUtils.clearAdapterService(mAdapterService);
+ }
+
+ /**
+ * Test to confirm that the state machine is capable of cycling through the 4
+ * connection states, and that upon completion, it cleans up aftwards.
+ */
+ @Test
+ public void testDisconnect() {
+ int numBroadcastsSent = setUpConnectedState(true, true);
+ StackEvent event =
+ StackEvent.connectionStateChanged(false, false);
+
+ mAvrcpStateMachine.disconnect();
+ numBroadcastsSent += 2;
+ verify(mAvrcpControllerService,
+ timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(numBroadcastsSent)).sendBroadcast(
+ mIntentArgument.capture(), eq(ProfileService.BLUETOOTH_PERM));
+ Assert.assertEquals(mTestDevice, mIntentArgument.getValue().getParcelableExtra(
+ BluetoothDevice.EXTRA_DEVICE));
+ Assert.assertEquals(BluetoothAvrcpController.ACTION_CONNECTION_STATE_CHANGED,
+ mIntentArgument.getValue().getAction());
+ Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED,
+ mIntentArgument.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1));
+ Assert.assertThat(mAvrcpStateMachine.getCurrentState(),
+ IsInstanceOf.instanceOf(AvrcpControllerStateMachine.Disconnected.class));
+ Assert.assertEquals(mAvrcpStateMachine.getState(), BluetoothProfile.STATE_DISCONNECTED);
+ verify(mAvrcpControllerService).removeStateMachine(eq(mAvrcpStateMachine));
+ }
+
+ /**
+ * Test to confirm that a control only device can be established (no browsing)
+ */
+ @Test
+ public void testControlOnly() {
+ int numBroadcastsSent = setUpConnectedState(true, false);
+ StackEvent event =
+ StackEvent.connectionStateChanged(false, false);
+ mAvrcpStateMachine.disconnect();
+ numBroadcastsSent += 2;
+ verify(mAvrcpControllerService,
+ timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(numBroadcastsSent)).sendBroadcast(
+ mIntentArgument.capture(), eq(ProfileService.BLUETOOTH_PERM));
+ Assert.assertEquals(mTestDevice, mIntentArgument.getValue().getParcelableExtra(
+ BluetoothDevice.EXTRA_DEVICE));
+ Assert.assertEquals(BluetoothAvrcpController.ACTION_CONNECTION_STATE_CHANGED,
+ mIntentArgument.getValue().getAction());
+ Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED,
+ mIntentArgument.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1));
+ Assert.assertThat(mAvrcpStateMachine.getCurrentState(),
+ IsInstanceOf.instanceOf(AvrcpControllerStateMachine.Disconnected.class));
+ Assert.assertEquals(mAvrcpStateMachine.getState(), BluetoothProfile.STATE_DISCONNECTED);
+ verify(mAvrcpControllerService).removeStateMachine(eq(mAvrcpStateMachine));
+ }
+
+ /**
+ * Test to confirm that a browsing only device can be established (no control)
+ */
+ @Test
+ public void testBrowsingOnly() {
+ Assert.assertEquals(0, mAvrcpControllerService.sBrowseTree.mRootNode.getChildrenCount());
+ int numBroadcastsSent = setUpConnectedState(false, true);
+ Assert.assertEquals(1, mAvrcpControllerService.sBrowseTree.mRootNode.getChildrenCount());
+ StackEvent event =
+ StackEvent.connectionStateChanged(false, false);
+ mAvrcpStateMachine.disconnect();
+ numBroadcastsSent += 2;
+ verify(mAvrcpControllerService,
+ timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(numBroadcastsSent)).sendBroadcast(
+ mIntentArgument.capture(), eq(ProfileService.BLUETOOTH_PERM));
+ Assert.assertEquals(mTestDevice, mIntentArgument.getValue().getParcelableExtra(
+ BluetoothDevice.EXTRA_DEVICE));
+ Assert.assertEquals(BluetoothAvrcpController.ACTION_CONNECTION_STATE_CHANGED,
+ mIntentArgument.getValue().getAction());
+ Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED,
+ mIntentArgument.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1));
+ Assert.assertThat(mAvrcpStateMachine.getCurrentState(),
+ IsInstanceOf.instanceOf(AvrcpControllerStateMachine.Disconnected.class));
+ Assert.assertEquals(mAvrcpStateMachine.getState(), BluetoothProfile.STATE_DISCONNECTED);
+ verify(mAvrcpControllerService).removeStateMachine(eq(mAvrcpStateMachine));
+ }
+
+ /**
+ * Test to make sure the state machine is tracking the correct device
+ */
+ @Test
+ public void testGetDevice() {
+ Assert.assertEquals(mAvrcpStateMachine.getDevice(), mTestDevice);
+ }
+
+ /**
+ * Test that dumpsys will generate information about connected devices
+ */
+ @Test
+ public void testDump() {
+ StringBuilder sb = new StringBuilder();
+ mAvrcpStateMachine.dump(sb);
+ Assert.assertEquals(sb.toString(),
+ " mDevice: " + mTestDevice.toString()
+ + "(null) name=AvrcpControllerStateMachine state=(null)\n");
+ }
+
+ /**
+ * Test media browser play command
+ */
+ @Test
+ public void testPlay() throws Exception {
+ setUpConnectedState(true, true);
+ MediaController.TransportControls transportControls =
+ BluetoothMediaBrowserService.getTransportControls();
+
+ //Play
+ transportControls.play();
+ verify(mAvrcpControllerService,
+ timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).sendPassThroughCommandNative(
+ eq(mTestAddress), eq(AvrcpControllerService.PASS_THRU_CMD_ID_PLAY), eq(KEY_DOWN));
+ verify(mAvrcpControllerService,
+ timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).sendPassThroughCommandNative(
+ eq(mTestAddress), eq(AvrcpControllerService.PASS_THRU_CMD_ID_PLAY), eq(KEY_UP));
+ }
+
+ /**
+ * Test media browser pause command
+ */
+ @Test
+ public void testPause() throws Exception {
+ setUpConnectedState(true, true);
+ MediaController.TransportControls transportControls =
+ BluetoothMediaBrowserService.getTransportControls();
+
+ //Pause
+ transportControls.pause();
+ verify(mAvrcpControllerService,
+ timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).sendPassThroughCommandNative(
+ eq(mTestAddress), eq(AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE), eq(KEY_DOWN));
+ verify(mAvrcpControllerService,
+ timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).sendPassThroughCommandNative(
+ eq(mTestAddress), eq(AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE), eq(KEY_UP));
+ }
+
+ /**
+ * Test media browser stop command
+ */
+ @Test
+ public void testStop() throws Exception {
+ setUpConnectedState(true, true);
+ MediaController.TransportControls transportControls =
+ BluetoothMediaBrowserService.getTransportControls();
+
+ //Stop
+ transportControls.stop();
+ verify(mAvrcpControllerService,
+ timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).sendPassThroughCommandNative(
+ eq(mTestAddress), eq(AvrcpControllerService.PASS_THRU_CMD_ID_STOP), eq(KEY_DOWN));
+ verify(mAvrcpControllerService,
+ timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).sendPassThroughCommandNative(
+ eq(mTestAddress), eq(AvrcpControllerService.PASS_THRU_CMD_ID_STOP), eq(KEY_UP));
+ }
+
+ /**
+ * Test media browser next command
+ */
+ @Test
+ public void testNext() throws Exception {
+ setUpConnectedState(true, true);
+ MediaController.TransportControls transportControls =
+ BluetoothMediaBrowserService.getTransportControls();
+
+ //Next
+ transportControls.skipToNext();
+ verify(mAvrcpControllerService,
+ timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).sendPassThroughCommandNative(
+ eq(mTestAddress), eq(AvrcpControllerService.PASS_THRU_CMD_ID_FORWARD),
+ eq(KEY_DOWN));
+ verify(mAvrcpControllerService,
+ timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).sendPassThroughCommandNative(
+ eq(mTestAddress), eq(AvrcpControllerService.PASS_THRU_CMD_ID_FORWARD), eq(KEY_UP));
+ }
+
+ /**
+ * Test media browser previous command
+ */
+ @Test
+ public void testPrevious() throws Exception {
+ setUpConnectedState(true, true);
+ MediaController.TransportControls transportControls =
+ BluetoothMediaBrowserService.getTransportControls();
+
+ //Previous
+ transportControls.skipToPrevious();
+ verify(mAvrcpControllerService,
+ timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).sendPassThroughCommandNative(
+ eq(mTestAddress), eq(AvrcpControllerService.PASS_THRU_CMD_ID_BACKWARD),
+ eq(KEY_DOWN));
+ verify(mAvrcpControllerService,
+ timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).sendPassThroughCommandNative(
+ eq(mTestAddress), eq(AvrcpControllerService.PASS_THRU_CMD_ID_BACKWARD), eq(KEY_UP));
+ }
+
+ /**
+ * Test media browser fast forward command
+ */
+ @Test
+ public void testFastForward() throws Exception {
+ setUpConnectedState(true, true);
+ MediaController.TransportControls transportControls =
+ BluetoothMediaBrowserService.getTransportControls();
+
+ //FastForward
+ transportControls.fastForward();
+ verify(mAvrcpControllerService,
+ timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).sendPassThroughCommandNative(
+ eq(mTestAddress), eq(AvrcpControllerService.PASS_THRU_CMD_ID_FF), eq(KEY_DOWN));
+ //Finish FastForwarding
+ transportControls.play();
+ verify(mAvrcpControllerService,
+ timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).sendPassThroughCommandNative(
+ eq(mTestAddress), eq(AvrcpControllerService.PASS_THRU_CMD_ID_FF), eq(KEY_UP));
+ }
+
+ /**
+ * Test media browser rewind command
+ */
+ @Test
+ public void testRewind() throws Exception {
+ setUpConnectedState(true, true);
+ MediaController.TransportControls transportControls =
+ BluetoothMediaBrowserService.getTransportControls();
+
+ //Rewind
+ transportControls.rewind();
+ verify(mAvrcpControllerService,
+ timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).sendPassThroughCommandNative(
+ eq(mTestAddress), eq(AvrcpControllerService.PASS_THRU_CMD_ID_REWIND), eq(KEY_DOWN));
+ //Finish Rewinding
+ transportControls.play();
+ verify(mAvrcpControllerService,
+ timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).sendPassThroughCommandNative(
+ eq(mTestAddress), eq(AvrcpControllerService.PASS_THRU_CMD_ID_REWIND), eq(KEY_UP));
+ }
+
+ /**
+ * Test media browsing
+ * Verify that a browse tree is created with the proper root
+ * Verify that a player can be fetched and added to the browse tree
+ * Verify that the contents of a player are fetched upon request
+ */
+ @Test
+ public void testBrowsingCommands() {
+ setUpConnectedState(true, true);
+ final String rootName = "__ROOT__";
+ final String playerName = "Player 1";
+
+ //Get the root of the device
+ BrowseTree.BrowseNode results = mAvrcpStateMachine.findNode(rootName);
+ Assert.assertEquals(rootName + mTestDevice.toString(), results.getID());
+
+ //Request fetch the list of players
+ BrowseTree.BrowseNode playerNodes = mAvrcpStateMachine.findNode(results.getID());
+ mAvrcpStateMachine.requestContents(results);
+ verify(mAvrcpControllerService,
+ timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).getPlayerListNative(eq(mTestAddress),
+ eq(0), eq(19));
+
+ //Provide back a player object
+ byte[] playerFeatures =
+ new byte[]{0, 0, 0, 0, 0, (byte) 0xb7, 0x01, 0x0c, 0x0a, 0, 0, 0, 0, 0, 0, 0};
+ AvrcpPlayer playerOne = new AvrcpPlayer(1, playerName, playerFeatures, 1, 1);
+ List<AvrcpPlayer> testPlayers = new ArrayList<>();
+ testPlayers.add(playerOne);
+ mAvrcpStateMachine.sendMessage(AvrcpControllerStateMachine.MESSAGE_PROCESS_GET_PLAYER_ITEMS,
+ testPlayers);
+
+ //Verify that the player object is available.
+ mAvrcpStateMachine.requestContents(results);
+ verify(mAvrcpControllerService,
+ timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).getPlayerListNative(eq(mTestAddress),
+ eq(1), eq(0));
+ mAvrcpStateMachine.sendMessage(
+ AvrcpControllerStateMachine.MESSAGE_PROCESS_GET_FOLDER_ITEMS_OUT_OF_RANGE);
+ playerNodes = mAvrcpStateMachine.findNode(results.getID());
+ Assert.assertEquals(true, results.isCached());
+ Assert.assertEquals("MediaItem{mFlags=1, mDescription=" + playerName + ", null, null}",
+ results.getChildren().get(0).getMediaItem().toString());
+
+ //Fetch contents of that player object
+ BrowseTree.BrowseNode playerOneNode = mAvrcpStateMachine.findNode(
+ results.getChildren().get(0).getID());
+ mAvrcpStateMachine.requestContents(playerOneNode);
+ verify(mAvrcpControllerService,
+ timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).setBrowsedPlayerNative(
+ eq(mTestAddress), eq(1));
+ mAvrcpStateMachine.sendMessage(AvrcpControllerStateMachine.MESSAGE_PROCESS_FOLDER_PATH, 5);
+ verify(mAvrcpControllerService,
+ timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).getFolderListNative(eq(mTestAddress),
+ eq(0), eq(4));
+ }
+
+ /**
+ * Test addressed media player changed
+ * Verify when the addressed media player changes browsing data updates
+ * Verify that the contents of a player are fetched upon request
+ */
+ @Test
+ public void testPlayerChanged() {
+ setUpConnectedState(true, true);
+ final String rootName = "__ROOT__";
+ final String playerName = "Player 1";
+
+ //Get the root of the device
+ BrowseTree.BrowseNode results = mAvrcpStateMachine.findNode(rootName);
+ Assert.assertEquals(rootName + mTestDevice.toString(), results.getID());
+
+ //Request fetch the list of players
+ BrowseTree.BrowseNode playerNodes = mAvrcpStateMachine.findNode(results.getID());
+ mAvrcpStateMachine.requestContents(results);
+ verify(mAvrcpControllerService,
+ timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).getPlayerListNative(eq(mTestAddress),
+ eq(0), eq(19));
+
+ //Provide back a player object
+ byte[] playerFeatures =
+ new byte[]{0, 0, 0, 0, 0, (byte) 0xb7, 0x01, 0x0c, 0x0a, 0, 0, 0, 0, 0, 0, 0};
+ AvrcpPlayer playerOne = new AvrcpPlayer(1, playerName, playerFeatures, 1, 1);
+ List<AvrcpPlayer> testPlayers = new ArrayList<>();
+ testPlayers.add(playerOne);
+ mAvrcpStateMachine.sendMessage(AvrcpControllerStateMachine.MESSAGE_PROCESS_GET_PLAYER_ITEMS,
+ testPlayers);
+
+ //Change players and verify that BT attempts to update the results
+ mAvrcpStateMachine.sendMessage(
+ AvrcpControllerStateMachine.MESSAGE_PROCESS_ADDRESSED_PLAYER_CHANGED, 4);
+ results = mAvrcpStateMachine.findNode(rootName);
+
+ mAvrcpStateMachine.requestContents(results);
+
+ verify(mAvrcpControllerService,
+ timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(2)).getPlayerListNative(eq(mTestAddress),
+ eq(0), eq(19));
+ }
+
+ /**
+ * Test that the Now Playing playlist is updated when it changes.
+ */
+ @Test
+ public void testNowPlaying() {
+ setUpConnectedState(true, true);
+ mAvrcpStateMachine.nowPlayingContentChanged();
+ verify(mAvrcpControllerService,
+ timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).getNowPlayingListNative(
+ eq(mTestAddress), eq(0), eq(19));
+ }
+
+ /**
+ * Test that AVRCP events such as playback commands can execute while performing browsing.
+ */
+ @Test
+ public void testPlayWhileBrowsing() {
+ setUpConnectedState(true, true);
+ final String rootName = "__ROOT__";
+ final String playerName = "Player 1";
+
+ //Get the root of the device
+ BrowseTree.BrowseNode results = mAvrcpStateMachine.findNode(rootName);
+ Assert.assertEquals(rootName + mTestDevice.toString(), results.getID());
+
+ //Request fetch the list of players
+ BrowseTree.BrowseNode playerNodes = mAvrcpStateMachine.findNode(results.getID());
+ mAvrcpStateMachine.requestContents(results);
+
+ MediaController.TransportControls transportControls =
+ BluetoothMediaBrowserService.getTransportControls();
+ transportControls.play();
+ verify(mAvrcpControllerService,
+ timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).sendPassThroughCommandNative(
+ eq(mTestAddress), eq(AvrcpControllerService.PASS_THRU_CMD_ID_PLAY), eq(KEY_DOWN));
+ verify(mAvrcpControllerService,
+ timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).sendPassThroughCommandNative(
+ eq(mTestAddress), eq(AvrcpControllerService.PASS_THRU_CMD_ID_PLAY), eq(KEY_UP));
+ }
+
+ /**
+ * Setup Connected State
+ *
+ * @return number of times mAvrcpControllerService.sendBroadcastAsUser() has been invoked
+ */
+ private int setUpConnectedState(boolean control, boolean browsing) {
+ // Put test state machine into connected state
+ mAvrcpStateMachine.start();
+ Assert.assertThat(mAvrcpStateMachine.getCurrentState(),
+ IsInstanceOf.instanceOf(AvrcpControllerStateMachine.Disconnected.class));
+
+ mAvrcpStateMachine.connect(StackEvent.connectionStateChanged(control, browsing));
+ verify(mAvrcpControllerService, timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(2)).sendBroadcast(
+ mIntentArgument.capture(), eq(ProfileService.BLUETOOTH_PERM));
+ Assert.assertThat(mAvrcpStateMachine.getCurrentState(),
+ IsInstanceOf.instanceOf(AvrcpControllerStateMachine.Connected.class));
+ Assert.assertEquals(mAvrcpStateMachine.getState(), BluetoothProfile.STATE_CONNECTED);
+
+ return BluetoothProfile.STATE_CONNECTED;
+ }
+
+}
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
index 29585ff..b9c8953 100644
--- a/tests/unit/src/com/android/bluetooth/btservice/BondStateMachineTest.java
+++ b/tests/unit/src/com/android/bluetooth/btservice/BondStateMachineTest.java
@@ -23,9 +23,10 @@
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 androidx.test.InstrumentationRegistry;
+import androidx.test.filters.MediumTest;
+import androidx.test.runner.AndroidJUnit4;
import com.android.bluetooth.TestUtils;
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..f5f9e76 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));
@@ -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..57a9a2f 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
@@ -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 939e068..ede535d 100644
--- a/tests/unit/src/com/android/bluetooth/hid/HidHostServiceTest.java
+++ b/tests/unit/src/com/android/bluetooth/hid/HidHostServiceTest.java
@@ -21,14 +21,16 @@
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;
@@ -51,6 +53,7 @@
@Rule public final ServiceTestRule mServiceRule = new ServiceTestRule();
@Mock private AdapterService mAdapterService;
+ @Mock private DatabaseManager mDatabaseManager;
@Before
public void setUp() throws Exception {
@@ -143,13 +146,15 @@
*
* @param device test device
* @param bondState bond state value, could be invalid
- * @param priority value, could be invalid
+ * @param priority value, could be invalid, could 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));
+ when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
+ when(mDatabaseManager.getProfilePriority(device, BluetoothProfile.HID_HOST))
+ .thenReturn(priority);
// Test when the AdapterService is in non-quiet mode.
doReturn(false).when(mAdapterService).isQuietModeEnabled();
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..05ec98a 100644
--- a/tests/unit/src/com/android/bluetooth/mapclient/MapClientStateMachineTest.java
+++ b/tests/unit/src/com/android/bluetooth/mapclient/MapClientStateMachineTest.java
@@ -27,12 +27,13 @@
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.filters.Suppress;
+import androidx.test.runner.AndroidJUnit4;
+
import com.android.bluetooth.R;
import org.junit.After;
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;