DO NOT MERGE
Merge pie-platform-release (PPRL.181205.001, history only) into master
Bug: 120502534
Change-Id: Ie1ceb243b20409cde1851c7971479f0dd66136ef
diff --git a/Android.mk b/Android.mk
index 5d3db9a..23dd1f8 100644
--- a/Android.mk
+++ b/Android.mk
@@ -27,7 +27,7 @@
libprotobuf-java-lite \
bluetooth-protos-lite
-LOCAL_STATIC_ANDROID_LIBRARIES := android-support-v4
+LOCAL_STATIC_ANDROID_LIBRARIES := androidx.core_core
LOCAL_REQUIRED_MODULES := libbluetooth
LOCAL_PROGUARD_ENABLED := disabled
include $(BUILD_PACKAGE)
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index d46ede5..a213973 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
@@ -309,7 +311,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">
@@ -393,8 +395,7 @@
</service>
<service
android:process="@string/process"
- android:name = ".hearingaid.HearingAidService"
- android:enabled="@bool/profile_supported_hearing_aid">
+ android:name = ".hearingaid.HearingAidService">
<intent-filter>
<action android:name="android.bluetooth.IBluetoothHearingAid" />
</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/jni/Android.bp b/jni/Android.bp
index c0fdd80..c3382eb 100644
--- a/jni/Android.bp
+++ b/jni/Android.bp
@@ -8,7 +8,6 @@
"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",
@@ -33,10 +32,10 @@
"libchrome",
"libnativehelper",
"liblog",
+ "libutils",
],
static_libs: [
"libbluetooth-types",
- "libutils",
"libcutils",
],
cflags: [
diff --git a/jni/bluetooth_socket_manager.cc b/jni/bluetooth_socket_manager.cc
index a63beed..50c06bb 100644
--- a/jni/bluetooth_socket_manager.cc
+++ b/jni/bluetooth_socket_manager.cc
@@ -57,8 +57,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 +97,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_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..3815411 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,18 +47,26 @@
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)));
@@ -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,8 +98,13 @@
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)));
@@ -104,8 +123,13 @@
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)));
@@ -123,8 +147,13 @@
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)));
@@ -144,8 +173,13 @@
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)));
@@ -191,8 +225,13 @@
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)));
@@ -229,8 +268,13 @@
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)));
@@ -248,8 +292,13 @@
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)));
@@ -273,8 +322,13 @@
* 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)));
@@ -323,8 +377,13 @@
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)));
@@ -341,8 +400,13 @@
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)));
@@ -364,8 +428,13 @@
* 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;
+ }
// Inspect if the first element is a folder/item or player listing. They are
// always exclusive.
@@ -550,8 +619,13 @@
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;
+ }
sCallbackEnv->CallVoidMethod(sCallbacksObj, method_handleChangeFolderRsp,
(jint)count);
@@ -561,8 +635,13 @@
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;
+ }
sCallbackEnv->CallVoidMethod(sCallbacksObj, method_handleSetBrowsedPlayerRsp,
(jint)num_items, (jint)depth);
@@ -571,12 +650,42 @@
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;
+ }
+
+ sCallbackEnv->CallVoidMethod(
+ sCallbacksObj, method_handleSetAddressedPlayerRsp, (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;
+ }
+
+ sCallbackEnv->CallVoidMethod(sCallbacksObj,
+ method_handleAddressedPlayerChanged, (jint)id);
+}
+
+static void btavrcp_now_playing_content_changed_callback(
+ const RawAddress& bd_addr) {
+ ALOGI("%s", __func__);
CallbackEnv sCallbackEnv(__func__);
if (!sCallbackEnv.valid()) return;
- sCallbackEnv->CallVoidMethod(
- sCallbacksObj, method_handleSetAddressedPlayerRsp, (jint)status);
+ sCallbackEnv->CallVoidMethod(sCallbacksObj,
+ method_handleNowPlayingContentChanged);
}
static btrc_ctrl_callbacks_t sBluetoothAvrcpCallbacks = {
@@ -596,7 +705,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 =
@@ -658,10 +769,17 @@
env->GetMethodID(clazz, "handleSetBrowsedPlayerRsp", "(II)V");
method_handleSetAddressedPlayerRsp =
env->GetMethodID(clazz, "handleSetAddressedPlayerRsp", "(I)V");
+ method_handleAddressedPlayerChanged =
+ env->GetMethodID(clazz, "handleAddressedPlayerChanged", "(I)V");
+ method_handleNowPlayingContentChanged =
+ env->GetMethodID(clazz, "handleNowPlayingContentChanged", "()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 +827,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");
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..c8a9d56 100644
--- a/jni/com_android_bluetooth_btservice_AdapterService.cpp
+++ b/jni/com_android_bluetooth_btservice_AdapterService.cpp
@@ -1217,6 +1217,25 @@
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},
@@ -1251,7 +1270,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 +1326,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);
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_hfp.cpp b/jni/com_android_bluetooth_hfp.cpp
index 8547aa6..d2bab34 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;
@@ -215,7 +215,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());
}
@@ -396,8 +396,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 +446,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 +805,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 +819,16 @@
return JNI_FALSE;
}
const char* number = env->GetStringUTFChars(number_str, nullptr);
+ const char* 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);
+ env->ReleaseStringUTFChars(name_str, name);
env->ReleaseByteArrayElements(address, addr, 0);
return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
}
@@ -908,7 +912,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/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/res/values-af/strings.xml b/res/values-af/strings.xml
index ce320e1..e2610b1 100644
--- a/res/values-af/strings.xml
+++ b/res/values-af/strings.xml
@@ -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..963fa4a 100644
--- a/res/values-am/strings.xml
+++ b/res/values-am/strings.xml
@@ -122,6 +122,7 @@
<string name="transfer_menu_open" msgid="3368984869083107200">"ክፈት"</string>
<string name="transfer_menu_clear" msgid="5854038118831427492">"ከዝርዝር አጽዳ"</string>
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"አጽዳ"</string>
+ <string name="bluetooth_a2dp_sink_queue_name" msgid="6864149958708669766">"አሁን እየተጫወተ ያለ"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"አስቀምጥ"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"ይቅር"</string>
<string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"በብሉቱዝ በኩል ማጋራት የሚፈልጓቸውን መለያዎች ይምረጡ። አሁንም በሚገናኙበት ወቅት ማንኛቸውም የመለያዎቹ መዳረሻ መፍቀድ አለብዎት።"</string>
diff --git a/res/values-ar/strings.xml b/res/values-ar/strings.xml
index 4bb0f8a..bae164a 100644
--- a/res/values-ar/strings.xml
+++ b/res/values-ar/strings.xml
@@ -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-az/strings.xml b/res/values-az/strings.xml
index 4511191..7dc468c 100644
--- a/res/values-az/strings.xml
+++ b/res/values-az/strings.xml
@@ -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..3ed0589 100644
--- a/res/values-b+sr+Latn/strings.xml
+++ b/res/values-b+sr+Latn/strings.xml
@@ -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..b89952a 100644
--- a/res/values-be/strings.xml
+++ b/res/values-be/strings.xml
@@ -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..11ef459 100644
--- a/res/values-bg/strings.xml
+++ b/res/values-bg/strings.xml
@@ -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..e79b001 100644
--- a/res/values-bn/strings.xml
+++ b/res/values-bn/strings.xml
@@ -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..3c53aa8 100644
--- a/res/values-bs/strings.xml
+++ b/res/values-bs/strings.xml
@@ -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 0418fbc..f53eef2 100644
--- a/res/values-ca/strings.xml
+++ b/res/values-ca/strings.xml
@@ -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..b14f701 100644
--- a/res/values-cs/strings.xml
+++ b/res/values-cs/strings.xml
@@ -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..4cd2947 100644
--- a/res/values-da/strings.xml
+++ b/res/values-da/strings.xml
@@ -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 9107800..bb6386d 100644
--- a/res/values-de/strings.xml
+++ b/res/values-de/strings.xml
@@ -87,7 +87,7 @@
<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_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. Versuche es später erneut."</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>
<string name="status_running" msgid="6562808920311008696">"Dateiübertragung läuft."</string>
<string name="status_success" msgid="239573225847565868">"Die Dateiübertragung wurde abgeschlossen."</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..2db78bf 100644
--- a/res/values-el/strings.xml
+++ b/res/values-el/strings.xml
@@ -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..fa9e8b2 100644
--- a/res/values-en-rAU/strings.xml
+++ b/res/values-en-rAU/strings.xml
@@ -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..ba19b9c 100644
--- a/res/values-en-rCA/strings.xml
+++ b/res/values-en-rCA/strings.xml
@@ -23,8 +23,8 @@
<string name="bt_share_picker_label" msgid="6268100924487046932">"Bluetooth"</string>
<string name="unknown_device" msgid="9221903979877041009">"Unknown device"</string>
<string name="unknownNumber" msgid="4994750948072751566">"Unknown"</string>
- <string name="airplane_error_title" msgid="2683839635115739939">"Airplane mode"</string>
- <string name="airplane_error_msg" msgid="8698965595254137230">"You can\'t use Bluetooth in Airplane mode."</string>
+ <string name="airplane_error_title" msgid="2683839635115739939">"Aeroplane mode"</string>
+ <string name="airplane_error_msg" msgid="8698965595254137230">"You can\'t use Bluetooth in Aeroplane mode."</string>
<string name="bt_enable_title" msgid="8657832550503456572"></string>
<string name="bt_enable_line1" msgid="7203551583048149">"To use Bluetooth services, you must first turn on Bluetooth."</string>
<string name="bt_enable_line2" msgid="4341936569415937994">"Turn on Bluetooth now?"</string>
diff --git a/res/values-en-rGB/strings.xml b/res/values-en-rGB/strings.xml
index ba19b9c..fa9e8b2 100644
--- a/res/values-en-rGB/strings.xml
+++ b/res/values-en-rGB/strings.xml
@@ -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..fa9e8b2 100644
--- a/res/values-en-rIN/strings.xml
+++ b/res/values-en-rIN/strings.xml
@@ -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-es-rUS/strings.xml b/res/values-es-rUS/strings.xml
index 1b142d4..b747c4d 100644
--- a/res/values-es-rUS/strings.xml
+++ b/res/values-es-rUS/strings.xml
@@ -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..285efda 100644
--- a/res/values-es/strings.xml
+++ b/res/values-es/strings.xml
@@ -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..ac7784e 100644
--- a/res/values-et/strings.xml
+++ b/res/values-et/strings.xml
@@ -122,6 +122,7 @@
<string name="transfer_menu_open" msgid="3368984869083107200">"Ava"</string>
<string name="transfer_menu_clear" msgid="5854038118831427492">"Eemaldage loendist"</string>
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"Kustuta"</string>
+ <string name="bluetooth_a2dp_sink_queue_name" msgid="6864149958708669766">"Hetkel mängimas"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"Salvesta"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"Tühista"</string>
<string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"Valige kontod, mida soovite Bluetoothi kaudu jagada. Ühendamisel peate ikka lubama mis tahes juurdepääsu kontodele."</string>
diff --git a/res/values-eu/strings.xml b/res/values-eu/strings.xml
index 6436c7f..64a3314 100644
--- a/res/values-eu/strings.xml
+++ b/res/values-eu/strings.xml
@@ -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..f27686a 100644
--- a/res/values-fa/strings.xml
+++ b/res/values-fa/strings.xml
@@ -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..3026d39 100644
--- a/res/values-fi/strings.xml
+++ b/res/values-fi/strings.xml
@@ -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..73a8f25 100644
--- a/res/values-fr-rCA/strings.xml
+++ b/res/values-fr-rCA/strings.xml
@@ -122,6 +122,7 @@
<string name="transfer_menu_open" msgid="3368984869083107200">"ouvrir"</string>
<string name="transfer_menu_clear" msgid="5854038118831427492">"Effacer de la liste"</string>
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"Effacer"</string>
+ <string name="bluetooth_a2dp_sink_queue_name" msgid="6864149958708669766">"En cours de lecture"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"Enregistrer"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"Annuler"</string>
<string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"Sélectionnez les comptes que vous souhaitez partager par Bluetooth. Vous devez toujours accepter l\'accès à ces comptes lors de la connexion."</string>
diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml
index 13c3444..65ce44f 100644
--- a/res/values-fr/strings.xml
+++ b/res/values-fr/strings.xml
@@ -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..36f91f2 100644
--- a/res/values-gl/strings.xml
+++ b/res/values-gl/strings.xml
@@ -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..4e00e08 100644
--- a/res/values-gu/strings.xml
+++ b/res/values-gu/strings.xml
@@ -122,6 +122,7 @@
<string name="transfer_menu_open" msgid="3368984869083107200">"ખોલો"</string>
<string name="transfer_menu_clear" msgid="5854038118831427492">"સૂચિમાંથી સાફ કરો"</string>
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"સાફ કરો"</string>
+ <string name="bluetooth_a2dp_sink_queue_name" msgid="6864149958708669766">"હમણાં વાગી રહ્યું છે"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"સાચવો"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"રદ કરો"</string>
<string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"તમે બ્લૂટૂથ મારફતે શેર કરવા માગતા હો તે એકાઉન્ટ્સ પસંદ કરો. તમારે હજી પણ કનેક્ટ કરતી વખતે એકાઉન્ટ્સ પરની કોઈપણ અૅક્સેસ સ્વીકારવી પડશે."</string>
diff --git a/res/values-hi/strings.xml b/res/values-hi/strings.xml
index 9e66d3b..59a10bb 100644
--- a/res/values-hi/strings.xml
+++ b/res/values-hi/strings.xml
@@ -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 7b50384..fc1c3a0 100644
--- a/res/values-hr/strings.xml
+++ b/res/values-hr/strings.xml
@@ -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..a5beb37 100644
--- a/res/values-hu/strings.xml
+++ b/res/values-hu/strings.xml
@@ -122,6 +122,7 @@
<string name="transfer_menu_open" msgid="3368984869083107200">"Megnyitás"</string>
<string name="transfer_menu_clear" msgid="5854038118831427492">"Törlés a listáról"</string>
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"Törlés"</string>
+ <string name="bluetooth_a2dp_sink_queue_name" msgid="6864149958708669766">"Now Playing"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"Mentés"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"Mégse"</string>
<string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"Válassza ki a Bluetooth használatával megosztani kívánt fiókokat. Kapcsolódásnál el kell fogadnia a fiókokhoz való hozzáférést is."</string>
diff --git a/res/values-hy/strings.xml b/res/values-hy/strings.xml
index 6c52e73..10b0aa7 100644
--- a/res/values-hy/strings.xml
+++ b/res/values-hy/strings.xml
@@ -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..a4c91b4 100644
--- a/res/values-in/strings.xml
+++ b/res/values-in/strings.xml
@@ -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..a6b749c 100644
--- a/res/values-is/strings.xml
+++ b/res/values-is/strings.xml
@@ -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..3ab8c63 100644
--- a/res/values-it/strings.xml
+++ b/res/values-it/strings.xml
@@ -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 8a6396a..5539eba 100644
--- a/res/values-iw/strings.xml
+++ b/res/values-iw/strings.xml
@@ -78,7 +78,7 @@
<string name="not_exist_file_desc" msgid="4059531573790529229">"הקובץ לא קיים. \n"</string>
<string name="enabling_progress_title" msgid="436157952334723406">"המתן..."</string>
<string name="enabling_progress_content" msgid="4601542238119927904">"מפעיל Bluetooth…"</string>
- <string name="bt_toast_1" msgid="972182708034353383">"הקובץ יתקבל. בדוק את ההתקדמות בחלונית \'הודעות\'."</string>
+ <string name="bt_toast_1" msgid="972182708034353383">"הקובץ יתקבל. יש לבדוק את ההתקדמות בלוח ההתראות."</string>
<string name="bt_toast_2" msgid="8602553334099066582">"לא ניתן לקבל את הקובץ."</string>
<string name="bt_toast_3" msgid="6707884165086862518">"הופסקה קבלת קובץ מאת \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
<string name="bt_toast_4" msgid="4678812947604395649">"שולח קובץ אל \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
@@ -126,6 +126,7 @@
<string name="transfer_menu_open" msgid="3368984869083107200">"פתח"</string>
<string name="transfer_menu_clear" msgid="5854038118831427492">"נקה מהרשימה"</string>
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"ניקוי"</string>
+ <string name="bluetooth_a2dp_sink_queue_name" msgid="6864149958708669766">"מושמע עכשיו"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"שמור"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"ביטול"</string>
<string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"בחר בחשבונות שברצונך לשתף באמצעות Bluetooth. עדיין יהיה עליך לאשר גישה אל החשבונות בעת החיבור."</string>
diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml
index b8cfee6..d222751 100644
--- a/res/values-ja/strings.xml
+++ b/res/values-ja/strings.xml
@@ -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..5c05021 100644
--- a/res/values-ka/strings.xml
+++ b/res/values-ka/strings.xml
@@ -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..17f3f0e 100644
--- a/res/values-kk/strings.xml
+++ b/res/values-kk/strings.xml
@@ -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..2141d97 100644
--- a/res/values-km/strings.xml
+++ b/res/values-km/strings.xml
@@ -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 143219a..3e16ba5 100644
--- a/res/values-kn/strings.xml
+++ b/res/values-kn/strings.xml
@@ -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-ko/strings.xml b/res/values-ko/strings.xml
index 83a1a33..bfc1328 100644
--- a/res/values-ko/strings.xml
+++ b/res/values-ko/strings.xml
@@ -122,6 +122,7 @@
<string name="transfer_menu_open" msgid="3368984869083107200">"열기"</string>
<string name="transfer_menu_clear" msgid="5854038118831427492">"목록에서 지우기"</string>
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"지우기"</string>
+ <string name="bluetooth_a2dp_sink_queue_name" msgid="6864149958708669766">"지금 재생 중"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"저장"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"취소"</string>
<string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"블루투스를 통해 공유하려는 계정을 선택하세요. 연결할 때 계정에 대한 모든 액세스를 수락해야 합니다."</string>
diff --git a/res/values-ky/strings.xml b/res/values-ky/strings.xml
index 64c1ede..572d8ef 100644
--- a/res/values-ky/strings.xml
+++ b/res/values-ky/strings.xml
@@ -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..2a1341e 100644
--- a/res/values-lo/strings.xml
+++ b/res/values-lo/strings.xml
@@ -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..0c662bd 100644
--- a/res/values-lt/strings.xml
+++ b/res/values-lt/strings.xml
@@ -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..f4adfed 100644
--- a/res/values-lv/strings.xml
+++ b/res/values-lv/strings.xml
@@ -124,6 +124,7 @@
<string name="transfer_menu_open" msgid="3368984869083107200">"Atvērt"</string>
<string name="transfer_menu_clear" msgid="5854038118831427492">"Notīrīt no saraksta"</string>
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"Notīrīšana"</string>
+ <string name="bluetooth_a2dp_sink_queue_name" msgid="6864149958708669766">"Tagad atskaņo"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"Saglabāt"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"Atcelt"</string>
<string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"Atlasiet kontus, kurus vēlaties koplietot, izmantojot Bluetooth. Kad tiks izveidots savienojums, jums būs arī jāapstiprina piekļuve kontiem."</string>
diff --git a/res/values-mk/strings.xml b/res/values-mk/strings.xml
index c6ec941..30a5b7e 100644
--- a/res/values-mk/strings.xml
+++ b/res/values-mk/strings.xml
@@ -122,6 +122,7 @@
<string name="transfer_menu_open" msgid="3368984869083107200">"Отвори"</string>
<string name="transfer_menu_clear" msgid="5854038118831427492">"Исчисти од списокот"</string>
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"Исчисти"</string>
+ <string name="bluetooth_a2dp_sink_queue_name" msgid="6864149958708669766">"Now Playing"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"Зачувај"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"Откажи"</string>
<string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"Изберете ги сметките што сакате да ги споделите преку Bluetooth. Сепак, ќе мора да го прифаќате секој пристап до сметките при поврзување."</string>
diff --git a/res/values-ml/strings.xml b/res/values-ml/strings.xml
index a7ddfeb..5d76fbf 100644
--- a/res/values-ml/strings.xml
+++ b/res/values-ml/strings.xml
@@ -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..1606637 100644
--- a/res/values-mn/strings.xml
+++ b/res/values-mn/strings.xml
@@ -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 ecc2a67..56bda2e 100644
--- a/res/values-mr/strings.xml
+++ b/res/values-mr/strings.xml
@@ -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..92d71bd 100644
--- a/res/values-ms/strings.xml
+++ b/res/values-ms/strings.xml
@@ -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 4aac349..469f49c 100644
--- a/res/values-my/strings.xml
+++ b/res/values-my/strings.xml
@@ -96,7 +96,7 @@
<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" product="default" msgid="5760944071743325592">"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..9195c2d 100644
--- a/res/values-nb/strings.xml
+++ b/res/values-nb/strings.xml
@@ -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..c504b65 100644
--- a/res/values-ne/strings.xml
+++ b/res/values-ne/strings.xml
@@ -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..61a35cb 100644
--- a/res/values-nl/strings.xml
+++ b/res/values-nl/strings.xml
@@ -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/config.xml b/res/values-or/config.xml
deleted file mode 100644
index 4f96544..0000000
--- a/res/values-or/config.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- Copyright (C) 2009-2012 Broadcom Corporation
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="pairing_ui_package" msgid="6399948348712579121">"com.android.settings"</string>
-</resources>
diff --git a/res/values-or/strings.xml b/res/values-or/strings.xml
deleted file mode 100644
index b93a78d..0000000
--- a/res/values-or/strings.xml
+++ /dev/null
@@ -1,136 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- Copyright (C) 2007 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="permlab_bluetoothShareManager" msgid="311492132450338925">"ଡାଉନଲୋଡ୍ ମ୍ୟାନେଜର୍କୁ ଆକ୍ସେସ୍ କରନ୍ତୁ।"</string>
- <string name="permdesc_bluetoothShareManager" msgid="8930572979123190223">"BluetoothShare ମ୍ୟାନେଜର୍ ଆକ୍ସେସ୍ କରି ଫାଇଲ୍ଗୁଡ଼ିକ ଟ୍ରାନ୍ସଫର୍ କରିବାକୁ ବ୍ୟବହାର କରିବା ପାଇଁ ଆପ୍କୁ ଅନୁମତି ଦିଅନ୍ତୁ।"</string>
- <string name="permlab_bluetoothWhitelist" msgid="7091552898592306386">"ବ୍ଲୁ-ଟୂଥ୍ ଡିଭାଇସ୍ ଆକ୍ସେସ୍କୁ ସ୍ୱୀକୃତି ଦିଅନ୍ତୁ।"</string>
- <string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"ୟୁଜର୍ଙ୍କ ସୁନିଶ୍ଚିତତା ବିନା ଏହି ଡିଭାଇସ୍କୁ ଫାଇଲ୍ ପଠାଇବା ନିମନ୍ତେ ଗୋଟିଏ ବ୍ଲୁ-ଟୂଥ୍ ଡିଭାଇସ୍କୁ ଅନୁମତି ଦେଇ କିଛି ସମୟ ପାଇଁ ଆପ୍କୁ ସ୍ୱୀକୃତି ଦେଇଥାଏ।"</string>
- <string name="bt_share_picker_label" msgid="6268100924487046932">"ବ୍ଲୁ-ଟୂଥ୍"</string>
- <string name="unknown_device" msgid="9221903979877041009">"ଅଜଣା ଡିଭାଇସ୍"</string>
- <string name="unknownNumber" msgid="4994750948072751566">"ଅଜଣା"</string>
- <string name="airplane_error_title" msgid="2683839635115739939">"ଏରୋପ୍ଲେନ୍ ମୋଡ୍"</string>
- <string name="airplane_error_msg" msgid="8698965595254137230">"ଆପଣ, ଏୟାରପ୍ଲେନ୍ ମୋଡ୍ରେ ବ୍ଲୁ-ଟୂଥ୍ ବ୍ୟବହାର କରିପାରିବେ ନାହିଁ।"</string>
- <string name="bt_enable_title" msgid="8657832550503456572"></string>
- <string name="bt_enable_line1" msgid="7203551583048149">"ବ୍ଲୁ-ଟୂଥ୍ ସେବା ବ୍ୟବହାର କରିବା ପାଇଁ, ଆପଣଙ୍କୁ ପ୍ରଥମେ ବ୍ଲୁ-ଟୂଥ୍ ଅନ୍ କରିବାକୁ ପଡ଼ିବ।"</string>
- <string name="bt_enable_line2" msgid="4341936569415937994">"ବ୍ଲୁ-ଟୁଥ୍କୁ ଏବେ ଅନ୍ କରିବେ?"</string>
- <string name="bt_enable_cancel" msgid="1988832367505151727">"କ୍ୟାନ୍ସଲ୍"</string>
- <string name="bt_enable_ok" msgid="3432462749994538265">"ଅନ୍ କରନ୍ତୁ"</string>
- <string name="incoming_file_confirm_title" msgid="8139874248612182627">"ଫାଇଲ୍ ଟ୍ରାନ୍ସଫର୍ କରନ୍ତୁ"</string>
- <string name="incoming_file_confirm_content" msgid="2752605552743148036">"ଆସୁଥିବା ଫାଇଲ୍କୁ ସ୍ୱୀକାର କରିବେ?"</string>
- <string name="incoming_file_confirm_cancel" msgid="2973321832477704805">"ଅସ୍ୱୀକାର"</string>
- <string name="incoming_file_confirm_ok" msgid="281462442932231475">"ସ୍ୱୀକାର କରନ୍ତୁ"</string>
- <string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"ଠିକ୍ ଅଛି"</string>
- <string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"\"<xliff:g id="SENDER">%1$s</xliff:g>\"ଙ୍କଠାରୁ ଆସୁଥିବା ଫାଇଲ୍ ସ୍ୱୀକାର କରୁଥିବାବେଳେ ସମୟ ସମାପ୍ତ ହୋଇଗଲା"</string>
- <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"ଆସୁଥିବା ଫାଇଲ୍"</string>
- <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="FILE">%2$s</xliff:g> ପଠାଇବା ପାଇଁ <xliff:g id="SENDER">%1$s</xliff:g> ପ୍ରସ୍ତୁତ"</string>
- <string name="notification_receiving" msgid="4674648179652543984">"ବ୍ଲୁ-ଟୂଥ୍ ଶେୟାର୍: <xliff:g id="FILE">%1$s</xliff:g> ପ୍ରାପ୍ତ କରୁଛି"</string>
- <string name="notification_received" msgid="3324588019186687985">"ବ୍ଲୁ-ଟୂଥ୍ ଶେୟାର୍: <xliff:g id="FILE">%1$s</xliff:g> ପ୍ରାପ୍ତ କରାଯାଇଛି"</string>
- <string name="notification_received_fail" msgid="3619350997285714746">"ବ୍ଲୁ-ଟୂଥ୍ ଶେୟାର୍: <xliff:g id="FILE">%1$s</xliff:g> ଫାଇଲ୍ ପ୍ରାପ୍ତ କରାଯାଇନାହିଁ"</string>
- <string name="notification_sending" msgid="3035748958534983833">"ବ୍ଲୁ-ଟୂଥ୍ ଶେୟାର୍: <xliff:g id="FILE">%1$s</xliff:g> ପଠାଉଛି"</string>
- <string name="notification_sent" msgid="9218710861333027778">"ବ୍ଲୁ-ଟୂଥ୍ ଶେୟାର୍: <xliff:g id="FILE">%1$s</xliff:g> ପଠାଗଲା"</string>
- <string name="notification_sent_complete" msgid="302943281067557969">"100% ସମ୍ପୂର୍ଣ୍ଣ"</string>
- <string name="notification_sent_fail" msgid="6696082233774569445">"ବ୍ଲୁ-ଟୂଥ୍ ଶେୟାର୍: <xliff:g id="FILE">%1$s</xliff:g> ଫାଇଲ୍ ପଠାଯାଇନାହିଁ"</string>
- <string name="download_title" msgid="3353228219772092586">"ଫାଇଲ୍ ଟ୍ରାନ୍ସଫର୍ କରନ୍ତୁ"</string>
- <string name="download_line1" msgid="4926604799202134144">"ପ୍ରେରକ: \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
- <string name="download_line2" msgid="5876973543019417712">"ଫାଇଲ୍: <xliff:g id="FILE">%1$s</xliff:g>"</string>
- <string name="download_line3" msgid="4384821622908676061">"ଫାଇଲ୍ ଆକାର: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
- <string name="download_line4" msgid="8535996869722666525"></string>
- <string name="download_line5" msgid="3069560415845295386">"ଫାଇଲ୍ ପ୍ରାପ୍ତ କରୁଛି…"</string>
- <string name="download_cancel" msgid="9177305996747500768">"ବନ୍ଦ କରନ୍ତୁ"</string>
- <string name="download_ok" msgid="5000360731674466039">"ଲୁଚାନ୍ତୁ"</string>
- <string name="incoming_line1" msgid="2127419875681087545">"ପ୍ରେରକ"</string>
- <string name="incoming_line2" msgid="3348994249285315873">"ଫାଇଲ୍ ନାମ"</string>
- <string name="incoming_line3" msgid="7954237069667474024">"ଆକାର"</string>
- <string name="download_fail_line1" msgid="3846450148862894552">"ଫାଇଲ୍ ପ୍ରାପ୍ତ ହେଲା ନାହିଁ"</string>
- <string name="download_fail_line2" msgid="8950394574689971071">"ଫାଇଲ୍: <xliff:g id="FILE">%1$s</xliff:g>"</string>
- <string name="download_fail_line3" msgid="3451040656154861722">"କାରଣ: <xliff:g id="REASON">%1$s</xliff:g>"</string>
- <string name="download_fail_ok" msgid="1521733664438320300">"ଠିକ୍ ଅଛି"</string>
- <string name="download_succ_line5" msgid="4509944688281573595">"ଫାଇଲ୍ ପ୍ରାପ୍ତ ହେଲା"</string>
- <string name="download_succ_ok" msgid="7053688246357050216">"ଖୋଲନ୍ତୁ"</string>
- <string name="upload_line1" msgid="2055952074059709052">"ପ୍ରାପ୍ତକର୍ତ୍ତା: \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
- <string name="upload_line3" msgid="4920689672457037437">"ଫାଇଲ୍ ପ୍ରକାର: <xliff:g id="TYPE">%1$s</xliff:g> (<xliff:g id="SIZE">%2$s</xliff:g>)"</string>
- <string name="upload_line5" msgid="7759322537674229752">"ଫାଇଲ୍ ପଠାଯାଉଛି…"</string>
- <string name="upload_succ_line5" msgid="5687317197463383601">"ଫାଇଲ୍ ପଠାଗଲା"</string>
- <string name="upload_succ_ok" msgid="7705428476405478828">"ଠିକ୍ ଅଛି"</string>
- <string name="upload_fail_line1" msgid="7899394672421491701">"ଫାଇଲ୍ \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\"ଙ୍କୁ ପଠାଯାଇନଥିଲା।"</string>
- <string name="upload_fail_line1_2" msgid="2108129204050841798">"ଫାଇଲ୍: <xliff:g id="FILE">%1$s</xliff:g>"</string>
- <string name="upload_fail_ok" msgid="5807702461606714296">"ପୁଣି ଚେଷ୍ଟା କରନ୍ତୁ"</string>
- <string name="upload_fail_cancel" msgid="9118496285835687125">"ବନ୍ଦ କରନ୍ତୁ"</string>
- <string name="bt_error_btn_ok" msgid="5965151173011534240">"ଠିକ୍ ଅଛି"</string>
- <string name="unknown_file" msgid="6092727753965095366">"ଅଜଣା ଫାଇଲ୍"</string>
- <string name="unknown_file_desc" msgid="480434281415453287">"ଏହିଭଳି ଫାଇଲ୍କୁ ସମ୍ଭାଳିବା ପାଇଁ କୌଣସି ଆପ୍ ନାହିଁ। \n"</string>
- <string name="not_exist_file" msgid="3489434189599716133">"କୌଣସି ଫାଇଲ୍ ନାହିଁ"</string>
- <string name="not_exist_file_desc" msgid="4059531573790529229">"ଏଭଳି କୌଣସି ଫାଇଲ୍ ନାହିଁ। \n"</string>
- <string name="enabling_progress_title" msgid="436157952334723406">"ଦୟାକରି ଅପେକ୍ଷା କରନ୍ତୁ…"</string>
- <string name="enabling_progress_content" msgid="4601542238119927904">"ବ୍ଲୁ-ଟୂଥ୍ ଅନ୍ କରୁଛି…"</string>
- <string name="bt_toast_1" msgid="972182708034353383">"ଫାଇଲ୍କୁ ଗ୍ରହଣ କରାଯିବ। ବିଜ୍ଞପ୍ତି ପ୍ୟାନେଲ୍ରେ ପ୍ରଗତିକୁ ଦେଖନ୍ତୁ।"</string>
- <string name="bt_toast_2" msgid="8602553334099066582">"ଫାଇଲ୍କୁ ଗ୍ରହଣ କରାଯାଇପାରିବ ନାହିଁ।"</string>
- <string name="bt_toast_3" msgid="6707884165086862518">"\"<xliff:g id="SENDER">%1$s</xliff:g>\"ଙ୍କଠାରୁ ଗ୍ରହଣ କରିବା ବନ୍ଦ ହୋଇଗଲା"</string>
- <string name="bt_toast_4" msgid="4678812947604395649">"\"<xliff:g id="RECIPIENT">%1$s</xliff:g>\"ଙ୍କୁ ଫାଇଲ୍ ପଠାଯାଉଛି"</string>
- <string name="bt_toast_5" msgid="2846870992823019494">"\"<xliff:g id="RECIPIENT">%2$s</xliff:g>\"ଙ୍କୁ <xliff:g id="NUMBER">%1$s</xliff:g>ଟି ଫାଇଲ୍ ପଠାଯାଉଛି"</string>
- <string name="bt_toast_6" msgid="1855266596936622458">"\"<xliff:g id="RECIPIENT">%1$s</xliff:g>\"ଙ୍କୁ ଫାଇଲ୍ ପଠାଇବା ବନ୍ଦ ହୋଇଗଲା"</string>
- <string name="bt_sm_2_1" product="nosdcard" msgid="352165168004521000">"\"<xliff:g id="SENDER">%1$s</xliff:g>\"ଙ୍କଠାରୁ ଫାଇଲ୍ ସେଭ୍ କରିବା ପାଇଁ USB ଷ୍ଟୋରେଜ୍ରେ ଯଥେଷ୍ଟ ସ୍ପେସ୍ ନାହିଁ"</string>
- <string name="bt_sm_2_1" product="default" msgid="1989018443456803630">"\"<xliff:g id="SENDER">%1$s</xliff:g>\"ଙ୍କଠାରୁ ଫାଇଲ୍ ସେଭ୍ କରିବା ପାଇଁ SD କାର୍ଡରେ ଯଥେଷ୍ଟ ସ୍ପେସ୍ ନାହିଁ"</string>
- <string name="bt_sm_2_2" msgid="2965243265852680543">"ସ୍ପେସ୍ ଆବଶ୍ୟକ: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
- <string name="ErrorTooManyRequests" msgid="8578277541472944529">"ଅନେକ ଅନୁରୋଧ ଉପରେ କାମ ଚାଲୁଛି। ପରେ ପୁଣି ଚେଷ୍ଟା କରନ୍ତୁ।"</string>
- <string name="status_pending" msgid="2503691772030877944">"ଏପର୍ଯ୍ୟନ୍ତ ଫାଇଲ୍ ଟ୍ରାନ୍ସଫର୍ ଆରମ୍ଭ ହୋଇନାହିଁ।"</string>
- <string name="status_running" msgid="6562808920311008696">"ଫାଇଲ୍ ଟ୍ରାନ୍ସଫର୍ ଚାଲୁଛି।"</string>
- <string name="status_success" msgid="239573225847565868">"ଫାଇଲ୍ ଟ୍ରାନ୍ସଫର୍ ସଫଳତାପୂର୍ବକ ସମ୍ପୂର୍ଣ୍ଣ ହେଲା।"</string>
- <string name="status_not_accept" msgid="1695082417193780738">"କଣ୍ଟେଣ୍ଟ ସପୋର୍ଟ କରୁନାହିଁ।"</string>
- <string name="status_forbidden" msgid="613956401054050725">"ଟାର୍ଗେଟ୍ ଡିଭାଇସ୍ ଦ୍ୱାରା ଟ୍ରାନ୍ସଫର୍ ପ୍ରତିବନ୍ଧିତ କରାଯାଇଛି।"</string>
- <string name="status_canceled" msgid="6664490318773098285">"ୟୁଜର୍ଙ୍କ ଦ୍ୱାରା ଟ୍ରାନ୍ସଫର୍ କ୍ୟାନ୍ସଲ୍ କରାଗଲା।"</string>
- <string name="status_file_error" msgid="3671917770630165299">"ଷ୍ଟୋରେଜ୍ ସମସ୍ୟା।"</string>
- <string name="status_no_sd_card" product="nosdcard" msgid="1112125377088421469">"କୌଣସି USB ଷ୍ଟୋରେଜ୍ ନାହିଁ।"</string>
- <string name="status_no_sd_card" product="default" msgid="5760944071743325592">"କୌଣସି SD କାର୍ଡ ନାହିଁ। ଟ୍ରାନ୍ସଫର୍ କରାଯାଇଥିବା ଫାଇଲ୍ଗୁଡ଼ିକୁ ସେଭ୍ କରିବା ପାଇଁ ଗୋଟିଏ SD କାର୍ଡ ଭର୍ତ୍ତି କରନ୍ତୁ।"</string>
- <string name="status_connection_error" msgid="947681831523219891">"ସଂଯୋଗ ବିଫଳ ହେଲା।"</string>
- <string name="status_protocol_error" msgid="3245444473429269539">"ଅନୁରୋଧକୁ ଠିକ୍ ଭାବେ ସମ୍ଭାଳି ହେବନାହିଁ।"</string>
- <string name="status_unknown_error" msgid="8156660554237824912">"ଅଜଣା ତୃଟି।"</string>
- <string name="btopp_live_folder" msgid="7967791481444474554">"ବ୍ଲୁ-ଟୂଥ୍ ପ୍ରାପ୍ତ ହେଲା"</string>
- <string name="opp_notification_group" msgid="3486303082135789982">"ବ୍ଲୁ-ଟୂଥ୍ ଶେୟାର୍"</string>
- <string name="download_success" msgid="7036160438766730871">"<xliff:g id="FILE_SIZE">%1$s</xliff:g> ପ୍ରାପ୍ତ କରିବା ସମ୍ପୂର୍ଣ୍ଣ ହେଲା।"</string>
- <string name="upload_success" msgid="4014469387779648949">"<xliff:g id="FILE_SIZE">%1$s</xliff:g> ପଠାଇବା ସମ୍ପୂର୍ଣ୍ଣ ହେଲା।"</string>
- <string name="inbound_history_title" msgid="6940914942271327563">"ଇନ୍ବାଉଣ୍ଡ ଟ୍ରାନ୍ସଫର୍"</string>
- <string name="outbound_history_title" msgid="4279418703178140526">"ଆଉଟ୍ବାଉଣ୍ଡ ଟ୍ରାନ୍ସଫର୍"</string>
- <string name="no_transfers" msgid="3482965619151865672">"ଟ୍ରାନ୍ସଫର୍ ହିଷ୍ଟୋରୀ ଖାଲି ଅଛି।"</string>
- <string name="transfer_clear_dlg_msg" msgid="1712376797268438075">"ତାଲିକାରୁ ସମସ୍ତ ଆଇଟମ୍କୁ ଖାଲି କରିଦିଆଯିବ।"</string>
- <string name="outbound_noti_title" msgid="8051906709452260849">"ବ୍ଲୁ-ଟୂଥ୍ ଶେୟାର୍: ପଠାଯାଇଥିବା ଫାଇଲ୍"</string>
- <string name="inbound_noti_title" msgid="4143352641953027595">"ବ୍ଲୁ-ଟୂଥ୍ ଶେୟାର୍: ପ୍ରାପ୍ତ କରାଯାଇଥିବା ଫାଇଲ୍"</string>
- <plurals name="noti_caption_unsuccessful" formatted="false" msgid="2020750076679526122">
- <item quantity="other"><xliff:g id="UNSUCCESSFUL_NUMBER_1">%1$d</xliff:g> ବିଫଳ.</item>
- <item quantity="one"><xliff:g id="UNSUCCESSFUL_NUMBER_0">%1$d</xliff:g> ବିଫଳ.</item>
- </plurals>
- <plurals name="noti_caption_success" formatted="false" msgid="1572472450257645181">
- <item quantity="other"><xliff:g id="SUCCESSFUL_NUMBER_1">%1$d</xliff:g> ସଫଳ, %2$s</item>
- <item quantity="one"><xliff:g id="SUCCESSFUL_NUMBER_0">%1$d</xliff:g> ସଫଳ, %2$s</item>
- </plurals>
- <string name="transfer_menu_clear_all" msgid="790017462957873132">"ତାଲିକା ଖାଲି କରନ୍ତୁ"</string>
- <string name="transfer_menu_open" msgid="3368984869083107200">"ଖୋଲନ୍ତୁ"</string>
- <string name="transfer_menu_clear" msgid="5854038118831427492">"ତାଲିକାରୁ ଖାଲି କରନ୍ତୁ"</string>
- <string name="transfer_clear_dlg_title" msgid="2953444575556460386">"ଖାଲି କରନ୍ତୁ"</string>
- <string name="bluetooth_map_settings_save" msgid="7635491847388074606">"ସେଭ୍ କରନ୍ତୁ"</string>
- <string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"କ୍ୟାନ୍ସଲ୍"</string>
- <string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"ବ୍ଲୁ-ଟୂଥ୍ ମାଧ୍ୟମରେ ଶେୟାର୍ କରିବାକୁ ଚାହୁଁଥିବା ଆକାଉଣ୍ଟଗୁଡ଼ିକୁ ଚୟନ କରନ୍ତୁ। ଆପଣଙ୍କୁ ତଥାପି ସଂଯୋଗ କରୁଥିବା ସମୟରେ ଆକାଉଣ୍ଟଗୁଡ଼ିକ ପ୍ରତି ଯେକୌଣସି ଆକ୍ସେସ୍କୁ ସ୍ୱୀକାର କରିବାକୁ ପଡ଼ିବ।"</string>
- <string name="bluetooth_map_settings_count" msgid="4557473074937024833">"ବଳକା ସ୍ଲଟ୍:"</string>
- <string name="bluetooth_map_settings_app_icon" msgid="7105805610929114707">"ଆପ୍ଲିକେଶନ୍ ଆଇକନ୍"</string>
- <string name="bluetooth_map_settings_title" msgid="7420332483392851321">"ବ୍ଲୁ-ଟୂଥ୍ ମେସେଜ୍ ଶେୟାରିଙ୍ଗ ସେଟିଙ୍ଗ"</string>
- <string name="bluetooth_map_settings_no_account_slots_left" msgid="1796029082612965251">"ଆକାଉଣ୍ଟ ଚୟନ କରାଯାଇପାରିବ ନାହିଁ। 0 ସ୍ଲଟ୍ ବଳକା ରହିଲା"</string>
- <string name="bluetooth_connected" msgid="6718623220072656906">"ବ୍ଲୁ-ଟୂଥ୍ ଅଡିଓ ସଂଯୁକ୍ତ କରାଗଲା"</string>
- <string name="bluetooth_disconnected" msgid="3318303728981478873">"ବ୍ଲୁ-ଟୂଥ୍ ଅଡିଓ ବିଚ୍ଛିନ୍ନ କରାଗଲା"</string>
- <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"ବ୍ଲୁ-ଟୂଥ୍ ଅଡିଓ"</string>
- <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"4GBରୁ ବଡ଼ ଫାଇଲ୍ଗୁଡ଼ିକୁ ଟ୍ରାନ୍ସଫର୍ କରାଯାଇପାରିବ ନାହିଁ"</string>
-</resources>
diff --git a/res/values-or/strings_pbap.xml b/res/values-or/strings_pbap.xml
deleted file mode 100644
index 804f4a2..0000000
--- a/res/values-or/strings_pbap.xml
+++ /dev/null
@@ -1,16 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="pbap_session_key_dialog_title" msgid="3580996574333882561">"%1$s ପାଇଁ ସେସନ୍ କୀ’ ଟାଇପ୍ କରନ୍ତୁ"</string>
- <string name="pbap_session_key_dialog_header" msgid="2772472422782758981">"ବ୍ଲୁ-ଟୂଥ୍ ସେସନ୍ କୀ ଆବଶ୍ୟକ"</string>
- <string name="pbap_acceptance_timeout_message" msgid="1107401415099814293">"%1$s ସହ ଯୋଡ଼ି ହେବାର ସମୟ ସମାପ୍ତ ହୋଇଯାଇଛି"</string>
- <string name="pbap_authentication_timeout_message" msgid="4166979525521902687">"%1$s ସହ ସେସନ୍ କୀ’ ଲେଖିବାର ସମୟ ସମାପ୍ତ ହୋଇଯାଇଛି"</string>
- <string name="auth_notif_ticker" msgid="1575825798053163744">"Obex ପ୍ରମାଣୀକରଣ ଅନୁରୋଧ"</string>
- <string name="auth_notif_title" msgid="7599854855681573258">"ସେସନ୍ କୀ’"</string>
- <string name="auth_notif_message" msgid="6667218116427605038">"%1$s ପାଇଁ ସେସନ୍ କୀ’ ଟାଇପ୍ କରନ୍ତୁ"</string>
- <string name="defaultname" msgid="4821590500649090078">"କାର୍ କିଟ୍"</string>
- <string name="unknownName" msgid="2841414754740600042">"ଅଜଣା ନାମ"</string>
- <string name="localPhoneName" msgid="2349001318925409159">"ମୋର ନାମ"</string>
- <string name="defaultnumber" msgid="8520116145890867338">"000000"</string>
- <string name="pbap_notification_group" msgid="8487669554703627168">"ବ୍ଲୁ-ଟୂଥ୍ ଯୋଗାଯୋଗ ଶେୟାର୍ କରନ୍ତୁ"</string>
-</resources>
diff --git a/res/values-or/strings_pbap_client.xml b/res/values-or/strings_pbap_client.xml
deleted file mode 100644
index 186b23a..0000000
--- a/res/values-or/strings_pbap_client.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="pbap_account_type" msgid="6257077123906049322">"com.android.bluetooth.pbapsink"</string>
-</resources>
diff --git a/res/values-or/strings_sap.xml b/res/values-or/strings_sap.xml
deleted file mode 100644
index f52d36f..0000000
--- a/res/values-or/strings_sap.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="bluetooth_sap_notif_title" msgid="6877860822993195074">"ବ୍ଲୁ-ଟୂଥ୍ SIM ଆକ୍ସେସ୍"</string>
- <string name="bluetooth_sap_notif_ticker" msgid="6807778527893726699">"ବ୍ଲୁ-ଟୂଥ୍ SIM ଆକ୍ସେସ୍"</string>
- <string name="bluetooth_sap_notif_message" msgid="7138657801087500690">"ବିଚ୍ଛିନ୍ନ କରିବା ପାଇଁ କ୍ଲାଏଣ୍ଟଙ୍କୁ ଅନୁରୋଧ କରିବେ?"</string>
- <string name="bluetooth_sap_notif_disconnecting" msgid="819150843490233288">"ବିଚ୍ଛିନ୍ନ କରିବା ପାଇଁ କ୍ଲାଏଣ୍ଟଙ୍କ ଅପେକ୍ଷା କରାଯାଉଛି"</string>
- <string name="bluetooth_sap_notif_disconnect_button" msgid="3678476872583356919">"ବିଚ୍ଛିନ୍ନ କରନ୍ତୁ"</string>
- <string name="bluetooth_sap_notif_force_disconnect_button" msgid="8144086340185532030">"ବିଚ୍ଛିନ୍ନ କରିବା ପାଇଁ ବାଧ୍ୟ କରନ୍ତୁ"</string>
-</resources>
diff --git a/res/values-or/test_strings.xml b/res/values-or/test_strings.xml
deleted file mode 100644
index 693cd65..0000000
--- a/res/values-or/test_strings.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="app_name" msgid="6006644116867509664">"ବ୍ଲୁ-ଟୂଥ୍"</string>
- <string name="insert_record" msgid="1450997173838378132">"ରେକର୍ଡ ଭର୍ତ୍ତି କରନ୍ତୁ"</string>
- <string name="update_record" msgid="2480425402384910635">"ରେକର୍ଡ ସୁନିଶ୍ଚିତ କରନ୍ତୁ"</string>
- <string name="ack_record" msgid="6716152390978472184">"ରେକର୍ଡ ସ୍ୱୀକୃତ କରନ୍ତୁ"</string>
- <string name="deleteAll_record" msgid="4383349788485210582">"ସମସ୍ତ ରେକର୍ଡ ଡିଲିଟ୍ କରନ୍ତୁ"</string>
- <string name="ok_button" msgid="6519033415223065454">"ଠିକ୍ ଅଛି"</string>
- <string name="delete_record" msgid="4645040331967533724">"ରେକର୍ଡ ଡିଲିଟ୍ କରନ୍ତୁ"</string>
- <string name="start_server" msgid="9034821924409165795">"TCP ସର୍ଭର୍ ଆରମ୍ଭ କରନ୍ତୁ"</string>
- <string name="notify_server" msgid="4369106744022969655">"TCP ସର୍ଭର୍କୁ ସୂଚିତ କରନ୍ତୁ"</string>
-</resources>
diff --git a/res/values-pa/strings.xml b/res/values-pa/strings.xml
index 0e6a0f1..4b29594 100644
--- a/res/values-pa/strings.xml
+++ b/res/values-pa/strings.xml
@@ -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..a7f8e9e 100644
--- a/res/values-pl/strings.xml
+++ b/res/values-pl/strings.xml
@@ -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..58460eb 100644
--- a/res/values-pt-rPT/strings.xml
+++ b/res/values-pt-rPT/strings.xml
@@ -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 d024a42..e11244f 100644
--- a/res/values-pt/strings.xml
+++ b/res/values-pt/strings.xml
@@ -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..87cc7e5 100644
--- a/res/values-ro/strings.xml
+++ b/res/values-ro/strings.xml
@@ -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..a6f9c55 100644
--- a/res/values-ru/strings.xml
+++ b/res/values-ru/strings.xml
@@ -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..f2e4077 100644
--- a/res/values-si/strings.xml
+++ b/res/values-si/strings.xml
@@ -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..fd4443e 100644
--- a/res/values-sk/strings.xml
+++ b/res/values-sk/strings.xml
@@ -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..804dfc7 100644
--- a/res/values-sl/strings.xml
+++ b/res/values-sl/strings.xml
@@ -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..f2275e8 100644
--- a/res/values-sq/strings.xml
+++ b/res/values-sq/strings.xml
@@ -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..a80645e 100644
--- a/res/values-sr/strings.xml
+++ b/res/values-sr/strings.xml
@@ -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..83d6c07 100644
--- a/res/values-sv/strings.xml
+++ b/res/values-sv/strings.xml
@@ -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..3a1ab83 100644
--- a/res/values-sw/strings.xml
+++ b/res/values-sw/strings.xml
@@ -122,6 +122,7 @@
<string name="transfer_menu_open" msgid="3368984869083107200">"Fungua"</string>
<string name="transfer_menu_clear" msgid="5854038118831427492">"Futa kutoka orodha"</string>
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"Futa"</string>
+ <string name="bluetooth_a2dp_sink_queue_name" msgid="6864149958708669766">"Inayocheza Sasa"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"Hifadhi"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"Ghairi"</string>
<string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"Chagua akaunti unazotaka kushiriki kupitia Bluetooth. Bado unatakiwa kutoa idhini ya ufikiaji wowote kwenye akaunti yako wakati unaunganisha."</string>
diff --git a/res/values-ta/strings.xml b/res/values-ta/strings.xml
index 47c5d33..6ddf0fc 100644
--- a/res/values-ta/strings.xml
+++ b/res/values-ta/strings.xml
@@ -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..bd724f5 100644
--- a/res/values-te/strings.xml
+++ b/res/values-te/strings.xml
@@ -122,6 +122,7 @@
<string name="transfer_menu_open" msgid="3368984869083107200">"తెరువు"</string>
<string name="transfer_menu_clear" msgid="5854038118831427492">"జాబితా నుండి క్లియర్ చేయి"</string>
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"క్లియర్ చేయి"</string>
+ <string name="bluetooth_a2dp_sink_queue_name" msgid="6864149958708669766">"ప్రస్తుతం ప్లే అవుతున్నవి"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"సేవ్ చేయి"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"రద్దు చేయి"</string>
<string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"మీరు బ్లూటూత్ ద్వారా భాగస్వామ్యం చేయాలనుకునే ఖాతాలను ఎంచుకోండి. మీరు ఇప్పటికీ కనెక్ట్ చేస్తున్నప్పుడు ఖాతాలకు అందించే ఏ ప్రాప్యతనైనా ఆమోదించాల్సి ఉంటుంది."</string>
diff --git a/res/values-th/strings.xml b/res/values-th/strings.xml
index 8163820..cc52fb7 100644
--- a/res/values-th/strings.xml
+++ b/res/values-th/strings.xml
@@ -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..a32c64f 100644
--- a/res/values-tl/strings.xml
+++ b/res/values-tl/strings.xml
@@ -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..d15991d 100644
--- a/res/values-tr/strings.xml
+++ b/res/values-tr/strings.xml
@@ -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..09720c3 100644
--- a/res/values-uk/strings.xml
+++ b/res/values-uk/strings.xml
@@ -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..ad96ddc 100644
--- a/res/values-ur/strings.xml
+++ b/res/values-ur/strings.xml
@@ -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 855cab3..88226e2 100644
--- a/res/values-uz/strings.xml
+++ b/res/values-uz/strings.xml
@@ -52,7 +52,7 @@
<string name="download_line4" msgid="8535996869722666525"></string>
<string name="download_line5" msgid="3069560415845295386">"Fayl qabul qilinmoqda…"</string>
<string name="download_cancel" msgid="9177305996747500768">"To‘xtatish"</string>
- <string name="download_ok" msgid="5000360731674466039">"Yashirish"</string>
+ <string name="download_ok" msgid="5000360731674466039">"Berkitish"</string>
<string name="incoming_line1" msgid="2127419875681087545">"Kimdan"</string>
<string name="incoming_line2" msgid="3348994249285315873">"Fayl nomi"</string>
<string name="incoming_line3" msgid="7954237069667474024">"Hajmi"</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..6b538f1 100644
--- a/res/values-vi/strings.xml
+++ b/res/values-vi/strings.xml
@@ -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..efac4c5 100644
--- a/res/values-zh-rCN/strings.xml
+++ b/res/values-zh-rCN/strings.xml
@@ -122,6 +122,7 @@
<string name="transfer_menu_open" msgid="3368984869083107200">"打开"</string>
<string name="transfer_menu_clear" msgid="5854038118831427492">"从列表中清除"</string>
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"清除"</string>
+ <string name="bluetooth_a2dp_sink_queue_name" msgid="6864149958708669766">"闻曲知音"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"保存"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"取消"</string>
<string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"选择您要通过蓝牙共享的帐号。连接时,您仍必须接受所有帐号访问请求。"</string>
diff --git a/res/values-zh-rHK/strings.xml b/res/values-zh-rHK/strings.xml
index e2bba89..e76c55d 100644
--- a/res/values-zh-rHK/strings.xml
+++ b/res/values-zh-rHK/strings.xml
@@ -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..d52b973 100644
--- a/res/values-zh-rTW/strings.xml
+++ b/res/values-zh-rTW/strings.xml
@@ -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..5f67683 100644
--- a/res/values-zu/strings.xml
+++ b/res/values-zu/strings.xml
@@ -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..ba9bb0d 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -32,7 +32,7 @@
<bool name="profile_supported_pbapclient">false</bool>
<bool name="profile_supported_mapmce">false</bool>
<bool name="profile_supported_hid_device">true</bool>
- <bool name="profile_supported_hearing_aid">false</bool>
+ <bool name="profile_supported_hearing_aid">true</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
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/a2dp/A2dpNativeInterface.java b/src/com/android/bluetooth/a2dp/A2dpNativeInterface.java
index e01b325..04d3b37 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.
diff --git a/src/com/android/bluetooth/a2dp/A2dpService.java b/src/com/android/bluetooth/a2dp/A2dpService.java
index 64a9cad..6e336b7 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;
@@ -31,22 +30,20 @@
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 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.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,10 +57,8 @@
private static A2dpService sA2dpService;
- private BluetoothAdapter mAdapter;
private AdapterService mAdapterService;
private HandlerThread mStateMachinesThread;
- private Avrcp mAvrcp;
@VisibleForTesting
A2dpNativeInterface mA2dpNativeInterface;
@@ -102,10 +97,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 +111,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 +139,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;
@@ -201,19 +191,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 +244,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 +339,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.
@@ -381,7 +380,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),
@@ -393,9 +398,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;
}
}
}
@@ -433,11 +439,9 @@
private void removeActiveDevice(boolean forceStopPlayingAudio) {
BluetoothDevice previousActiveDevice = mActiveDevice;
synchronized (mStateMachines) {
- // Clear the active device
- mActiveDevice = null;
// 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;
@@ -477,10 +481,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);
@@ -506,10 +506,9 @@
codecStatus = sm.getCodecStatus();
boolean deviceChanged = !Objects.equals(device, mActiveDevice);
- mActiveDevice = device;
// 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) {
@@ -532,8 +531,6 @@
int rememberedVolume = -1;
if (AvrcpTargetService.get() != null) {
- AvrcpTargetService.get().volumeDeviceSwitched(mActiveDevice);
-
rememberedVolume = AvrcpTargetService.get()
.getRememberedVolumeForDevice(mActiveDevice);
}
@@ -591,11 +588,12 @@
return priority;
}
- /* 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.
@@ -603,18 +601,6 @@
AvrcpTargetService.get().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) {
@@ -844,9 +830,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 (AvrcpTargetService.get() != null) {
+ if (mActiveDevice != null) {
+ AvrcpTargetService.get().storeVolumeForDevice(mActiveDevice);
+ }
+
+ AvrcpTargetService.get().volumeDeviceSwitched(device);
+ }
+
+ mActiveDevice = device;
}
Intent intent = new Intent(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED);
@@ -1218,8 +1219,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..4cc04e6 100644
--- a/src/com/android/bluetooth/a2dp/A2dpStateMachine.java
+++ b/src/com/android/bluetooth/a2dp/A2dpStateMachine.java
@@ -53,10 +53,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;
@@ -130,7 +130,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 +157,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);
}
@@ -559,7 +557,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 +568,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);
}
@@ -752,7 +748,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..17095c0 100644
--- a/src/com/android/bluetooth/a2dpsink/A2dpSinkService.java
+++ b/src/com/android/bluetooth/a2dpsink/A2dpSinkService.java
@@ -25,8 +25,8 @@
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.avrcpcontroller.BluetoothMediaBrowserService;
import com.android.bluetooth.btservice.ProfileService;
import java.util.ArrayList;
@@ -54,7 +54,7 @@
Log.d(TAG, "start()");
}
// Start the media browser service.
- Intent startIntent = new Intent(this, A2dpMediaBrowserService.class);
+ Intent startIntent = new Intent(this, BluetoothMediaBrowserService.class);
startService(startIntent);
mStateMachine = A2dpSinkStateMachine.make(this, this);
setA2dpSinkService(this);
@@ -70,7 +70,7 @@
if (mStateMachine != null) {
mStateMachine.doQuit();
}
- Intent stopIntent = new Intent(this, A2dpMediaBrowserService.class);
+ Intent stopIntent = new Intent(this, BluetoothMediaBrowserService.class);
stopService(stopIntent);
return true;
}
diff --git a/src/com/android/bluetooth/a2dpsink/A2dpSinkStateMachine.java b/src/com/android/bluetooth/a2dpsink/A2dpSinkStateMachine.java
index fb64318..58a6109 100644
--- a/src/com/android/bluetooth/a2dpsink/A2dpSinkStateMachine.java
+++ b/src/com/android/bluetooth/a2dpsink/A2dpSinkStateMachine.java
@@ -74,6 +74,7 @@
private static final int IS_INVALID_DEVICE = 0;
private static final int IS_VALID_DEVICE = 1;
+ private static final int CONNECT_TIMEOUT_MS = 5000;
public static final int AVRC_ID_PLAY = 0x44;
public static final int AVRC_ID_PAUSE = 0x46;
public static final int KEY_STATE_PRESSED = 0;
@@ -177,10 +178,11 @@
}
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, "mTargetDevice: " + mTargetDevice);
ProfileService.println(sb, "mIncomingDevice: " + mIncomingDevice);
- ProfileService.println(sb, "StateMachine: " + this.toString());
}
private class Disconnected extends State {
@@ -214,9 +216,6 @@
mTargetDevice = device;
transitionTo(mPending);
}
- // TODO(BT) remove CONNECT_TIMEOUT when the stack
- // sends back events consistently
- sendMessageDelayed(CONNECT_TIMEOUT, 30000);
break;
case DISCONNECT:
// ignore
@@ -297,6 +296,7 @@
@Override
public void enter() {
log("Enter Pending: " + getCurrentMessage().what);
+ sendMessageDelayed(CONNECT_TIMEOUT, CONNECT_TIMEOUT_MS);
}
@Override
@@ -329,7 +329,6 @@
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:
@@ -346,6 +345,11 @@
return retValue;
}
+ @Override
+ public void exit() {
+ removeMessages(CONNECT_TIMEOUT);
+ }
+
// in Pending state
private void processConnectionEvent(BluetoothDevice device, int state) {
log("processConnectionEvent state " + state);
@@ -621,10 +625,20 @@
switch (state) {
case AUDIO_STATE_STARTED:
mStreaming.obtainMessage(A2dpSinkStreamHandler.SRC_STR_START).sendToTarget();
+ if (mPlayingDevice == null) {
+ mPlayingDevice = device;
+ broadcastAudioState(device, BluetoothA2dpSink.STATE_NOT_PLAYING,
+ BluetoothA2dpSink.STATE_PLAYING);
+ }
break;
case AUDIO_STATE_REMOTE_SUSPEND:
case AUDIO_STATE_STOPPED:
mStreaming.obtainMessage(A2dpSinkStreamHandler.SRC_STR_STOP).sendToTarget();
+ if (mPlayingDevice != null) {
+ broadcastAudioState(device, BluetoothA2dpSink.STATE_PLAYING,
+ BluetoothA2dpSink.STATE_NOT_PLAYING);
+ mPlayingDevice = null;
+ }
break;
default:
loge("Audio State Device: " + device + " bad state: " + state);
@@ -660,7 +674,7 @@
}
if (currentState == mConnected) {
- if (mCurrentDevice.equals(device)) {
+ if (mCurrentDevice != null && mCurrentDevice.equals(device)) {
return BluetoothProfile.STATE_CONNECTED;
}
return BluetoothProfile.STATE_DISCONNECTED;
diff --git a/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandler.java b/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandler.java
index f442c49..6fae8dc 100644
--- a/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandler.java
+++ b/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandler.java
@@ -29,6 +29,7 @@
import com.android.bluetooth.R;
import com.android.bluetooth.avrcpcontroller.AvrcpControllerService;
+import com.android.bluetooth.hfpclient.HeadsetClientService;
import java.util.List;
@@ -56,7 +57,7 @@
// 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 +69,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;
@@ -111,7 +112,6 @@
}
switch (message.what) {
case SRC_STR_START:
- mStreamAvailable = true;
// Always request audio focus if on TV.
if (isTvDevice()) {
if (mAudioFocus == AudioManager.AUDIOFOCUS_NONE) {
@@ -121,15 +121,11 @@
// Audio stream has started, stop it if we don't have focus.
if (mAudioFocus == AudioManager.AUDIOFOCUS_NONE) {
sendAvrcpPause();
- } else {
- startAvrcpUpdates();
}
break;
case SRC_STR_STOP:
// Audio stream has stopped, maintain focus but stop avrcp updates.
- mStreamAvailable = false;
- stopAvrcpUpdates();
break;
case SNK_PLAY:
@@ -137,35 +133,32 @@
if (mAudioFocus == AudioManager.AUDIOFOCUS_NONE) {
requestAudioFocus();
}
- startAvrcpUpdates();
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();
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:
@@ -176,7 +169,6 @@
case DISCONNECT:
// Remote device has disconnected, restore everything to default state.
- stopAvrcpUpdates();
mSentPause = false;
break;
@@ -184,11 +176,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 +203,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 +216,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);
}
@@ -259,7 +250,6 @@
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;
}
@@ -286,34 +276,6 @@
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.");
- }
- }
-
private void sendAvrcpPause() {
// Since AVRCP gets started after A2DP we may need to request it later in cycle.
AvrcpControllerService avrcpService = AvrcpControllerService.getAvrcpControllerService();
@@ -366,6 +328,22 @@
}
}
+ private boolean inCallFromStreamingDevice() {
+ AvrcpControllerService avrcpService = AvrcpControllerService.getAvrcpControllerService();
+ BluetoothDevice targetDevice = null;
+ if (avrcpService != null) {
+ List<BluetoothDevice> connectedDevices = avrcpService.getConnectedDevices();
+ if (!connectedDevices.isEmpty()) {
+ targetDevice = connectedDevices.get(0);
+ }
+ }
+ HeadsetClientService headsetClientService = HeadsetClientService.getHeadsetClientService();
+ if (targetDevice != null && headsetClientService != null) {
+ return headsetClientService.getCurrentCalls(targetDevice).size() > 0;
+ }
+ return false;
+ }
+
synchronized int getAudioFocus() {
return mAudioFocus;
}
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 99%
rename from src/com/android/bluetooth/newavrcp/AvrcpTargetService.java
rename to src/com/android/bluetooth/avrcp/AvrcpTargetService.java
index c7029ce..5ab3996 100644
--- a/src/com/android/bluetooth/newavrcp/AvrcpTargetService.java
+++ b/src/com/android/bluetooth/avrcp/AvrcpTargetService.java
@@ -43,7 +43,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";
@@ -140,11 +140,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 +151,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;
diff --git a/src/com/android/bluetooth/newavrcp/AvrcpVolumeManager.java b/src/com/android/bluetooth/avrcp/AvrcpVolumeManager.java
similarity index 91%
rename from src/com/android/bluetooth/newavrcp/AvrcpVolumeManager.java
rename to src/com/android/bluetooth/avrcp/AvrcpVolumeManager.java
index 4b87fac..24a1755 100644
--- a/src/com/android/bluetooth/newavrcp/AvrcpVolumeManager.java
+++ b/src/com/android/bluetooth/avrcp/AvrcpVolumeManager.java
@@ -32,15 +32,16 @@
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
- public static final String VOLUME_MAP = "bluetooth_volume_map";
- public static final String VOLUME_BLACKLIST = "absolute_volume_blacklist";
- public static final int AVRCP_MAX_VOL = 127;
- public static int sDeviceMaxVolume = 0;
- public static final int STREAM_MUSIC = AudioManager.STREAM_MUSIC;
+ private static final String VOLUME_MAP = "bluetooth_volume_map";
+ private static final String VOLUME_BLACKLIST = "absolute_volume_blacklist";
+ private static final int AVRCP_MAX_VOL = 127;
+ private static final int STREAM_MUSIC = AudioManager.STREAM_MUSIC;
+ private static int sDeviceMaxVolume = 0;
+ private static int sNewDeviceVolume = 0;
Context mContext;
AudioManager mAudioManager;
@@ -72,10 +73,9 @@
mAudioManager.avrcpSupportsAbsoluteVolume(device.getAddress(), mDeviceMap.get(device));
// Get the current system volume and try to get the preference volume
- int currVolume = mAudioManager.getStreamVolume(STREAM_MUSIC);
- int savedVolume = getVolume(device, currVolume);
+ int savedVolume = getVolume(device, sNewDeviceVolume);
- d("switchVolumeDevice: currVolume=" + currVolume + " savedVolume=" + savedVolume);
+ d("switchVolumeDevice: savedVolume=" + savedVolume);
// If absolute volume for the device is supported, set the volume for the device
if (mDeviceMap.get(device)) {
@@ -91,6 +91,7 @@
mAudioManager = audioManager;
mNativeInterface = nativeInterface;
sDeviceMaxVolume = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
+ sNewDeviceVolume = sDeviceMaxVolume / 2;
mAudioManager.registerAudioDeviceCallback(this, null);
@@ -114,7 +115,7 @@
volumeMapEditor.apply();
}
- void storeVolumeForDevice(BluetoothDevice device) {
+ synchronized void storeVolumeForDevice(BluetoothDevice device) {
SharedPreferences.Editor pref = getVolumeMap().edit();
int storeVolume = mAudioManager.getStreamVolume(STREAM_MUSIC);
Log.i(TAG, "storeVolume: Storing stream volume level for device " + device
@@ -127,7 +128,7 @@
pref.apply();
}
- int getVolume(@NonNull BluetoothDevice device, int defaultValue) {
+ 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;
@@ -194,7 +195,7 @@
mCurrentDevice = device;
}
- void deviceDisconnected(@NonNull BluetoothDevice device) {
+ synchronized void deviceDisconnected(@NonNull BluetoothDevice device) {
d("deviceDisconnected: device=" + device);
mDeviceMap.remove(device);
}
diff --git a/src/com/android/bluetooth/newavrcp/BrowsablePlayerConnector.java b/src/com/android/bluetooth/avrcp/BrowsablePlayerConnector.java
similarity index 98%
rename from src/com/android/bluetooth/newavrcp/BrowsablePlayerConnector.java
rename to src/com/android/bluetooth/avrcp/BrowsablePlayerConnector.java
index ab23dbf..99e9976 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
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 99%
rename from src/com/android/bluetooth/newavrcp/MediaPlayerList.java
rename to src/com/android/bluetooth/avrcp/MediaPlayerList.java
index 74ea039..6148d4d 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;
@@ -207,8 +207,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 96%
rename from src/com/android/bluetooth/newavrcp/MediaPlayerWrapper.java
rename to src/com/android/bluetooth/avrcp/MediaPlayerWrapper.java
index d27054d..883b6ef 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,7 +39,7 @@
* with that.
*/
class MediaPlayerWrapper {
- private static final String TAG = "NewAvrcpMediaPlayerWrapper";
+ private static final String TAG = "AvrcpMediaPlayerWrapper";
private static final boolean DEBUG = true;
static boolean sTesting = false;
@@ -53,7 +54,6 @@
private final Object mCallbackLock = new Object();
private Callback mRegisteredCallback = null;
-
protected MediaPlayerWrapper() {
mCurrentData = new MediaData(null, null, null);
}
@@ -122,16 +122,6 @@
}
Metadata getCurrentMetadata() {
- // Try to use the now playing list if the information exists.
- if (getActiveQueueID() != -1) {
- for (Metadata data : getCurrentQueue()) {
- if (data.mediaId.equals(Util.NOW_PLAYING_PREFIX + getActiveQueueID())) {
- d("getCurrentMetadata: Using playlist data: " + data.toString());
- return data.clone();
- }
- }
- }
-
return Util.toMetadata(getMetadata());
}
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..f6ce92f 100644
--- a/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerService.java
+++ b/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerService.java
@@ -172,8 +172,8 @@
/* 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;
+ public static final byte FOLDER_NAVIGATION_DIRECTION_UP = 0x00;
+ public static final byte FOLDER_NAVIGATION_DIRECTION_DOWN = 0x01;
/* Folder/Media Item scopes.
* Keep in sync with AVRCP 1.6 sec. 6.10.1
@@ -316,16 +316,6 @@
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");
@@ -398,77 +388,12 @@
}
/**
- * 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
+ * Retreive the contents of the directory from the associated bluetooth device.
*/
- 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 List<MediaItem> getContents(BluetoothDevice device, String parentMediaId) {
+ return mAvrcpCtSm.getContents(parentMediaId);
}
-
- 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) {
@@ -499,100 +424,7 @@
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);
@@ -884,7 +716,7 @@
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;
@@ -1031,7 +863,7 @@
"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;
}
@@ -1063,6 +895,23 @@
mAvrcpCtSm.sendMessage(msg);
}
+ private void handleAddressedPlayerChanged(int id) {
+ if (DBG) {
+ Log.d(TAG, "handleAddressedPlayerChanged id: " + id);
+ }
+ Message msg = mAvrcpCtSm.obtainMessage(
+ AvrcpControllerStateMachine.MESSAGE_PROCESS_ADDRESSED_PLAYER_CHANGED, id);
+ mAvrcpCtSm.sendMessage(msg);
+ }
+
+ private void handleNowPlayingContentChanged() {
+ if (DBG) {
+ Log.d(TAG, "handleNowPlayingContentChanged");
+ }
+ mAvrcpCtSm.sendMessage(
+ AvrcpControllerStateMachine.MESSAGE_PROCESS_NOW_PLAYING_CONTENTS_CHANGED);
+ }
+
@Override
public void dump(StringBuilder sb) {
super.dump(sb);
diff --git a/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java b/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java
index 3077664..f13f0c4 100644
--- a/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java
+++ b/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java
@@ -17,7 +17,6 @@
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;
@@ -25,13 +24,12 @@
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.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.Utils;
@@ -53,12 +51,8 @@
// 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;
// commands from native layer
static final int MESSAGE_PROCESS_SET_ABS_VOL_CMD = 103;
@@ -73,10 +67,8 @@
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;
-
- // commands from A2DP sink
- static final int MESSAGE_STOP_METADATA_BROADCASTS = 201;
- static final int MESSAGE_START_METADATA_BROADCASTS = 202;
+ static final int MESSAGE_PROCESS_ADDRESSED_PLAYER_CHANGED = 115;
+ static final int MESSAGE_PROCESS_NOW_PLAYING_CONTENTS_CHANGED = 116;
// commands for connection
static final int MESSAGE_PROCESS_RC_FEATURES = 301;
@@ -84,8 +76,6 @@
static final int MESSAGE_PROCESS_BROWSE_CONNECTION_CHANGE = 303;
// 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;
static final int MESSAGE_INTERNAL_ABS_VOL_TIMEOUT = 404;
@@ -117,15 +107,10 @@
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;
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();
// APIs exist to access these so they must be thread safe
@@ -136,9 +121,8 @@
// Only accessed from State Machine processMessage
private int mVolumeChangedNotificationsToIgnore = 0;
private int mPreviousPercentageVol = -1;
-
- // Depth from root of current browsing. This can be used to move to root directly.
- private int mBrowseDepth = 0;
+ private int mAddressedPlayerID = -1;
+ private SparseArray<AvrcpPlayer> mAvailablePlayerList = new SparseArray<AvrcpPlayer>();
// Browse tree.
private BrowseTree mBrowseTree = new BrowseTree();
@@ -155,12 +139,8 @@
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();
addState(mDisconnected);
addState(mConnected);
@@ -169,12 +149,8 @@
// 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);
addState(mGetFolderList, mConnected);
- addState(mGetPlayerListing, mConnected);
- addState(mMoveToRoot, mConnected);
setInitialState(mDisconnected);
}
@@ -187,7 +163,7 @@
switch (msg.what) {
case MESSAGE_PROCESS_CONNECTION_CHANGE:
if (msg.arg1 == BluetoothProfile.STATE_CONNECTED) {
- mBrowseTree.init();
+ mBrowseTree = new BrowseTree();
transitionTo(mConnected);
BluetoothDevice rtDevice = (BluetoothDevice) msg.obj;
synchronized (mLock) {
@@ -239,68 +215,36 @@
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);
+ if (DBG) Log.d(TAG, "Message_GET_FOLDER_LIST" + (String) msg.obj);
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();
+ BrowseTree.BrowseNode itemToPlay =
+ mBrowseTree.findBrowseNodeByID(playItemUid);
if (DBG) {
Log.d(TAG, "currBrPlayer " + currBrPlayer + " currAddrPlayer "
+ currAddrPlayer);
}
-
- if (currBrPlayer == null || currBrPlayer.equals(currAddrPlayer)) {
+ if (currBrPlayer == null
+ || currBrPlayer.equals(currAddrPlayer)
+ || scope == AvrcpControllerService.BROWSE_SCOPE_NOW_PLAYING) {
// 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);
+ AvrcpControllerService.hexStringToByteUID(playItemUid), 0);
} else {
// Send out the request for setting addressed player.
AvrcpControllerService.setAddressedPlayerNative(
@@ -313,22 +257,6 @@
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) {
@@ -366,9 +294,6 @@
} 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);
}
@@ -434,9 +359,12 @@
case MESSAGE_PROCESS_TRACK_CHANGED:
// Music start playing automatically and update Metadata
- mAddressedPlayer.updateCurrentTrack((TrackInfo) msg.obj);
- broadcastMetaDataChanged(
- mAddressedPlayer.getCurrentTrack().getMediaMetaData());
+ boolean updateTrack =
+ mAddressedPlayer.updateCurrentTrack((TrackInfo) msg.obj);
+ if (updateTrack) {
+ broadcastMetaDataChanged(
+ mAddressedPlayer.getCurrentTrack().getMediaMetaData());
+ }
break;
case MESSAGE_PROCESS_PLAY_POS_CHANGED:
@@ -449,7 +377,6 @@
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
@@ -458,7 +385,27 @@
}
break;
+ case MESSAGE_PROCESS_ADDRESSED_PLAYER_CHANGED:
+ mAddressedPlayerID = msg.arg1;
+ if (DBG) Log.d(TAG, "AddressedPlayer = " + mAddressedPlayerID);
+ AvrcpPlayer updatedPlayer = mAvailablePlayerList.get(mAddressedPlayerID);
+ if (updatedPlayer != null) {
+ mAddressedPlayer = updatedPlayer;
+ if (DBG) Log.d(TAG, "AddressedPlayer = " + mAddressedPlayer.getName());
+ } else {
+ mBrowseTree.mRootNode.setCached(false);
+ }
+ sendMessage(MESSAGE_PROCESS_SET_ADDRESSED_PLAYER);
+ break;
+
+ case MESSAGE_PROCESS_NOW_PLAYING_CONTENTS_CHANGED:
+ mBrowseTree.mNowPlayingNode.setCached(false);
+ mGetFolderList.setFolder(mBrowseTree.mNowPlayingNode.getID());
+ transitionTo(mGetFolderList);
+ break;
+
default:
+ Log.d(TAG, "Unhandled message" + msg.what);
return false;
}
}
@@ -466,133 +413,31 @@
}
}
- // 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;
- }
-
- @Override
- public void enter() {
- super.enter();
- mTmpIncrDirection = -1;
- }
-
- @Override
- public boolean processMessage(Message msg) {
- if (DBG) Log.d(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:
- 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 state.");
- }
- deferMessage(msg);
- }
- return true;
- }
- }
-
// 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";
- 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() {
// Setup the timeouts.
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;
+ mAbort = false;
+ if (mBrowseNode == null) {
+ transitionTo(mConnected);
+ } else {
+ navigateToFolderOrRetrieve(mBrowseNode);
+ }
}
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);
- }
- mStartInd = startInd;
- mEndInd = Math.min(endInd, MAX_FOLDER_ITEMS);
+ mBrowseNode = mBrowseTree.findBrowseNodeByID(id);
}
@Override
@@ -601,37 +446,75 @@
switch (msg.what) {
case MESSAGE_PROCESS_GET_FOLDER_ITEMS:
ArrayList<MediaItem> folderList = (ArrayList<MediaItem>) msg.obj;
- mFolderList.addAll(folderList);
+ int endIndicator = mBrowseNode.getExpectedChildren() - 1;
if (DBG) {
Log.d(STATE_TAG,
- "Start " + mStartInd + " End " + mEndInd + " Curr " + mCurrInd
+ " End " + endIndicator
+ " received " + folderList.size());
}
- mCurrInd += folderList.size();
// Always update the node so that the user does not wait forever
// for the list to populate.
- sendFolderBroadcastAndUpdateNode();
+ mBrowseNode.addChildren(folderList);
+ broadcastFolderList(mBrowseNode.getID());
- 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);
+ broadcastFolderList(BrowseTree.ROOT);
+ }
+ 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();
+ broadcastFolderList(mBrowseNode.getID());
transitionTo(mConnected);
break;
@@ -639,16 +522,24 @@
// 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);
+ broadcastFolderList(mBrowseNode.getID());
transitionTo(mConnected);
break;
- case MESSAGE_CHANGE_FOLDER_PATH:
+ case MESSAGE_GET_FOLDER_LIST:
+ if (!mBrowseNode.equals((String) msg.obj)) {
+ mAbort = true;
+ deferMessage(msg);
+ Log.d(STATE_TAG, "Go Get Another Directory");
+ } else {
+ Log.d(STATE_TAG, "Get The Same Directory, ignore");
+ }
+ break;
+
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;
+ mAbort = true;
deferMessage(msg);
break;
@@ -660,8 +551,6 @@
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.
@@ -674,35 +563,15 @@
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;
- }
- 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);
- }
- }
-
- 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()
+ + GET_FOLDER_ITEMS_PAGINATION_SIZE) - 1;
+ switch (target.getScope()) {
+ case AvrcpControllerService.BROWSE_SCOPE_PLAYER_LIST:
+ AvrcpControllerService.getPlayerListNative(mRemoteDevice.getBluetoothAddress(),
+ start, end);
+ break;
case AvrcpControllerService.BROWSE_SCOPE_NOW_PLAYING:
AvrcpControllerService.getNowPlayingListNative(
mRemoteDevice.getBluetoothAddress(), start, end);
@@ -712,198 +581,67 @@
start, end);
break;
default:
- Log.e(STATE_TAG, "Scope " + mScope + " cannot be handled here.");
+ Log.e(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);
- transitionTo(mConnected);
- break;
-
- 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;
-
- 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 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) {
+ /* 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);
if (DBG) {
- Log.d(STATE_TAG, "processMessage " + msg.what + " browse depth " + mBrowseDepth);
+ Log.d(TAG, "NAVIGATING From " + mBrowseTree.getCurrentBrowsedFolder().toString());
+ Log.d(TAG, "NAVIGATING Toward " + target.toString());
}
- 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);
+ if (mNextStep == null) {
+ sendMessage(MESSAGE_INTERNAL_CMD_TIMEOUT);
+ } else if (target.equals(mBrowseTree.mNowPlayingNode)
+ || target.equals(mBrowseTree.mRootNode)
+ || mNextStep.equals(mBrowseTree.getCurrentBrowsedFolder())) {
+ fetchContents(mNextStep);
+ } else if (mNextStep.isPlayer()) {
+ if (DBG) Log.d(TAG, "NAVIGATING Player " + mNextStep.toString());
+ if (mNextStep.isBrowsable()) {
+ AvrcpControllerService.setBrowsedPlayerNative(
+ mRemoteDevice.getBluetoothAddress(), mNextStep.getPlayerID());
+ } else {
+ if (DBG) Log.d(TAG, "Player doesn't support browsing");
+ mNextStep.setCached(true);
+ broadcastFolderList(mNextStep.getID());
transitionTo(mConnected);
- break;
+ }
+ } else if (mNextStep.equals(mBrowseTree.mNavigateUpNode)) {
+ if (DBG) Log.d(TAG, "NAVIGATING UP " + mNextStep.toString());
+ mNextStep = mBrowseTree.getCurrentBrowsedFolder().getParent();
+ mBrowseTree.getCurrentBrowsedFolder().setCached(false);
- 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;
+ AvrcpControllerService.changeFolderPathNative(
+ mRemoteDevice.getBluetoothAddress(),
+ AvrcpControllerService.FOLDER_NAVIGATION_DIRECTION_UP,
+ AvrcpControllerService.hexStringToByteUID(null));
- default:
- if (DBG) Log.d(STATE_TAG, "deferring message " + msg.what + " to connected!");
- deferMessage(msg);
+ } else {
+ if (DBG) Log.d(TAG, "NAVIGATING DOWN " + mNextStep.toString());
+ AvrcpControllerService.changeFolderPathNative(
+ mRemoteDevice.getBluetoothAddress(),
+ AvrcpControllerService.FOLDER_NAVIGATION_DIRECTION_DOWN,
+ AvrcpControllerService.hexStringToByteUID(mNextStep.getFolderUID()));
}
- 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;
+ public void exit() {
+ mBrowseNode = null;
+ super.exit();
}
}
@@ -948,8 +686,6 @@
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.
@@ -999,7 +735,11 @@
}
void dump(StringBuilder sb) {
- ProfileService.println(sb, "StateMachine: " + this.toString());
+ if (mRemoteDevice == null) return;
+ BluetoothDevice device = mRemoteDevice.mBTDevice;
+ if (device == null) return;
+ ProfileService.println(sb, "mCurrentDevice: " + device.getAddress() + "("
+ + device.getName() + ") " + this.toString());
}
MediaMetadata getCurrentMetaData() {
@@ -1034,110 +774,26 @@
}
}
- // 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;
- }
+ List<MediaItem> getContents(String uid) {
+ BrowseTree.BrowseNode currentNode = mBrowseTree.findBrowseNodeByID(uid);
- 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.");
+ if (DBG) Log.d(TAG, "getContents(" + uid + ") currentNode = " + currentNode);
+ if (currentNode != null) {
+ if (!currentNode.isCached()) {
+ sendMessage(AvrcpControllerStateMachine.MESSAGE_GET_FOLDER_LIST, uid);
}
- 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;
+ return currentNode.getContents();
}
-
- 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);
- }
+ return null;
}
public void fetchAttrAndPlayItem(String uid) {
- BrowseTree.BrowseNode currItem = mBrowseTree.findFolderByIDLocked(uid);
+ BrowseTree.BrowseNode currItem = mBrowseTree.findBrowseNodeByID(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
+ int scope = currItem.getParent().isNowPlaying()
+ ? AvrcpControllerService.BROWSE_SCOPE_NOW_PLAYING
: AvrcpControllerService.BROWSE_SCOPE_VFS;
Message msg =
obtainMessage(AvrcpControllerStateMachine.MESSAGE_FETCH_ATTR_AND_PLAY_ITEM,
@@ -1153,14 +809,18 @@
Log.d(TAG, " broadcastMetaDataChanged = " + metadata.getDescription());
}
mContext.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
+
}
- private void broadcastFolderList(String id, ArrayList<MediaItem> items) {
+ private void broadcastFolderList(String id) {
Intent intent = new Intent(AvrcpControllerService.ACTION_FOLDER_LIST);
- if (VDBG) Log.d(TAG, "broadcastFolderList id " + id + " items " + items);
+ if (VDBG) Log.d(TAG, "broadcastFolderList id " + id);
intent.putExtra(AvrcpControllerService.EXTRA_FOLDER_ID, id);
- intent.putParcelableArrayListExtra(AvrcpControllerService.EXTRA_FOLDER_LIST, items);
- mContext.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
+ BluetoothMediaBrowserService bluetoothMediaBrowserService =
+ BluetoothMediaBrowserService.getBluetoothMediaBrowserService();
+ if (bluetoothMediaBrowserService != null) {
+ bluetoothMediaBrowserService.processInternalEvent(intent);
+ }
}
private void broadcastPlayBackStateChanged(PlaybackState state) {
@@ -1257,33 +917,4 @@
}
return str;
}
-
- 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();
- }
}
diff --git a/src/com/android/bluetooth/avrcpcontroller/AvrcpPlayer.java b/src/com/android/bluetooth/avrcpcontroller/AvrcpPlayer.java
index d9e4924..e3cc2db 100644
--- a/src/com/android/bluetooth/avrcpcontroller/AvrcpPlayer.java
+++ b/src/com/android/bluetooth/avrcpcontroller/AvrcpPlayer.java
@@ -19,30 +19,50 @@
import android.media.session.PlaybackState;
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 = false;
+ private static final boolean VDBG = false;
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 int mId;
private String mName = "";
private int mPlayerType;
+ private byte[] mPlayerFeatures;
+ private long mAvailableActions;
private TrackInfo mCurrentTrack = new TrackInfo();
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;
}
- 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();
}
public int getId() {
@@ -65,6 +85,16 @@
mPlayStatus = playStatus;
}
+ 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);
@@ -87,14 +117,48 @@
speed = -3;
break;
}
- return new PlaybackState.Builder().setState(mPlayStatus, position, speed).build();
+ return new PlaybackState.Builder().setState(mPlayStatus, position, speed)
+ .setActions(mAvailableActions).setActiveQueueItemId(mCurrentTrack.mTrackNum - 1)
+ .build();
}
- public synchronized void updateCurrentTrack(TrackInfo update) {
+ public synchronized boolean updateCurrentTrack(TrackInfo update) {
+ if (update != null && mCurrentTrack != null
+ && update.toString().equals(mCurrentTrack.toString())) {
+ if (DBG) Log.d(TAG, "Update same as original");
+ return false;
+ }
+ if (VDBG) Log.d(TAG, "Track Changed Was:" + mCurrentTrack + "now " + update);
mCurrentTrack = update;
+ return true;
}
public synchronized TrackInfo 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/a2dpsink/mbs/A2dpMediaBrowserService.java b/src/com/android/bluetooth/avrcpcontroller/BluetoothMediaBrowserService.java
similarity index 79%
rename from src/com/android/bluetooth/a2dpsink/mbs/A2dpMediaBrowserService.java
rename to src/com/android/bluetooth/avrcpcontroller/BluetoothMediaBrowserService.java
index 739f17c..323dbfb 100644
--- a/src/com/android/bluetooth/a2dpsink/mbs/A2dpMediaBrowserService.java
+++ b/src/com/android/bluetooth/avrcpcontroller/BluetoothMediaBrowserService.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.bluetooth.a2dpsink.mbs;
+package com.android.bluetooth.avrcpcontroller;
import android.bluetooth.BluetoothAvrcpController;
import android.bluetooth.BluetoothDevice;
@@ -33,15 +33,12 @@
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;
@@ -64,8 +61,8 @@
* 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";
+public class BluetoothMediaBrowserService extends MediaBrowserService {
+ private static final String TAG = "BluetoothMediaBrowserService";
private static final boolean DBG = false;
private static final boolean VDBG = false;
@@ -91,12 +88,13 @@
// Custom actions for PTS testing.
private static final String CUSTOM_ACTION_VOL_UP =
- "com.android.bluetooth.a2dpsink.mbs.CUSTOM_ACTION_VOL_UP";
+ "com.android.bluetooth.avrcpcontroller.CUSTOM_ACTION_VOL_UP";
private static final String CUSTOM_ACTION_VOL_DN =
- "com.android.bluetooth.a2dpsink.mbs.CUSTOM_ACTION_VOL_DN";
+ "com.android.bluetooth.avrcpcontroller.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";
+ "com.android.bluetooth.avrcpcontroller.CUSTOM_ACTION_GET_PLAY_STATUS_NATIVE";
+ private static BluetoothMediaBrowserService sBluetoothMediaBrowserService;
private MediaSession mSession;
private MediaMetadata mA2dpMetadata;
@@ -106,24 +104,22 @@
private A2dpSinkService mA2dpSinkService = null;
private Handler mAvrcpCommandQueue;
private final Map<String, Result<List<MediaItem>>> mParentIdToRequestMap = new HashMap<>();
+ private int mCurrentlyHeldKey = 0;
// 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 List<MediaSession.QueueItem> mMediaQueue = new ArrayList<>();
private static final class AvrcpCommandQueueHandler extends Handler {
- WeakReference<A2dpMediaBrowserService> mInst;
+ WeakReference<BluetoothMediaBrowserService> mInst;
- AvrcpCommandQueueHandler(Looper looper, A2dpMediaBrowserService sink) {
+ AvrcpCommandQueueHandler(Looper looper, BluetoothMediaBrowserService sink) {
super(looper);
- mInst = new WeakReference<A2dpMediaBrowserService>(sink);
+ mInst = new WeakReference<BluetoothMediaBrowserService>(sink);
}
@Override
public void handleMessage(Message msg) {
- A2dpMediaBrowserService inst = mInst.get();
+ BluetoothMediaBrowserService inst = mInst.get();
if (inst == null) {
Log.e(TAG, "Parent class has died; aborting.");
return;
@@ -173,6 +169,8 @@
mSession.setFlags(MediaSession.FLAG_HANDLES_MEDIA_BUTTONS
| MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS);
mSession.setActive(true);
+ mSession.setQueueTitle(getString(R.string.bluetooth_a2dp_sink_queue_name));
+ mSession.setQueue(mMediaQueue);
mAvrcpCommandQueue = new AvrcpCommandQueueHandler(Looper.getMainLooper(), this);
refreshInitialPlayingState();
@@ -187,18 +185,45 @@
synchronized (this) {
mParentIdToRequestMap.clear();
}
+ setBluetoothMediaBrowserService(this);
+
}
@Override
public void onDestroy() {
if (DBG) Log.d(TAG, "onDestroy");
+ setBluetoothMediaBrowserService(null);
mSession.release();
unregisterReceiver(mBtReceiver);
super.onDestroy();
}
+
+ /**
+ * getBluetoothMediaBrowserService()
+ * Routine to get direct access to MediaBrowserService from within the same process.
+ */
+ public static synchronized BluetoothMediaBrowserService getBluetoothMediaBrowserService() {
+ if (sBluetoothMediaBrowserService == null) {
+ Log.w(TAG, "getBluetoothMediaBrowserService(): service is NULL");
+ return null;
+ }
+ if (DBG) {
+ Log.d(TAG, "getBluetoothMediaBrowserService(): returning "
+ + sBluetoothMediaBrowserService);
+ }
+ return sBluetoothMediaBrowserService;
+ }
+
+ private static synchronized void setBluetoothMediaBrowserService(
+ BluetoothMediaBrowserService instance) {
+ if (DBG) Log.d(TAG, "setBluetoothMediaBrowserService(): set to: " + instance);
+ sBluetoothMediaBrowserService = instance;
+ }
+
@Override
public BrowserRoot onGetRoot(String clientPackageName, int clientUid, Bundle rootHints) {
+ if (DBG) Log.d(TAG, "onGetRoot");
return new BrowserRoot(BrowseTree.ROOT, null);
}
@@ -212,17 +237,15 @@
}
if (DBG) Log.d(TAG, "onLoadChildren parentMediaId=" + parentMediaId);
- if (!mAvrcpCtrlSrvc.getChildren(mA2dpDevice, parentMediaId, 0, 0xff)) {
- result.sendResult(Collections.emptyList());
- return;
+ List<MediaItem> contents = mAvrcpCtrlSrvc.getContents(mA2dpDevice, parentMediaId);
+ if (contents == null) {
+ mParentIdToRequestMap.put(parentMediaId, result);
+ result.detach();
+ } else {
+ result.sendResult(contents);
}
- // 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();
+ return;
}
@Override
@@ -264,6 +287,19 @@
}
@Override
+ public void onSkipToQueueItem(long id) {
+ if (DBG) Log.d(TAG, "onSkipToQueueItem" + id);
+ if (mA2dpSinkService != null) {
+ mA2dpSinkService.requestAudioFocus(mA2dpDevice, true);
+ }
+ MediaSession.QueueItem queueItem = mMediaQueue.get((int) id);
+ if (queueItem != null) {
+ String mediaId = queueItem.getDescription().getMediaId();
+ mAvrcpCtrlSrvc.fetchAttrAndPlayItem(mA2dpDevice, mediaId);
+ }
+ }
+
+ @Override
public void onStop() {
if (DBG) Log.d(TAG, "onStop");
mAvrcpCommandQueue.obtainMessage(MSG_AVRCP_PASSTHRU,
@@ -296,12 +332,12 @@
@Override
public void onPlayFromMediaId(String mediaId, Bundle extras) {
- synchronized (A2dpMediaBrowserService.this) {
+ synchronized (BluetoothMediaBrowserService.this) {
// Play the item if possible.
+ if (mA2dpSinkService != null) {
+ mA2dpSinkService.requestAudioFocus(mA2dpDevice, true);
+ }
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.
@@ -408,10 +444,6 @@
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);
@@ -435,7 +467,6 @@
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());
@@ -443,6 +474,8 @@
mA2dpDevice = null;
mBrowseConnected = false;
// update playerList.
+ mMediaQueue.clear();
+ mSession.setQueue(mMediaQueue);
notifyChildrenChanged("__ROOT__");
}
@@ -470,8 +503,6 @@
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
@@ -482,6 +513,11 @@
}
}
+ private boolean isHoldableKey(int cmd) {
+ return (cmd == AvrcpControllerService.PASS_THRU_CMD_ID_REWIND)
+ || (cmd == AvrcpControllerService.PASS_THRU_CMD_ID_FF);
+ }
+
private synchronized void msgPassThru(int cmd) {
if (DBG) Log.d(TAG, "msgPassThru " + cmd);
if (mA2dpDevice == null) {
@@ -489,12 +525,34 @@
Log.w(TAG, "Already disconnected ignoring.");
return;
}
+ // Some keys should be held until the next event.
+ if (mCurrentlyHeldKey != 0) {
+ mAvrcpCtrlSrvc.sendPassThroughCmd(mA2dpDevice, 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.
mAvrcpCtrlSrvc.sendPassThroughCmd(mA2dpDevice, cmd,
AvrcpControllerService.KEY_STATE_PRESSED);
- mAvrcpCtrlSrvc.sendPassThroughCmd(mA2dpDevice, cmd,
- AvrcpControllerService.KEY_STATE_RELEASED);
+
+ if (isHoldableKey(cmd)) {
+ // Release cmd next time a command is sent.
+ mCurrentlyHeldKey = cmd;
+ } else {
+ mAvrcpCtrlSrvc.sendPassThroughCmd(mA2dpDevice, cmd,
+ AvrcpControllerService.KEY_STATE_RELEASED);
+ }
}
private synchronized void msgGetPlayStatusNative() {
@@ -524,16 +582,9 @@
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);
+ updateNowPlayingQueue();
+ if (VDBG) Log.d(TAG, "Parent: " + id);
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
@@ -543,11 +594,33 @@
Log.w(TAG, "Request no longer exists, notifying that children changed.");
notifyChildrenChanged(id);
} else {
+ List<MediaItem> folderList = mAvrcpCtrlSrvc.getContents(mA2dpDevice, id);
results.sendResult(folderList);
}
}
}
+ private void updateNowPlayingQueue() {
+ List<MediaItem> songList = mAvrcpCtrlSrvc.getContents(mA2dpDevice, "NOW_PLAYING");
+ Log.d(TAG, "NowPlaying" + songList.size());
+ mMediaQueue.clear();
+ for (MediaItem song : songList) {
+ mMediaQueue.add(new MediaSession.QueueItem(song.getDescription(), mMediaQueue.size()));
+ }
+ mSession.setQueue(mMediaQueue);
+ }
+
+ /**
+ * processInternalEvent(Intent intent)
+ * Routine to provide MediaBrowserService with content updates from within the same process.
+ */
+ public void processInternalEvent(Intent intent) {
+ String action = intent.getAction();
+ if (AvrcpControllerService.ACTION_FOLDER_LIST.equals(action)) {
+ mAvrcpCommandQueue.obtainMessage(MSG_FOLDER_LIST, intent).sendToTarget();
+ }
+ }
+
private void msgDeviceBrowseDisconnect(BluetoothDevice device) {
if (DBG) Log.d(TAG, "msgDeviceBrowseDisconnect device " + device);
// Disconnect only if mA2dpDevice is non null
@@ -558,4 +631,5 @@
}
mBrowseConnected = false;
}
+
}
diff --git a/src/com/android/bluetooth/avrcpcontroller/BrowseTree.java b/src/com/android/bluetooth/avrcpcontroller/BrowseTree.java
index 4482bd7..7f4f3bf 100644
--- a/src/com/android/bluetooth/avrcpcontroller/BrowseTree.java
+++ b/src/com/android/bluetooth/avrcpcontroller/BrowseTree.java
@@ -20,7 +20,6 @@
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;
@@ -43,12 +42,8 @@
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;
-
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 +52,36 @@
private BrowseNode mCurrentBrowseNode;
private BrowseNode mCurrentBrowsedPlayer;
private BrowseNode mCurrentAddressedPlayer;
+ private int mDepth = 0;
+ final BrowseNode mRootNode;
+ final BrowseNode mNavigateUpNode;
+ final BrowseNode mNowPlayingNode;
BrowseTree() {
- }
-
- 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 = new BrowseNode(new MediaItem(new MediaDescription.Builder().setExtras(mdBundle)
+ .setMediaId(ROOT).setTitle(ROOT).build(), MediaItem.FLAG_BROWSABLE));
+ mRootNode.mBrowseScope = AvrcpControllerService.BROWSE_SCOPE_PLAYER_LIST;
+ mRootNode.setExpectedChildren(255);
+
+ Bundle upnodeBundle = new Bundle();
+ upnodeBundle.putString(AvrcpControllerService.MEDIA_ITEM_UID_KEY, UP);
+ mNavigateUpNode = new BrowseNode(new MediaItem(new MediaDescription.Builder()
+ .setExtras(upnodeBundle).setMediaId(UP).setTitle(UP).build(),
+ MediaItem.FLAG_BROWSABLE));
+
+ Bundle nowPlayingBundle = new Bundle();
+ nowPlayingBundle.putString(AvrcpControllerService.MEDIA_ITEM_UID_KEY, NOW_PLAYING_PREFIX);
+ mNowPlayingNode = new BrowseNode(new MediaItem(new MediaDescription.Builder()
+ .setExtras(nowPlayingBundle).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() {
@@ -91,12 +103,12 @@
// 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;
+ int 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;
@@ -113,28 +125,90 @@
mdb.setExtras(mdExtra);
mdb.setMediaId(playerKey);
mdb.setTitle(player.getName());
+ int mediaItemFlags = player.supportsFeature(AvrcpPlayer.FEATURE_BROWSING)
+ ? MediaBrowser.MediaItem.FLAG_BROWSABLE : 0;
+ mItem = new MediaBrowser.MediaItem(mdb.build(), mediaItemFlags);
+ }
+
+ private BrowseNode(String name) {
+ MediaDescription.Builder mdb = new MediaDescription.Builder();
+ Bundle mdExtra = new Bundle();
+ mdExtra.putString(AvrcpControllerService.MEDIA_ITEM_UID_KEY, name);
+ mdb.setExtras(mdExtra);
+ 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);
+ }
+ if (currentNode != null) {
+ currentNode.mParent = this;
+ mChildren.add(currentNode);
+ mBrowseMap.put(currentNode.getID(), currentNode);
+ }
+ }
+ return newChildren.size();
+ }
+
+ 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 != null) {
+ 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,6 +221,10 @@
return Integer.parseInt(getID().replace(PLAYER_PREFIX, ""));
}
+ synchronized int 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.
@@ -180,50 +258,16 @@
@Override
public String toString() {
if (VDBG) {
- return "ID: " + getID() + " desc: " + mItem;
+ return "[ Name: " + mItem.getDescription().getTitle() + " expected Children: "
+ + mExpectedChildrenCount + "] ";
} 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 +277,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 +291,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 +301,21 @@
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;
+ mCurrentBrowseNode = dummyNode;
+ }
+ mCurrentBrowseNode.setExpectedChildren(items);
+ mDepth = depth;
return true;
}
@@ -311,9 +324,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 +342,60 @@
@Override
public String toString() {
- return mBrowseMap.toString();
+ return "Size: " + mBrowseMap.size();
+ }
+
+ // 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)) {
+ 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;
+ }
+ }
+ /*
+ if (mCurrentBrowseNode.isDescendant(target)) {
+ return getEldestChild(mCurrentBrowseNode, target);
+ } else {
+ if (DBG) Log.d(TAG, "NAVIGATING UP");
+ return mNavigateUpNode;
+ }
+ */
+ }
+
+ 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/TrackInfo.java b/src/com/android/bluetooth/avrcpcontroller/TrackInfo.java
index e89fb4c..7764e60 100644
--- a/src/com/android/bluetooth/avrcpcontroller/TrackInfo.java
+++ b/src/com/android/bluetooth/avrcpcontroller/TrackInfo.java
@@ -58,7 +58,7 @@
private final String mTrackTitle;
private final String mAlbumTitle;
private final String mGenre;
- private final long mTrackNum; // number of audio file on original recording.
+ 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.
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 d594850..508eda6 100644
--- a/src/com/android/bluetooth/btservice/AdapterProperties.java
+++ b/src/com/android/bluetooth/btservice/AdapterProperties.java
@@ -76,6 +76,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>();
@@ -286,6 +289,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
*/
@@ -792,6 +834,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);
}
diff --git a/src/com/android/bluetooth/btservice/AdapterService.java b/src/com/android/bluetooth/btservice/AdapterService.java
index ad78955..04eefa8 100644
--- a/src/com/android/bluetooth/btservice/AdapterService.java
+++ b/src/com/android/bluetooth/btservice/AdapterService.java
@@ -72,6 +72,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.IBatteryStats;
+import com.google.protobuf.ByteString;
import com.google.protobuf.InvalidProtocolBufferException;
import java.io.FileDescriptor;
@@ -224,19 +225,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 +253,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);
@@ -277,6 +278,8 @@
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;
@@ -873,6 +876,7 @@
return service.setName(name);
}
+ @Override
public BluetoothClass getBluetoothClass() {
if (!Utils.checkCaller()) {
Log.w(TAG, "getBluetoothClass() - Not allowed for non-active user");
@@ -884,6 +888,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 +903,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");
@@ -1690,6 +1743,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");
@@ -2515,6 +2609,16 @@
}
}
+ /**
+ * Obfuscate Bluetooth MAC address into a PII free ID string
+ *
+ * @param device Bluetooth device whose MAC address will be obfuscated
+ * @return a {@link ByteString} that is unique to this MAC address on this device
+ */
+ public ByteString obfuscateAddress(BluetoothDevice device) {
+ return ByteString.copyFrom(obfuscateAddressNative(Utils.getByteAddress(device)));
+ }
+
static native void classInitNative();
native boolean initNative();
@@ -2598,6 +2702,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 13ef2ad..04488b3 100644
--- a/src/com/android/bluetooth/btservice/BondStateMachine.java
+++ b/src/com/android/bluetooth/btservice/BondStateMachine.java
@@ -473,11 +473,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 ae65863..8a9c0a1 100644
--- a/src/com/android/bluetooth/btservice/Config.java
+++ b/src/com/android/bluetooth/btservice/Config.java
@@ -21,6 +21,7 @@
import android.content.Context;
import android.content.res.Resources;
import android.provider.Settings;
+import android.util.FeatureFlagUtils;
import android.util.Log;
import com.android.bluetooth.R;
@@ -117,6 +118,13 @@
ArrayList<Class> profiles = new ArrayList<>(PROFILE_SERVICES_AND_FLAGS.length);
for (ProfileConfig config : PROFILE_SERVICES_AND_FLAGS) {
boolean supported = resources.getBoolean(config.mSupported);
+
+ if (!supported && (config.mClass == HearingAidService.class) && FeatureFlagUtils
+ .isEnabled(ctx, FeatureFlagUtils.HEARING_AID_SETTINGS)) {
+ Log.v(TAG, "Feature Flag enables support for HearingAidService");
+ supported = true;
+ }
+
if (supported && !isProfileDisabled(ctx, config.mMask)) {
Log.v(TAG, "Adding " + config.mClass.getSimpleName());
profiles.add(config.mClass);
diff --git a/src/com/android/bluetooth/btservice/PhonePolicy.java b/src/com/android/bluetooth/btservice/PhonePolicy.java
index a0ad490..e20bfa5 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;
diff --git a/src/com/android/bluetooth/btservice/RemoteDevices.java b/src/com/android/bluetooth/btservice/RemoteDevices.java
index 12897fe..f32c7cc 100644
--- a/src/com/android/bluetooth/btservice/RemoteDevices.java
+++ b/src/com/android/bluetooth/btservice/RemoteDevices.java
@@ -30,12 +30,12 @@
import android.os.Looper;
import android.os.Message;
import android.os.ParcelUuid;
-import android.support.annotation.VisibleForTesting;
import android.util.Log;
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 +79,8 @@
case MESSAGE_UUID_INTENT:
BluetoothDevice device = (BluetoothDevice) msg.obj;
if (device != null) {
- sendUuidIntent(device);
+ DeviceProperties prop = getDeviceProperties(device);
+ sendUuidIntent(device, prop);
}
break;
}
@@ -365,8 +366,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);
@@ -545,7 +545,7 @@
}
device.mUuids = newUuids;
if (sAdapterService.getState() == BluetoothAdapter.STATE_ON) {
- sendUuidIntent(bdDevice);
+ sendUuidIntent(bdDevice, device);
}
break;
case AbstractionLayer.BT_PROPERTY_TYPE_OF_DEVICE:
@@ -829,13 +829,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/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/GattService.java b/src/com/android/bluetooth/gatt/GattService.java
index fd8551a..25c30d0 100644
--- a/src/com/android/bluetooth/gatt/GattService.java
+++ b/src/com/android/bluetooth/gatt/GattService.java
@@ -2457,7 +2457,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:
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/hdp/HealthService.java b/src/com/android/bluetooth/hdp/HealthService.java
index 6a4af04..6b44103 100644
--- a/src/com/android/bluetooth/hdp/HealthService.java
+++ b/src/com/android/bluetooth/hdp/HealthService.java
@@ -28,13 +28,13 @@
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 com.android.internal.annotations.VisibleForTesting;
import java.io.FileDescriptor;
import java.io.IOException;
diff --git a/src/com/android/bluetooth/hearingaid/HearingAidNativeInterface.java b/src/com/android/bluetooth/hearingaid/HearingAidNativeInterface.java
index 2603659..1b56e81 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));
}
@@ -108,7 +108,7 @@
* Sets the HearingAid volume
* @param volume
*/
- @VisibleForTesting (otherwise = VisibleForTesting.PACKAGE_PRIVATE)
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
public void setVolume(int volume) {
setVolumeNative(volume);
}
diff --git a/src/com/android/bluetooth/hearingaid/HearingAidService.java b/src/com/android/bluetooth/hearingaid/HearingAidService.java
index bbc61ba..738da08 100644
--- a/src/com/android/bluetooth/hearingaid/HearingAidService.java
+++ b/src/com/android/bluetooth/hearingaid/HearingAidService.java
@@ -29,7 +29,6 @@
import android.os.HandlerThread;
import android.os.ParcelUuid;
import android.provider.Settings;
-import android.support.annotation.VisibleForTesting;
import android.util.Log;
import com.android.bluetooth.BluetoothMetricsProto;
@@ -37,6 +36,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 java.util.ArrayList;
import java.util.HashMap;
@@ -50,7 +50,7 @@
* @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
@@ -326,7 +326,7 @@
* @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()) {
@@ -405,6 +405,16 @@
}
}
+ /**
+ * Get the HiSyncIdMap for testing
+ *
+ * @return mDeviceHiSyncIdMap
+ */
+ @VisibleForTesting
+ Map<BluetoothDevice, Long> getHiSyncIdMap() {
+ return mDeviceHiSyncIdMap;
+ }
+
int getConnectionState(BluetoothDevice device) {
enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
synchronized (mStateMachines) {
@@ -608,19 +618,24 @@
if (DBG) {
Log.d(TAG, "Set Hearing Aid audio to disconnected");
}
- mAudioManager.setHearingAidDeviceConnectionState(mPreviousAudioDevice,
- BluetoothProfile.STATE_DISCONNECTED);
+ boolean suppressNoisyIntent =
+ (getConnectionState(mPreviousAudioDevice) == BluetoothProfile.STATE_CONNECTED);
+ mAudioManager.setBluetoothHearingAidDeviceConnectionState(
+ mPreviousAudioDevice, BluetoothProfile.STATE_DISCONNECTED,
+ suppressNoisyIntent, 0);
mPreviousAudioDevice = null;
} else {
if (DBG) {
Log.d(TAG, "Set Hearing Aid audio to connected");
}
if (mPreviousAudioDevice != null) {
- mAudioManager.setHearingAidDeviceConnectionState(mPreviousAudioDevice,
- BluetoothProfile.STATE_DISCONNECTED);
+ mAudioManager.setBluetoothHearingAidDeviceConnectionState(
+ mPreviousAudioDevice, BluetoothProfile.STATE_DISCONNECTED,
+ true, 0);
}
- mAudioManager.setHearingAidDeviceConnectionState(device,
- BluetoothProfile.STATE_CONNECTED);
+ mAudioManager.setBluetoothHearingAidDeviceConnectionState(
+ device, BluetoothProfile.STATE_CONNECTED,
+ true, 0);
mPreviousAudioDevice = device;
}
}
@@ -658,6 +673,7 @@
if (bondState != BluetoothDevice.BOND_NONE) {
return;
}
+ mDeviceHiSyncIdMap.remove(device);
synchronized (mStateMachines) {
HearingAidStateMachine sm = mStateMachines.get(device);
if (sm == null) {
diff --git a/src/com/android/bluetooth/hearingaid/HearingAidStateMachine.java b/src/com/android/bluetooth/hearingaid/HearingAidStateMachine.java
index d02e102..06a8aee 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;
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..a1fb2fd 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;
@@ -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;
diff --git a/src/com/android/bluetooth/hfp/HeadsetService.java b/src/com/android/bluetooth/hfp/HeadsetService.java
index 2ccc1e4..6a7d249 100644
--- a/src/com/android/bluetooth/hfp/HeadsetService.java
+++ b/src/com/android/bluetooth/hfp/HeadsetService.java
@@ -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)}
*
@@ -600,12 +601,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
@@ -1221,9 +1222,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 +1240,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 +1453,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 +1499,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()) {
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..2ac2429 100644
--- a/src/com/android/bluetooth/hfp/HeadsetStateMachine.java
+++ b/src/com/android/bluetooth/hfp/HeadsetStateMachine.java
@@ -26,13 +26,13 @@
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.util.Log;
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;
@@ -527,8 +527,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 +586,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;
@@ -921,7 +919,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 +1009,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);
@@ -1880,19 +1875,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 +1899,6 @@
log("Invalid HF Indicator Received");
break;
}
-
- iter = iter1 + 1; // move past comma
}
}
diff --git a/src/com/android/bluetooth/hfpclient/HeadsetClientService.java b/src/com/android/bluetooth/hfpclient/HeadsetClientService.java
index 46c017c..7cf7035 100644
--- a/src/com/android/bluetooth/hfpclient/HeadsetClientService.java
+++ b/src/com/android/bluetooth/hfpclient/HeadsetClientService.java
@@ -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;
}
@@ -914,8 +915,6 @@
super.dump(sb);
for (HeadsetClientStateMachine sm : mStateMachineMap.values()) {
if (sm != null) {
- println(sb, "State machine:");
- println(sb, "=============");
sm.dump(sb);
}
}
@@ -930,7 +929,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..de23b46 100644
--- a/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachine.java
+++ b/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachine.java
@@ -48,7 +48,6 @@
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;
@@ -58,6 +57,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 +185,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 +210,6 @@
ProfileService.println(sb, " " + call);
}
}
-
- ProfileService.println(sb, "State machine stats:");
- ProfileService.println(sb, this.toString());
}
private void clearPendingAction() {
@@ -777,6 +776,9 @@
public void doQuit() {
Log.d(TAG, "doQuit");
+ if (mCurrentDevice != null) {
+ NativeInterface.disconnectNative(getByteAddress(mCurrentDevice));
+ }
routeHfpAudio(false);
returnAudioFocusIfNecessary();
quitNow();
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/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 ff1a608..accb206 100644
--- a/src/com/android/bluetooth/hid/HidHostService.java
+++ b/src/com/android/bluetooth/hid/HidHostService.java
@@ -180,7 +180,7 @@
if (DBG) {
Log.d(TAG, "Incoming HID connection rejected");
}
- disconnectHidNative(Utils.getByteAddress(device));
+ virtualUnPlugNative(Utils.getByteAddress(device));
} else {
broadcastConnectionState(device, convertHalState(halState));
}
diff --git a/src/com/android/bluetooth/map/BluetoothMapService.java b/src/com/android/bluetooth/map/BluetoothMapService.java
index c00b39b..d50005d 100644
--- a/src/com/android/bluetooth/map/BluetoothMapService.java
+++ b/src/com/android/bluetooth/map/BluetoothMapService.java
@@ -37,7 +37,6 @@
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;
@@ -47,6 +46,7 @@
import com.android.bluetooth.Utils;
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;
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..d97f744 100644
--- a/src/com/android/bluetooth/mapclient/MapClientService.java
+++ b/src/com/android/bluetooth/mapclient/MapClientService.java
@@ -31,11 +31,11 @@
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.ProfileService;
+import com.android.internal.annotations.VisibleForTesting;
import java.util.ArrayList;
import java.util.Arrays;
@@ -189,20 +189,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;
}
@@ -346,10 +346,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 +500,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 +517,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..d7a207d 100644
--- a/src/com/android/bluetooth/mapclient/MceStateMachine.java
+++ b/src/com/android/bluetooth/mapclient/MceStateMachine.java
@@ -280,6 +280,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 +311,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 {
@@ -349,9 +358,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 +374,9 @@
break;
case MSG_MAS_DISCONNECTED:
+ if (mMasClient != null) {
+ mMasClient.shutdown();
+ }
transitionTo(mDisconnected);
break;
@@ -456,8 +473,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);
}
@@ -492,10 +513,8 @@
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()) {
@@ -607,6 +626,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 +710,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..545f1ec 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;
@@ -330,8 +332,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 +352,7 @@
mUpdateThread = null;
}
}
+
mNotifier.cancelNotifications();
break;
case START_LISTENER:
@@ -551,6 +565,7 @@
if (mUpdateThread == null) {
mUpdateThread = new UpdateThread();
mUpdateThread.start();
+ mUpdateThreadRunning = true;
}
}
}
@@ -580,6 +595,7 @@
while (!mIsInterrupted) {
synchronized (BluetoothOppService.this) {
if (mUpdateThread != this) {
+ mUpdateThreadRunning = false;
throw new IllegalStateException(
"multiple UpdateThreads in BluetoothOppService");
}
@@ -589,6 +605,7 @@
}
if (!mPendingUpdate) {
mUpdateThread = null;
+ mUpdateThreadRunning = false;
return;
}
mPendingUpdate = false;
@@ -598,6 +615,7 @@
BluetoothShare._ID);
if (cursor == null) {
+ mUpdateThreadRunning = false;
return;
}
@@ -634,9 +652,6 @@
}
}
- if (shouldScanFile(arrayPos)) {
- scanFile(arrayPos);
- }
deleteShare(arrayPos); // this advances in the array
} else {
int id = cursor.getInt(idColumn);
@@ -660,14 +675,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 +703,8 @@
cursor.close();
}
+
+ mUpdateThreadRunning = false;
}
}
@@ -1031,8 +1045,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 +1060,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/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..91652a6 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,7 +88,7 @@
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(";")) {
values.put(CallLog.Calls.NUMBER, "");
diff --git a/src/com/android/bluetooth/pbapclient/PbapClientConnectionHandler.java b/src/com/android/bluetooth/pbapclient/PbapClientConnectionHandler.java
index 913ba0e..c060ab2 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;
@@ -272,19 +273,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 +307,7 @@
boolean connectionSuccessful = false;
try {
- if (DBG) {
+ if (VDBG) {
Log.v(TAG, "Start Obex Client Session");
}
BluetoothObexTransport transport = new BluetoothObexTransport(mSocket);
@@ -351,7 +352,7 @@
this.getLooper().getThread().interrupt();
}
- private void closeSocket() {
+ private synchronized void closeSocket() {
try {
if (mSocket != null) {
if (DBG) {
@@ -410,8 +411,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..9998e1b 100644
--- a/src/com/android/bluetooth/pbapclient/PbapClientService.java
+++ b/src/com/android/bluetooth/pbapclient/PbapClientService.java
@@ -30,7 +30,6 @@
import android.util.Log;
import com.android.bluetooth.R;
-import com.android.bluetooth.Utils;
import com.android.bluetooth.btservice.ProfileService;
import java.util.ArrayList;
@@ -44,7 +43,9 @@
* @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";
// MAXIMUM_DEVICES set to 10 to prevent an excessive number of simultaneous devices.
private static final int MAXIMUM_DEVICES = 10;
@@ -60,8 +61,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);
@@ -98,7 +99,7 @@
}
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 +113,25 @@
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 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 +162,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 +257,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 +268,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;
}
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/src/com/android/bluetooth/pbapclient/Utils.java b/src/com/android/bluetooth/pbapclient/Utils.java
new file mode 100644
index 0000000..c59d8d5
--- /dev/null
+++ b/src/com/android/bluetooth/pbapclient/Utils.java
@@ -0,0 +1,22 @@
+/*
+ * 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.pbapclient;
+
+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..4b95d3f 100644
--- a/src/com/android/bluetooth/sap/SapService.java
+++ b/src/com/android/bluetooth/sap/SapService.java
@@ -21,7 +21,6 @@
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;
@@ -31,6 +30,7 @@
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();
diff --git a/tests/robotests/Android.mk b/tests/robotests/Android.mk
index 5a6c0bb..73f7706 100644
--- a/tests/robotests/Android.mk
+++ b/tests/robotests/Android.mk
@@ -12,8 +12,7 @@
LOCAL_JAVA_LIBRARIES := \
junit \
- platform-robolectric-3.6.1-prebuilt \
- sdk_vcurrent
+ platform-robolectric-3.6.2-prebuilt
LOCAL_INSTRUMENTATION_FOR := Bluetooth
LOCAL_MODULE := BluetoothRoboTests
@@ -38,4 +37,4 @@
LOCAL_INSTRUMENT_SOURCE_DIRS := $(dir $(LOCAL_PATH))../src
-include prebuilts/misc/common/robolectric/3.6.1/run_robotests.mk
+include prebuilts/misc/common/robolectric/3.6.2/run_robotests.mk
diff --git a/tests/unit/AndroidManifest.xml b/tests/unit/AndroidManifest.xml
index e344399..dee051d 100644
--- a/tests/unit/AndroidManifest.xml
+++ b/tests/unit/AndroidManifest.xml
@@ -54,12 +54,7 @@
<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
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/TestUtils.java b/tests/unit/src/com/android/bluetooth/TestUtils.java
index d0e3324..4a9054b 100644
--- a/tests/unit/src/com/android/bluetooth/TestUtils.java
+++ b/tests/unit/src/com/android/bluetooth/TestUtils.java
@@ -33,9 +33,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 +275,41 @@
}
/**
+ * Read Bluetooth adapter configuration from the filesystem
+ *
+ * @return A {@link HashMap} of Bluetooth configs in the format:
+ * section -> key1 -> value1
+ * -> key2 -> value2
+ * Assume no empty section name, no duplicate keys in the same section
+ */
+ public static HashMap<String, HashMap<String, String>> readAdapterConfig() {
+ HashMap<String, HashMap<String, String>> adapterConfig = new HashMap<>();
+ try (BufferedReader reader =
+ new BufferedReader(new FileReader("/data/misc/bluedroid/bt_config.conf"))) {
+ String section = "";
+ for (String line; (line = reader.readLine()) != null;) {
+ line = line.trim();
+ if (line.isEmpty() || line.startsWith("#")) {
+ continue;
+ }
+ if (line.startsWith("[")) {
+ if (line.charAt(line.length() - 1) != ']') {
+ return null;
+ }
+ section = line.substring(1, line.length() - 1);
+ adapterConfig.put(section, new HashMap<>());
+ } else {
+ String[] keyValue = line.split("=");
+ adapterConfig.get(section).put(keyValue[0].trim(), keyValue[1].trim());
+ }
+ }
+ } catch (IOException e) {
+ return null;
+ }
+ return adapterConfig;
+ }
+
+ /**
* Helper class used to run synchronously a runnable action on a looper.
*/
private static final class SyncRunnable implements Runnable {
diff --git a/tests/unit/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandlerTest.java b/tests/unit/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandlerTest.java
index eddab48..1274e48 100644
--- a/tests/unit/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandlerTest.java
+++ b/tests/unit/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandlerTest.java
@@ -189,7 +189,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/btservice/AdapterServiceTest.java b/tests/unit/src/com/android/bluetooth/btservice/AdapterServiceTest.java
index 2b19a20..47db348 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;
@@ -42,20 +38,38 @@
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 com.android.bluetooth.R;
+import com.android.bluetooth.TestUtils;
+import com.android.bluetooth.Utils;
+
+import com.google.protobuf.ByteString;
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;
@@ -77,6 +91,23 @@
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();
+ 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);
@@ -128,6 +155,9 @@
mAdapterService.registerCallback(mIBluetoothCallback);
Config.init(mMockContext);
+
+ mAdapterConfig = TestUtils.readAdapterConfig();
+ Assert.assertNotNull(mAdapterConfig);
}
@After
@@ -183,6 +213,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);
@@ -463,4 +495,209 @@
// Restore earlier setting
SystemProperties.set(AdapterService.BLUETOOTH_BTSNOOP_ENABLE_PROPERTY, snoopSetting);
}
+
+ /**
+ * 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);
+ ByteString obfuscatedAddress = mAdapterService.obfuscateAddress(device);
+ Assert.assertFalse(obfuscatedAddress.isEmpty());
+ Assert.assertFalse(isByteArrayAllZero(obfuscatedAddress.toByteArray()));
+ Assert.assertArrayEquals(obfuscateInJava(metricsSalt, device),
+ obfuscatedAddress.toByteArray());
+ }
+
+ /**
+ * 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);
+ ByteString obfuscatedAddress = mAdapterService.obfuscateAddress(device);
+ Assert.assertFalse(obfuscatedAddress.isEmpty());
+ Assert.assertFalse(isByteArrayAllZero(obfuscatedAddress.toByteArray()));
+ Assert.assertArrayEquals(obfuscateInJava(metricsSalt, device),
+ obfuscatedAddress.toByteArray());
+ }
+
+ /**
+ * 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);
+ ByteString obfuscatedAddress1 = mAdapterService.obfuscateAddress(device);
+ Assert.assertFalse(obfuscatedAddress1.isEmpty());
+ Assert.assertFalse(isByteArrayAllZero(obfuscatedAddress1.toByteArray()));
+ Assert.assertArrayEquals(obfuscateInJava(metricsSalt, device),
+ obfuscatedAddress1.toByteArray());
+ // Enable
+ doEnable(0, false);
+ Assert.assertTrue(mAdapterService.isEnabled());
+ ByteString obfuscatedAddress3 = mAdapterService.obfuscateAddress(device);
+ Assert.assertFalse(obfuscatedAddress3.isEmpty());
+ Assert.assertFalse(isByteArrayAllZero(obfuscatedAddress3.toByteArray()));
+ Assert.assertArrayEquals(obfuscatedAddress3.toByteArray(),
+ obfuscatedAddress1.toByteArray());
+ // Disable
+ doDisable(0, false);
+ Assert.assertFalse(mAdapterService.isEnabled());
+ ByteString obfuscatedAddress4 = mAdapterService.obfuscateAddress(device);
+ Assert.assertFalse(obfuscatedAddress4.isEmpty());
+ Assert.assertFalse(isByteArrayAllZero(obfuscatedAddress4.toByteArray()));
+ Assert.assertArrayEquals(obfuscatedAddress4.toByteArray(),
+ obfuscatedAddress1.toByteArray());
+ }
+
+ /**
+ * 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);
+ ByteString obfuscatedAddress1 = mAdapterService.obfuscateAddress(device);
+ Assert.assertFalse(obfuscatedAddress1.isEmpty());
+ Assert.assertFalse(isByteArrayAllZero(obfuscatedAddress1.toByteArray()));
+ Assert.assertArrayEquals(obfuscateInJava(metricsSalt, device),
+ obfuscatedAddress1.toByteArray());
+ tearDown();
+ setUp();
+ Assert.assertFalse(mAdapterService.isEnabled());
+ ByteString obfuscatedAddress2 = mAdapterService.obfuscateAddress(device);
+ Assert.assertFalse(obfuscatedAddress2.isEmpty());
+ Assert.assertFalse(isByteArrayAllZero(obfuscatedAddress2.toByteArray()));
+ Assert.assertArrayEquals(obfuscatedAddress2.toByteArray(),
+ obfuscatedAddress1.toByteArray());
+ }
+
+ /**
+ * 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);
+ ByteString obfuscatedAddress1 = mAdapterService.obfuscateAddress(device);
+ Assert.assertFalse(obfuscatedAddress1.isEmpty());
+ Assert.assertFalse(isByteArrayAllZero(obfuscatedAddress1.toByteArray()));
+ mAdapterService.factoryReset();
+ ByteString obfuscatedAddress2 = mAdapterService.obfuscateAddress(device);
+ Assert.assertFalse(obfuscatedAddress2.isEmpty());
+ Assert.assertFalse(isByteArrayAllZero(obfuscatedAddress2.toByteArray()));
+ Assert.assertFalse(Arrays.equals(obfuscatedAddress2.toByteArray(),
+ obfuscatedAddress1.toByteArray()));
+ doEnable(0, false);
+ ByteString obfuscatedAddress3 = mAdapterService.obfuscateAddress(device);
+ Assert.assertFalse(obfuscatedAddress3.isEmpty());
+ Assert.assertFalse(isByteArrayAllZero(obfuscatedAddress3.toByteArray()));
+ Assert.assertArrayEquals(obfuscatedAddress3.toByteArray(),
+ obfuscatedAddress2.toByteArray());
+ mAdapterService.factoryReset();
+ ByteString obfuscatedAddress4 = mAdapterService.obfuscateAddress(device);
+ Assert.assertFalse(obfuscatedAddress4.isEmpty());
+ Assert.assertFalse(isByteArrayAllZero(obfuscatedAddress4.toByteArray()));
+ Assert.assertFalse(Arrays.equals(obfuscatedAddress4.toByteArray(),
+ obfuscatedAddress3.toByteArray()));
+ }
+
+ /**
+ * 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);
+ ByteString obfuscatedAddress1 = mAdapterService.obfuscateAddress(device);
+ Assert.assertFalse(obfuscatedAddress1.isEmpty());
+ Assert.assertFalse(isByteArrayAllZero(obfuscatedAddress1.toByteArray()));
+ Assert.assertArrayEquals(obfuscateInJava(metricsSalt1, device),
+ obfuscatedAddress1.toByteArray());
+ mAdapterService.factoryReset();
+ tearDown();
+ setUp();
+ // Cannot verify metrics salt since it is not written to disk until native cleanup
+ ByteString obfuscatedAddress2 = mAdapterService.obfuscateAddress(device);
+ Assert.assertFalse(obfuscatedAddress2.isEmpty());
+ Assert.assertFalse(isByteArrayAllZero(obfuscatedAddress2.toByteArray()));
+ Assert.assertFalse(Arrays.equals(obfuscatedAddress2.toByteArray(),
+ obfuscatedAddress1.toByteArray()));
+ }
+
+ 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/RemoteDevicesTest.java b/tests/unit/src/com/android/bluetooth/btservice/RemoteDevicesTest.java
index 35215ef..bc5f0dc 100644
--- a/tests/unit/src/com/android/bluetooth/btservice/RemoteDevicesTest.java
+++ b/tests/unit/src/com/android/bluetooth/btservice/RemoteDevicesTest.java
@@ -336,7 +336,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 +364,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,
diff --git a/tests/unit/src/com/android/bluetooth/hearingaid/HearingAidServiceTest.java b/tests/unit/src/com/android/bluetooth/hearingaid/HearingAidServiceTest.java
index b0a4755..921dc1c 100644
--- a/tests/unit/src/com/android/bluetooth/hearingaid/HearingAidServiceTest.java
+++ b/tests/unit/src/com/android/bluetooth/hearingaid/HearingAidServiceTest.java
@@ -498,8 +498,9 @@
Assert.assertTrue(mService.getConnectedDevices().contains(mRightDevice));
// Verify the audio is routed to Hearing Aid Profile
- verify(mAudioManager).setHearingAidDeviceConnectionState(any(BluetoothDevice.class),
- eq(BluetoothProfile.STATE_CONNECTED));
+ verify(mAudioManager).setBluetoothHearingAidDeviceConnectionState(
+ any(BluetoothDevice.class), eq(BluetoothProfile.STATE_CONNECTED),
+ eq(true), eq(0));
// Send a disconnect request
Assert.assertTrue("Disconnect failed", mService.disconnect(mLeftDevice));
@@ -546,8 +547,9 @@
Assert.assertFalse(mService.getConnectedDevices().contains(mRightDevice));
// Verify the audio is not routed to Hearing Aid Profile
- verify(mAudioManager).setHearingAidDeviceConnectionState(any(BluetoothDevice.class),
- eq(BluetoothProfile.STATE_DISCONNECTED));
+ verify(mAudioManager).setBluetoothHearingAidDeviceConnectionState(
+ any(BluetoothDevice.class), eq(BluetoothProfile.STATE_DISCONNECTED),
+ eq(false), eq(0));
}
/**
@@ -916,6 +918,31 @@
mService.getConnectionState(mLeftDevice));
}
+ /**
+ * Test that the service can update HiSyncId from native message
+ */
+ @Test
+ public void getHiSyncIdFromNative_addToMap() {
+ getHiSyncIdFromNative();
+ Assert.assertTrue("hiSyncIdMap should contain mLeftDevice",
+ mService.getHiSyncIdMap().containsKey(mLeftDevice));
+ Assert.assertTrue("hiSyncIdMap should contain mRightDevice",
+ mService.getHiSyncIdMap().containsKey(mRightDevice));
+ Assert.assertTrue("hiSyncIdMap should contain mSingleDevice",
+ mService.getHiSyncIdMap().containsKey(mSingleDevice));
+ }
+
+ /**
+ * Test that the service removes the device from HiSyncIdMap when it's unbonded
+ */
+ @Test
+ public void deviceUnbonded_removeHiSyncId() {
+ getHiSyncIdFromNative();
+ mService.bondStateChanged(mLeftDevice, BluetoothDevice.BOND_NONE);
+ Assert.assertFalse("hiSyncIdMap shouldn't contain mLeftDevice",
+ mService.getHiSyncIdMap().containsKey(mLeftDevice));
+ }
+
private void connectDevice(BluetoothDevice device) {
HearingAidStackEvent connCompletedEvent;
diff --git a/tests/unit/src/com/android/bluetooth/hfp/HeadsetServiceAndStateMachineTest.java b/tests/unit/src/com/android/bluetooth/hfp/HeadsetServiceAndStateMachineTest.java
index 39249a9..2d8255e 100644
--- a/tests/unit/src/com/android/bluetooth/hfp/HeadsetServiceAndStateMachineTest.java
+++ b/tests/unit/src/com/android/bluetooth/hfp/HeadsetServiceAndStateMachineTest.java
@@ -85,6 +85,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();
@@ -473,12 +474,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 +576,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 +1073,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 =
@@ -1178,19 +1205,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 188792d..0d6418a 100644
--- a/tests/unit/src/com/android/bluetooth/hfp/HeadsetServiceTest.java
+++ b/tests/unit/src/com/android/bluetooth/hfp/HeadsetServiceTest.java
@@ -655,7 +655,7 @@
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 +687,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 +705,7 @@
public void testPhoneStateChange_oneDeviceSaveState() throws RemoteException {
HeadsetCallState headsetCallState =
new HeadsetCallState(1, 0, HeadsetHalConstants.CALL_STATE_ALERTING,
- TEST_PHONE_NUMBER, 128);
+ TEST_PHONE_NUMBER, 128, "");
mCurrentDevice = TestUtils.getTestDevice(mAdapter, 0);
final ArrayList<BluetoothDevice> connectedDevices = new ArrayList<>();
// Connect one device
@@ -741,7 +741,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,7 +760,7 @@
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<>();
for (int i = 0; i < MAX_HEADSET_CONNECTIONS; ++i) {
mCurrentDevice = TestUtils.getTestDevice(mAdapter, i);
@@ -801,7 +801,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,
diff --git a/tests/unit/src/com/android/bluetooth/hfp/HeadsetStateMachineTest.java b/tests/unit/src/com/android/bluetooth/hfp/HeadsetStateMachineTest.java
index 80192a6..d321113 100644
--- a/tests/unit/src/com/android/bluetooth/hfp/HeadsetStateMachineTest.java
+++ b/tests/unit/src/com/android/bluetooth/hfp/HeadsetStateMachineTest.java
@@ -941,6 +941,75 @@
}
/**
+ * 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));
+ }
+
+ /**
* Setup Connecting State
* @return number of times mHeadsetService.sendBroadcastAsUser() has been invoked
*/
diff --git a/tests/unit/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachineTest.java b/tests/unit/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachineTest.java
index 334597b..45997e0 100644
--- a/tests/unit/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachineTest.java
+++ b/tests/unit/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachineTest.java
@@ -68,7 +68,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/newavrcp/MediaPlayerWrapperTest.java b/tests/unit/src/com/android/bluetooth/newavrcp/MediaPlayerWrapperTest.java
index cdae46b..85ad147 100644
--- a/tests/unit/src/com/android/bluetooth/newavrcp/MediaPlayerWrapperTest.java
+++ b/tests/unit/src/com/android/bluetooth/newavrcp/MediaPlayerWrapperTest.java
@@ -311,28 +311,6 @@
verify(mFailHandler, never()).onTerribleFailure(any(), any(), anyBoolean());
}
- /*
- * This test checks whether getCurrentMetadata() returns the corresponding item from
- * the now playing list instead of the current metadata if there is a match.
- */
- @Test
- public void testCurrentSongFromQueue() {
- // Create the wrapper object and register the looper with the timeout handler
- TestLooperManager looperManager = new TestLooperManager(mThread.getLooper());
-
- mTestState.setActiveQueueItemId(101);
- doReturn(mTestState.build()).when(mMockController).getPlaybackState();
-
- MediaPlayerWrapper wrapper =
- MediaPlayerWrapper.wrap(mMockController, mThread.getLooper());
- wrapper.registerCallback(mTestCbs);
-
- // The current metadata doesn't contain track number info so check that
- // field to see if the correct data was used.
- Assert.assertEquals(wrapper.getCurrentMetadata().trackNum, "2");
- Assert.assertEquals(wrapper.getCurrentMetadata().numTracks, "3");
- }
-
@Test
public void testNullMetadata() {
// Create the wrapper object and register the looper with the timeout handler