/*
- * Copyright (C) 2012 The Android Open Source Project
+ * 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.
#include <string.h>
+#define CHECK_CALLBACK_ENV()\
+do { \
+ if (!checkCallbackThread()) { \
+ ALOGE("Callback: '%s' is not called on the correct thread", __func__); \
+ return; \
+ } \
+} while (0)
+
+
namespace android {
static jmethodID method_getRcFeatures;
static jmethodID method_getPlayStatus;
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;
static JNIEnv *sCallbackEnv = 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 bool checkCallbackThread() {
// Always fetch the latest callbackEnv from AdapterService.
// Caching this could cause this sCallbackEnv to go out-of-sync
return true;
}
-static void btavrcp_remote_features_callback(bt_bdaddr_t* bd_addr, btrc_remote_features_t features) {
- ALOGI("%s", __FUNCTION__);
+static void btavrcp_remote_features_callback(bt_bdaddr_t* bd_addr,
+ btrc_remote_features_t features) {
jbyteArray addr;
- if (!checkCallbackThread()) {
- ALOGE("Callback: '%s' is not called on the correct thread", __FUNCTION__);
- return;
- }
+ CHECK_CALLBACK_ENV();
+
addr = sCallbackEnv->NewByteArray(sizeof(bt_bdaddr_t));
if (!addr) {
ALOGE("Unable to allocate byte array for bd_addr");
- checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+ checkAndClearExceptionFromCallback(sCallbackEnv, __func__);
return;
}
sCallbackEnv->SetByteArrayRegion(addr, 0, sizeof(bt_bdaddr_t), (jbyte*) bd_addr);
sCallbackEnv->CallVoidMethod(mCallbacksObj, method_getRcFeatures, addr, (jint)features);
} else {
- ALOGE("%s: mCallbacksObj is null", __FUNCTION__);
+ ALOGE("%s: mCallbacksObj is null", __func__);
}
- checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+
+ checkAndClearExceptionFromCallback(sCallbackEnv, __func__);
sCallbackEnv->DeleteLocalRef(addr);
}
-static void btavrcp_get_play_status_callback() {
- ALOGI("%s", __FUNCTION__);
- if (!checkCallbackThread()) {
- ALOGE("Callback: '%s' is not called on the correct thread", __FUNCTION__);
+/** Callback for play status request */
+static void btavrcp_get_play_status_callback(bt_bdaddr_t* bd_addr) {
+ ALOGI("%s", __func__);
+
+ jbyteArray addr;
+ CHECK_CALLBACK_ENV();
+
+ addr = sCallbackEnv->NewByteArray(sizeof(bt_bdaddr_t));
+ if (!addr) {
+ ALOGE("Fail to new jbyteArray bd addr for get_play_status command");
+ checkAndClearExceptionFromCallback(sCallbackEnv, __func__);
return;
}
if (mCallbacksObj) {
- sCallbackEnv->CallVoidMethod(mCallbacksObj, method_getPlayStatus);
+ sCallbackEnv->SetByteArrayRegion(addr, 0, sizeof(bt_bdaddr_t), (jbyte*) bd_addr);
+ sCallbackEnv->CallVoidMethod(mCallbacksObj, method_getPlayStatus, addr);
} else {
- ALOGE("%s: mCallbacksObj is null", __FUNCTION__);
+ ALOGE("%s: mCallbacksObj is null", __func__);
}
- checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+ checkAndClearExceptionFromCallback(sCallbackEnv, __func__);
+ sCallbackEnv->DeleteLocalRef(addr);
}
-static void btavrcp_get_element_attr_callback(uint8_t num_attr, btrc_media_attr_t *p_attrs) {
+static void btavrcp_get_element_attr_callback(uint8_t num_attr, btrc_media_attr_t *p_attrs,
+ bt_bdaddr_t *bd_addr) {
+ jbyteArray addr;
jintArray attrs;
- ALOGI("%s", __FUNCTION__);
+ CHECK_CALLBACK_ENV();
- if (!checkCallbackThread()) {
- ALOGE("Callback: '%s' is not called on the correct thread", __FUNCTION__);
+ addr = sCallbackEnv->NewByteArray(sizeof(bt_bdaddr_t));
+ if (!addr) {
+ ALOGE("Fail to new jbyteArray bd addr for get_element_attr command");
+ checkAndClearExceptionFromCallback(sCallbackEnv, __func__);
return;
}
+
attrs = (jintArray)sCallbackEnv->NewIntArray(num_attr);
if (!attrs) {
ALOGE("Fail to new jintArray for attrs");
- checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+ checkAndClearExceptionFromCallback(sCallbackEnv, __func__);
+ sCallbackEnv->DeleteLocalRef(addr);
return;
}
+
sCallbackEnv->SetIntArrayRegion(attrs, 0, num_attr, (jint *)p_attrs);
+
if (mCallbacksObj) {
- sCallbackEnv->CallVoidMethod(mCallbacksObj, method_getElementAttr, (jbyte)num_attr, attrs);
+ sCallbackEnv->SetByteArrayRegion(addr, 0, sizeof(bt_bdaddr_t), (jbyte*) bd_addr);
+ sCallbackEnv->CallVoidMethod(mCallbacksObj, method_getElementAttr, addr, (jbyte)num_attr,
+ attrs);
} else {
- ALOGE("%s: mCallbacksObj is null", __FUNCTION__);
+ ALOGE("%s: mCallbacksObj is null", __func__);
}
- checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+
+ checkAndClearExceptionFromCallback(sCallbackEnv, __func__);
sCallbackEnv->DeleteLocalRef(attrs);
+ sCallbackEnv->DeleteLocalRef(addr);
}
-static void btavrcp_register_notification_callback(btrc_event_id_t event_id, uint32_t param) {
- if (!checkCallbackThread()) {
- ALOGE("Callback: '%s' is not called on the correct thread", __FUNCTION__);
+static void btavrcp_register_notification_callback(btrc_event_id_t event_id, uint32_t param,
+ bt_bdaddr_t *bd_addr) {
+ jbyteArray addr;
+
+ CHECK_CALLBACK_ENV();
+
+ addr = sCallbackEnv->NewByteArray(sizeof(bt_bdaddr_t));
+ if (!addr) {
+ ALOGE("Fail to new jbyteArray bd addr for register_notification command");
+ checkAndClearExceptionFromCallback(sCallbackEnv, __func__);
return;
}
+
if (mCallbacksObj) {
+ sCallbackEnv->SetByteArrayRegion(addr, 0, sizeof(bt_bdaddr_t), (jbyte*) bd_addr);
sCallbackEnv->CallVoidMethod(mCallbacksObj, method_registerNotification,
- (jint)event_id, (jint)param);
+ addr, (jint)event_id, (jint)param);
+ } else {
+ ALOGE("%s: mCallbacksObj is null", __func__);
+ }
+
+ checkAndClearExceptionFromCallback(sCallbackEnv, __func__);
+ sCallbackEnv->DeleteLocalRef(addr);
+}
+
+
+static void btavrcp_volume_change_callback(uint8_t volume, uint8_t ctype,
+ bt_bdaddr_t *bd_addr) {
+ jbyteArray addr;
+
+ CHECK_CALLBACK_ENV();
+
+ addr = sCallbackEnv->NewByteArray(sizeof(bt_bdaddr_t));
+ if (!addr) {
+ ALOGE("Fail to new jbyteArray bd addr for volume_change command");
+ checkAndClearExceptionFromCallback(sCallbackEnv, __func__);
+ return;
+ }
+ sCallbackEnv->SetByteArrayRegion(addr, 0, sizeof(bt_bdaddr_t), (jbyte*) bd_addr);
+
+ if (mCallbacksObj) {
+ sCallbackEnv->CallVoidMethod(mCallbacksObj, method_volumeChangeCallback, addr, (jint)volume,
+ (jint)ctype);
+ } else {
+ ALOGE("%s: mCallbacksObj is null", __func__);
+ }
+
+ sCallbackEnv->DeleteLocalRef(addr);
+ checkAndClearExceptionFromCallback(sCallbackEnv, __func__);
+}
+
+static void btavrcp_passthrough_command_callback(int id, int pressed,
+ bt_bdaddr_t* bd_addr) {
+ jbyteArray addr;
+
+ CHECK_CALLBACK_ENV();
+
+ addr = sCallbackEnv->NewByteArray(sizeof(bt_bdaddr_t));
+ if (!addr) {
+ ALOGE("Fail to new jbyteArray bd addr for passthrough_command command");
+ checkAndClearExceptionFromCallback(sCallbackEnv, __func__);
+ return;
+ }
+ sCallbackEnv->SetByteArrayRegion(addr, 0, sizeof(bt_bdaddr_t), (jbyte*) bd_addr);
+
+ if (mCallbacksObj) {
+ sCallbackEnv->CallVoidMethod(mCallbacksObj, method_handlePassthroughCmd, addr, (jint)id,
+ (jint)pressed);
+ } else {
+ ALOGE("%s: mCallbacksObj is null", __func__);
+ }
+
+ checkAndClearExceptionFromCallback(sCallbackEnv, __func__);
+ sCallbackEnv->DeleteLocalRef(addr);
+}
+
+static void btavrcp_set_addressed_player_callback(uint16_t player_id,
+ bt_bdaddr_t *bd_addr) {
+ jbyteArray addr;
+
+ CHECK_CALLBACK_ENV();
+
+ addr = sCallbackEnv->NewByteArray(sizeof(bt_bdaddr_t));
+ if (!addr) {
+ ALOGE("Fail to new jbyteArray bd addr for set_addressed_player command");
+ checkAndClearExceptionFromCallback(sCallbackEnv, __func__);
+ return;
+ }
+
+ if (mCallbacksObj) {
+ sCallbackEnv->SetByteArrayRegion(addr, 0, sizeof(bt_bdaddr_t), (jbyte*) bd_addr);
+ sCallbackEnv->CallVoidMethod(mCallbacksObj, method_setAddressedPlayerCallback, addr, \
+ (jint) player_id);
+ } else {
+ ALOGE("%s: mCallbacksObj is null", __func__);
+ }
+
+ checkAndClearExceptionFromCallback(sCallbackEnv, __func__);
+ sCallbackEnv->DeleteLocalRef(addr);
+}
+
+static void btavrcp_set_browsed_player_callback(uint16_t player_id, bt_bdaddr_t *bd_addr) {
+ jbyteArray addr;
+
+ CHECK_CALLBACK_ENV();
+
+ addr = sCallbackEnv->NewByteArray(sizeof(bt_bdaddr_t));
+ if (!addr) {
+ ALOGE("Fail to new jbyteArray bd addr for set_browsed_player command");
+ checkAndClearExceptionFromCallback(sCallbackEnv, __func__);
+ return;
+ }
+ sCallbackEnv->SetByteArrayRegion(addr, 0, sizeof(bt_bdaddr_t), (jbyte*) bd_addr);
+
+ if (mCallbacksObj) {
+ sCallbackEnv->CallVoidMethod(
+ mCallbacksObj, method_setBrowsedPlayerCallback, addr, (jint) player_id);
+ } else {
+ ALOGE("%s: mCallbacksObj is null", __func__);
+ }
+
+ checkAndClearExceptionFromCallback(sCallbackEnv, __func__);
+ sCallbackEnv->DeleteLocalRef(addr);
+}
+
+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, bt_bdaddr_t *bd_addr) {
+ jbyteArray addr;
+ jintArray attr_ids = NULL;
+ uint32_t *puiAttr = (uint32_t *)p_attr_ids;
+
+ CHECK_CALLBACK_ENV();
+
+ addr = sCallbackEnv->NewByteArray(sizeof(bt_bdaddr_t));
+ if (!addr) {
+ ALOGE("Fail to new jbyteArray bd addr for get_folder_items command");
+ checkAndClearExceptionFromCallback(sCallbackEnv, __func__);
+ return;
+ }
+
+ if (mCallbacksObj) {
+ sCallbackEnv->SetByteArrayRegion(addr, 0, sizeof(bt_bdaddr_t), (jbyte*) bd_addr);
+
+ /* 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 = (jintArray)sCallbackEnv->NewIntArray(num_attr);
+ if (!attr_ids) {
+ ALOGE("Fail to allocate new jintArray for attrs");
+ checkAndClearExceptionFromCallback(sCallbackEnv, __func__);
+ sCallbackEnv->DeleteLocalRef(addr);
+ return;
+ }
+ sCallbackEnv->SetIntArrayRegion(attr_ids, 0, num_attr, (jint *)puiAttr);
+ }
+
+ sCallbackEnv->CallVoidMethod(mCallbacksObj, method_getFolderItemsCallback, addr,
+ (jbyte) scope, (jint) start_item, (jint) end_item, (jbyte) num_attr, attr_ids);
+ } else {
+ ALOGE("%s: mCallbacksObj is null", __func__);
+ }
+
+ if (attr_ids != NULL) sCallbackEnv->DeleteLocalRef(attr_ids);
+ checkAndClearExceptionFromCallback(sCallbackEnv, __func__);
+ sCallbackEnv->DeleteLocalRef(addr);
+}
+
+static void btavrcp_change_path_callback(uint8_t direction, uint8_t* folder_uid,
+ bt_bdaddr_t *bd_addr) {
+
+ jbyteArray addr;
+ jbyteArray attrs;;
+
+ CHECK_CALLBACK_ENV();
+
+ attrs = sCallbackEnv->NewByteArray(BTRC_UID_SIZE);
+ if (!attrs) {
+ ALOGE("Fail to new jintArray for attrs");
+ checkAndClearExceptionFromCallback(sCallbackEnv, __func__);
+ return;
+ }
+
+ addr = sCallbackEnv->NewByteArray(sizeof(bt_bdaddr_t));
+ if (!addr) {
+ ALOGE("Fail to new jbyteArray bd addr for change_path command");
+ checkAndClearExceptionFromCallback(sCallbackEnv, __func__);
+ sCallbackEnv->DeleteLocalRef(attrs);
+ return;
+ }
+
+ if (mCallbacksObj) {
+ sCallbackEnv->SetByteArrayRegion(addr, 0, sizeof(bt_bdaddr_t), (jbyte*) bd_addr);
+ sCallbackEnv->SetByteArrayRegion(
+ attrs, 0, sizeof(uint8_t)*BTRC_UID_SIZE, (jbyte *)folder_uid);
+ sCallbackEnv->CallVoidMethod(mCallbacksObj, method_changePathCallback, addr,
+ (jbyte) direction, attrs);
+ } else {
+ ALOGE("%s: mCallbacksObj is null", __func__);
+ }
+
+ checkAndClearExceptionFromCallback(sCallbackEnv, __func__);
+ sCallbackEnv->DeleteLocalRef(attrs);
+ sCallbackEnv->DeleteLocalRef(addr);
+}
+
+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, bt_bdaddr_t *bd_addr) {
+
+ jbyteArray addr;
+ jintArray attrs;
+ jbyteArray attr_uid;
+
+ CHECK_CALLBACK_ENV();
+
+ attr_uid = sCallbackEnv->NewByteArray(BTRC_UID_SIZE);
+ if (!attr_uid) {
+ ALOGE("Fail to new jintArray for attr_uid");
+ checkAndClearExceptionFromCallback(sCallbackEnv, __func__);
+ return;
+ }
+
+ addr = sCallbackEnv->NewByteArray(sizeof(bt_bdaddr_t));
+ if (!addr) {
+ ALOGE("Fail to new jbyteArray bd addr for get_item_attr command");
+ checkAndClearExceptionFromCallback(sCallbackEnv, __func__);
+ sCallbackEnv->DeleteLocalRef(attr_uid);
+ return;
+ }
+
+ attrs = (jintArray)sCallbackEnv->NewIntArray(num_attr);
+ if (!attrs) {
+ ALOGE("Fail to new jintArray for attrs");
+ checkAndClearExceptionFromCallback(sCallbackEnv, __func__);
+ sCallbackEnv->DeleteLocalRef(attr_uid);
+ sCallbackEnv->DeleteLocalRef(addr);
+ return;
+ }
+
+ if (mCallbacksObj) {
+ sCallbackEnv->SetByteArrayRegion(addr, 0, sizeof(bt_bdaddr_t), (jbyte*) bd_addr);
+ sCallbackEnv->SetIntArrayRegion(attrs, 0, num_attr, (jint *)p_attrs);
+ sCallbackEnv->SetByteArrayRegion(attr_uid, 0, sizeof(uint8_t)*BTRC_UID_SIZE, (jbyte *)uid);
+
+ sCallbackEnv->CallVoidMethod(mCallbacksObj, method_getItemAttrCallback, addr,
+ (jbyte) scope, attr_uid, (jint) uid_counter, (jbyte)num_attr, attrs);
+ } else {
+ ALOGE("%s: mCallbacksObj is null", __func__);
+ }
+
+ checkAndClearExceptionFromCallback(sCallbackEnv, __func__);
+ sCallbackEnv->DeleteLocalRef(attr_uid);
+ sCallbackEnv->DeleteLocalRef(attrs);
+ sCallbackEnv->DeleteLocalRef(addr);
+}
+
+static void btavrcp_play_item_callback(uint8_t scope, uint16_t uid_counter, uint8_t* uid,
+ bt_bdaddr_t *bd_addr) {
+
+ jbyteArray addr;
+ jbyteArray attrs;
+
+ CHECK_CALLBACK_ENV();
+
+ attrs = sCallbackEnv->NewByteArray(BTRC_UID_SIZE);
+ if (!attrs) {
+ ALOGE("%s:Fail to new jByteArray attrs for play_item command", __func__);
+ checkAndClearExceptionFromCallback(sCallbackEnv, __func__);
+ return;
+ }
+
+ addr = sCallbackEnv->NewByteArray(sizeof(bt_bdaddr_t));
+ if (!addr) {
+ ALOGE("Fail to new jbyteArray bd addr for play_item command");
+ checkAndClearExceptionFromCallback(sCallbackEnv, __func__);
+ sCallbackEnv->DeleteLocalRef(attrs);
+ return;
+ }
+
+ if (mCallbacksObj) {
+ sCallbackEnv->SetByteArrayRegion(addr, 0, sizeof(bt_bdaddr_t), (jbyte*) bd_addr);
+ sCallbackEnv->SetByteArrayRegion(attrs, 0, sizeof(uint8_t)*BTRC_UID_SIZE, (jbyte *)uid);
+ sCallbackEnv->CallVoidMethod(mCallbacksObj, method_playItemCallback, addr,
+ (jbyte) scope, (jint) uid_counter, attrs);
+ } else {
+ ALOGE("%s: mCallbacksObj is null", __func__);
+ }
+
+ checkAndClearExceptionFromCallback(sCallbackEnv, __func__);
+ sCallbackEnv->DeleteLocalRef(attrs);
+ sCallbackEnv->DeleteLocalRef(addr);
+}
+
+static void btavrcp_get_total_num_items_callback(uint8_t scope, bt_bdaddr_t *bd_addr) {
+
+ jbyteArray addr;
+
+ CHECK_CALLBACK_ENV();
+
+ addr = sCallbackEnv->NewByteArray(sizeof(bt_bdaddr_t));
+ if (!addr) {
+ ALOGE("Fail to new jbyteArray bd addr for get_total_num_items command");
+ checkAndClearExceptionFromCallback(sCallbackEnv, __func__);
+ return;
+ }
+
+ if (mCallbacksObj) {
+ sCallbackEnv->SetByteArrayRegion(addr, 0, sizeof(bt_bdaddr_t), (jbyte*) bd_addr);
+ sCallbackEnv->CallVoidMethod(
+ mCallbacksObj, method_getTotalNumOfItemsCallback, addr, (jbyte) scope);
} else {
- ALOGE("%s: mCallbacksObj is null", __FUNCTION__);
+ ALOGE("%s: mCallbacksObj is null", __func__);
}
- checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+
+ checkAndClearExceptionFromCallback(sCallbackEnv, __func__);
+ sCallbackEnv->DeleteLocalRef(addr);
}
-static void btavrcp_volume_change_callback(uint8_t volume, uint8_t ctype) {
- ALOGI("%s", __FUNCTION__);
+static void btavrcp_search_callback(uint16_t charset_id, uint16_t str_len, uint8_t* p_str,
+ bt_bdaddr_t *bd_addr) {
+
+ jbyteArray addr;
+ jbyteArray attrs;
+
+ CHECK_CALLBACK_ENV();
+
+ attrs = sCallbackEnv->NewByteArray(str_len);
+ if (!attrs) {
+ ALOGE("Fail to new jintArray for attrs");
+ checkAndClearExceptionFromCallback(sCallbackEnv, __func__);
+ return;
+ }
- if (!checkCallbackThread()) {
- ALOGE("Callback: '%s' is not called on the correct thread", __FUNCTION__);
+ addr = sCallbackEnv->NewByteArray(sizeof(bt_bdaddr_t));
+ if (!addr) {
+ ALOGE("Fail to new jbyteArray bd addr for search command");
+ checkAndClearExceptionFromCallback(sCallbackEnv, __func__);
+ sCallbackEnv->DeleteLocalRef(attrs);
return;
}
+
if (mCallbacksObj) {
- sCallbackEnv->CallVoidMethod(mCallbacksObj, method_volumeChangeCallback, (jint)volume,
- (jint)ctype);
+ sCallbackEnv->SetByteArrayRegion(addr, 0, sizeof(bt_bdaddr_t), (jbyte*) bd_addr);
+ sCallbackEnv->SetByteArrayRegion(
+ attrs, 0, str_len*sizeof(uint8_t), (jbyte *)p_str);
+ sCallbackEnv->CallVoidMethod(
+ mCallbacksObj, method_searchCallback, addr, (jint) charset_id, attrs);
} else {
- ALOGE("%s: mCallbacksObj is null", __FUNCTION__);
+ ALOGE("%s: mCallbacksObj is null", __func__);
}
- checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+ checkAndClearExceptionFromCallback(sCallbackEnv, __func__);
+ sCallbackEnv->DeleteLocalRef(attrs);
+ sCallbackEnv->DeleteLocalRef(addr);
}
-static void btavrcp_passthrough_command_callback(int id, int pressed) {
- ALOGI("%s", __FUNCTION__);
+static void btavrcp_add_to_play_list_callback(uint8_t scope,
+ uint8_t* uid, uint16_t uid_counter, bt_bdaddr_t *bd_addr) {
+ jbyteArray attrs;
+ jbyteArray addr;
+
+ CHECK_CALLBACK_ENV();
+
+ addr = sCallbackEnv->NewByteArray(sizeof(bt_bdaddr_t));
+ if (!addr) {
+ ALOGE("Fail to new jbyteArray bd addr for add_to_play_list command");
+ checkAndClearExceptionFromCallback(sCallbackEnv, __func__);
+ return;
+ }
- if (!checkCallbackThread()) {
- ALOGE("Callback: '%s' is not called on the correct thread", __FUNCTION__);
+ attrs = sCallbackEnv->NewByteArray(BTRC_UID_SIZE);
+ if (!attrs) {
+ ALOGE("Fail to new jByteArray for attrs");
+ checkAndClearExceptionFromCallback(sCallbackEnv, __func__);
+ sCallbackEnv->DeleteLocalRef(addr);
return;
}
+
if (mCallbacksObj) {
- sCallbackEnv->CallVoidMethod(mCallbacksObj, method_handlePassthroughCmd,
- (jint)id, (jint)pressed);
+ sCallbackEnv->SetByteArrayRegion(addr, 0, sizeof(bt_bdaddr_t), (jbyte*) bd_addr);
+ sCallbackEnv->SetByteArrayRegion(attrs, 0, sizeof(uint8_t)*BTRC_UID_SIZE, (jbyte *)uid);
+ sCallbackEnv->CallVoidMethod(mCallbacksObj, method_addToPlayListCallback, addr,
+ (jbyte) scope, attrs, (jint) uid_counter);
} else {
- ALOGE("%s: mCallbacksObj is null", __FUNCTION__);
+ ALOGE("%s: mCallbacksObj is null", __func__);
}
- checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+
+ checkAndClearExceptionFromCallback(sCallbackEnv, __func__);
+ sCallbackEnv->DeleteLocalRef(attrs);
+ sCallbackEnv->DeleteLocalRef(addr);
}
+
static btrc_callbacks_t sBluetoothAvrcpCallbacks = {
sizeof(sBluetoothAvrcpCallbacks),
btavrcp_remote_features_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, "getRcFeatures", "([BI)V");
+ env->GetMethodID(clazz, "getRcFeaturesRequestFromNative", "([BI)V");
method_getPlayStatus =
- env->GetMethodID(clazz, "getPlayStatus", "()V");
+ env->GetMethodID(clazz, "getPlayStatusRequestFromNative", "([B)V");
method_getElementAttr =
- env->GetMethodID(clazz, "getElementAttr", "(B[I)V");
+ env->GetMethodID(clazz, "getElementAttrRequestFromNative", "([BB[I)V");
method_registerNotification =
- env->GetMethodID(clazz, "registerNotification", "(II)V");
+ env->GetMethodID(clazz, "registerNotificationRequestFromNative", "([BII)V");
method_volumeChangeCallback =
- env->GetMethodID(clazz, "volumeChangeCallback", "(II)V");
+ env->GetMethodID(clazz, "volumeChangeRequestFromNative", "([BII)V");
method_handlePassthroughCmd =
- env->GetMethodID(clazz, "handlePassthroughCmd", "(II)V");
+ 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", "([BBIIB[I)V");
+
+ method_changePathCallback =
+ env->GetMethodID(clazz, "changePathRequestFromNative", "([BB[B)V");
+
+ method_getItemAttrCallback =
+ env->GetMethodID(clazz, "getItemAttrRequestFromNative", "([BB[BIB[I)V");
- ALOGI("%s: succeeds", __FUNCTION__);
+ 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) {
}
}
-static jboolean getPlayStatusRspNative(JNIEnv *env, jobject object, jint playStatus,
- jint songLen, jint songPos) {
+static jboolean getPlayStatusRspNative(JNIEnv *env, jobject object,
+ jbyteArray address, jint playStatus, jint songLen, jint songPos) {
bt_status_t status;
+ bt_bdaddr_t * btAddr;
+ jbyte *addr;
- ALOGI("%s: sBluetoothAvrcpInterface: %p", __FUNCTION__, sBluetoothAvrcpInterface);
- if (!sBluetoothAvrcpInterface) return JNI_FALSE;
+ if (!sBluetoothAvrcpInterface) {
+ ALOGE("%s: sBluetoothAvrcpInterface is null", __func__);
+ return JNI_FALSE;
+ }
- if ((status = sBluetoothAvrcpInterface->get_play_status_rsp((btrc_play_status_t)playStatus,
- songLen, songPos)) != BT_STATUS_SUCCESS) {
- ALOGE("Failed get_play_status_rsp, status: %d", status);
+ addr = env->GetByteArrayElements(address, NULL);
+ btAddr = (bt_bdaddr_t *) addr;
+ if (!addr) {
+ jniThrowIOException(env, EINVAL);
+ return JNI_FALSE;
}
+ if ((status = sBluetoothAvrcpInterface->get_play_status_rsp((bt_bdaddr_t*)btAddr,
+ (btrc_play_status_t)playStatus,songLen, songPos)) != 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, jbyte numAttr,
- jintArray attrIds, jobjectArray textArray) {
+static jboolean getElementAttrRspNative(JNIEnv *env, jobject object,
+ jbyteArray address, jbyte numAttr, jintArray attrIds,
+ jobjectArray textArray) {
jint *attr;
bt_status_t status;
jstring text;
- int i;
+ int attr_cnt;
btrc_element_attr_val_t *pAttrs = NULL;
- const char* textStr;
- if (!sBluetoothAvrcpInterface) return JNI_FALSE;
+ bt_bdaddr_t * btAddr;
+ jbyte *addr;
+
+ 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;
}
-
+ addr = env->GetByteArrayElements(address, NULL);
+ btAddr = (bt_bdaddr_t *) addr;
+ if (!addr) {
+ jniThrowIOException(env, EINVAL);
+ return JNI_FALSE;
+ }
+
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;
}
if (!attr) {
delete[] pAttrs;
jniThrowIOException(env, EINVAL);
+ env->ReleaseByteArrayElements(address, addr, 0);
return JNI_FALSE;
}
- for (i = 0; i < numAttr; ++i) {
- text = (jstring) env->GetObjectArrayElement(textArray, i);
- textStr = env->GetStringUTFChars(text, NULL);
- if (!textStr) {
- ALOGE("get_element_attr_rsp: GetStringUTFChars return NULL");
- env->DeleteLocalRef(text);
+ for (attr_cnt = 0; attr_cnt < numAttr; ++attr_cnt) {
+ pAttrs[attr_cnt].attr_id = attr[attr_cnt];
+ text = (jstring) env->GetObjectArrayElement(textArray, attr_cnt);
+
+ if (!copy_jstring(pAttrs[attr_cnt].text, BTRC_MAX_ATTR_STR_LEN, text, env))
break;
- }
- pAttrs[i].attr_id = attr[i];
- if (strlen(textStr) >= BTRC_MAX_ATTR_STR_LEN) {
- ALOGE("get_element_attr_rsp: string length exceed maximum");
- strncpy((char *)pAttrs[i].text, textStr, BTRC_MAX_ATTR_STR_LEN-1);
- pAttrs[i].text[BTRC_MAX_ATTR_STR_LEN-1] = 0;
- } else {
- strcpy((char *)pAttrs[i].text, textStr);
- }
- env->ReleaseStringUTFChars(text, textStr);
env->DeleteLocalRef(text);
}
- if (i < numAttr) {
+ if (attr_cnt < numAttr) {
delete[] pAttrs;
+ env->DeleteLocalRef(text);
env->ReleaseIntArrayElements(attrIds, attr, 0);
+ ALOGE("%s: Failed to copy attributes", __func__);
return JNI_FALSE;
}
- if ((status = sBluetoothAvrcpInterface->get_element_attr_rsp(numAttr, pAttrs)) !=
- BT_STATUS_SUCCESS) {
+ if ((status = sBluetoothAvrcpInterface->get_element_attr_rsp(btAddr, numAttr, pAttrs)) !=
+ 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) {
+ jint *attr = NULL;
+ bt_status_t status;
+ int attr_cnt;
+ btrc_element_attr_val_t *pAttrs = NULL;
+
+ bt_bdaddr_t * btAddr;
+ jbyte *addr;
+
+ if (!sBluetoothAvrcpInterface) {
+ ALOGE("%s: sBluetoothAvrcpInterface is null", __func__);
+ return JNI_FALSE;
+ }
+
+ addr = env->GetByteArrayElements(address, NULL);
+ btAddr = (bt_bdaddr_t *) addr;
+ 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;
+ }
+
+ 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;
+ }
+
+ if (attrIds != NULL) {
+ attr = env->GetIntArrayElements(attrIds, NULL);
+ if (!attr) {
+ delete[] pAttrs;
+ jniThrowIOException(env, EINVAL);
+ env->ReleaseByteArrayElements(address, addr, 0);
+ return JNI_FALSE;
+ }
+ }
+
+ for (attr_cnt = 0; attr_cnt < numAttr; ++attr_cnt) {
+ pAttrs[attr_cnt].attr_id = attr[attr_cnt];
+ jstring text = (jstring) env->GetObjectArrayElement(textArray, attr_cnt);
+
+ if (!copy_jstring(pAttrs[attr_cnt].text, BTRC_MAX_ATTR_STR_LEN, text, env)) {
+ rspStatus = BTRC_STS_INTERNAL_ERR;
+ env->DeleteLocalRef(text);
+ ALOGE("%s: Failed to copy attributes", __func__);
+ break;
+ }
+
+ env->DeleteLocalRef(text);
+ }
+
+ if ((status = sBluetoothAvrcpInterface->get_item_attr_rsp(btAddr,
+ (btrc_status_t)rspStatus, numAttr, pAttrs)) != 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;
}
bt_status_t status;
btrc_register_notification_t param;
- ALOGI("%s: sBluetoothAvrcpInterface: %p", __FUNCTION__, sBluetoothAvrcpInterface);
- if (!sBluetoothAvrcpInterface) return JNI_FALSE;
+ if (!sBluetoothAvrcpInterface) {
+ ALOGE("%s: sBluetoothAvrcpInterface is null", __func__);
+ return JNI_FALSE;
+ }
param.play_status = (btrc_play_status_t)playStatus;
if ((status = sBluetoothAvrcpInterface->register_notification_rsp(BTRC_EVT_PLAY_STATUS_CHANGED,
bt_status_t status;
btrc_register_notification_t param;
jbyte *trk;
- int i;
+ int uid_idx;
- ALOGI("%s: sBluetoothAvrcpInterface: %p", __FUNCTION__, sBluetoothAvrcpInterface);
- if (!sBluetoothAvrcpInterface) return JNI_FALSE;
+ if (!sBluetoothAvrcpInterface) {
+ ALOGE("%s: sBluetoothAvrcpInterface is null", __func__);
+ return JNI_FALSE;
+ }
trk = env->GetByteArrayElements(track, NULL);
if (!trk) {
return JNI_FALSE;
}
- for (i = 0; i < BTRC_UID_SIZE; ++i) {
- param.track[i] = trk[i];
+ for (uid_idx = 0; uid_idx < BTRC_UID_SIZE; ++uid_idx) {
+ param.track[uid_idx] = trk[uid_idx];
}
if ((status = sBluetoothAvrcpInterface->register_notification_rsp(BTRC_EVT_TRACK_CHANGE,
bt_status_t status;
btrc_register_notification_t param;
- if (!sBluetoothAvrcpInterface) return JNI_FALSE;
+ if (!sBluetoothAvrcpInterface) {
+ ALOGE("%s: sBluetoothAvrcpInterface is null", __func__);
+ return JNI_FALSE;
+ }
param.song_pos = (uint32_t)playPos;
if ((status = sBluetoothAvrcpInterface->register_notification_rsp(BTRC_EVT_PLAY_POS_CHANGED,
return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
}
-static jboolean setVolumeNative(JNIEnv *env, jobject object, jint volume) {
+static jboolean registerNotificationRspNowPlayingChangedNative(
+ JNIEnv *env, jobject object,jint type) {
bt_status_t status;
+ btrc_register_notification_t param;
- //TODO: delete test code
- ALOGI("%s: jint: %d, uint8_t: %u", __FUNCTION__, volume, (uint8_t) volume);
+ if (!sBluetoothAvrcpInterface) {
+ ALOGE("%s: sBluetoothAvrcpInterface is null", __func__);
+ return JNI_FALSE;
+ }
- ALOGI("%s: sBluetoothAvrcpInterface: %p", __FUNCTION__, sBluetoothAvrcpInterface);
- if (!sBluetoothAvrcpInterface) return JNI_FALSE;
+ if ((status = sBluetoothAvrcpInterface->register_notification_rsp(
+ BTRC_EVT_NOW_PLAYING_CONTENT_CHANGED,
+ (btrc_notification_type_t)type, ¶m)) != 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) {
+ bt_status_t status;
+ btrc_register_notification_t param;
+
+ if (!sBluetoothAvrcpInterface) {
+ ALOGE("%s: sBluetoothAvrcpInterface is null", __func__);
+ return JNI_FALSE;
+ }
+
+ param.uids_changed.uid_counter = (uint16_t)uidCounter;
+
+ if ((status = sBluetoothAvrcpInterface->register_notification_rsp(
+ BTRC_EVT_UIDS_CHANGED,
+ (btrc_notification_type_t)type,¶m)) != 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) {
+ bt_status_t status;
+ btrc_register_notification_t param;
+
+ if (!sBluetoothAvrcpInterface) {
+ ALOGE("%s: sBluetoothAvrcpInterface is null", __func__);
+ return JNI_FALSE;
+ }
+
+ param.addr_player_changed.player_id = (uint16_t)playerId;
+ param.addr_player_changed.uid_counter = (uint16_t)uidCounter;
+
+ if ((status = sBluetoothAvrcpInterface->register_notification_rsp(BTRC_EVT_ADDR_PLAYER_CHANGE,
+ (btrc_notification_type_t)type, ¶m)) != 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) {
+ bt_status_t status;
+ btrc_register_notification_t param;
+
+ if (!sBluetoothAvrcpInterface) {
+ ALOGE("%s: sBluetoothAvrcpInterface is null", __func__);
+ return JNI_FALSE;
+ }
+
+ if ((status = sBluetoothAvrcpInterface->register_notification_rsp(BTRC_EVT_AVAL_PLAYER_CHANGE,
+ (btrc_notification_type_t)type, ¶m)) != 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) {
+ bt_status_t status;
+
+ if (!sBluetoothAvrcpInterface) {
+ ALOGE("%s: sBluetoothAvrcpInterface is null", __func__);
+ return JNI_FALSE;
+ }
if ((status = sBluetoothAvrcpInterface->set_volume((uint8_t)volume)) != 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,
+ jbyteArray playerTypes,jintArray playerSubtypes,
+ jbyteArray playStatusValues,
+ jshortArray featureBitmask,jobjectArray textArray) {
+ bt_status_t status;
+ btrc_folder_items_t *p_items = NULL;
+ int itemIdx = 0, InnCnt = 0;
+ jbyte* p_playerTypes = NULL,*p_PlayStatusValues = NULL;
+ jshort *p_FeatBitMaskValues = NULL;
+ jint *p_playerSubTypes = NULL;
+ jstring text;
+
+ bt_bdaddr_t * btAddr;
+ jbyte *addr;
+
+ if (!sBluetoothAvrcpInterface) {
+ ALOGE("%s: sBluetoothAvrcpInterface is null", __func__);
+ return JNI_FALSE;
+ }
+
+ addr = env->GetByteArrayElements(address, NULL);
+ btAddr = (bt_bdaddr_t *) addr;
+ if (!addr) {
+ jniThrowIOException(env, EINVAL);
+ return JNI_FALSE;
+ }
+
+ if (rspStatus == BTRC_STS_NO_ERROR) {
+ /* allocate memory */
+ 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_playerTypes || !p_playerSubTypes || !p_PlayStatusValues || !p_FeatBitMaskValues
+ || !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);
+ 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 */
+ for (itemIdx = 0; itemIdx < numItems; ++itemIdx) {
+ p_items[itemIdx].player.player_id = (uint16_t)(itemIdx+1);
+ 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;
+
+ text = (jstring) env->GetObjectArrayElement(textArray, itemIdx);
+ /* copy player name */
+ if (!copy_jstring(p_items[itemIdx].player.name, BTRC_MAX_ATTR_STR_LEN, text, env))
+ break;
+
+ env->DeleteLocalRef(text);
+
+ /* Feature bit mask is 128-bit value each */
+ for (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__);
+ }
+
+ }
+
+ if ((status = sBluetoothAvrcpInterface->get_folder_items_list_rsp(btAddr,
+ (btrc_status_t)rspStatus, uidCounter, numItems, p_items)) != 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) {
+ jstring text; /* folder or item name */
+ bt_status_t status = BT_STATUS_SUCCESS;
+ 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;
+ int item_idx = 0;
+
+ bt_bdaddr_t * btAddr;
+ jbyte *addr;
+
+ if (!sBluetoothAvrcpInterface) {
+ ALOGE("%s: sBluetoothAvrcpInterface is null", __func__);
+ return JNI_FALSE;
+ }
+
+ addr = env->GetByteArrayElements(address, NULL);
+ btAddr = (bt_bdaddr_t *) addr;
+ if (!addr) {
+ jniThrowIOException(env, EINVAL);
+ return JNI_FALSE;
+ }
+
+ /* 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 (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];
+
+ text = (jstring) env->GetObjectArrayElement(displayNameArray, item_idx);
+ if (!copy_jstring(pitem->folder.name, BTRC_MAX_ATTR_STR_LEN,
+ text, env)) {
+ rspStatus = BTRC_STS_INTERNAL_ERR;
+ env->DeleteLocalRef(text);
+ ALOGE("%s: failed to copy display name of folder item", __func__);
+ break;
+ }
+ env->DeleteLocalRef(text);
+ }
+ 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;
+
+ text = (jstring) env->GetObjectArrayElement(displayNameArray, item_idx);
+ if (!copy_jstring(pitem->media.name, BTRC_MAX_ATTR_STR_LEN, text, env)){
+ rspStatus = BTRC_STS_INTERNAL_ERR;
+ env->DeleteLocalRef(text);
+ ALOGE("%s: failed to copy display name of media item", __func__);
+ break;
+ }
+ env->DeleteLocalRef(text);
+
+ /* 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__);
+ }
+ }
+
+ if ((status = sBluetoothAvrcpInterface->get_folder_items_list_rsp(btAddr,
+ (btrc_status_t)rspStatus, uidCounter,numItems,p_items)) != 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) {
+ bt_status_t status;
+ bt_bdaddr_t * btAddr;
+ jbyte *addr;
+
+ if (!sBluetoothAvrcpInterface) {
+ ALOGE("%s: sBluetoothAvrcpInterface is null", __func__);
+ return JNI_FALSE;
+ }
+
+ addr = env->GetByteArrayElements(address, NULL);
+ btAddr = (bt_bdaddr_t *) addr;
+ if (!addr) {
+ jniThrowIOException(env, EINVAL);
+ return JNI_FALSE;
+ }
+
+ if ((status = sBluetoothAvrcpInterface->set_addressed_player_rsp(btAddr,
+ (btrc_status_t)rspStatus)) != 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) {
+ bt_status_t status;
+ bt_bdaddr_t * btAddr;
+ jbyte *addr;
+
+ uint16_t charset_id = BTRC_CHARSET_ID_UTF8;
+ uint8_t folder_depth = depth; /* folder_depth is 0 if current folder is root */
+
+ btrc_br_folder_name_t *p_folders = NULL;
+ int folder_idx = 0;
+
+ if (!sBluetoothAvrcpInterface) {
+ ALOGE("%s: sBluetoothAvrcpInterface is null", __func__);
+ return JNI_FALSE;
+ }
+
+ addr = env->GetByteArrayElements(address, NULL);
+ btAddr = (bt_bdaddr_t *) addr;
+ if (!addr) {
+ jniThrowIOException(env, EINVAL);
+ return JNI_FALSE;
+ }
+
+ if (rspStatus == BTRC_STS_NO_ERROR) {
+ if (depth > 0)
+ p_folders = new btrc_br_folder_name_t[depth];
+
+ for (folder_idx = 0; folder_idx < depth; folder_idx++) {
+ /* copy folder names */
+ jstring text = (jstring) env->GetObjectArrayElement(textArray, folder_idx);
+
+ if (!copy_jstring(p_folders[folder_idx].p_str, BTRC_MAX_ATTR_STR_LEN,
+ text, env)) {
+ rspStatus = BTRC_STS_INTERNAL_ERR;
+ env->DeleteLocalRef(text);
+ 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);
+
+ env->DeleteLocalRef(text);
+ }
+
+ }
+
+ if ((status = sBluetoothAvrcpInterface->set_browsed_player_rsp(btAddr,
+ (btrc_status_t) rspStatus, numItems, charset_id, folder_depth,
+ p_folders)) != 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) {
+ bt_status_t status;
+ uint32_t nItems = (uint32_t)numItems;
+ bt_bdaddr_t * btAddr;
+ jbyte *addr;
+
+ if (!sBluetoothAvrcpInterface) {
+ ALOGE("%s: sBluetoothAvrcpInterface is null", __func__);
+ return JNI_FALSE;
+ }
+
+ addr = env->GetByteArrayElements(address, NULL);
+ btAddr = (bt_bdaddr_t *) addr;
+ if (!addr) {
+ jniThrowIOException(env, EINVAL);
+ return JNI_FALSE;
+ }
+
+ if ((status = sBluetoothAvrcpInterface->change_path_rsp(btAddr, (btrc_status_t)rspStatus,
+ (uint32_t) nItems)) != 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) {
+ bt_status_t status;
+ uint32_t nItems = (uint32_t)numItems;
+ bt_bdaddr_t * btAddr;
+ jbyte *addr;
+
+ if (!sBluetoothAvrcpInterface) {
+ ALOGE("%s: sBluetoothAvrcpInterface is null", __func__);
+ return JNI_FALSE;
+ }
+
+ addr = env->GetByteArrayElements(address, NULL);
+ btAddr = (bt_bdaddr_t *) addr;
+ if (!addr) {
+ jniThrowIOException(env, EINVAL);
+ return JNI_FALSE;
+ }
+
+ if ((status = sBluetoothAvrcpInterface->search_rsp(btAddr, (btrc_status_t)rspStatus,
+ (uint32_t) uidCounter, (uint32_t) nItems)) != 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) {
+ bt_status_t status;
+ bt_bdaddr_t * btAddr;
+ jbyte *addr;
+
+ if (!sBluetoothAvrcpInterface) {
+ ALOGE("%s: sBluetoothAvrcpInterface is null", __func__);
+ return JNI_FALSE;
+ }
+
+ addr = env->GetByteArrayElements(address, NULL);
+ btAddr = (bt_bdaddr_t *) addr;
+ if (!addr) {
+ jniThrowIOException(env, EINVAL);
+ return JNI_FALSE;
+ }
+
+ if ((status = sBluetoothAvrcpInterface->play_item_rsp(btAddr, (btrc_status_t)rspStatus))
+ != 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) {
+ bt_status_t status;
+ uint32_t nItems = (uint32_t)numItems;
+ bt_bdaddr_t * btAddr;
+ jbyte *addr;
+
+ if (!sBluetoothAvrcpInterface) {
+ ALOGE("%s: sBluetoothAvrcpInterface is null", __func__);
+ return JNI_FALSE;
+ }
+
+ addr = env->GetByteArrayElements(address, NULL);
+ btAddr = (bt_bdaddr_t *) addr;
+ if (!addr) {
+ jniThrowIOException(env, EINVAL);
+ return JNI_FALSE;
+ }
+
+ if ((status = sBluetoothAvrcpInterface->get_total_num_of_items_rsp(btAddr,
+ (btrc_status_t)rspStatus, (uint32_t) uidCounter, (uint32_t) nItems))
+ != 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) {
+ bt_status_t status;
+ bt_bdaddr_t * btAddr;
+ jbyte *addr;
+
+ if (!sBluetoothAvrcpInterface) {
+ ALOGE("%s: sBluetoothAvrcpInterface is null", __func__);
+ return JNI_FALSE;
+ }
+
+ addr = env->GetByteArrayElements(address, NULL);
+ btAddr = (bt_bdaddr_t *) addr;
+ if (!addr) {
+ jniThrowIOException(env, EINVAL);
+ return JNI_FALSE;
+ }
+
+ if ((status = sBluetoothAvrcpInterface->add_to_now_playing_rsp(btAddr,
+ (btrc_status_t)rspStatus)) != 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", "(III)Z", (void *) getPlayStatusRspNative},
- {"getElementAttrRspNative", "(B[I[Ljava/lang/String;)Z", (void *) getElementAttrRspNative},
+ {"getPlayStatusRspNative", "([BIII)Z", (void *) getPlayStatusRspNative},
+ {"getElementAttrRspNative", "([BB[I[Ljava/lang/String;)Z", (void *) getElementAttrRspNative},
{"registerNotificationRspPlayStatusNative", "(II)Z",
(void *) registerNotificationRspPlayStatusNative},
{"registerNotificationRspTrackChangeNative", "(I[B)Z",
(void *) registerNotificationRspPlayPosNative},
{"setVolumeNative", "(I)Z",
(void *) setVolumeNative},
+
+ {"setAddressedPlayerRspNative", "([BI)Z",
+ (void *) setAddressedPlayerRspNative},
+
+ {"setBrowsedPlayerRspNative", "([BIBI[Ljava/lang/String;)Z",
+ (void *) setBrowsedPlayerRspNative},
+
+ {"mediaPlayerListRspNative", "([BIIBI[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)
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;
+ jstring text;
+
+ /* 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);
+
+ if (!(pitem->media.p_attrs = new btrc_element_attr_val_t[num_attrs])) {
+ return false;
+ }
+
+ for (int tempAtrCount = 0; tempAtrCount < pitem->media.num_attrs; ++tempAtrCount) {
+ pitem->media.p_attrs[tempAtrCount].attr_id =
+ p_attributesIds[attribCopiedIndex + tempAtrCount];
+
+ text = (jstring) env->GetObjectArrayElement(attributesArray,
+ attribCopiedIndex + tempAtrCount);
+
+ if (!copy_jstring(pitem->media.p_attrs[tempAtrCount].text, BTRC_MAX_ATTR_STR_LEN,
+ text, env)) {
+ success = false;
+ env->DeleteLocalRef(text);
+ ALOGE("%s: failed to copy attributes", __func__);
+ break;
+ }
+ env->DeleteLocalRef(text);
+ }
+ }
+ 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) {
+ const char* p_str;
+ int len;
+
+ if (str == NULL || jstr == NULL || env == NULL)
+ return false;
+
+ memset(str, 0, maxBytes);
+ p_str = env->GetStringUTFChars(jstr, NULL);
+ 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;
+ }
+}
+
}
/*
- * Copyright (C) 2012 The Android Open Source Project
+ * 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.
return true;
}
-static void btavrcp_passthrough_response_callback(int id, int pressed) {
- ALOGI("%s", __FUNCTION__);
+static void btavrcp_passthrough_response_callback(int id, int pressed, bt_bdaddr_t* bd_addr) {
+ jbyteArray addr;
+
+ ALOGI("%s", __func__);
ALOGI("id: %d, pressed: %d", id, pressed);
if (!checkCallbackThread()) {
- ALOGE("Callback: '%s' is not called on the correct thread", __FUNCTION__);
+ ALOGE("Callback: '%s' is not called on the correct thread", __func__);
+ return;
+ }
+ addr = sCallbackEnv->NewByteArray(sizeof(bt_bdaddr_t));
+ if (!addr) {
+ ALOGE("Fail to new jbyteArray bd addr for passthrough response");
+ checkAndClearExceptionFromCallback(sCallbackEnv, __func__);
return;
}
+ sCallbackEnv->SetByteArrayRegion(addr, 0, sizeof(bt_bdaddr_t), (jbyte*) bd_addr);
+
sCallbackEnv->CallVoidMethod(mCallbacksObj, method_handlePassthroughRsp, (jint)id,
- (jint)pressed);
- checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+ (jint)pressed,
+ addr);
+ checkAndClearExceptionFromCallback(sCallbackEnv, __func__);
+
+ sCallbackEnv->DeleteLocalRef(addr);
}
static void btavrcp_groupnavigation_response_callback(int id, int pressed) {
static void classInitNative(JNIEnv* env, jclass clazz) {
method_handlePassthroughRsp =
- env->GetMethodID(clazz, "handlePassthroughRsp", "(II)V");
+ env->GetMethodID(clazz, "handlePassthroughRsp", "(II[B)V");
method_handleGroupNavigationRsp =
env->GetMethodID(clazz, "handleGroupNavigationRsp", "(II)V");
--- /dev/null
+/*
+ * 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.bluetooth.BluetoothAvrcp;
+import android.media.session.MediaSession;
+import android.media.session.MediaSession.QueueItem;
+import android.media.session.MediaController;
+import android.media.MediaDescription;
+import android.media.MediaMetadata;
+import android.os.Bundle;
+import android.util.Log;
+
+import java.nio.ByteBuffer;
+import java.util.List;
+import java.util.Arrays;
+import java.util.ArrayList;
+
+/*************************************************************************************************
+ * 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 {
+ static private final String TAG = "AddressedMediaPlayer";
+ static private final Boolean DEBUG = false;
+
+ private AvrcpMediaRspInterface mMediaInterface;
+ private NowPlayingListManager mNowPlayingListManager = new NowPlayingListManager();
+
+ /* Now playing UID */
+ private static final byte[] NOW_PLAYING_UID = {(byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00,
+ (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00};
+
+ public AddressedMediaPlayer(AvrcpMediaRspInterface _mediaInterface) {
+ mMediaInterface = _mediaInterface;
+ }
+
+ void cleanup() {
+ if (DEBUG) Log.v(TAG, "cleanup");
+ mNowPlayingListManager = null;
+ mMediaInterface = null;
+ }
+
+ /* get now playing list from addressed player */
+ void getFolderItemsNowPlaying(byte[] bdaddr, AvrcpCmd.FolderItemsCmd reqObj,
+ MediaController mediaController) {
+ List<QueueItem> tempItems;
+ List<MediaSession.QueueItem> mNowPlayingItems = mNowPlayingListManager.getNowPlayingList();
+ if (DEBUG) Log.v(TAG, "getFolderItemsNowPlaying");
+
+ if (mNowPlayingItems != null) {
+ // We already have the cached list sending the response to remote
+ if (DEBUG) Log.i(TAG, "sending cached now playing list");
+ /* Filter attributes from cached NowPlayingList and send response */
+ getFolderItemsFilterAttr(bdaddr, reqObj, mNowPlayingItems,
+ AvrcpConstants.BTRC_SCOPE_FILE_SYSTEM, reqObj.mStartItem, reqObj.mEndItem);
+ } else if (mediaController == null) {
+ Log.e(TAG, "mediaController = null, sending internal error response");
+ mMediaInterface.folderItemsRsp(bdaddr, AvrcpConstants.RSP_INTERNAL_ERR, null);
+ } else {
+ // We don't have the cached list, fetching it from Media Controller
+ mNowPlayingItems = mediaController.getQueue();
+ if (mNowPlayingItems == null) {
+ Log.w(TAG, "Received Now playing list is null from: " +
+ mediaController.getPackageName() + ", sending internal error response");
+ mMediaInterface.folderItemsRsp(bdaddr, AvrcpConstants.RSP_INTERNAL_ERR, null);
+ } else {
+ mNowPlayingListManager.setNowPlayingList(mNowPlayingItems);
+ getFolderItemsFilterAttr(bdaddr, reqObj, mNowPlayingItems,
+ AvrcpConstants.BTRC_SCOPE_NOW_PLAYING, reqObj.mStartItem,
+ reqObj.mEndItem);
+ }
+ }
+ }
+
+ /* get item attributes for item in now playing list */
+ void getItemAttr(byte[] bdaddr, AvrcpCmd.ItemAttrCmd itemAttr,
+ MediaController mediaController) {
+ int status = AvrcpConstants.RSP_NO_ERROR;
+ int idx;
+ long mediaID = ByteBuffer.wrap(itemAttr.mUid).getLong();
+ List<MediaSession.QueueItem> mNowPlayingItems = mNowPlayingListManager.getNowPlayingList();
+
+ /* checking if item attributes has been asked for now playing item or
+ * some other item with specific media id */
+ if (Arrays.equals(itemAttr.mUid, NOW_PLAYING_UID)) {
+ if (DEBUG) Log.d(TAG, "getItemAttr: Remote requests for now playing contents:");
+
+ // get the current playing song metadata and sending the queueitem.
+ if (mediaController != null) {
+ MediaMetadata metadata = mediaController.getMetadata();
+ if (metadata != null) {
+ getItemAttrFilterAttr(bdaddr, itemAttr, getQueueItem(metadata));
+ } else {
+ Log.e(TAG, "getItemAttr: metadata = null");
+ status = AvrcpConstants.RSP_INTERNAL_ERR;
+ }
+ } else {
+ Log.e(TAG, "getItemAttr: mediaController = null");
+ status = AvrcpConstants.RSP_INTERNAL_ERR;
+ }
+ } else if (mNowPlayingItems != null) {
+ if(DEBUG) printByteArray("getItemAttr-UID", itemAttr.mUid);
+ for (idx = 0; idx < mNowPlayingItems.size(); idx++) {
+ if (mediaID == mNowPlayingItems.get(idx).getQueueId()) {
+ getItemAttrFilterAttr(bdaddr, itemAttr, mNowPlayingItems.get(idx));
+ break;
+ }
+ }
+ if (idx >= mNowPlayingItems.size()) {
+ Log.e(TAG, "getItemAttr: idx is more than now playing list: idx = " + idx
+ + ", now playing list size = " + mNowPlayingItems.size());
+ status = AvrcpConstants.RSP_INV_ITEM;
+ }
+ } else {
+ Log.e(TAG, "getItemAttr: mNowPlayingItems is null!");
+ status = AvrcpConstants.RSP_INTERNAL_ERR;
+ }
+
+ // sending error status in case of error
+ if (status != AvrcpConstants.RSP_NO_ERROR) {
+ mMediaInterface.getItemAttrRsp(bdaddr, status, null);
+ }
+ }
+
+ private MediaSession.QueueItem getQueueItem(MediaMetadata metadata) {
+ MediaDescription.Builder builder = new MediaDescription.Builder();
+
+ // getting the media id
+ String mediaId = metadata.getDescription().getMediaId();
+ if (mediaId != null) {
+ builder.setMediaId(mediaId);
+ if(DEBUG) Log.d(TAG, "Item mediaId = " + mediaId);
+ }
+
+ // getting the title
+ if (metadata.getDescription().getTitle() != null) {
+ String title = metadata.getDescription().getTitle().toString();
+ builder.setTitle(title);
+ if(DEBUG) Log.d(TAG, "Item title = " + title);
+ }
+
+ // getting the metadata from the key-value pairs and filling to bundle
+ String artist = metadata.getString(MediaMetadata.METADATA_KEY_ARTIST);
+ String album = metadata.getString(MediaMetadata.METADATA_KEY_ALBUM);
+ String track_num = metadata.getLong(MediaMetadata.METADATA_KEY_TRACK_NUMBER) + "";
+ String num_tracks = metadata.getLong(MediaMetadata.METADATA_KEY_NUM_TRACKS) + "";
+ String genre = metadata.getString(MediaMetadata.METADATA_KEY_GENRE);
+ String duration = metadata.getLong(MediaMetadata.METADATA_KEY_DURATION) + "";
+
+ Bundle bundle = fillBundle(artist, album, track_num, num_tracks, genre, duration);
+ builder.setExtras(bundle);
+
+ // building a queue item from the above metadata
+ MediaDescription desc = builder.build();
+ return new QueueItem((desc), ByteBuffer.wrap(NOW_PLAYING_UID).getLong());
+ }
+
+ private Bundle fillBundle(String artist, String album, String trackNum, String numTracks,
+ String genre, String playTime) {
+
+ Bundle bundle = new Bundle();
+
+ bundle.putString(MediaMetadata.METADATA_KEY_ARTIST, artist);
+ bundle.putString(MediaMetadata.METADATA_KEY_ALBUM, album);
+ bundle.putString(MediaMetadata.METADATA_KEY_GENRE, genre);
+ bundle.putString(MediaMetadata.METADATA_KEY_NUM_TRACKS, numTracks);
+ bundle.putString(MediaMetadata.METADATA_KEY_DURATION, playTime);
+ bundle.putString(MediaMetadata.METADATA_KEY_TRACK_NUMBER, trackNum);
+ return bundle;
+ }
+
+ void updateNowPlayingList(List<MediaSession.QueueItem> queue){
+ mNowPlayingListManager.setNowPlayingList(queue);
+ }
+
+ /* Instructs media player to play particular media item */
+ void playItem(byte[] bdaddr, byte[] uid, byte scope, MediaController mediaController) {
+ long qid = ByteBuffer.wrap(uid).getLong();
+ List<MediaSession.QueueItem> mNowPlayingItems = mNowPlayingListManager.getNowPlayingList();
+
+ if (mediaController != null) {
+ MediaController.TransportControls mediaControllerCntrl =
+ mediaController.getTransportControls();
+ if (DEBUG) Log.d(TAG, "Sending playID");
+
+ if (scope == AvrcpConstants.BTRC_SCOPE_NOW_PLAYING) {
+ int idx;
+ /* find the queueId of the mediaId to play */
+ if (mNowPlayingItems != null) {
+ for (idx = 0; idx < mNowPlayingItems.size(); idx++) {
+ if (qid == mNowPlayingItems.get(idx).getQueueId()) {
+ mediaControllerCntrl.skipToQueueItem(qid);
+ break;
+ }
+ }
+ /* if mediaId is not found in nowplaying list */
+ if (idx >= mNowPlayingItems.size()) {
+ Log.w(TAG, "item is not present in queue");
+ mMediaInterface.playItemRsp(bdaddr, AvrcpConstants.RSP_INV_ITEM);
+ }
+ } else {
+ Log.w(TAG, "nowPlayingItems is null");
+ mMediaInterface.playItemRsp(bdaddr, AvrcpConstants.RSP_INTERNAL_ERR);
+ }
+ }
+ mMediaInterface.playItemRsp(bdaddr, AvrcpConstants.RSP_NO_ERROR);
+ } else {
+ Log.e(TAG, "mediaController is null");
+ mMediaInterface.playItemRsp(bdaddr, AvrcpConstants.RSP_INTERNAL_ERR);
+ }
+ }
+
+ void getTotalNumOfItems(byte[] bdaddr, byte scope, MediaController mediaController) {
+ if (DEBUG) Log.d(TAG, "getTotalNumOfItems scope = " + scope);
+ List<MediaSession.QueueItem> mNowPlayingItems = mNowPlayingListManager.getNowPlayingList();
+ if (mNowPlayingItems != null) {
+ // We already have the cached list sending the response to remote
+ mMediaInterface.getTotalNumOfItemsRsp(bdaddr, AvrcpConstants.RSP_NO_ERROR, 0,
+ mNowPlayingItems.size());
+ } else if (mediaController == null) {
+ Log.e(TAG, "mediaController is null");
+ mMediaInterface.getTotalNumOfItemsRsp(bdaddr,
+ AvrcpConstants.RSP_INTERNAL_ERR, 0, 0);
+ } else {
+ // We don't have the cached list, fetching it from Media Controller
+ mNowPlayingItems = mediaController.getQueue();
+ if (mNowPlayingItems == null) {
+ Log.e(TAG, "mNowPlayingItems is null");
+ mMediaInterface.getTotalNumOfItemsRsp(bdaddr,
+ AvrcpConstants.RSP_INV_ITEM, 0, 0);
+ } else {
+ mNowPlayingListManager.setNowPlayingList(mediaController.getQueue());
+ mMediaInterface.getTotalNumOfItemsRsp(bdaddr,
+ AvrcpConstants.RSP_NO_ERROR, 0, mNowPlayingItems.size());
+ }
+ }
+ }
+
+ void sendTrackChangeWithId(int trackChangedNT, long trackNumber,
+ MediaController mediaController) {
+ if (DEBUG) Log.d(TAG, "sendTrackChangeWithId");
+ try {
+ String mediaId = mediaController.getMetadata().getDescription().getMediaId();
+ long qid = 0;
+ List<MediaSession.QueueItem> mNowPlayingItems = mNowPlayingListManager.getNowPlayingList();
+ /* traverse now playing list for current playing item */
+ for (QueueItem qitem : mNowPlayingItems) {
+ if (qitem.getDescription().getMediaId().equals(mediaId)) {
+ qid = qitem.getQueueId();
+ if (DEBUG) Log.d(TAG, "sendTrackChangeWithId: Found matching qid= " + qid);
+ break;
+ }
+ }
+ /* for any item associated with NowPlaying, uid is queueId */
+ byte[] uid = ByteBuffer.allocate(AvrcpConstants.UID_SIZE).putLong(qid).array();
+ if (DEBUG) printByteArray("trackChangedRsp", uid);
+ mMediaInterface.trackChangedRsp(trackChangedNT, uid);
+ } catch (NullPointerException e) {
+ Log.w(TAG, "Null Pointer while getting current track Uid from media player");
+ sendTrackChangeRsp(trackChangedNT, trackNumber);
+ }
+ }
+
+ /*
+ * utility function to respond for track change when failed to get current track UID
+ * from media controller
+ */
+ private void sendTrackChangeRsp(int trackChangedNT, long trackNumber) {
+ byte[] track = new byte[AvrcpConstants.TRACK_ID_SIZE];
+ /* track is stored in big endian format */
+ for (int idx = 0; idx < AvrcpConstants.TRACK_ID_SIZE; ++idx) {
+ if (trackChangedNT == AvrcpConstants.NOTIFICATION_TYPE_INTERIM
+ && trackNumber == -1) {
+ /* if no track is currently selected then return 0xFF in interim response */
+ track[idx] = AvrcpConstants.NO_TRACK_SELECTED;
+ } else {
+ /* if Browsing is not supported and a track is selected, then return 0x00 */
+ track[idx] = AvrcpConstants.TRACK_IS_SELECTED;
+ }
+ }
+ if (DEBUG) printByteArray("sendTrackChangeRsp", track);
+ mMediaInterface.trackChangedRsp(trackChangedNT, track);
+ }
+
+ /*
+ * helper method to check if startItem and endItem index is with range of
+ * MediaItem list. (Resultset containing all items in current path)
+ */
+ private List<MediaSession.QueueItem> checkIndexOutofBounds(byte[] bdaddr,
+ List<MediaSession.QueueItem> children, int startItem, int endItem) {
+ try {
+ List<MediaSession.QueueItem> childrenSubList =
+ children.subList(startItem, Math.min(children.size(), endItem + 1));
+ if (childrenSubList.isEmpty()) {
+ Log.i(TAG, "childrenSubList is empty.");
+ throw new IndexOutOfBoundsException();
+ }
+ return childrenSubList;
+ } catch (IndexOutOfBoundsException ex) {
+ Log.i(TAG, "Index out of bounds start item =" + startItem + " end item = "
+ + Math.min(children.size(), endItem + 1));
+ mMediaInterface.folderItemsRsp(bdaddr, AvrcpConstants.RSP_INV_RANGE, null);
+ return null;
+ } catch (IllegalArgumentException ex) {
+ Log.i(TAG, "Index out of bounds start item =" + startItem + " > size");
+ mMediaInterface.folderItemsRsp(bdaddr, AvrcpConstants.RSP_INV_RANGE, null);
+ return null;
+ }
+ }
+
+ /*
+ * helper method to filter required attibutes before sending GetFolderItems
+ * response
+ */
+ private void getFolderItemsFilterAttr(byte[] bdaddr,
+ AvrcpCmd.FolderItemsCmd mFolderItemsReqObj,
+ List<MediaSession.QueueItem> children, byte scope, int startItem, int endItem) {
+ if (DEBUG) Log.d(TAG, "getFolderItemsFilterAttr: startItem =" + startItem + ", endItem = "
+ + endItem);
+
+ List<MediaSession.QueueItem> result_items = new ArrayList<MediaSession.QueueItem>();
+
+ if (children != null) {
+ /* check for index out of bound errors */
+ if ((result_items = checkIndexOutofBounds(bdaddr, children, startItem, endItem))
+ == null) {
+ Log.w(TAG, "result_items is null.");
+ mMediaInterface.folderItemsRsp(bdaddr, AvrcpConstants.RSP_INV_RANGE, null);
+ return;
+ }
+ FolderItemsData folderDataNative = new FolderItemsData(result_items.size());
+
+ /* variables to temperorily add attrs */
+ ArrayList<String> attrArray = new ArrayList<String>();
+ ArrayList<Integer> attrId = new ArrayList<Integer>();
+
+ for (int itemIndex = 0; itemIndex < result_items.size(); itemIndex++) {
+ // get the queue id
+ long qid = result_items.get(itemIndex).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] = result_items.get(itemIndex)
+ .getDescription().getTitle().toString();
+
+ 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];
+ if (attribId >= AvrcpConstants.ATTRID_TITLE
+ && attribId <= AvrcpConstants.ATTRID_PLAY_TIME) {
+ if ((value = getAttrValue(attribId, result_items, itemIndex))
+ != null) {
+ attrArray.add(value);
+ attrId.add(attribId);
+ attrCnt++;
+ }
+ } else {
+ Log.w(TAG, "invalid attributed id is requested: " + attribId);
+ }
+ }
+ /* 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 (mFolderItemsReqObj.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);
+ } else {
+ Log.e(TAG, "Error: children are null in getFolderItemsFilterAttr");
+ mMediaInterface.folderItemsRsp(bdaddr, AvrcpConstants.RSP_INV_RANGE, null);
+ return;
+ }
+ }
+
+ private String getAttrValue(int attr, List<MediaSession.QueueItem> resultItems,
+ int itemIndex) {
+ String attrValue = null;
+ try {
+ switch (attr) {
+ /* Title is mandatory attribute */
+ case AvrcpConstants.ATTRID_TITLE:
+ attrValue = resultItems.get(itemIndex).getDescription().getTitle().toString();
+ break;
+
+ case AvrcpConstants.ATTRID_ARTIST:
+ attrValue = resultItems.get(itemIndex).getDescription().getExtras()
+ .getString(MediaMetadata.METADATA_KEY_ARTIST);
+ break;
+
+ case AvrcpConstants.ATTRID_ALBUM:
+ attrValue = resultItems.get(itemIndex).getDescription().getExtras()
+ .getString(MediaMetadata.METADATA_KEY_ALBUM);
+ break;
+
+ case AvrcpConstants.ATTRID_TRACK_NUM:
+ attrValue = resultItems.get(itemIndex).getDescription().getExtras()
+ .getString(MediaMetadata.METADATA_KEY_TRACK_NUMBER);
+ break;
+
+ case AvrcpConstants.ATTRID_NUM_TRACKS:
+ attrValue = resultItems.get(itemIndex).getDescription().getExtras()
+ .getString(MediaMetadata.METADATA_KEY_NUM_TRACKS);
+ break;
+
+ case AvrcpConstants.ATTRID_GENRE:
+ attrValue = resultItems.get(itemIndex).getDescription().getExtras()
+ .getString(MediaMetadata.METADATA_KEY_GENRE);
+ break;
+
+ case AvrcpConstants.ATTRID_PLAY_TIME:
+ attrValue = resultItems.get(itemIndex).getDescription().getExtras()
+ .getString(MediaMetadata.METADATA_KEY_DURATION);
+ break;
+
+ default:
+ Log.e(TAG, "Unknown attribute ID");
+ }
+ } catch (IndexOutOfBoundsException ex) {
+ Log.w(TAG, "getAttrValue: requested item index out of bounds");
+ 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) {
+ return "<Unknown Title>";
+ }
+ 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) {
+ /* Response parameters */
+ int[] attrIds = null; /* array of attr ids */
+ String[] attrValues = null; /* array of attr values */
+ int attrCounter = 0; /* num attributes for each item */
+ List<MediaSession.QueueItem> resultItems = new ArrayList<MediaSession.QueueItem>();
+ resultItems.add(mediaItem);
+ /* 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, "getAttrValue: attr id[" + idx + "] :" +
+ mItemAttrReqObj.mAttrIDs[idx]);
+ attrTempId.add(mItemAttrReqObj.mAttrIDs[idx]);
+ }
+ }
+
+ if (DEBUG) Log.d(TAG, "getAttrValue: 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 = null;
+ if ((value = getAttrValue(attrTempId.get(idx), resultItems, 0)) != null) {
+ attrArray.add(value);
+ attrId.add(attrTempId.get(idx));
+ attrCounter++;
+ }
+ }
+ attrTempId = null;
+ }
+
+ /* 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,
+ (byte)attrCounter, attrIds, attrValues);
+ mMediaInterface.getItemAttrRsp(bdaddr, AvrcpConstants.RSP_NO_ERROR, rspObj);
+ return;
+ }
+ }
+
+ void handlePassthroughCmd(int id, int keyState, byte[] bdAddr,
+ MediaController mediaController) {
+
+ if (mediaController != null) {
+ MediaController.TransportControls mediaControllerCntrl =
+ mediaController.getTransportControls();
+ if (DEBUG) Log.v(TAG, "handlePassthroughCmd - id:" + id + " keyState:" + keyState);
+ if (keyState == AvrcpConstants.KEY_STATE_PRESS) {
+ switch (id) {
+ case BluetoothAvrcp.PASSTHROUGH_ID_REWIND:
+ mediaControllerCntrl.rewind();
+ break;
+ case BluetoothAvrcp.PASSTHROUGH_ID_FAST_FOR:
+ mediaControllerCntrl.fastForward();
+ break;
+ case BluetoothAvrcp.PASSTHROUGH_ID_PLAY:
+ mediaControllerCntrl.play();
+ break;
+ case BluetoothAvrcp.PASSTHROUGH_ID_PAUSE:
+ mediaControllerCntrl.pause();
+ break;
+ case BluetoothAvrcp.PASSTHROUGH_ID_STOP:
+ mediaControllerCntrl.stop();
+ break;
+ case BluetoothAvrcp.PASSTHROUGH_ID_FORWARD:
+ mediaControllerCntrl.skipToNext();
+ break;
+ case BluetoothAvrcp.PASSTHROUGH_ID_BACKWARD:
+ mediaControllerCntrl.skipToPrevious();
+ break;
+ default:
+ Log.w(TAG, "unknown id:" + id + " keyState:" + keyState);
+ }
+ } else {
+ Log.i(TAG, "ignoring the release event for id:" + id + " keyState:" + keyState);
+ }
+ } else {
+ Log.e(TAG, "Unable to handlePassthroughCmd, mediaController is null!");
+ }
+ }
+
+ private void printByteArray(String arrName, byte[] array) {
+ StringBuilder byteArray = new StringBuilder(arrName + ": 0x");
+
+ for (int idx = 0; idx < array.length; idx++) {
+ byteArray.append(String.format(" %02x", array[idx]));
+ }
+ Log.d(TAG, byteArray + "");
+ }
+
+}
/*
- * Copyright (C) 2012 The Android Open Source Project
+ * 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.
import android.bluetooth.BluetoothA2dp;
import android.bluetooth.BluetoothAvrcp;
+import android.bluetooth.BluetoothDevice;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+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.content.SharedPreferences;
import android.media.AudioManager;
import android.media.MediaDescription;
import android.media.MediaMetadata;
import android.media.session.MediaController;
+import android.media.session.MediaSession;
+import android.media.session.MediaSession.QueueItem;
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.util.Log;
import android.view.KeyEvent;
-import com.android.bluetooth.R;
import com.android.bluetooth.btservice.ProfileService;
+import com.android.bluetooth.R;
import com.android.bluetooth.Utils;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.Iterator;
import java.util.List;
+import java.util.Map;
+
+/******************************************************************************
+ * support Bluetooth AVRCP profile. support metadata, play status, event
+ * notifications, address player selection and browse feature implementation.
+ ******************************************************************************/
-/**
- * support Bluetooth AVRCP profile.
- * support metadata, play status and event notification
- */
public final class Avrcp {
private static final boolean DEBUG = false;
private static final String TAG = "Avrcp";
private final AudioManager mAudioManager;
private AvrcpMessageHandler mHandler;
private MediaSessionManager mMediaSessionManager;
- private MediaSessionChangeListener mSessionChangeListener;
private MediaController mMediaController;
private MediaControllerListener mMediaControllerCb;
private MediaAttributes mMediaAttributes;
+ private PackageManager mPackageManager;
private int mTransportControlFlags;
private PlaybackState mCurrentPlayState;
private long mLastStateUpdate;
private boolean mVolCmdSetInProgress;
private int mAbsVolRetryTimes;
private int mSkipAmount;
+ private int mCurrAddrPlayerID;
+ private int mCurrBrowsePlayerID;
+ private MediaPlayerListRsp mMPLObj;
+ private AvrcpMediaRsp mAvrcpMediaRsp;
+
+ /* UID counter to be shared across different files. */
+ static short sUIDCounter;
/* BTRC features */
public static final int BTRC_FEAT_METADATA = 0x01;
private static final int AVRC_RSP_CHANGED = 13;
private static final int AVRC_RSP_INTERIM = 15;
- private static final int MESSAGE_GET_RC_FEATURES = 1;
- private static final int MESSAGE_GET_PLAY_STATUS = 2;
- private static final int MESSAGE_GET_ELEM_ATTRS = 3;
- private static final int MESSAGE_REGISTER_NOTIFICATION = 4;
- private static final int MESSAGE_PLAY_INTERVAL_TIMEOUT = 5;
- private static final int MESSAGE_VOLUME_CHANGED = 6;
- private static final int MESSAGE_ADJUST_VOLUME = 7;
- private static final int MESSAGE_SET_ABSOLUTE_VOLUME = 8;
- private static final int MESSAGE_ABS_VOL_TIMEOUT = 9;
- private static final int MESSAGE_FAST_FORWARD = 10;
- private static final int MESSAGE_REWIND = 11;
- private static final int MESSAGE_CHANGE_PLAY_POS = 12;
- private static final int MESSAGE_SET_A2DP_AUDIO_STATE = 13;
+ /* 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_ADJUST_VOLUME = 15;
+ private static final int MSG_SET_ABSOLUTE_VOLUME = 16;
+ private static final int MSG_ABS_VOL_TIMEOUT = 17;
+ private static final int MSG_FAST_FORWARD = 18;
+ private static final int MSG_REWIND = 19;
+ private static final int MSG_CHANGE_PLAY_POS = 20;
+ private static final int MSG_SET_A2DP_AUDIO_STATE = 21;
private static final int BUTTON_TIMEOUT_TIME = 2000;
private static final int BASE_SKIP_AMOUNT = 2000;
- private static final int KEY_STATE_PRESS = 1;
- private static final int KEY_STATE_RELEASE = 0;
private static final int SKIP_PERIOD = 400;
private static final int SKIP_DOUBLE_INTERVAL = 3000;
private static final long MAX_MULTIPLIER_VALUE = 128L;
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 */
+ private AddressedMediaPlayer mAddressedMediaPlayer;
+
+ /* List of Media player instances, useful for retrieving MediaPlayerList or MediaPlayerInfo */
+ private ArrayList<MediaPlayerInfo> mMediaPlayerInfoList;
+
+ /* List of media players which supports browse */
+ private ArrayList<BrowsePlayerInfo> mBrowsePlayerInfoList;
+
+ /* Manage browsed players */
+ private AvrcpBrowseManager mAvrcpBrowseManager;
+
+ /* Broadcast receiver for device connections intent broadcasts */
+ private final BroadcastReceiver mAvrcpReceiver = new AvrcpServiceBroadcastReceiver();
+
static {
classInitNative();
}
private Avrcp(Context context) {
mMediaAttributes = new MediaAttributes(null);
mCurrentPlayState = new PlaybackState.Builder().setState(PlaybackState.STATE_NONE, -1L, 0.0f).build();
- mPlayStatusChangedNT = NOTIFICATION_TYPE_CHANGED;
- mTrackChangedNT = NOTIFICATION_TYPE_CHANGED;
+ mPlayStatusChangedNT = AvrcpConstants.NOTIFICATION_TYPE_CHANGED;
+ mTrackChangedNT = AvrcpConstants.NOTIFICATION_TYPE_CHANGED;
mTrackNumber = -1L;
mLastStateUpdate = -1L;
mSongLengthMs = 0L;
mPlaybackIntervalMs = 0L;
- mPlayPosChangedNT = NOTIFICATION_TYPE_CHANGED;
+ mPlayPosChangedNT = AvrcpConstants.NOTIFICATION_TYPE_CHANGED;
mLastReportedPosition = -1;
mNextPosMs = -1;
mPrevPosMs = -1;
mLastLocalVolume = -1;
mAbsVolThreshold = 0;
mVolumeMapping = new HashMap<Integer, Integer>();
-
+ sUIDCounter = AvrcpConstants.DEFAULT_UID_COUNTER;
+ mCurrAddrPlayerID = -1;
+ mCurrBrowsePlayerID = -1;
mContext = context;
+ mMPLObj = null;
+ mAddressedMediaPlayer = null;
initNative();
- mMediaSessionManager = (MediaSessionManager) context.getSystemService(Context.MEDIA_SESSION_SERVICE);
+ 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);
}
+
+ // 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);
}
private void start() {
thread.start();
Looper looper = thread.getLooper();
mHandler = new AvrcpMessageHandler(looper);
-
- mSessionChangeListener = new MediaSessionChangeListener();
mMediaControllerCb = new MediaControllerListener();
- mMediaSessionManager.addOnActiveSessionsChangedListener(mSessionChangeListener, null, mHandler);
- updateCurrentMediaControllers(mMediaSessionManager.getActiveSessions(null));
+ mAvrcpMediaRsp = new AvrcpMediaRsp();
+ mMediaPlayerInfoList = new ArrayList<MediaPlayerInfo>();
+ mBrowsePlayerInfoList = new ArrayList<BrowsePlayerInfo>();
+ mMediaSessionManager.addOnActiveSessionsChangedListener(mActiveSessionListener, null,
+ mHandler);
+ mPackageManager = mContext.getApplicationContext().getPackageManager();
+
+ /* create object to communicate with addressed player */
+ mAddressedMediaPlayer = new AddressedMediaPlayer(mAvrcpMediaRsp);
+
+ /* initializing media player's list */
+ buildBrowsablePlayersList();
+ buildMediaPlayersList();
+
+ /* initialize BrowseMananger which manages Browse commands and response */
+ mAvrcpBrowseManager = new AvrcpBrowseManager(mContext, mAvrcpMediaRsp);
}
public static Avrcp make(Context context) {
}
public void doQuit() {
+ if (DEBUG) Log.d(TAG, "doQuit");
mHandler.removeCallbacksAndMessages(null);
Looper looper = mHandler.getLooper();
if (looper != null) {
looper.quit();
}
- mMediaSessionManager.removeOnActiveSessionsChangedListener(mSessionChangeListener);
+
+ unregOldMediaControllerCb();
+ mMediaSessionManager.removeOnActiveSessionsChangedListener(mActiveSessionListener);
+
+ mHandler = null;
+ mMPLObj = null;
+ mContext.unregisterReceiver(mAvrcpReceiver);
+
+ mAddressedMediaPlayer.cleanup();
+ mAvrcpBrowseManager.cleanup();
}
public void cleanup() {
+ if (DEBUG) Log.d(TAG, "cleanup");
cleanupNative();
if (mVolumeMapping != null)
mVolumeMapping.clear();
}
@Override
- public void onPlaybackStateChanged(PlaybackState state) {
+ public synchronized void onPlaybackStateChanged(PlaybackState state) {
Log.v(TAG, "MediaController playback changed: " + state.toString());
+
updatePlaybackState(state);
+
+ if (DEBUG) Log.v(TAG, "onPlaybackStateChanged: state=" + state.getState());
+ byte stateBytes = (byte) convertPlayStateToBytes(state.getState());
+
+ /* updating play status in global media player list */
+ if (!isCurrentMediaPlayerListEmpty() && isIdValid(mCurrAddrPlayerID)) {
+ try {
+ mMediaPlayerInfoList.get(mCurrAddrPlayerID - 1).setPlayStatus(stateBytes);
+ } catch (IndexOutOfBoundsException e) {
+ Log.i(TAG, "onPlaybackStateChanged: list size = " + getPlayerListSize() +
+ ", mCurrAddrPlayerID = " + mCurrAddrPlayerID);
+ e.printStackTrace();
+ }
+ }
+
}
@Override
public void onSessionDestroyed() {
Log.v(TAG, "MediaController session destroyed");
}
- }
-
- private class MediaSessionChangeListener implements MediaSessionManager.OnActiveSessionsChangedListener {
- public MediaSessionChangeListener() {
- }
@Override
- public void onActiveSessionsChanged(List<MediaController> controllers) {
- Log.v(TAG, "Active sessions changed, " + controllers.size() + " sessions");
- updateCurrentMediaControllers(controllers);
- }
- }
-
- private void updateCurrentMediaControllers(List<MediaController> controllers) {
- MediaController controller = null;
- for (MediaController c : controllers) {
- controller = c;
- if (c.getMetadata() != null)
- break; // We found a suitable controller
- }
+ public void onQueueChanged(List<MediaSession.QueueItem> queue) {
+ if (queue == null) {
+ Log.v(TAG, "onQueueChanged: received null queue");
+ return;
+ }
- String name = (controller == null) ? "null" : controller.getPackageName();
- if (mMediaController == controller) {
- Log.v(TAG, "MediaController still " + name);
- return;
- }
+ Log.v(TAG, "onQueueChanged: NowPlaying list changed, Queue Size = "+ queue.size());
+ mAddressedMediaPlayer.updateNowPlayingList(queue);
- Log.v(TAG, "MediaController changed to " + name);
- if (mMediaController != null) {
- mMediaController.unregisterCallback(mMediaControllerCb);
- }
- mMediaController = controller;
- if (mMediaController == null) {
- updateMetadata(null);
- return;
+ /* sent notification to remote for NowPlayingList changed */
+ if(!registerNotificationRspNowPlayingChangedNative(
+ AvrcpConstants.NOTIFICATION_TYPE_CHANGED)){
+ Log.e(TAG, "onQueueChanged-registerNotificationRspNowPlayingChangedNative failed");
+ }
}
- mMediaController.registerCallback(mMediaControllerCb, mHandler);
- updateMetadata(mMediaController.getMetadata());
}
/** Handles Avrcp messages. */
@Override
public void handleMessage(Message msg) {
+ if (DEBUG) Log.v(TAG, "AvrcpMessageHandler: received message=" + msg.what);
+
switch (msg.what) {
- case MESSAGE_GET_RC_FEATURES:
+ case MSG_NATIVE_REQ_GET_RC_FEATURES:
+ {
String address = (String) msg.obj;
- if (DEBUG) Log.v(TAG, "MESSAGE_GET_RC_FEATURES: address="+address+
- ", features="+msg.arg1);
+ if (DEBUG) Log.v(TAG, "MSG_NATIVE_REQ_GET_RC_FEATURES: address="+address+
+ ", features="+msg.arg1);
mFeatures = msg.arg1;
mFeatures = modifyRcFeatureFromBlacklist(mFeatures, address);
mAudioManager.avrcpSupportsAbsoluteVolume(address, isAbsoluteVolumeSupported());
if (mVolumeMapping != null)
mVolumeMapping.clear();
break;
+ }
- case MESSAGE_GET_PLAY_STATUS:
- if (DEBUG) Log.v(TAG, "MESSAGE_GET_PLAY_STATUS");
- getPlayStatusRspNative(convertPlayStateToPlayStatus(mCurrentPlayState),
- (int)mSongLengthMs, (int)getPlayPosition());
+ case MSG_NATIVE_REQ_GET_PLAY_STATUS:
+ {
+ byte[] address = (byte[]) msg.obj;
+ if (DEBUG) Log.v(TAG, "MSG_NATIVE_REQ_GET_PLAY_STATUS");
+ getPlayStatusRspNative(address, convertPlayStateToPlayStatus(mCurrentPlayState),
+ (int) mSongLengthMs, (int) getPlayPosition());
break;
+ }
- case MESSAGE_GET_ELEM_ATTRS:
+ case MSG_NATIVE_REQ_GET_ELEM_ATTRS:
+ {
String[] textArray;
- int[] attrIds;
- byte numAttr = (byte) msg.arg1;
- ArrayList<Integer> attrList = (ArrayList<Integer>) msg.obj;
- Log.v(TAG, "MESSAGE_GET_ELEM_ATTRS:numAttr=" + numAttr);
- attrIds = new int[numAttr];
+ 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];
for (int i = 0; i < numAttr; ++i) {
- attrIds[i] = attrList.get(i).intValue();
textArray[i] = mMediaAttributes.getString(attrIds[i]);
Log.v(TAG, "getAttributeString:attrId=" + attrIds[i] +
- " str=" + textArray[i]);
+ " str=" + textArray[i]);
}
- getElementAttrRspNative(numAttr, attrIds, textArray);
+ byte[] bdaddr = elem.mAddress;
+ getElementAttrRspNative(bdaddr, numAttr, attrIds, textArray);
break;
+ }
- case MESSAGE_REGISTER_NOTIFICATION:
- if (DEBUG) Log.v(TAG, "MESSAGE_REGISTER_NOTIFICATION:event=" + msg.arg1 +
- " param=" + msg.arg2);
- processRegisterNotification(msg.arg1, msg.arg2);
+ 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 MESSAGE_PLAY_INTERVAL_TIMEOUT:
- if (DEBUG) Log.v(TAG, "MESSAGE_PLAY_INTERVAL_TIMEOUT");
+ case MSG_PLAY_INTERVAL_TIMEOUT:
+ if (DEBUG) Log.v(TAG, "MSG_PLAY_INTERVAL_TIMEOUT");
sendPlayPosNotificationRsp(false);
break;
- case MESSAGE_VOLUME_CHANGED:
+ case MSG_NATIVE_REQ_VOLUME_CHANGE:
if (!isAbsoluteVolumeSupported()) {
- if (DEBUG) Log.v(TAG, "ignore MESSAGE_VOLUME_CHANGED");
+ if (DEBUG) Log.v(TAG, "ignore MSG_NATIVE_REQ_VOLUME_CHANGE");
break;
}
- if (DEBUG) Log.v(TAG, "MESSAGE_VOLUME_CHANGED: volume=" + ((byte)msg.arg1 & 0x7f)
- + " ctype=" + msg.arg2);
-
+ if (DEBUG) Log.v(TAG, "MSG_NATIVE_REQ_VOLUME_CHANGE: volume=" + ((byte) msg.arg1 & 0x7f)
+ + " ctype=" + msg.arg2);
boolean volAdj = false;
if (msg.arg2 == AVRC_RSP_ACCEPT || msg.arg2 == AVRC_RSP_REJ) {
Log.e(TAG, "Unsolicited response, ignored");
break;
}
- removeMessages(MESSAGE_ABS_VOL_TIMEOUT);
+ removeMessages(MSG_ABS_VOL_TIMEOUT);
volAdj = mVolCmdAdjustInProgress;
mVolCmdAdjustInProgress = false;
mAbsVolRetryTimes = 0;
}
- byte absVol = (byte)((byte)msg.arg1 & 0x7f); // discard MSB as it is RFD
+ byte absVol = (byte) ((byte) msg.arg1 & 0x7f); // discard MSB as it is RFD
// 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 = mHandler.obtainMessage(MESSAGE_SET_ABSOLUTE_VOLUME, mAbsVolThreshold , 0);
+ Message msg1 = mHandler.obtainMessage(MSG_SET_ABSOLUTE_VOLUME, mAbsVolThreshold , 0);
mHandler.sendMessage(msg1);
mRemoteVolume = absVol;
mLocalVolume = 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 + " vs " + volIndex);
mLastLocalVolume = mLocalVolume;
}
}
// remember the remote volume value, as it's the one supported by remote
if (volAdj) {
synchronized (mVolumeMapping) {
- mVolumeMapping.put(volIndex, (int)absVol);
+ mVolumeMapping.put(volIndex, (int) absVol);
if (DEBUG) Log.v(TAG, "remember volume mapping " +volIndex+ "-"+absVol);
}
}
notifyVolumeChanged(mLocalVolume);
mRemoteVolume = absVol;
- long pecentVolChanged = ((long)absVol * 100) / 0x7f;
+ 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");
} else if (volAdj && mLastRemoteVolume > 0 && mLastRemoteVolume < AVRCP_MAX_VOL &&
mLocalVolume == volIndex &&
- (msg.arg2 == AVRC_RSP_ACCEPT )) {
+ (msg.arg2 == AVRC_RSP_ACCEPT)) {
/* oops, the volume is still same, remote does not like the value
* retry a volume one step up/down */
if (DEBUG) Log.d(TAG, "Remote device didn't tune volume, let's try one more step.");
Math.max(0, mLastRemoteVolume + mLastDirection));
if (setVolumeNative(retry_volume)) {
mLastRemoteVolume = retry_volume;
- sendMessageDelayed(obtainMessage(MESSAGE_ABS_VOL_TIMEOUT),
- CMD_TIMEOUT_DELAY);
+ sendMessageDelayed(obtainMessage(MSG_ABS_VOL_TIMEOUT), CMD_TIMEOUT_DELAY);
mVolCmdAdjustInProgress = true;
}
}
break;
- case MESSAGE_ADJUST_VOLUME:
+ case MSG_ADJUST_VOLUME:
if (!isAbsoluteVolumeSupported()) {
- if (DEBUG) Log.v(TAG, "ignore MESSAGE_ADJUST_VOLUME");
+ if (DEBUG) Log.v(TAG, "ignore MSG_ADJUST_VOLUME");
break;
}
- if (DEBUG) Log.d(TAG, "MESSAGE_ADJUST_VOLUME: direction=" + msg.arg1);
+ if (DEBUG) Log.d(TAG, "MSG_ADJUST_VOLUME: direction=" + msg.arg1);
if (mVolCmdAdjustInProgress || mVolCmdSetInProgress) {
if (DEBUG) Log.w(TAG, "There is already a volume command in progress.");
if (setVol == -1) {
/* otherwise use phone steps */
setVol = Math.min(AVRCP_MAX_VOL,
- convertToAvrcpVolume(Math.max(0, targetVolIndex)));
+ convertToAvrcpVolume(Math.max(0, targetVolIndex)));
if (DEBUG) Log.d(TAG, "set volume from local volume "+ targetVolIndex+"-"+ setVol);
}
if (setVolumeNative(setVol)) {
- sendMessageDelayed(obtainMessage(MESSAGE_ABS_VOL_TIMEOUT),
- CMD_TIMEOUT_DELAY);
+ sendMessageDelayed(obtainMessage(MSG_ABS_VOL_TIMEOUT), CMD_TIMEOUT_DELAY);
mVolCmdAdjustInProgress = true;
mLastDirection = msg.arg1;
mLastRemoteVolume = setVol;
if (DEBUG) Log.d(TAG, "setVolumeNative failed");
}
} else {
- Log.e(TAG, "Unknown direction in MESSAGE_ADJUST_VOLUME");
+ Log.e(TAG, "Unknown direction in MSG_ADJUST_VOLUME");
}
break;
- case MESSAGE_SET_ABSOLUTE_VOLUME:
+ case MSG_SET_ABSOLUTE_VOLUME:
if (!isAbsoluteVolumeSupported()) {
- if (DEBUG) Log.v(TAG, "ignore MESSAGE_SET_ABSOLUTE_VOLUME");
+ if (DEBUG) Log.v(TAG, "ignore MSG_SET_ABSOLUTE_VOLUME");
break;
}
- if (DEBUG) Log.v(TAG, "MESSAGE_SET_ABSOLUTE_VOLUME");
+ if (DEBUG) Log.v(TAG, "MSG_SET_ABSOLUTE_VOLUME");
if (mVolCmdSetInProgress || mVolCmdAdjustInProgress) {
if (DEBUG) Log.w(TAG, "There is already a volume command in progress.");
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(MESSAGE_ABS_VOL_TIMEOUT), CMD_TIMEOUT_DELAY);
+ sendMessageDelayed(obtainMessage(MSG_ABS_VOL_TIMEOUT), CMD_TIMEOUT_DELAY);
mVolCmdSetInProgress = true;
mLastRemoteVolume = avrcpVolume;
mLastLocalVolume = msg.arg1;
}
break;
- case MESSAGE_ABS_VOL_TIMEOUT:
- if (DEBUG) Log.v(TAG, "MESSAGE_ABS_VOL_TIMEOUT: Volume change cmd timed out.");
+ case MSG_ABS_VOL_TIMEOUT:
+ if (DEBUG) Log.v(TAG, "MSG_ABS_VOL_TIMEOUT: Volume change cmd timed out.");
mVolCmdAdjustInProgress = false;
mVolCmdSetInProgress = false;
if (mAbsVolRetryTimes >= MAX_ERROR_RETRY_TIMES) {
} else {
mAbsVolRetryTimes += 1;
if (setVolumeNative(mLastRemoteVolume)) {
- sendMessageDelayed(obtainMessage(MESSAGE_ABS_VOL_TIMEOUT),
- CMD_TIMEOUT_DELAY);
+ sendMessageDelayed(obtainMessage(MSG_ABS_VOL_TIMEOUT), CMD_TIMEOUT_DELAY);
mVolCmdSetInProgress = true;
}
}
break;
- case MESSAGE_FAST_FORWARD:
- case MESSAGE_REWIND:
- if (msg.what == MESSAGE_FAST_FORWARD) {
+ case MSG_FAST_FORWARD:
+ case MSG_REWIND:
+ if (msg.what == MSG_FAST_FORWARD) {
if ((mCurrentPlayState.getActions() &
- PlaybackState.ACTION_FAST_FORWARD) != 0) {
- int keyState = msg.arg1 == KEY_STATE_PRESS ?
+ PlaybackState.ACTION_FAST_FORWARD) != 0) {
+ int keyState = msg.arg1 == AvrcpConstants.KEY_STATE_PRESS ?
KeyEvent.ACTION_DOWN : KeyEvent.ACTION_UP;
KeyEvent keyEvent =
new KeyEvent(keyState, KeyEvent.KEYCODE_MEDIA_FAST_FORWARD);
}
} else if ((mCurrentPlayState.getActions() &
PlaybackState.ACTION_REWIND) != 0) {
- int keyState = msg.arg1 == KEY_STATE_PRESS ?
+ int keyState = msg.arg1 == AvrcpConstants.KEY_STATE_PRESS ?
KeyEvent.ACTION_DOWN : KeyEvent.ACTION_UP;
KeyEvent keyEvent =
new KeyEvent(keyState, KeyEvent.KEYCODE_MEDIA_REWIND);
}
int skipAmount;
- if (msg.what == MESSAGE_FAST_FORWARD) {
- if (DEBUG) Log.v(TAG, "MESSAGE_FAST_FORWARD");
- removeMessages(MESSAGE_FAST_FORWARD);
+ int playStatus;
+ if (msg.what == MSG_FAST_FORWARD) {
+ if (DEBUG) Log.v(TAG, "MSG_FAST_FORWARD");
+ removeMessages(MSG_FAST_FORWARD);
skipAmount = BASE_SKIP_AMOUNT;
+ playStatus = PLAYSTATUS_FWD_SEEK;
} else {
- if (DEBUG) Log.v(TAG, "MESSAGE_REWIND");
- removeMessages(MESSAGE_REWIND);
+ if (DEBUG) Log.v(TAG, "MSG_REWIND");
+ removeMessages(MSG_REWIND);
skipAmount = -BASE_SKIP_AMOUNT;
+ playStatus = PLAYSTATUS_REV_SEEK;
}
- if (hasMessages(MESSAGE_CHANGE_PLAY_POS) &&
+ if (hasMessages(MSG_CHANGE_PLAY_POS) &&
(skipAmount != mSkipAmount)) {
Log.w(TAG, "missing release button event:" + mSkipAmount);
}
- if ((!hasMessages(MESSAGE_CHANGE_PLAY_POS)) ||
+ if ((!hasMessages(MSG_CHANGE_PLAY_POS)) ||
(skipAmount != mSkipAmount)) {
mSkipStartTime = SystemClock.elapsedRealtime();
}
- removeMessages(MESSAGE_CHANGE_PLAY_POS);
- if (msg.arg1 == KEY_STATE_PRESS) {
+ removeMessages(MSG_CHANGE_PLAY_POS);
+ if (msg.arg1 == AvrcpConstants.KEY_STATE_PRESS) {
mSkipAmount = skipAmount;
changePositionBy(mSkipAmount * getSkipMultiplier());
- Message posMsg = obtainMessage(MESSAGE_CHANGE_PLAY_POS);
+ Message posMsg = obtainMessage(MSG_CHANGE_PLAY_POS);
posMsg.arg1 = 1;
sendMessageDelayed(posMsg, SKIP_PERIOD);
}
+ registerNotificationRspPlayStatusNative(
+ AvrcpConstants.NOTIFICATION_TYPE_CHANGED, playStatus);
+
break;
- case MESSAGE_CHANGE_PLAY_POS:
- if (DEBUG) Log.v(TAG, "MESSAGE_CHANGE_PLAY_POS:" + msg.arg1);
+ case MSG_CHANGE_PLAY_POS:
+ if (DEBUG) Log.v(TAG, "MSG_CHANGE_PLAY_POS:" + msg.arg1);
changePositionBy(mSkipAmount * getSkipMultiplier());
if (msg.arg1 * SKIP_PERIOD < BUTTON_TIMEOUT_TIME) {
- Message posMsg = obtainMessage(MESSAGE_CHANGE_PLAY_POS);
+ Message posMsg = obtainMessage(MSG_CHANGE_PLAY_POS);
posMsg.arg1 = msg.arg1 + 1;
sendMessageDelayed(posMsg, SKIP_PERIOD);
}
break;
- case MESSAGE_SET_A2DP_AUDIO_STATE:
- if (DEBUG) Log.v(TAG, "MESSAGE_SET_A2DP_AUDIO_STATE:" + msg.arg1);
+ case MSG_SET_A2DP_AUDIO_STATE:
+ if (DEBUG) Log.v(TAG, "MSG_SET_A2DP_AUDIO_STATE:" + msg.arg1);
updateA2dpAudioState(msg.arg1);
break;
+
+ case MSG_NATIVE_REQ_GET_FOLDER_ITEMS: {
+ AvrcpCmd.FolderItemsCmd folderObj = (AvrcpCmd.FolderItemsCmd) msg.obj;
+ 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
+ setAddressedPlayer((byte[]) msg.obj, msg.arg1);
+ break;
+
+ case MSG_NATIVE_REQ_GET_ITEM_ATTR:
+ // msg object contains the item attribute object
+ handleGetItemAttr((AvrcpCmd.ItemAttrCmd) msg.obj);
+ break;
+
+ case MSG_NATIVE_REQ_SET_BR_PLAYER:
+ // argument 1 is the selected player id
+ setBrowsedPlayer((byte[]) msg.obj, msg.arg1);
+ break;
+
+ case 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");
+ handlePlayItemResponse(bdaddr, uid, scope);
+ break;
+ }
+
+ case MSG_NATIVE_REQ_GET_TOTAL_NUM_OF_ITEMS:
+ // argument 1 is scope, object is bdaddr
+ handleGetTotalNumOfItemsResponse((byte[]) msg.obj, (byte) msg.arg1);
+ break;
+
+ case MSG_NATIVE_REQ_PASS_THROUGH:
+ // argument 1 is id, argument 2 is keyState, object is bdaddr
+ mAddressedMediaPlayer.handlePassthroughCmd(msg.arg1, msg.arg2, (byte[]) msg.obj,
+ mMediaController);
+ break;
+
+ default:
+ Log.e(TAG, "unknown message! msg.what=" + msg.what);
+ break;
}
}
}
PlaybackState.Builder builder = new PlaybackState.Builder();
if (isPlaying) {
builder.setState(PlaybackState.STATE_PLAYING,
- PlaybackState.PLAYBACK_POSITION_UNKNOWN, 1.0f);
+ PlaybackState.PLAYBACK_POSITION_UNKNOWN, 1.0f);
} else {
builder.setState(PlaybackState.STATE_PAUSED,
- PlaybackState.PLAYBACK_POSITION_UNKNOWN, 0.0f);
+ PlaybackState.PLAYBACK_POSITION_UNKNOWN, 0.0f);
}
updatePlaybackState(builder.build());
}
private void updatePlaybackState(PlaybackState state) {
if (state == null) {
state = new PlaybackState.Builder().setState(PlaybackState.STATE_NONE,
- PlaybackState.PLAYBACK_POSITION_UNKNOWN, 0.0f).build();
+ PlaybackState.PLAYBACK_POSITION_UNKNOWN, 0.0f).build();
}
int oldPlayStatus = convertPlayStateToPlayStatus(mCurrentPlayState);
if (DEBUG) {
Log.v(TAG, "updatePlaybackState (" + mPlayStatusChangedNT + "): "+
- "old=" + mCurrentPlayState + "(" + oldPlayStatus + "), "+
- "new=" + state + "(" + newPlayStatus + ")");
+ "old=" + mCurrentPlayState + "(" + oldPlayStatus + "), "+
+ "new=" + state + "(" + newPlayStatus + ")");
}
mCurrentPlayState = state;
sendPlayPosNotificationRsp(false);
- if (mPlayStatusChangedNT == NOTIFICATION_TYPE_INTERIM &&
- (oldPlayStatus != newPlayStatus)) {
- mPlayStatusChangedNT = NOTIFICATION_TYPE_CHANGED;
+ if (mPlayStatusChangedNT == AvrcpConstants.NOTIFICATION_TYPE_INTERIM &&
+ (oldPlayStatus != newPlayStatus)) {
+ mPlayStatusChangedNT = AvrcpConstants.NOTIFICATION_TYPE_CHANGED;
registerNotificationRspPlayStatusNative(mPlayStatusChangedNT, newPlayStatus);
}
}
}
public String toString() {
- if (!exists)
+ if (!exists) {
return "[MediaAttributes: none]";
+ }
return "[MediaAttributes: " + title + " - " + albumName + " by "
- + artistName + " (" + mediaNumber + "/" + mediaTotalNumber + ") "
- + genre + "]";
+ + artistName + " (" + mediaNumber + "/" + mediaTotalNumber + ") "
+ + genre + "]";
}
}
} else {
mSongLengthMs = data.getLong(MediaMetadata.METADATA_KEY_DURATION);
}
+
if (!oldAttributes.equals(mMediaAttributes)) {
Log.v(TAG, "MediaAttributes Changed to " + mMediaAttributes.toString());
mTrackNumber++;
- if (mTrackChangedNT == NOTIFICATION_TYPE_INTERIM) {
- mTrackChangedNT = NOTIFICATION_TYPE_CHANGED;
+ if (mTrackChangedNT == AvrcpConstants.NOTIFICATION_TYPE_INTERIM) {
+ mTrackChangedNT = AvrcpConstants.NOTIFICATION_TYPE_CHANGED;
sendTrackChangedRsp();
}
} else {
// Update the play state, which sends play state and play position
// notifications if needed.
if (mMediaController != null) {
- updatePlaybackState(mMediaController.getPlaybackState());
+ updatePlaybackState(mMediaController.getPlaybackState());
} else {
- updatePlaybackState(null);
+ updatePlaybackState(null);
}
}
- private void getRcFeatures(byte[] address, int features) {
- Message msg = mHandler.obtainMessage(MESSAGE_GET_RC_FEATURES, features, 0,
- Utils.getAddressStringFromByte(address));
+ private void getRcFeaturesRequestFromNative(byte[] address, int features) {
+ if (DEBUG) Log.v(TAG, "getRcFeaturesRequestFromNative: address=" + address.toString());
+ Message msg = mHandler.obtainMessage(MSG_NATIVE_REQ_GET_RC_FEATURES, features, 0,
+ Utils.getAddressStringFromByte(address));
mHandler.sendMessage(msg);
}
- private void getPlayStatus() {
- Message msg = mHandler.obtainMessage(MESSAGE_GET_PLAY_STATUS);
+ private void getPlayStatusRequestFromNative(byte[] address) {
+ if (DEBUG) Log.v(TAG, "getPlayStatusRequestFromNative: address" + address.toString());
+ Message msg = mHandler.obtainMessage(MSG_NATIVE_REQ_GET_PLAY_STATUS);
+ msg.obj = address;
mHandler.sendMessage(msg);
}
- private void getElementAttr(byte numAttr, int[] attrs) {
- int i;
- ArrayList<Integer> attrList = new ArrayList<Integer>();
- for (i = 0; i < numAttr; ++i) {
- attrList.add(attrs[i]);
- }
- Message msg = mHandler.obtainMessage(MESSAGE_GET_ELEM_ATTRS, numAttr, 0, attrList);
+ private void getElementAttrRequestFromNative(byte[] address,byte numAttr, int[] attrs) {
+ if (DEBUG) Log.v(TAG, "getElementAttrRequestFromNative: numAttr=" + numAttr);
+ 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 registerNotification(int eventId, int param) {
- Message msg = mHandler.obtainMessage(MESSAGE_REGISTER_NOTIFICATION, eventId, param);
+ private void registerNotificationRequestFromNative(byte[] address,int eventId, int param) {
+ if (DEBUG) Log.v(TAG, "registerNotificationRequestFromNative: eventId=" + eventId);
+ Message msg = mHandler.obtainMessage(MSG_NATIVE_REQ_REGISTER_NOTIFICATION, eventId, param);
+ msg.obj = address;
mHandler.sendMessage(msg);
}
- private void processRegisterNotification(int eventId, int param) {
+ private void processRegisterNotification(byte[] address, int eventId, int param) {
switch (eventId) {
case EVT_PLAY_STATUS_CHANGED:
- mPlayStatusChangedNT = NOTIFICATION_TYPE_INTERIM;
+ mPlayStatusChangedNT = AvrcpConstants.NOTIFICATION_TYPE_INTERIM;
registerNotificationRspPlayStatusNative(mPlayStatusChangedNT,
convertPlayStateToPlayStatus(mCurrentPlayState));
break;
case EVT_TRACK_CHANGED:
Log.v(TAG, "Track changed notification enabled");
- mTrackChangedNT = NOTIFICATION_TYPE_INTERIM;
+ mTrackChangedNT = AvrcpConstants.NOTIFICATION_TYPE_INTERIM;
sendTrackChangedRsp();
break;
case EVT_PLAY_POS_CHANGED:
- mPlayPosChangedNT = NOTIFICATION_TYPE_INTERIM;
- mPlaybackIntervalMs = (long)param * 1000L;
+ 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, "sending availablePlayersChanged to remote ");
+ registerNotificationRspAvalPlayerChangedNative(
+ AvrcpConstants.NOTIFICATION_TYPE_INTERIM);
+ break;
+
+ case EVT_ADDR_PLAYER_CHANGED:
+ /* Notify remote addressed players changed */
+ if (DEBUG) Log.d (TAG, "sending addressedPlayersChanged to remote ");
+ registerNotificationRspAddrPlayerChangedNative(
+ AvrcpConstants.NOTIFICATION_TYPE_INTERIM,
+ mCurrAddrPlayerID, sUIDCounter);
+ break;
+
+ case EVENT_UIDS_CHANGED:
+ if (DEBUG) Log.d(TAG, "sending UIDs changed to remote");
+ registerNotificationRspUIDsChangedNative(
+ AvrcpConstants.NOTIFICATION_TYPE_INTERIM, sUIDCounter);
+ break;
+
+ case EVENT_NOW_PLAYING_CONTENT_CHANGED:
+ if (DEBUG) Log.d(TAG, "sending NowPlayingList changed to remote");
+ /* send interim response to remote device */
+ if (!registerNotificationRspNowPlayingChangedNative(
+ AvrcpConstants.NOTIFICATION_TYPE_INTERIM)) {
+ Log.e(TAG, "EVENT_NOW_PLAYING_CONTENT_CHANGED: " +
+ "registerNotificationRspNowPlayingChangedNative for Interim rsp failed!");
+ }
+ break;
}
}
- private void handlePassthroughCmd(int id, int keyState) {
+ private void handlePassthroughCmdRequestFromNative(byte[] address, int id, int keyState) {
switch (id) {
case BluetoothAvrcp.PASSTHROUGH_ID_REWIND:
- rewind(keyState);
- break;
+ rewind(address, keyState);
+ return;
case BluetoothAvrcp.PASSTHROUGH_ID_FAST_FOR:
- fastForward(keyState);
- break;
+ fastForward(address, keyState);
+ return;
}
+
+ /* For all other pass through commands other than fast forward and backward
+ * (like play, pause, next, previous, stop, etc.); sending to current addressed player.
+ */
+ Message msg = mHandler.obtainMessage(MSG_NATIVE_REQ_PASS_THROUGH, id, keyState, address);
+ mHandler.sendMessage(msg);
}
- private void fastForward(int keyState) {
- Message msg = mHandler.obtainMessage(MESSAGE_FAST_FORWARD, keyState, 0);
+ private void fastForward(byte[] address, int keyState) {
+ Message msg = mHandler.obtainMessage(MSG_FAST_FORWARD, keyState, 0);
+ Bundle data = new Bundle();
+ data.putByteArray("BdAddress" , address);
+ msg.setData(data);
mHandler.sendMessage(msg);
}
- private void rewind(int keyState) {
- Message msg = mHandler.obtainMessage(MESSAGE_REWIND, keyState, 0);
+ private void rewind(byte[] address, int keyState) {
+ Message msg = mHandler.obtainMessage(MSG_REWIND, keyState, 0);
+ Bundle data = new Bundle();
+ data.putByteArray("BdAddress" , address);
+ msg.setData(data);
mHandler.sendMessage(msg);
}
}
private void sendTrackChangedRsp() {
- byte[] track = new byte[TRACK_ID_SIZE];
-
- /* If no track is currently selected, then return
- 0xFFFFFFFFFFFFFFFF in the interim response */
- long trackNumberRsp = -1L;
-
- if (mCurrentPlayState.getState() != PlaybackState.STATE_NONE &&
- mCurrentPlayState.getState() != PlaybackState.STATE_ERROR) {
- trackNumberRsp = mTrackNumber;
+ // for players which does not support Browse or when no track is currently selected
+ if (!isBrowseSupported(getCurrentAddrPlayer()) || (mTrackNumber == -1)) {
+ trackChangeRspForBrowseUnsupported();
+ } else {
+ // for players which support browsing
+ mAddressedMediaPlayer.sendTrackChangeWithId(mTrackChangedNT, mTrackNumber,
+ mMediaController);
}
+ }
+ private void trackChangeRspForBrowseUnsupported() {
+ byte[] track = new byte[AvrcpConstants.TRACK_ID_SIZE];
/* track is stored in big endian format */
- for (int i = 0; i < TRACK_ID_SIZE; ++i) {
- track[i] = (byte) (trackNumberRsp >> (56 - 8 * i));
+ for (int idx = 0; idx < AvrcpConstants.TRACK_ID_SIZE; ++idx) {
+ if (mTrackChangedNT == AvrcpConstants.NOTIFICATION_TYPE_INTERIM &&
+ mTrackNumber == -1) {
+ /* if no track is currently selected then return 0xFF in interim response */
+ track[idx] = AvrcpConstants.NO_TRACK_SELECTED;
+ } else {
+ /* if Browsing is not supported and a track is selected, then return 0x00 */
+ track[idx] = AvrcpConstants.TRACK_IS_SELECTED;
+ }
}
registerNotificationRspTrackChangeNative(mTrackChangedNT, track);
}
private long getPlayPosition() {
- if (mCurrentPlayState == null)
+ if (mCurrentPlayState == null) {
return -1L;
+ }
- if (mCurrentPlayState.getPosition() == PlaybackState.PLAYBACK_POSITION_UNKNOWN)
+ if (mCurrentPlayState.getPosition() == PlaybackState.PLAYBACK_POSITION_UNKNOWN) {
return -1L;
+ }
if (isPlayingState(mCurrentPlayState)) {
return SystemClock.elapsedRealtime() - mLastStateUpdate + mCurrentPlayState.getPosition();
private boolean isPlayingState(PlaybackState state) {
return (state.getState() == PlaybackState.STATE_PLAYING) ||
- (state.getState() == PlaybackState.STATE_BUFFERING);
+ (state.getState() == PlaybackState.STATE_BUFFERING);
}
/**
* TG.
*/
private void sendPlayPosNotificationRsp(boolean requested) {
- if (!requested && mPlayPosChangedNT != NOTIFICATION_TYPE_INTERIM) {
+ if (!requested && mPlayPosChangedNT != AvrcpConstants.NOTIFICATION_TYPE_INTERIM) {
if (DEBUG) Log.d(TAG, "sendPlayPosNotificationRsp: Not registered or requesting.");
return;
}
// and the old mPrevPosMs is >= 0 so this is true when the new is invalid
// and the old was valid.
if (DEBUG) Log.d(TAG, "sendPlayPosNotificationRsp: (" + requested + ") "
- + mPrevPosMs + " <=? " + playPositionMs + " <=? " + mNextPosMs);
+ + mPrevPosMs + " <=? " + playPositionMs + " <=? " + mNextPosMs);
if (requested || ((mLastReportedPosition != playPositionMs) &&
- (playPositionMs >= mNextPosMs) || (playPositionMs <= mPrevPosMs))) {
- if (!requested) mPlayPosChangedNT = NOTIFICATION_TYPE_CHANGED;
- registerNotificationRspPlayPosNative(mPlayPosChangedNT, (int)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;
}
}
- mHandler.removeMessages(MESSAGE_PLAY_INTERVAL_TIMEOUT);
- if (mPlayPosChangedNT == NOTIFICATION_TYPE_INTERIM && isPlayingState(mCurrentPlayState)) {
- Message msg = mHandler.obtainMessage(MESSAGE_PLAY_INTERVAL_TIMEOUT);
+ mHandler.removeMessages(MSG_PLAY_INTERVAL_TIMEOUT);
+ if (mPlayPosChangedNT == AvrcpConstants.NOTIFICATION_TYPE_INTERIM && isPlayingState(mCurrentPlayState)) {
+ Message msg = mHandler.obtainMessage(MSG_PLAY_INTERVAL_TIMEOUT);
long delay = mPlaybackIntervalMs;
if (mNextPosMs != -1) {
delay = mNextPosMs - (playPositionMs > 0 ? playPositionMs : 0);
* requesting our handler to call setVolumeNative()
*/
public void adjustVolume(int direction) {
- Message msg = mHandler.obtainMessage(MESSAGE_ADJUST_VOLUME, direction, 0);
+ Message msg = mHandler.obtainMessage(MSG_ADJUST_VOLUME, direction, 0);
mHandler.sendMessage(msg);
}
return;
}
- mHandler.removeMessages(MESSAGE_ADJUST_VOLUME);
- Message msg = mHandler.obtainMessage(MESSAGE_SET_ABSOLUTE_VOLUME, volume, 0);
+ mHandler.removeMessages(MSG_ADJUST_VOLUME);
+ Message msg = mHandler.obtainMessage(MSG_SET_ABSOLUTE_VOLUME, volume, 0);
mHandler.sendMessage(msg);
}
* This method will send a message to our handler to change the local stored volume and notify
* AudioService to update the UI
*/
- private void volumeChangeCallback(int volume, int ctype) {
- Message msg = mHandler.obtainMessage(MESSAGE_VOLUME_CHANGED, volume, ctype);
+ private void volumeChangeRequestFromNative(byte[] address, int volume, int ctype) {
+ Message msg = mHandler.obtainMessage(MSG_NATIVE_REQ_VOLUME_CHANGE, volume, ctype);
+ Bundle data = new Bundle();
+ data.putByteArray("BdAddress" , address);
+ msg.setData(data);
+ mHandler.sendMessage(msg);
+ }
+
+ private void getFolderItemsRequestFromNative(byte[] address, byte scope, int startItem, int endItem,
+ byte numAttr, int[] attrIds) {
+ if (DEBUG) Log.v(TAG, "getFolderItemsRequestFromNative: scope=" + scope + ", numAttr=" + numAttr);
+ AvrcpCmd avrcpCmdobj = new AvrcpCmd();
+ AvrcpCmd.FolderItemsCmd folderObj = avrcpCmdobj.new FolderItemsCmd(address, scope,
+ startItem, endItem, numAttr, attrIds);
+ Message msg = mHandler.obtainMessage(MSG_NATIVE_REQ_GET_FOLDER_ITEMS, 0, 0);
+ msg.obj = folderObj;
+ mHandler.sendMessage(msg);
+ }
+
+ private void setAddressedPlayerRequestFromNative(byte[] address, int playerId) {
+ if (DEBUG) Log.v(TAG, "setAddrPlayerRequestFromNative: playerId=" + playerId);
+ Message msg = mHandler.obtainMessage(MSG_NATIVE_REQ_SET_ADDR_PLAYER, playerId, 0);
+ msg.obj = address;
+ mHandler.sendMessage(msg);
+ }
+
+ private void setBrowsedPlayerRequestFromNative(byte[] address, int playerId) {
+ if (DEBUG) Log.v(TAG, "setBrPlayerRequestFromNative: playerId=" + playerId);
+ Message msg = mHandler.obtainMessage(MSG_NATIVE_REQ_SET_BR_PLAYER, playerId, 0);
+ msg.obj = address;
+ mHandler.sendMessage(msg);
+ }
+
+ private void changePathRequestFromNative(byte[] address, byte direction, byte[] folderUid) {
+ if (DEBUG) Log.v(TAG, "changePathRequestFromNative: direction=" + direction);
+ Bundle data = new Bundle();
+ Message msg = mHandler.obtainMessage(MSG_NATIVE_REQ_CHANGE_PATH);
+ data.putByteArray("BdAddress" , address);
+ data.putByteArray("folderUid" , folderUid);
+ data.putByte("direction" , direction);
+ msg.setData(data);
+ mHandler.sendMessage(msg);
+ }
+
+ private void getItemAttrRequestFromNative(byte[] address, byte scope, byte[] itemUid, int uidCounter,
+ byte numAttr, int[] attrs) {
+ if (DEBUG) Log.v(TAG, "getItemAttrRequestFromNative: scope=" + scope + ", numAttr=" + numAttr);
+ AvrcpCmd avrcpCmdobj = new AvrcpCmd();
+ AvrcpCmd.ItemAttrCmd itemAttr = avrcpCmdobj.new ItemAttrCmd(address, scope,
+ itemUid, uidCounter, numAttr, attrs);
+ Message msg = mHandler.obtainMessage(MSG_NATIVE_REQ_GET_ITEM_ATTR);
+ msg.obj = itemAttr;
+ mHandler.sendMessage(msg);
+ }
+
+ private void searchRequestFromNative(byte[] address, int charsetId, byte[] searchStr) {
+ if (DEBUG) Log.v(TAG, "searchRequestFromNative");
+ /* Search is not supported */
+ if (DEBUG) Log.d(TAG, "search is not supported");
+ searchRspNative(address, AvrcpConstants.RSP_SRCH_NOT_SPRTD, 0, 0);
+ }
+
+ private void playItemRequestFromNative(byte[] address, byte scope, int uidCounter, byte[] uid) {
+ if (DEBUG) Log.v(TAG, "playItemRequestFromNative: scope=" + scope);
+ Bundle data = new Bundle();
+ Message msg = mHandler.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);
+ mHandler.sendMessage(msg);
+ }
+
+ private void addToPlayListRequestFromNative(byte[] address, byte scope, byte[] uid, int uidCounter) {
+ if (DEBUG) Log.v(TAG, "addToPlayListRequestFromNative: scope=" + scope);
+ /* add to NowPlaying not supported */
+ Log.w(TAG, "Add to NowPlayingList is not supported");
+ addToNowPlayingRspNative(address, AvrcpConstants.RSP_INTERNAL_ERR);
+ }
+
+ private void getTotalNumOfItemsRequestFromNative(byte[] address, byte scope) {
+ if (DEBUG) Log.v(TAG, "getTotalNumOfItemsRequestFromNative: scope=" + scope);
+ Bundle data = new Bundle();
+ Message msg = mHandler.obtainMessage(MSG_NATIVE_REQ_GET_TOTAL_NUM_OF_ITEMS);
+ msg.arg1 = scope;
+ msg.obj = address;
mHandler.sendMessage(msg);
}
* This is called from A2dpStateMachine to set A2dp audio state.
*/
public void setA2dpAudioState(int state) {
- Message msg = mHandler.obtainMessage(MESSAGE_SET_A2DP_AUDIO_STATE, state, 0);
+ Message msg = mHandler.obtainMessage(MSG_SET_A2DP_AUDIO_STATE, state, 0);
mHandler.sendMessage(msg);
}
- public void dump(StringBuilder sb) {
- sb.append("AVRCP:\n");
- ProfileService.println(sb, "mMediaAttributes: " + mMediaAttributes);
- ProfileService.println(sb, "mTransportControlFlags: " + mTransportControlFlags);
- ProfileService.println(sb, "mCurrentPlayState: " + mCurrentPlayState);
- ProfileService.println(sb, "mLastStateUpdate: " + mLastStateUpdate);
- ProfileService.println(sb, "mPlayStatusChangedNT: " + mPlayStatusChangedNT);
- ProfileService.println(sb, "mTrackChangedNT: " + mTrackChangedNT);
- ProfileService.println(sb, "mTrackNumber: " + mTrackNumber);
- ProfileService.println(sb, "mSongLengthMs: " + mSongLengthMs);
- ProfileService.println(sb, "mPlaybackIntervalMs: " + mPlaybackIntervalMs);
- ProfileService.println(sb, "mPlayPosChangedNT: " + mPlayPosChangedNT);
- ProfileService.println(sb, "mNextPosMs: " + mNextPosMs);
- ProfileService.println(sb, "mPrevPosMs: " + mPrevPosMs);
- ProfileService.println(sb, "mSkipStartTime: " + mSkipStartTime);
- 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, "mVolCmdAdjustInProgress: " + mVolCmdAdjustInProgress);
- ProfileService.println(sb, "mVolCmdSetInProgress: " + mVolCmdSetInProgress);
- ProfileService.println(sb, "mAbsVolRetryTimes: " + mAbsVolRetryTimes);
- ProfileService.println(sb, "mSkipAmount: " + mSkipAmount);
- ProfileService.println(sb, "mVolumeMapping: " + mVolumeMapping.toString());
- if (mMediaController != null)
- ProfileService.println(sb, "mMediaSession pkg: " + mMediaController.getPackageName());
+ 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);
+ }
+ }
+ }
}
- // Do not modify without updating the HAL bt_rc.h files.
+ private void handlePackageModified(String packageName, boolean removed) {
+ if (DEBUG) Log.d(TAG, "packageName: " + packageName + " removed: " + removed);
- // match up with btrc_play_status_t enum of bt_rc.h
- final static int PLAYSTATUS_STOPPED = 0;
- final static int PLAYSTATUS_PLAYING = 1;
- final static int PLAYSTATUS_PAUSED = 2;
- final static int PLAYSTATUS_FWD_SEEK = 3;
- final static int PLAYSTATUS_REV_SEEK = 4;
- final static int PLAYSTATUS_ERROR = 255;
+ if (removed) {
+ // 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
+ buildBrowsablePlayersList();
+ }
+ }
+ }
- // match up with btrc_media_attr_t enum of bt_rc.h
- final static int MEDIA_ATTR_TITLE = 1;
- final static int MEDIA_ATTR_ARTIST = 2;
- final static int MEDIA_ATTR_ALBUM = 3;
- final static int MEDIA_ATTR_TRACK_NUM = 4;
- final static int MEDIA_ATTR_NUM_TRACKS = 5;
- final static int MEDIA_ATTR_GENRE = 6;
- final static int MEDIA_ATTR_PLAYING_TIME = 7;
+ private boolean isBrowsableListUpdated(String newPackageName) {
- // match up with btrc_event_id_t enum of bt_rc.h
- final static int EVT_PLAY_STATUS_CHANGED = 1;
- final static int EVT_TRACK_CHANGED = 2;
- final static int EVT_TRACK_REACHED_END = 3;
- final static int EVT_TRACK_REACHED_START = 4;
- final static int EVT_PLAY_POS_CHANGED = 5;
- final static int EVT_BATT_STATUS_CHANGED = 6;
- final static int EVT_SYSTEM_STATUS_CHANGED = 7;
- final static int EVT_APP_SETTINGS_CHANGED = 8;
+ boolean isUpdated = false;
+
+ // getting the browsable media players list from package manager
+ ArrayList<String> browsePlayersList = new ArrayList<String>();
+ Intent intent = new Intent("android.media.browse.MediaBrowserService");
+ List<ResolveInfo> resInfos = mPackageManager.queryIntentServices(intent, 0);
+ for (ResolveInfo resolveInfo : resInfos) {
+ browsePlayersList.add(resolveInfo.serviceInfo.packageName);
+ }
- // match up with btrc_notification_type_t enum of bt_rc.h
- final static int NOTIFICATION_TYPE_INTERIM = 0;
- final static int NOTIFICATION_TYPE_CHANGED = 1;
+ // if new added package is browsable or list has been updated from the global object
+ if (browsePlayersList.contains(newPackageName)
+ || browsePlayersList.size() != getBrowsePlayersListSize()) {
+ isUpdated = true;
+ }
+ if (DEBUG) Log.d(TAG, "isBrowsableListUpdated " + newPackageName +
+ " isUpdated:" + isUpdated);
+ return isUpdated;
+ }
+
+ private synchronized void removePackageFromBrowseList(String packageName) {
+ if (DEBUG) Log.d(TAG, "removePackageFromBrowseList: " + packageName);
+ 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 synchronized int getBrowseId(String packageName) {
+
+ boolean response = false;
+ int browseInfoID = 0;
+
+ 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) {
+ int status = AvrcpConstants.RSP_NO_ERROR;
+
+ if (isCurrentMediaPlayerListEmpty()) {
+ status = AvrcpConstants.RSP_NO_AVBL_PLAY;
+ Log.w(TAG, " No Available Players to set, sending response back ");
+ } else if (!isIdValid(selectedId)) {
+ status = AvrcpConstants.RSP_INV_PLAYER;
+ Log.w(TAG, " Invalid Player id: " + selectedId + " to set, sending response back ");
+ } else if (!isPlayerAlreadyAddressed(selectedId)) {
+ // register new Media Controller Callback and update the current Ids
+ if (!updateCurrentController(selectedId, mCurrBrowsePlayerID)) {
+ status = AvrcpConstants.RSP_INTERNAL_ERR;
+ Log.e(TAG, "register for new Address player failed: " + mCurrAddrPlayerID);
+ }
+ } else {
+ Log.i(TAG, "requested addressPlayer is already focused:" + getCurrentAddrPlayer());
+ }
+
+ if (DEBUG) Log.d(TAG, "setAddressedPlayer for selectedId: " + selectedId +
+ " , status: " + status);
+ // Sending address player response to remote
+ setAddressedPlayerRspNative(bdaddr, status);
+ }
+
+ private void setBrowsedPlayer(byte[] bdaddr, int selectedId) {
+ int status = AvrcpConstants.RSP_NO_ERROR;
+
+ // checking for error cases
+ if (isCurrentMediaPlayerListEmpty()) {
+ status = AvrcpConstants.RSP_NO_AVBL_PLAY;
+ Log.w(TAG, " No Available Players to set, sending response back ");
+ } else {
+ // 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<MediaController> mediaControllerList) {
+ if (DEBUG) Log.v(TAG, "received onActiveSessionsChanged");
+
+ if (isAvailablePlayersChanged(mediaControllerList)) {
+ // rebuild the list cached locally in this file
+ buildMediaPlayersList();
+
+ // inform the remote device that the player list has changed
+ sendAvailablePlayersChanged();
+ } else if (isAddressedPlayerChanged(mediaControllerList)) {
+ int newAddrPlayerID = getNewAddrPlayerID(mediaControllerList.get(0)
+ .getPackageName());
+ // inform the remote device that the addressed player has changed
+ sendAddressedPlayerChanged(newAddrPlayerID);
+
+ if (!updateCurrentController(newAddrPlayerID, mCurrBrowsePlayerID)) {
+ Log.e(TAG, "register for new Address player failed. id: " + newAddrPlayerID);
+ }
+ } else {
+ if (DEBUG) Log.d(TAG, "Active sessions same, ignoring onActiveSessionsChanged.");
+ }
+ }
+
+ private void sendAddressedPlayerChanged(int newAddrPlayerID) {
+ if (DEBUG) Log.d(TAG, "sendAddressedPlayerChanged: new PlayerID=" + newAddrPlayerID);
+
+ /* notify remote addressed player changed */
+ registerNotificationRspAddrPlayerChangedNative(
+ AvrcpConstants.NOTIFICATION_TYPE_CHANGED, newAddrPlayerID, sUIDCounter);
+ }
+
+ private void sendAvailablePlayersChanged() {
+ if (DEBUG) Log.d(TAG, "sendAvailablePlayersChanged");
+
+ /* Notify remote available players changed */
+ registerNotificationRspAvalPlayerChangedNative(
+ AvrcpConstants.NOTIFICATION_TYPE_CHANGED);
+ }
+
+ private boolean isAddressedPlayerChanged(List<MediaController> mediaControllerList) {
+ boolean isAddrPlayerChanged = false;
+
+ // checking top of the controller's list with current addressed player
+ if (mediaControllerList != null && !mediaControllerList.isEmpty()) {
+ if (!mediaControllerList.get(0).getPackageName().equals(getCurrentAddrPlayer())) {
+ isAddrPlayerChanged = true;
+ }
+ }
+
+ if (DEBUG) Log.d(TAG, "isAddressedPlayerChanged: " + isAddrPlayerChanged);
+ return isAddrPlayerChanged;
+ }
+
+ private boolean isAvailablePlayersChanged(List<MediaController> mediaControllerList) {
+ boolean isListModified = false;
+
+ /* comparing media controller list from framework and from local cached list */
+ if (mediaControllerList == null && isCurrentMediaPlayerListEmpty()) {
+ if (DEBUG) Log.d(TAG,
+ "both player list, received from framework and local are empty");
+ return isListModified;
+ }
+
+ if (mediaControllerList == null && !isCurrentMediaPlayerListEmpty()) {
+ isListModified = true;
+ if (DEBUG) Log.d(TAG, "players list is empty, local player list is not empty");
+ } else if (isCurrentMediaPlayerListEmpty() && mediaControllerList != null) {
+ if (DEBUG) Log.d(TAG, "players list is not empty, but local player list is empty");
+ isListModified = true;
+ } else if (isCtrlListChanged(mediaControllerList, mMPLObj.mControllersList)) {
+ isListModified = true;
+ }
+
+ if (DEBUG) Log.d(TAG, "isAvailablePlayersChanged: " + isListModified);
+ return isListModified;
+ }
+
+ private int getNewAddrPlayerID(String newAddressedPlayer) {
+ int newAddrPlayerId = -1;
+
+ for (int id = 0; id < mMPLObj.mPackageNameList.length; id++) {
+ if (mMPLObj.mPackageNameList[id].equals(newAddressedPlayer)) {
+ // increment Id by one, because list Ids starts from 1.
+ newAddrPlayerId = id + 1;
+ break;
+ }
+ }
+
+ if (DEBUG) Log.d(TAG, "getNewAddrPlayerID: " + newAddrPlayerId);
+ return newAddrPlayerId;
+ }
- // match up with BTRC_UID_SIZE of bt_rc.h
- final static int TRACK_ID_SIZE = 8;
+ private boolean isCtrlListChanged(List<MediaController> mediaControllerList,
+ List<MediaController> mControllersList) {
+ boolean isListChanged = false;
+
+ if (mControllersList.size() != mediaControllerList.size()) {
+ if (DEBUG) Log.d(TAG, "size of new list and old list are different");
+ isListChanged = true;
+ } else {
+ // loop through both the list and check if any new entry found
+ for (MediaController newCtrller : mediaControllerList) {
+ boolean isPackageExist = false;
+
+ for (MediaController oldCtrller : mControllersList) {
+ if (oldCtrller.getPackageName().equals(newCtrller.getPackageName())) {
+ isPackageExist = true;
+ break;
+ }
+ }
+
+ if (!isPackageExist) {
+ if (DEBUG) Log.d(TAG, "no match found for " + newCtrller.getPackageName());
+ isListChanged = true;
+ break;
+ }
+ }
+ }
+
+ if (DEBUG) Log.d(TAG, "isCtrlListChanged: " + isListChanged);
+ return isListChanged;
+ }
+
+ };
+
+ 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 synchronized String getBrowseServiceName(String packageName) {
+ String browseServiceName = "";
+
+ // getting the browse service name from browse player info
+ 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;
+ }
+
+ /*
+ * utility function to build list of browsable players identified from
+ * browse service implementation.
+ */
+ private synchronized void buildBrowsablePlayersList() {
+ if (DEBUG) Log.i(TAG, "buildBrowsablePlayersList()");
+
+ // Clearing old browsable player's list
+ mBrowsePlayerInfoList.clear();
+
+ Intent intent = new Intent(android.service.media.MediaBrowserService.SERVICE_INTERFACE);
+ List<ResolveInfo> resInfos = mPackageManager.queryIntentServices(intent, 0);
+
+ for (ResolveInfo resolveInfo : resInfos) {
+ String displayableName = resolveInfo.loadLabel(mPackageManager).toString();
+ String serviceName = resolveInfo.serviceInfo.name;
+ String packageName = resolveInfo.serviceInfo.packageName;
+
+ BrowsePlayerInfo infoObj = new BrowsePlayerInfo(packageName, displayableName,
+ serviceName);
+ if (DEBUG)
+ Log.d(TAG, infoObj.toString());
+ mBrowsePlayerInfoList.add(infoObj);
+ }
+
+ if (DEBUG) Log.i(TAG, "buildBrowsablePlayersList: found " + resInfos.size() + " players");
+ }
+
+ /* initializing media player info list and prepare media player response object */
+ private void buildMediaPlayersList() {
+
+ initMediaPlayersInfoList();
+ mMPLObj = prepareMediaPlayerRspObj();
+
+ if (mMPLObj.mNumItems > 0) {
+ // Setting player which is on the Top (id=1) of the list as an Addressed player
+ updateCurrentController(1, -1);
+ } else {
+ Log.i(TAG, "No players available in the media players list");
+ /* If there are no players available in the media players list, meaning none of the
+ * players are yet open, so no active players are in the list. But in this case none
+ * of the AVRCP player related commands can be satisfied. So, launching first available
+ * browsable player service to avail atleast one player to do AVRCP operations. */
+ /* Starting media player service */
+ if ((mBrowsePlayerInfoList != null) && (mBrowsePlayerInfoList.size()!=0)) {
+ BrowsePlayerInfo player = mBrowsePlayerInfoList.get(0);
+ Intent intent = new Intent();
+ intent.setComponent(new ComponentName(player.packageName, player.serviceClass));
+ Log.i(TAG, "Starting service:" + player.packageName + ", " + player.serviceClass);
+ mContext.startService(intent);
+ } else {
+ Log.e(TAG, "Opening player to support AVRCP operations failed, " +
+ "No browsable players available!");
+ }
+ }
+
+ }
+
+ /*
+ * utility function to build list of active media players identified from
+ * session manager by getting the active sessions
+ */
+ private synchronized void initMediaPlayersInfoList() {
+ if (DEBUG) Log.v(TAG, "initMediaPlayersInfoList");
+
+ // Clearing old browsable player's list
+ mMediaPlayerInfoList.clear();
+
+ /* Initializing all media players */
+ for (MediaController mediaController : getActiveControllersList()) {
+ initMediaPlayer(mediaController);
+ }
+ }
+
+ /* Using session manager apis, getting the list of active media controllers */
+ private List<MediaController> getActiveControllersList() {
+ List<MediaController> controllersList = new ArrayList<MediaController>();
+ controllersList = mMediaSessionManager.getActiveSessions(null);
+ Log.i(TAG, "getActiveControllersList: " + controllersList.size() + " controllers");
+ return controllersList;
+ }
+
+ /*
+ * utility function to initialize media players info and add them to global
+ * media player info list
+ */
+ private synchronized void initMediaPlayer(MediaController mediaController) {
+
+ String packageName = mediaController.getPackageName();
+
+ MediaPlayerInfo mMediaPlayerInfo = new MediaPlayerInfo(packageName,
+ AvrcpConstants.PLAYER_TYPE_AUDIO, AvrcpConstants.PLAYER_SUBTYPE_NONE,
+ getPlayBackState(mediaController), getFeatureBitMask(packageName),
+ getAppLabel(packageName), mediaController);
+
+ if (DEBUG) Log.d(TAG, mMediaPlayerInfo.toString());
+
+ mMediaPlayerInfoList.add(mMediaPlayerInfo);
+ }
+
+ /*
+ * utility function to get the playback state of any media player through
+ * media controller APIs.
+ */
+ private byte getPlayBackState(MediaController mediaController) {
+ PlaybackState pbState = mediaController.getPlaybackState();
+ byte playStateBytes = PLAYSTATUS_STOPPED;
+
+ if (pbState != null) {
+ playStateBytes = (byte)convertPlayStateToBytes(pbState.getState());
+ Log.v(TAG, "getPlayBackState: playStateBytes = " + playStateBytes);
+ } else {
+ Log.w(TAG, "playState object null, sending playStateBytes = " + playStateBytes);
+ }
+
+ return playStateBytes;
+ }
+
+ /*
+ * utility function to map framework's play state values to AVRCP spec
+ * defined play status values
+ */
+ private int convertPlayStateToBytes(int playState) {
+ switch (playState) {
+ case PlaybackState.STATE_PLAYING:
+ case PlaybackState.STATE_BUFFERING:
+ return PLAYSTATUS_PLAYING;
+
+ 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 synchronized boolean isBrowseSupported(String packageName) {
+ boolean response = false;
+
+ /* check if Browsable Player's list contains this package name */
+ for (BrowsePlayerInfo info : mBrowsePlayerInfoList) {
+ if (info.packageName.equals(packageName)) {
+ // TODO: (apanicke) Currently browsing isn't implemented
+ // properly and causes metadata to break. Fix browsing
+ // interface and change this to true.
+ response = false;
+ }
+ }
+
+ if (DEBUG) Log.v(TAG, "isBrowseSupported for " + packageName + ": " + response);
+ return response;
+ }
+
+ /* from the global object, getting the current addressed player's package name */
+ private String getCurrentAddrPlayer() {
+ String addrPlayerPackage = "";
+
+ if (!isCurrentMediaPlayerListEmpty() && isIdValid(mCurrAddrPlayerID)) {
+ addrPlayerPackage = mMPLObj.mPackageNameList[mCurrAddrPlayerID - 1];
+ if (DEBUG) Log.v(TAG, "Current Addressed Player's Package: " + addrPlayerPackage);
+ } else {
+ Log.w(TAG, "current addressed player is not yet set.");
+ }
+ return addrPlayerPackage;
+ }
+
+ private String getPackageName(int id) {
+ String packageName = "";
+
+ if (!isCurrentMediaPlayerListEmpty() && isIdValid(id)) {
+ packageName = mMPLObj.mPackageNameList[id - 1];
+ if (DEBUG) Log.v(TAG, "Current Player's Package: " + packageName);
+ } else {
+ Log.w(TAG, "Current media player is empty or id is invalid");
+ }
+ 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;
+ }
+
+ /*
+ * utility function to get the media controller from the current addressed
+ * player id, can return null in error cases
+ */
+ private synchronized MediaController getCurrentMediaController() {
+ MediaController mediaController = null;
+
+ if (mMediaPlayerInfoList == null || mMediaPlayerInfoList.isEmpty()) {
+ Log.w(TAG, " No available players , sending response back ");
+ return mediaController;
+ }
+
+ if (!isIdValid(mCurrAddrPlayerID)) {
+ Log.w(TAG, "CurrPlayerID is not yet set:" + mCurrAddrPlayerID + ", PlayerList length="
+ + mMediaPlayerInfoList.size() + " , sending response back");
+ return mediaController;
+ }
+
+ mediaController = mMediaPlayerInfoList.get(mCurrAddrPlayerID - 1).getMediaController();
+
+ if (mediaController != null) {
+ if (DEBUG)
+ Log.v(TAG, "getCurrentMediaController: " + mediaController.getPackageName());
+ }
+
+ return mediaController;
+ }
+
+ /*
+ * Utility function to get the Media player info from package name returns
+ * null if package name not found in media players list
+ */
+ private synchronized MediaPlayerInfo getMediaPlayerInfo(String packageName) {
+ if (DEBUG) Log.v(TAG, "getMediaPlayerInfo: " + packageName);
+ if (mMediaPlayerInfoList.size() > 0) {
+ for (MediaPlayerInfo info : mMediaPlayerInfoList) {
+ if (packageName.equals(info.getPackageName())) {
+ if (DEBUG) Log.v(TAG, "Found " + info.getPackageName());
+ return info;
+ }
+ }
+ } else {
+ if (DEBUG) Log.v(TAG, "Media players list empty");
+ }
+ return null;
+ }
+
+ /* prepare media list & return the media player list response object */
+ private synchronized MediaPlayerListRsp prepareMediaPlayerRspObj() {
+
+ /* Forming player list -- */
+ int numPlayers = mMediaPlayerInfoList.size();
+
+ byte[] playerTypes = new byte[numPlayers];
+ int[] playerSubTypes = new int[numPlayers];
+ String[] displayableNameArray = new String[numPlayers];
+ String[] packageNameArray = new String[numPlayers];
+ byte[] playStatusValues = new byte[numPlayers];
+ short[] featureBitMaskValues = new short[numPlayers
+ * AvrcpConstants.AVRC_FEATURE_MASK_SIZE];
+ List<MediaController> mediaControllerList = new ArrayList<MediaController>();
+
+ int playerId = 0;
+ for (MediaPlayerInfo info : mMediaPlayerInfoList) {
+ playerTypes[playerId] = info.getMajorType();
+ playerSubTypes[playerId] = info.getSubType();
+ packageNameArray[playerId] = info.getPackageName();
+ displayableNameArray[playerId] = info.getDisplayableName();
+ playStatusValues[playerId] = info.getPlayStatus();
+ mediaControllerList.add(info.getMediaController());
+
+ for (int numBit = 0; numBit < info.getFeatureBitMask().length; numBit++) {
+ /* gives which octet this belongs to */
+ byte octet = (byte) (info.getFeatureBitMask()[numBit] / 8);
+ /* gives the bit position within the octet */
+ byte bit = (byte) (info.getFeatureBitMask()[numBit] % 8);
+ featureBitMaskValues[(playerId * AvrcpConstants.AVRC_FEATURE_MASK_SIZE) + octet] |=
+ (1 << bit);
+ }
+
+ /* printLogs */
+ if (DEBUG) {
+ Log.d(TAG, "\n +++ Player " + playerId + " +++ ");
+ Log.d(TAG, "display Name[" + playerId + "]: " + displayableNameArray[playerId]);
+ Log.d(TAG, "Package Name[" + playerId + "]: " + packageNameArray[playerId]);
+ Log.d(TAG, "player Types[" + playerId + "]: " + playerTypes[playerId]);
+ Log.d(TAG, "Play Status Value[" + playerId + "]: " + playStatusValues[playerId]);
+ Log.d(TAG, "\n");
+ }
+
+ playerId++;
+ }
+
+ if (DEBUG) Log.d(TAG, "prepareMediaPlayerRspObj: numPlayers = " + numPlayers);
+
+ return new MediaPlayerListRsp(AvrcpConstants.RSP_NO_ERROR, sUIDCounter,
+ numPlayers, AvrcpConstants.BTRC_ITEM_PLAYER, playerTypes, playerSubTypes,
+ playStatusValues, featureBitMaskValues,
+ displayableNameArray, packageNameArray, mediaControllerList);
+
+ }
+
+ /* build media player list and send it to remote. */
+ private void handleMediaPlayerListRsp(AvrcpCmd.FolderItemsCmd folderObj) {
+ if (folderObj.mStartItem >= mMPLObj.mNumItems) {
+ Log.i(TAG, "handleMediaPlayerListRsp: start item = " + folderObj.mStartItem +
+ ", but available num of items = " + mMPLObj.mNumItems);
+ mediaPlayerListRspNative(folderObj.mAddress, AvrcpConstants.RSP_INV_RANGE,
+ (short) 0, (byte) 0, 0, null, null, null, null, null);
+ } else {
+ if (DEBUG) Log.d(TAG, "handleMediaPlayerListRsp: num items = " + mMPLObj.mNumItems);
+ sendFolderItems(mMPLObj, folderObj.mAddress);
+ }
+ }
+
+ /* unregister to the old controller, update new IDs and register to the new controller */
+ private boolean updateCurrentController(int addrId, int browseId) {
+ boolean registerRsp = true;
+
+ if (!unregOldMediaControllerCb()) {
+ Log.d(TAG, "unregisterOldMediaControllerCallback return false");
+ }
+
+ updateNewIds(addrId, browseId);
+ if (!regNewMediaControllerCb()) {
+ Log.d(TAG, "registerOldMediaControllerCallback return false");
+ registerRsp = false;
+ }
+
+ if (DEBUG) Log.d(TAG, "updateCurrentController: registerRsp = " + registerRsp);
+ return registerRsp;
+ }
+
+ /* get the current media controller and unregister for the media controller callback */
+ private boolean unregOldMediaControllerCb() {
+ boolean isUnregistered = false;
+
+ // unregistering callback for old media controller.
+ MediaController oldController = getCurrentMediaController();
+ if (oldController != null) {
+ oldController.unregisterCallback(mMediaControllerCb);
+ isUnregistered = true;
+ } else {
+ Log.i(TAG, "old controller is null, addressPlayerId:" + mCurrAddrPlayerID);
+ }
+
+ if (DEBUG) Log.d(TAG, "unregOldMediaControllerCb: isUnregistered = " + isUnregistered);
+ return isUnregistered;
+ }
+
+ /* get the current media controller and register for the media controller callback */
+ private boolean regNewMediaControllerCb() {
+ // registering callback for new media controller.
+ MediaController newController = getCurrentMediaController();
+ mMediaController = newController;
+
+ String name = (mMediaController == null) ? "null" : mMediaController.getPackageName();
+ Log.v(TAG, "MediaController changed to " + name);
+
+ if (mMediaController == null) {
+ Log.i(TAG, "new controller is null, addressPlayerId:" + mCurrAddrPlayerID);
+ updateMetadata(null);
+ mAddressedMediaPlayer.updateNowPlayingList(null);
+ return false;
+ }
+
+ mMediaController.registerCallback(mMediaControllerCb, mHandler);
+ updateMetadata(mMediaController.getMetadata());
+ mAddressedMediaPlayer.updateNowPlayingList(mMediaController.getQueue());
+ return true;
+ }
+
+ /* 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 */
+ switch (folderObj.mScope) {
+ case AvrcpConstants.BTRC_SCOPE_FILE_SYSTEM:
+ if (mAvrcpBrowseManager.getBrowsedMediaPlayer(bdaddr) != null) {
+ mAvrcpBrowseManager.getBrowsedMediaPlayer(bdaddr).getFolderItemsVFS(folderObj);
+ } else {
+ /* No browsed player set. Browsed player should be set by CT before performing browse.*/
+ Log.e(TAG, "handleGetFolderItemBrowseResponse: mBrowsedMediaPlayer is null");
+ status = AvrcpConstants.RSP_INTERNAL_ERR;
+ }
+ break;
+
+ case AvrcpConstants.BTRC_SCOPE_NOW_PLAYING:
+ mAddressedMediaPlayer.getFolderItemsNowPlaying(bdaddr, folderObj, mMediaController);
+ break;
+
+ default:
+ /* invalid scope */
+ Log.e(TAG, "handleGetFolderItemBrowseResponse:invalid scope");
+ status = AvrcpConstants.RSP_INV_SCOPE;
+ }
+
+
+ if (status != AvrcpConstants.RSP_NO_ERROR) {
+ getFolderItemsRspNative(bdaddr, status, (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 synchronized void updateNewIds(int addrId, int browseId) {
+ mCurrAddrPlayerID = addrId;
+ mCurrBrowsePlayerID = browseId;
+
+ if (DEBUG) Log.v(TAG, "Updated CurrentIds: AddrPlayerID:" + mCurrAddrPlayerID + " to "
+ + addrId + ", BrowsePlayerID:" + mCurrBrowsePlayerID + " to " + 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 sendFolderItems(MediaPlayerListRsp rspObj, byte[] bdaddr) {
+ mediaPlayerListRspNative(bdaddr, rspObj.mStatus, rspObj.mUIDCounter, rspObj.itemType,
+ rspObj.mNumItems, rspObj.mPlayerTypes, rspObj.mPlayerSubTypes,
+ rspObj.mPlayStatusValues, rspObj.mFeatureBitMaskValues, rspObj.mPlayerNameList);
+ }
+
+ private void handlePlayItemResponse(byte[] bdaddr, byte[] uid, byte scope) {
+
+ if(scope == AvrcpConstants.BTRC_SCOPE_NOW_PLAYING) {
+ mAddressedMediaPlayer.playItem(bdaddr, uid, scope, 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.mScope == AvrcpConstants.BTRC_SCOPE_NOW_PLAYING) {
+ mAddressedMediaPlayer.getItemAttr(itemAttr.mAddress, itemAttr, mMediaController);
+ }
+ else {
+ 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 = getPlayerListSize();
+ if (DEBUG) Log.d(TAG, "handleGetTotalNumOfItemsResponse: sending total " + numPlayers +
+ " media players.");
+ getTotalNumOfItemsRspNative(bdaddr, AvrcpConstants.RSP_NO_ERROR, 0,
+ numPlayers);
+ } else if(scope == AvrcpConstants.BTRC_SCOPE_NOW_PLAYING) {
+ mAddressedMediaPlayer.getTotalNumOfItems(bdaddr, scope, 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);
+ }
+ }
+
+ }
+
+ private synchronized int getPlayerListSize() {
+ return mMediaPlayerInfoList.size();
+ }
+
+ private synchronized int getBrowsePlayersListSize() {
+ return mBrowsePlayerInfoList.size();
+ }
+
+ /* check if browsed player and addressed player are same */
+ private boolean isAddrPlayerSameAsBrowsed(byte[] bdaddr) {
+ boolean isSame = true;
+ String browsedPlayer = getCurrentBrowsedPlayer(bdaddr);
+ String addressedPlayer = getCurrentAddrPlayer();
+
+ if (!isPackageNameValid(browsedPlayer)) {
+ Log.w(TAG, "Browsed player name empty");
+ isSame = false;
+ } else if (!addressedPlayer.equals(browsedPlayer)) {
+ Log.w(TAG, browsedPlayer + " is not current Addressed Player : "
+ + addressedPlayer);
+ isSame = false;
+ }
+
+ if (DEBUG) Log.d(TAG, "isAddrPlayerSameAsBrowsed: isSame = " + isSame);
+ return isSame;
+ }
+
+ /* checks if global object containing media player list is empty */
+ private boolean isCurrentMediaPlayerListEmpty() {
+ boolean isEmpty = (mMPLObj == null || mMPLObj.mPackageNameList == null
+ || mMPLObj.mPackageNameList.length == 0 || mMediaPlayerInfoList.isEmpty());
+ if (DEBUG) Log.d(TAG, "Current MediaPlayer List Empty.= " + isEmpty);
+ return isEmpty;
+ }
+
+ /* checks if the id is within the range of global object containing media player list */
+ private boolean isIdValid(int id) {
+ boolean isValid = (id > 0 && id <= mMPLObj.mPackageNameList.length);
+ if (DEBUG) Log.d(TAG, "Id = " + id + "isIdValid = " + isValid);
+ return isValid;
+ }
+
+ /* 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);
+ ProfileService.println(sb, "mTransportControlFlags: " + mTransportControlFlags);
+ ProfileService.println(sb, "mCurrentPlayState: " + mCurrentPlayState);
+ ProfileService.println(sb, "mLastStateUpdate: " + mLastStateUpdate);
+ ProfileService.println(sb, "mPlayStatusChangedNT: " + mPlayStatusChangedNT);
+ ProfileService.println(sb, "mTrackChangedNT: " + mTrackChangedNT);
+ ProfileService.println(sb, "mTrackNumber: " + mTrackNumber);
+ ProfileService.println(sb, "mSongLengthMs: " + mSongLengthMs);
+ ProfileService.println(sb, "mPlaybackIntervalMs: " + mPlaybackIntervalMs);
+ ProfileService.println(sb, "mPlayPosChangedNT: " + mPlayPosChangedNT);
+ ProfileService.println(sb, "mNextPosMs: " + mNextPosMs);
+ ProfileService.println(sb, "mPrevPosMs: " + mPrevPosMs);
+ ProfileService.println(sb, "mSkipStartTime: " + mSkipStartTime);
+ 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, "mVolCmdAdjustInProgress: " + mVolCmdAdjustInProgress);
+ ProfileService.println(sb, "mVolCmdSetInProgress: " + mVolCmdSetInProgress);
+ ProfileService.println(sb, "mAbsVolRetryTimes: " + mAbsVolRetryTimes);
+ ProfileService.println(sb, "mSkipAmount: " + mSkipAmount);
+ ProfileService.println(sb, "mVolumeMapping: " + mVolumeMapping.toString());
+ if (mMediaController != null)
+ ProfileService.println(sb, "mMediaSession pkg: " + mMediaController.getPackageName());
+ }
+
+ public class AvrcpBrowseManager {
+ 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();
+ }
+
+ public void cleanupConn(BluetoothDevice device) {
+ if(null == device)
+ return;
+ String bdaddr = new String(hexStringToByteArray(device.getAddress().replace(":","")));
+ /* check to see remote device performed setBrowsedPlayer */
+ if(connList.containsKey(bdaddr)) {
+ BrowsedMediaPlayer browsedMediaPlayer = connList.get(bdaddr);
+ /* cleanup browsing connection to media player for disconnected remote device */
+ if(browsedMediaPlayer != null)
+ browsedMediaPlayer.cleanup();
+ /* remove bdaddr of disconnected device */
+ connList.remove(bdaddr);
+ }
+ }
+
+ // 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";
+
+ public void setAddrPlayerRsp(byte[] address, int rspStatus) {
+ if (!setAddressedPlayerRspNative(address, rspStatus)) {
+ Log.e(TAG, "setAddrPlayerRsp failed!");
+ }
+ }
+
+ 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!");
+ }
+ }
+
+ public void mediaPlayerListRsp(byte[] address, int rspStatus, MediaPlayerListRsp rspObj) {
+ if (rspObj != null && rspStatus == AvrcpConstants.RSP_NO_ERROR) {
+ if (!mediaPlayerListRspNative(address, rspStatus, sUIDCounter, rspObj.itemType,
+ rspObj.mNumItems, 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))
+ Log.e(TAG, "mediaPlayerListRsp failed!");
+ }
+ }
+
+ 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!");
+ }
+
+ }
+
+ public void changePathRsp(byte[] address, int rspStatus, int numItems) {
+ if (!changePathRspNative(address, rspStatus, numItems))
+ Log.e(TAG, "changePathRspNative failed!");
+ }
+
+ 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!");
+ }
+ }
+
+ public void playItemRsp(byte[] address, int rspStatus) {
+ if (!playItemRspNative(address, rspStatus)) {
+ Log.e(TAG, "playItemRspNative failed!");
+ }
+ }
+
+ public void getTotalNumOfItemsRsp(byte[] address, int rspStatus, int uidCounter,
+ int numItems) {
+ if (!getTotalNumOfItemsRspNative(address, rspStatus, sUIDCounter, numItems)) {
+ Log.e(TAG, "getTotalNumOfItemsRspNative failed!");
+ }
+ }
+
+ public void addrPlayerChangedRsp(byte[] address, int type, int playerId, int uidCounter) {
+ if (!registerNotificationRspAddrPlayerChangedNative(type, playerId, sUIDCounter)) {
+ Log.e(TAG, "registerNotificationRspAddrPlayerChangedNative failed!");
+ }
+ }
+
+ public void avalPlayerChangedRsp(byte[] address, int type) {
+ if (!registerNotificationRspAvalPlayerChangedNative(type)) {
+ Log.e(TAG, "registerNotificationRspAvalPlayerChangedNative failed!");
+ }
+ }
+
+ public void uidsChangedRsp(byte[] address, int type, int uidCounter) {
+ if (!registerNotificationRspUIDsChangedNative(type, sUIDCounter)) {
+ Log.e(TAG, "registerNotificationRspUIDsChangedNative failed!");
+ }
+ }
+
+ public void nowPlayingChangedRsp(int type) {
+ if (!registerNotificationRspNowPlayingChangedNative(type)) {
+ Log.e(TAG, "registerNotificationRspNowPlayingChangedNative failed!");
+ }
+ }
+
+ 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;
+ }
+
+ // Do not modify without updating the HAL bt_rc.h files.
+
+ // match up with btrc_play_status_t enum of bt_rc.h
+ final static int PLAYSTATUS_STOPPED = 0;
+ final static int PLAYSTATUS_PLAYING = 1;
+ final static int PLAYSTATUS_PAUSED = 2;
+ final static int PLAYSTATUS_FWD_SEEK = 3;
+ final static int PLAYSTATUS_REV_SEEK = 4;
+ final static int PLAYSTATUS_ERROR = 255;
+
+ // match up with btrc_media_attr_t enum of bt_rc.h
+ final static int MEDIA_ATTR_TITLE = 1;
+ final static int MEDIA_ATTR_ARTIST = 2;
+ final static int MEDIA_ATTR_ALBUM = 3;
+ final static int MEDIA_ATTR_TRACK_NUM = 4;
+ final static int MEDIA_ATTR_NUM_TRACKS = 5;
+ final static int MEDIA_ATTR_GENRE = 6;
+ final static int MEDIA_ATTR_PLAYING_TIME = 7;
+
+ // match up with btrc_event_id_t enum of bt_rc.h
+ final static int EVT_PLAY_STATUS_CHANGED = 1;
+ final static int EVT_TRACK_CHANGED = 2;
+ final static int EVT_TRACK_REACHED_END = 3;
+ final static int EVT_TRACK_REACHED_START = 4;
+ final static int EVT_PLAY_POS_CHANGED = 5;
+ final static int EVT_BATT_STATUS_CHANGED = 6;
+ final static int EVT_SYSTEM_STATUS_CHANGED = 7;
+ final static int EVT_APP_SETTINGS_CHANGED = 8;
+ final static int EVENT_NOW_PLAYING_CONTENT_CHANGED = 9;
+ final static int EVT_AVBL_PLAYERS_CHANGED = 0xa;
+ final static int EVT_ADDR_PLAYER_CHANGED = 0xb;
+ final static int EVENT_UIDS_CHANGED = 0x0c;
private native static void classInitNative();
private native void initNative();
private native void cleanupNative();
- private native boolean getPlayStatusRspNative(int playStatus, int songLen, int songPos);
- private native boolean getElementAttrRspNative(byte numAttr, int[] attrIds, String[] textArray);
+ 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 item_type, int numItems, 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);
}
--- /dev/null
+/*
+ * 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;
+
+ /* constants to send in Track change response */
+ static final byte NO_TRACK_SELECTED = (byte)0xFF;
+ static final byte TRACK_IS_SELECTED = (byte)0x00;
+
+ /* Max 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;
+}
}
};
- private void handlePassthroughRsp(int id, int keyState) {
+ private void handlePassthroughRsp(int id, int keyState, byte[] address) {
Log.d(TAG, "passthrough response received as: key: " + id + " state: " + keyState);
}
--- /dev/null
+/*
+ * 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.media.session.MediaController;
+import android.media.session.MediaSession;
+
+import java.util.List;
+import java.util.Arrays;
+
+/*************************************************************************************************
+ * 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 {
+
+ public AvrcpCmd() {}
+
+ /* Helper classes to pass parameters from callbacks to Avrcp handler */
+ class FolderItemsCmd {
+ byte mScope;
+ int mStartItem;
+ int mEndItem;
+ byte mNumAttr;
+ int[] mAttrIDs;
+ public byte[] mAddress;
+
+ public FolderItemsCmd(byte[] address,byte scope, int startItem, int endItem, byte numAttr,
+ int[] attrIds) {
+ mAddress = address;
+ this.mScope = scope;
+ this.mStartItem = startItem;
+ this.mEndItem = endItem;
+ this.mNumAttr = numAttr;
+ this.mAttrIDs = attrIds;
+ }
+ }
+
+ class ItemAttrCmd {
+ byte mScope;
+ byte[] mUid;
+ int mUidCounter;
+ byte mNumAttr;
+ int[] mAttrIDs;
+ public byte[] mAddress;
+
+ public 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;
+ }
+ }
+
+ class ElementAttrCmd {
+ byte mNumAttr;
+ int[] mAttrIDs;
+ public byte[] mAddress;
+
+ public 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 itemType;
+ byte[] mPlayerTypes;
+ int[] mPlayerSubTypes;
+ byte[] mPlayStatusValues;
+ short[] mFeatureBitMaskValues;
+ String[] mPlayerNameList, mPackageNameList;
+ List<MediaController> mControllersList;
+ int mNumItems;
+
+ public MediaPlayerListRsp(byte status, short UIDCounter, int numItems, byte itemType,
+ byte[] playerTypes, int[] playerSubTypes, byte[] playStatusValues,
+ short[] featureBitMaskValues, String[] playerNameList, String packageNameList[],
+ List<MediaController> mediaControllerList) {
+ this.mStatus = status;
+ this.mUIDCounter = UIDCounter;
+ this.mNumItems = numItems;
+ this.itemType = itemType;
+ 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;
+ this.mPackageNameList = packageNameList;
+ this.mControllersList = mediaControllerList;
+ }
+}
+
+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;
+
+ public 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;
+
+ public ItemAttrRsp(byte status, byte numAttr, int[] attributesIds, String[] attributesArray) {
+ this.mStatus = status;
+ this.mNumAttr = numAttr;
+ this.mAttributesIds = attributesIds;
+ this.mAttributesArray = attributesArray;
+ }
+}
+
+/* Helps managing the NowPlayingList */
+class NowPlayingListManager {
+ private List<MediaSession.QueueItem> mNowPlayingItems = null;
+
+ synchronized void setNowPlayingList(List<MediaSession.QueueItem> queue) {
+ mNowPlayingItems = queue;
+ }
+
+ synchronized List<MediaSession.QueueItem> getNowPlayingList() {
+ return mNowPlayingItems;
+ }
+}
+
+/* stores information of Media Players in the system */
+class MediaPlayerInfo {
+
+ private String packageName;
+ private byte majorType;
+ private int subType;
+ private byte playStatus;
+ private short[] featureBitMask;
+ private String displayableName;
+ private MediaController mediaController;
+
+ MediaPlayerInfo(String packageName, byte majorType, int subType, byte playStatus,
+ short[] featureBitMask, String displayableName, MediaController mediaController) {
+ this.setPackageName(packageName);
+ this.setMajorType(majorType);
+ this.setSubType(subType);
+ this.playStatus = playStatus;
+
+ // copying the FeatureBitMask array
+ this.setFeatureBitMask(new short[featureBitMask.length]);
+ for (int count = 0; count < featureBitMask.length; count++) {
+ this.getFeatureBitMask()[count] = featureBitMask[count];
+ }
+
+ this.setDisplayableName(displayableName);
+ this.setMediaController(mediaController);
+ }
+
+ /* getters and setters */
+ byte getPlayStatus() {
+ return playStatus;
+ }
+
+ void setPlayStatus(byte playStatus) {
+ this.playStatus = playStatus;
+ }
+
+ MediaController getMediaController() {
+ return mediaController;
+ }
+
+ void setMediaController(MediaController mediaController) {
+ this.mediaController = mediaController;
+ }
+
+ String getPackageName() {
+ return packageName;
+ }
+
+ void setPackageName(String packageName) {
+ this.packageName = packageName;
+ }
+
+ byte getMajorType() {
+ return majorType;
+ }
+
+ void setMajorType(byte majorType) {
+ this.majorType = majorType;
+ }
+
+ int getSubType() {
+ return subType;
+ }
+
+ void setSubType(int subType) {
+ this.subType = subType;
+ }
+
+ String getDisplayableName() {
+ return displayableName;
+ }
+
+ void setDisplayableName(String displayableName) {
+ this.displayableName = displayableName;
+ }
+
+ short[] getFeatureBitMask() {
+ return featureBitMask;
+ }
+
+ void setFeatureBitMask(short[] featureBitMask) {
+ this.featureBitMask = featureBitMask;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("\n+++ MediaPlayerInfo: +++");
+ sb.append("\nPlayer Package Name = " + getPackageName());
+ sb.append("\nMajor Player Type = " + getMajorType());
+ sb.append("\nPlayer SubType = " + getSubType());
+ sb.append("\nPlay Status = " + playStatus);
+ sb.append("\nFeatureBitMask:\n ");
+ for (int count = 0; count < getFeatureBitMask().length; count++) {
+ sb.append("\nFeature BitMask[" + count + "] = " + getFeatureBitMask()[count]);
+ }
+ sb.append("\nDisplayable Name = " + getDisplayableName());
+ sb.append("\nMedia Controller = " + getMediaController().toString());
+
+ return sb.toString();
+ }
+}
+
+/* stores information for browsable Media Players available in the system */
+class BrowsePlayerInfo {
+ String packageName;
+ String displayableName;
+ String serviceClass;
+
+ public 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("\n+++ BrowsePlayerInfo: +++");
+ sb.append("\nPackage Name = " + packageName);
+ sb.append("\nDisplayable Name = " + displayableName);
+ sb.append("\nService Class = " + 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 attrCounter;
+
+ public 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 */
+ }
+}
--- /dev/null
+/*
+ * 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 {
+ public void setAddrPlayerRsp(byte[] address, int rspStatus);
+
+ public void setBrowsedPlayerRsp(byte[] address, int rspStatus, byte depth, int numItems,
+ String[] textArray);
+
+ public void mediaPlayerListRsp(byte[] address, int rspStatus, MediaPlayerListRsp rspObj);
+
+ public void folderItemsRsp(byte[] address, int rspStatus, FolderItemsRsp rspObj);
+
+ public void changePathRsp(byte[] address, int rspStatus, int numItems);
+
+ public void getItemAttrRsp(byte[] address, int rspStatus, ItemAttrRsp rspObj);
+
+ public void playItemRsp(byte[] address, int rspStatus);
+
+ public void getTotalNumOfItemsRsp(byte[] address, int rspStatus, int uidCounter,
+ int numItems);
+
+ public void addrPlayerChangedRsp(byte[] address, int type, int playerId, int uidCounter);
+
+ public void avalPlayerChangedRsp(byte[] address, int type);
+
+ public void uidsChangedRsp(byte[] address, int type, int uidCounter);
+
+ public void nowPlayingChangedRsp(int type);
+
+ public void trackChangedRsp(int type, byte[] uid);
+}
+
--- /dev/null
+/*
+ * 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.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.MediaController;
+import android.media.session.MediaSession;
+import android.media.session.MediaSession.QueueItem;
+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 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;
+
+ private AvrcpCmd.ItemAttrCmd mItemAttrReqObj;
+
+ /* store result of getfolderitems with scope="vfs" */
+ private List<MediaBrowser.MediaItem> mFolderItems = null;
+
+ /* Connection state callback handler */
+ private MediaBrowser.ConnectionCallback browseMediaConnectionCallback =
+ new MediaBrowser.ConnectionCallback() {
+
+ @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();
+ }
+
+ @Override
+ public void onConnectionFailed() {
+ mConnState = DISCONNECTED;
+ 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() {
+ mConnState = SUSPENDED;
+ Log.e(TAG, "mediaBrowser SUSPENDED connection with " + mPackageName);
+ }
+ };
+
+ /* Subscription callback handler. Subscribe to a folder to get its contents */
+ private MediaBrowser.SubscriptionCallback folderItemsCb =
+ 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)0x01, 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 MediaBrowser.SubscriptionCallback itemAttrCb =
+ new MediaBrowser.SubscriptionCallback() {
+ @Override
+ public void onChildrenLoaded(String parentId, List<MediaBrowser.MediaItem> children) {
+ if (DEBUG) Log.d(TAG, "itemAttrCb OnChildren Loaded");
+ int status = AvrcpConstants.RSP_NO_ERROR;
+
+ if (children != null) {
+ boolean isChildrenFound = false;
+ /* some players may return all items in folder containing requested media item */
+ for (int itemIndex = 0; itemIndex < children.size(); itemIndex++) {
+ if (children.get(itemIndex).getMediaId().equals(parentId)) {
+ if (DEBUG) Log.d(TAG, "found an item " + itemIndex +
+ children.get(itemIndex).getMediaId());
+ getItemAttrFilterAttr(children.get(itemIndex));
+ isChildrenFound = true;
+ break;
+ }
+ }
+
+ if (!isChildrenFound) {
+ Log.e(TAG, "not able to find the item:" + parentId);
+ status = AvrcpConstants.RSP_INV_ITEM;
+ }
+ } else {
+ Log.e(TAG, "children list is null for parent id:" + parentId);
+ status = AvrcpConstants.RSP_INV_ITEM;
+ }
+ // send only error from here, in case of success it will sent the attributes from getItemAttrFilterAttr
+ if (status != AvrcpConstants.RSP_NO_ERROR) {
+ /* send invalid uid rsp to remote device */
+ mMediaInterface.getItemAttrRsp(mBDAddr, status, null);
+ }
+ }
+ @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);
+ }
+ };
+
+ /* Constructor */
+ public 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() {
+ boolean isError = false;
+ MediaSession.Token token = null;
+ try {
+ /* 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);
+ }
+
+ if (!mMediaBrowser.isConnected()) {
+ isError = true;
+ Log.e(TAG, "setBrowsedPlayer : Not connected");
+ }
+
+ if ((token = mMediaBrowser.getSessionToken()) == null) {
+ isError = true;
+ Log.e(TAG, "setBrowsedPlayer : No Session token");
+ }
+
+ if (isError == false) {
+ mMediaController = new MediaController(mContext, token);
+ /* get root folder items */
+ mMediaBrowser.subscribe(mRootFolderUid, folderItemsCb);
+ }
+ } catch (NullPointerException ex) {
+ isError = true;
+ Log.e(TAG, "setBrowsedPlayer : Null pointer during init");
+ ex.printStackTrace();
+ }
+
+ if (isError) {
+ mMediaInterface.setBrowsedPlayerRsp(mBDAddr, AvrcpConstants.RSP_INTERNAL_ERR,
+ (byte)0x00, 0, null);
+ }
+ }
+
+ public void setBrowsed(String packageName, String cls) {
+ mPackageName = 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 */
+ mMediaBrowser = new MediaBrowser(mContext, new ComponentName(mPackageName, mClassName),
+ browseMediaConnectionCallback, null);
+ connectToPlayer();
+ }
+
+ /* called when connection to media player is closed */
+ public void cleanup() {
+ if (DEBUG)
+ Log.d(TAG, "cleanup");
+ if (mConnState != DISCONNECTED) {
+ disconnectFromPlayer();
+ }
+
+ mHmap = null;
+ mMediaController = null;
+ mMediaBrowser = null;
+ }
+
+ private void connectToPlayer() {
+ if (DEBUG) Log.d(TAG, "connectToPlayer");
+ mMediaBrowser.connect();
+ }
+
+ public void disconnectFromPlayer() {
+ if (DEBUG) Log.d(TAG, "disconnectFromPlayer");
+ mMediaBrowser.disconnect();
+ }
+
+ public boolean isPlayerConnected() {
+ if (mMediaBrowser != null) {
+ return mMediaBrowser.isConnected();
+ } else {
+ if (DEBUG) Log.d(TAG, "isPlayerConnected: mMediaBrowser = null!");
+ return false;
+ }
+ }
+
+ /* 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() == false) {
+ 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, "mediaController 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) == false) {
+ /* 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) == true) {
+ /* 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, folderItemsCb);
+ /* 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() == false) {
+ /* 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, folderItemsCb);
+ }
+ } 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");
+
+ /*
+ * store request parameters for reference. To be used to filter
+ * attributes when sending response
+ */
+ mItemAttrReqObj = itemAttr;
+
+ /* check if uid is valid by doing a lookup in hashmap */
+ if ((mediaID = byteToString(itemAttr.mUid)) == 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) {
+ if (mMediaBrowser != null) {
+ mMediaBrowser.subscribe(mediaID, itemAttrCb);
+ } else {
+ Log.e(TAG, "mMediaBrowser is null");
+ mMediaInterface.getItemAttrRsp(mBDAddr, AvrcpConstants.RSP_INTERNAL_ERR, null);
+ }
+ } else {
+ Log.e(TAG, "invalid scope");
+ mMediaInterface.getItemAttrRsp(mBDAddr, AvrcpConstants.RSP_INV_SCOPE, null);
+ }
+ }
+
+ public void getTotalNumOfItems(byte scope) {
+ if (DEBUG) Log.d(TAG, "getTotalNumOfItems scope = " + scope);
+ switch (scope) {
+ case AvrcpConstants.BTRC_SCOPE_FILE_SYSTEM:
+ if (mFolderItems != null) {
+ /* find num items using size of already cached folder items */
+ mMediaInterface.getTotalNumOfItemsRsp(mBDAddr,
+ AvrcpConstants.RSP_NO_ERROR, 0, mFolderItems.size());
+ } else {
+ 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);
+ }
+ break;
+ default:
+ Log.e(TAG, "getTotalNumOfItems error" + scope);
+ mMediaInterface.getTotalNumOfItemsRsp(mBDAddr, AvrcpConstants.RSP_INV_SCOPE, 0, 0);
+ break;
+ }
+ }
+
+ public void getFolderItemsVFS(AvrcpCmd.FolderItemsCmd reqObj) {
+ if (isPlayerConnected()) {
+ 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);
+ } else {
+ /* 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);
+ }
+ } else {
+ 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);
+ }
+ }
+
+ /* 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, int startItem, int endItem) {
+ try {
+ List<MediaBrowser.MediaItem> childrenSubList =
+ children.subList(startItem, Math.min(children.size(), 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));
+ mMediaInterface.folderItemsRsp(bdaddr, AvrcpConstants.RSP_INV_RANGE, null);
+ return null;
+ } catch (IllegalArgumentException ex) {
+ Log.i(TAG, "Index out of bounds start item =" + startItem + " > size");
+ mMediaInterface.folderItemsRsp(bdaddr, AvrcpConstants.RSP_INV_RANGE, null);
+ 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, int startItem, int endItem) {
+ if (DEBUG) Log.d(TAG, "getFolderItemsFilterAttr: startItem =" + startItem +
+ ", endItem = " + endItem);
+
+ List<MediaBrowser.MediaItem> result_items = new ArrayList<MediaBrowser.MediaItem>();
+
+ if (children != null) {
+ /* check for index out of bound errors */
+ if ((result_items = checkIndexOutofBounds(bdaddr, children, startItem, endItem)) == null) {
+ Log.w(TAG, "result_items is null.");
+ mMediaInterface.folderItemsRsp(bdaddr, AvrcpConstants.RSP_INV_RANGE, null);
+ return;
+ }
+ FolderItemsData folderDataNative = new FolderItemsData(result_items.size());
+
+ /* variables to temperorily add attrs */
+ ArrayList<String> attrArray = new ArrayList<String>();
+ ArrayList<Integer> attrId = new ArrayList<Integer>();
+
+ for (int itemIndex = 0; itemIndex < result_items.size(); itemIndex++) {
+ /* item type. Needs to be set by media player */
+ if ((result_items.get(itemIndex).getFlags() &
+ MediaBrowser.MediaItem.FLAG_BROWSABLE) != 0) {
+ folderDataNative.mItemTypes[itemIndex] = AvrcpConstants.BTRC_ITEM_FOLDER;
+ } else {
+ folderDataNative.mItemTypes[itemIndex] = AvrcpConstants.BTRC_ITEM_MEDIA;
+ }
+
+ /* set playable */
+ if ((result_items.get(itemIndex).getFlags()
+ & 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(result_items.get(itemIndex).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] =
+ result_items.get(itemIndex).getDescription().getTitle().toString();
+
+ 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];
+ if(attribId >= AvrcpConstants.ATTRID_TITLE &&
+ attribId <= AvrcpConstants.ATTRID_PLAY_TIME) {
+ if ((value = getAttrValue(attribId, result_items,
+ itemIndex)) != null) {
+ attrArray.add(value);
+ attrId.add(attribId);
+ attrCnt++;
+ }
+ } else {
+ Log.d(TAG, "invalid attributed id is requested: " + attribId);
+ }
+ }
+ /* 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 (mFolderItemsReqObj.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()]);
+ }
+
+ /* 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);
+ } else {
+ Log.e(TAG, "Error: children are null in getFolderItemsFilterAttr");
+ mMediaInterface.folderItemsRsp(bdaddr, AvrcpConstants.RSP_INV_RANGE, null);
+ return;
+ }
+ }
+
+ public static String getAttrValue(int attr, List<MediaBrowser.MediaItem> resultItems,
+ int itemIndex) {
+
+ String attrValue = null;
+ try {
+ switch (attr) {
+ /* Title is mandatory attribute */
+ case AvrcpConstants.ATTRID_TITLE:
+ attrValue = resultItems.get(itemIndex).getDescription().getTitle().toString();
+ break;
+ case AvrcpConstants.ATTRID_ARTIST:
+ attrValue = resultItems.get(itemIndex).getDescription().getExtras()
+ .getString(MediaMetadata.METADATA_KEY_ARTIST);
+ break;
+
+ case AvrcpConstants.ATTRID_ALBUM:
+ attrValue = resultItems.get(itemIndex).getDescription().getExtras()
+ .getString(MediaMetadata.METADATA_KEY_ALBUM);
+ break;
+
+ case AvrcpConstants.ATTRID_TRACK_NUM:
+ attrValue = resultItems.get(itemIndex).getDescription().getExtras()
+ .getString(MediaMetadata.METADATA_KEY_TRACK_NUMBER);
+ break;
+
+ case AvrcpConstants.ATTRID_NUM_TRACKS:
+ attrValue = resultItems.get(itemIndex).getDescription().getExtras()
+ .getString(MediaMetadata.METADATA_KEY_NUM_TRACKS);
+ break;
+
+ case AvrcpConstants.ATTRID_GENRE:
+ attrValue = resultItems.get(itemIndex).getDescription().getExtras()
+ .getString(MediaMetadata.METADATA_KEY_GENRE);
+
+ case AvrcpConstants.ATTRID_PLAY_TIME:
+ attrValue = resultItems.get(itemIndex).getDescription().getExtras()
+ .getString(MediaMetadata.METADATA_KEY_DURATION);
+ break;
+
+ default:
+ Log.e(TAG, "Unknown attribute ID");
+ }
+ } catch (IndexOutOfBoundsException ex) {
+ Log.w(TAG, "getAttrValue: requested item index out of bounds");
+ 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) {
+ return "<Unknown Title>";
+ }
+ return null;
+ }
+ if(DEBUG) Log.d(TAG, "getAttrValue: attrvalue = "+ attrValue + "attr id:" + attr);
+ return attrValue;
+ }
+
+ /* helper method to filter required attibutes before sending getItemAttrdg response */
+ private void getItemAttrFilterAttr(MediaBrowser.MediaItem mediaItem) {
+ /* Response parameters */
+ int[] attrIds = null; /* array of attr ids */
+ String[] attrValues = null; /* array of attr values */
+ int attrCounter = 0; /* num attributes for each item */
+ List<MediaBrowser.MediaItem> resultItems = new ArrayList<MediaBrowser.MediaItem>();
+ resultItems.add(mediaItem);
+ /* variables to temperorily add attrs */
+ ArrayList<String> attrArray = new ArrayList<String>();
+ ArrayList<Integer> attrId = new ArrayList<Integer>();
+
+ if (mediaItem == null) {
+ Log.e(TAG, "getItemAttrFilterAttr: media item is null");
+ mMediaInterface.getItemAttrRsp(mBDAddr, AvrcpConstants.RSP_INTERNAL_ERR, null);
+ return;
+ }
+
+ 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 ||
+ mItemAttrReqObj.mNumAttr == AvrcpConstants.MAX_NUM_ATTR) {
+ 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++) {
+ attrTempId.add(mItemAttrReqObj.mAttrIDs[idx]);
+ }
+ }
+
+ /* 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 = null;
+ if ((value = getAttrValue(attrTempId.get(idx), resultItems, 0)) != null) {
+ attrArray.add(value);
+ attrId.add(attrTempId.get(idx));
+ attrCounter++;
+ }
+ }
+ attrTempId = null;
+ } else {
+ Log.i(TAG, "getItemAttrFilterAttr: No attributes requested");
+ }
+
+ /* copy filtered attr ids and attr values to response parameters */
+ if (this.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,
+ (byte)attrCounter, attrIds, attrValues);
+ mMediaInterface.getItemAttrRsp(mBDAddr, AvrcpConstants.RSP_NO_ERROR, rspObj);
+ }
+ }
+
+ 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 key = new BigInteger(byteArray).intValue();
+ String value = mHmap.get(key);
+ return value;
+ }
+
+ /* convert mediaId to uid */
+ private byte[] stringToByte(String mediaId) {
+ /* check if this mediaId already exists in hashmap */
+ if (!mHmap.containsValue(mediaId)) { /* add to hashmap */
+ int key = mHmap.size();
+ mHmap.put(key, mediaId);
+ return intToByteArray(key);
+ } else { /* search key for give mediaId */
+ for (int key : mHmap.keySet()) {
+ if (mHmap.get(key).equals(mediaId)) {
+ return intToByteArray(key);
+ }
+ }
+ }
+ 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;
+ }
+}