From e90c830b72f97189935e99e9e4f5a0c4f216ba72 Mon Sep 17 00:00:00 2001 From: Avish Shah Date: Wed, 22 Jun 2016 06:53:35 +0530 Subject: [PATCH] AVRCP 1.6: media browsing support on Target(3/3) Description:- This patch provides Media browsing support for AVRCP using new Media Browsing APIs provided from Android 5.0 Features:- 1) setAddressedPlayer - Change the control to the specified player 2) setBrowsedPlayer - Change the player with which fileSystem is being browsed Browsing commands: 3) getFolderItems scope=MediaPlayers/VFS/NowPlaying 4) changePath 5) getItemAttributes 6) playItem 7) getTotalNumberOfItems Handling notifications:- 1) ADDRESSED_PLAYER_CHANGED 2) AVAILABLE_PLAYERS_CHANGED 3) NOW_PLAYING_CONTENT_CHANGED Added support for dual RC New files added:- AvrcpHelperClasses.java - Helper classes used for callback/response of browsing commands. AvrcpConstants.java - Group all the constants used in Avrcp. AddressedMediaPlayer.java - Interface to communicate with media controller for NowPlayingItems. BrowseMediaPlayer.java - Interface to communicate with MediaPlayer for browsing FileSystem. AvrcpMediaRspInterface.java - Interface to communicate with the native layer. Verification:- AVRCP 1.5/1.6 functionality has been verified with google MediaBrowserService sample app. Bug: 19361366 Merged-In: I0ab7f0c7d87c06fe4f454151d20494c56aceae12 Change-Id: I40b9e7aae81d994a709559844928b0749c603ea8 --- jni/com_android_bluetooth_avrcp.cpp | 1392 +++++++++++++- jni/com_android_bluetooth_avrcp_controller.cpp | 27 +- .../bluetooth/avrcp/AddressedMediaPlayer.java | 601 ++++++ src/com/android/bluetooth/avrcp/Avrcp.java | 1989 +++++++++++++++++--- .../android/bluetooth/avrcp/AvrcpConstants.java | 146 ++ .../bluetooth/avrcp/AvrcpControllerService.java | 2 +- .../bluetooth/avrcp/AvrcpHelperClasses.java | 341 ++++ .../bluetooth/avrcp/AvrcpMediaRspInterface.java | 54 + .../bluetooth/avrcp/BrowsedMediaPlayer.java | 799 ++++++++ 9 files changed, 4972 insertions(+), 379 deletions(-) create mode 100644 src/com/android/bluetooth/avrcp/AddressedMediaPlayer.java mode change 100755 => 100644 src/com/android/bluetooth/avrcp/Avrcp.java create mode 100644 src/com/android/bluetooth/avrcp/AvrcpConstants.java create mode 100644 src/com/android/bluetooth/avrcp/AvrcpHelperClasses.java create mode 100644 src/com/android/bluetooth/avrcp/AvrcpMediaRspInterface.java create mode 100644 src/com/android/bluetooth/avrcp/BrowsedMediaPlayer.java diff --git a/jni/com_android_bluetooth_avrcp.cpp b/jni/com_android_bluetooth_avrcp.cpp index ba1d78bb..7fda163d 100644 --- a/jni/com_android_bluetooth_avrcp.cpp +++ b/jni/com_android_bluetooth_avrcp.cpp @@ -1,5 +1,5 @@ /* - * 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. @@ -25,6 +25,15 @@ #include +#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; @@ -32,11 +41,29 @@ static jmethodID method_getElementAttr; static jmethodID method_registerNotification; static jmethodID method_volumeChangeCallback; static jmethodID method_handlePassthroughCmd; +static jmethodID method_getFolderItemsCallback; +static jmethodID method_setAddressedPlayerCallback; + +static jmethodID method_setBrowsedPlayerCallback; +static jmethodID method_changePathCallback; +static jmethodID method_searchCallback; +static jmethodID method_playItemCallback; +static jmethodID method_getItemAttrCallback; +static jmethodID method_addToPlayListCallback; +static jmethodID method_getTotalNumOfItemsCallback; static const btrc_interface_t *sBluetoothAvrcpInterface = NULL; static jobject mCallbacksObj = NULL; 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 @@ -49,18 +76,16 @@ static bool checkCallbackThread() { 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; } @@ -68,101 +93,469 @@ static void btavrcp_remote_features_callback(bt_bdaddr_t* bd_addr, btrc_remote_f 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, @@ -177,27 +570,63 @@ static btrc_callbacks_t sBluetoothAvrcpCallbacks = { 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) { @@ -256,40 +685,64 @@ static void cleanupNative(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; } @@ -297,43 +750,105 @@ static jboolean getPlayStatusRspNative(JNIEnv *env, jobject object, jint playSta 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; } @@ -342,8 +857,10 @@ static jboolean registerNotificationRspPlayStatusNative(JNIEnv *env, jobject obj 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, @@ -359,10 +876,12 @@ static jboolean registerNotificationRspTrackChangeNative(JNIEnv *env, jobject ob 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) { @@ -370,8 +889,8 @@ static jboolean registerNotificationRspTrackChangeNative(JNIEnv *env, jobject ob 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, @@ -388,7 +907,10 @@ static jboolean registerNotificationRspPlayPosNative(JNIEnv *env, jobject object 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, @@ -399,14 +921,96 @@ static jboolean registerNotificationRspPlayPosNative(JNIEnv *env, jobject object 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); @@ -415,12 +1019,484 @@ static jboolean setVolumeNative(JNIEnv *env, jobject object, jint volume) { 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", @@ -429,6 +1505,49 @@ static JNINativeMethod sMethods[] = { (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) @@ -437,4 +1556,75 @@ 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; + } +} + } diff --git a/jni/com_android_bluetooth_avrcp_controller.cpp b/jni/com_android_bluetooth_avrcp_controller.cpp index 2c07a57f..c28b15a7 100644 --- a/jni/com_android_bluetooth_avrcp_controller.cpp +++ b/jni/com_android_bluetooth_avrcp_controller.cpp @@ -1,5 +1,5 @@ /* - * 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. @@ -56,18 +56,31 @@ static bool checkCallbackThread() { 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) { @@ -429,7 +442,7 @@ static btrc_ctrl_callbacks_t sBluetoothAvrcpCallbacks = { 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"); diff --git a/src/com/android/bluetooth/avrcp/AddressedMediaPlayer.java b/src/com/android/bluetooth/avrcp/AddressedMediaPlayer.java new file mode 100644 index 00000000..bb3136a2 --- /dev/null +++ b/src/com/android/bluetooth/avrcp/AddressedMediaPlayer.java @@ -0,0 +1,601 @@ +/* + * 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 tempItems; + List 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 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 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 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 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 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 checkIndexOutofBounds(byte[] bdaddr, + List children, int startItem, int endItem) { + try { + List 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 children, byte scope, int startItem, int endItem) { + if (DEBUG) Log.d(TAG, "getFolderItemsFilterAttr: startItem =" + startItem + ", endItem = " + + endItem); + + List result_items = new ArrayList(); + + 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 attrArray = new ArrayList(); + ArrayList attrId = new ArrayList(); + + 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 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 ""; + } + 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 resultItems = new ArrayList(); + resultItems.add(mediaItem); + /* variables to temperorily add attrs */ + ArrayList attrArray = new ArrayList(); + ArrayList attrId = new ArrayList(); + + ArrayList attrTempId = new ArrayList(); + + /* 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 + ""); + } + +} diff --git a/src/com/android/bluetooth/avrcp/Avrcp.java b/src/com/android/bluetooth/avrcp/Avrcp.java old mode 100755 new mode 100644 index 308dcdc3..8952b790 --- a/src/com/android/bluetooth/avrcp/Avrcp.java +++ b/src/com/android/bluetooth/avrcp/Avrcp.java @@ -1,5 +1,5 @@ /* - * 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. @@ -18,15 +18,27 @@ package com.android.bluetooth.avrcp; 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; @@ -35,18 +47,21 @@ import android.os.SystemClock; 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"; @@ -56,10 +71,10 @@ public final class 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; @@ -93,6 +108,13 @@ public final class Avrcp { 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; @@ -108,24 +130,33 @@ public final class Avrcp { 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; @@ -134,6 +165,24 @@ public final class Avrcp { 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 mMediaPlayerInfoList; + + /* List of media players which supports browse */ + private ArrayList mBrowsePlayerInfoList; + + /* Manage browsed players */ + private AvrcpBrowseManager mAvrcpBrowseManager; + + /* Broadcast receiver for device connections intent broadcasts */ + private final BroadcastReceiver mAvrcpReceiver = new AvrcpServiceBroadcastReceiver(); + static { classInitNative(); } @@ -141,13 +190,13 @@ public final class Avrcp { 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; @@ -163,19 +212,34 @@ public final class Avrcp { mLastLocalVolume = -1; mAbsVolThreshold = 0; mVolumeMapping = new HashMap(); - + 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() { @@ -183,11 +247,23 @@ public final class Avrcp { 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(); + mBrowsePlayerInfoList = new ArrayList(); + 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) { @@ -198,15 +274,26 @@ public final class Avrcp { } 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(); @@ -220,53 +307,48 @@ public final class Avrcp { } @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 controllers) { - Log.v(TAG, "Active sessions changed, " + controllers.size() + " sessions"); - updateCurrentMediaControllers(controllers); - } - } - - private void updateCurrentMediaControllers(List controllers) { - MediaController controller = null; - for (MediaController c : controllers) { - controller = c; - if (c.getMetadata() != null) - break; // We found a suitable controller - } + public void onQueueChanged(List 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. */ @@ -277,11 +359,14 @@ public final class Avrcp { @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()); @@ -293,50 +378,54 @@ public final class Avrcp { 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 attrList = (ArrayList) 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) { @@ -344,7 +433,7 @@ public final class Avrcp { Log.e(TAG, "Unsolicited response, ignored"); break; } - removeMessages(MESSAGE_ABS_VOL_TIMEOUT); + removeMessages(MSG_ABS_VOL_TIMEOUT); volAdj = mVolCmdAdjustInProgress; mVolCmdAdjustInProgress = false; @@ -352,14 +441,14 @@ public final class Avrcp { 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; @@ -377,28 +466,27 @@ public final class Avrcp { /* 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."); @@ -406,20 +494,19 @@ public final class Avrcp { 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."); @@ -467,13 +554,12 @@ public final class Avrcp { 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; @@ -482,17 +568,17 @@ public final class Avrcp { 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."); @@ -510,7 +596,7 @@ public final class Avrcp { 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; @@ -519,8 +605,8 @@ public final class Avrcp { } 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) { @@ -530,19 +616,18 @@ public final class Avrcp { } 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); @@ -551,7 +636,7 @@ public final class Avrcp { } } 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); @@ -560,51 +645,133 @@ public final class Avrcp { } 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; } } } @@ -618,10 +785,10 @@ public final class Avrcp { 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()); } @@ -630,7 +797,7 @@ public final class Avrcp { 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); @@ -638,8 +805,8 @@ public final class Avrcp { if (DEBUG) { Log.v(TAG, "updatePlaybackState (" + mPlayStatusChangedNT + "): "+ - "old=" + mCurrentPlayState + "(" + oldPlayStatus + "), "+ - "new=" + state + "(" + newPlayStatus + ")"); + "old=" + mCurrentPlayState + "(" + oldPlayStatus + "), "+ + "new=" + state + "(" + newPlayStatus + ")"); } mCurrentPlayState = state; @@ -647,9 +814,9 @@ public final class Avrcp { 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); } } @@ -757,12 +924,13 @@ public final class Avrcp { } public String toString() { - if (!exists) + if (!exists) { return "[MediaAttributes: none]"; + } return "[MediaAttributes: " + title + " - " + albumName + " by " - + artistName + " (" + mediaNumber + "/" + mediaTotalNumber + ") " - + genre + "]"; + + artistName + " (" + mediaNumber + "/" + mediaTotalNumber + ") " + + genre + "]"; } } @@ -774,12 +942,13 @@ public final class Avrcp { } 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 { @@ -789,79 +958,125 @@ public final class Avrcp { // 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 attrList = new ArrayList(); - 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); } @@ -879,30 +1094,40 @@ public final class Avrcp { } 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(); @@ -949,7 +1174,7 @@ public final class Avrcp { private boolean isPlayingState(PlaybackState state) { return (state.getState() == PlaybackState.STATE_PLAYING) || - (state.getState() == PlaybackState.STATE_BUFFERING); + (state.getState() == PlaybackState.STATE_BUFFERING); } /** @@ -959,7 +1184,7 @@ public final class Avrcp { * 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; } @@ -972,11 +1197,11 @@ public final class Avrcp { // 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; @@ -987,9 +1212,9 @@ public final class Avrcp { } } - 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); @@ -1012,7 +1237,7 @@ public final class Avrcp { * 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); } @@ -1022,8 +1247,8 @@ public final class Avrcp { 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); } @@ -1034,8 +1259,93 @@ public final class Avrcp { * 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); } @@ -1088,85 +1398,1224 @@ public final class Avrcp { * 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 browsePlayersList = new ArrayList(); + Intent intent = new Intent("android.media.browse.MediaBrowserService"); + List 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 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 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 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 mediaControllerList, + List 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 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 getActiveControllersList() { + List controllersList = new ArrayList(); + 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 featureBitsList = new ArrayList(); + + /* 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 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 mediaControllerList = new ArrayList(); + + 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 connList = new HashMap(); + 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 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); } diff --git a/src/com/android/bluetooth/avrcp/AvrcpConstants.java b/src/com/android/bluetooth/avrcp/AvrcpConstants.java new file mode 100644 index 00000000..e0a5ff43 --- /dev/null +++ b/src/com/android/bluetooth/avrcp/AvrcpConstants.java @@ -0,0 +1,146 @@ +/* + * 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; +} diff --git a/src/com/android/bluetooth/avrcp/AvrcpControllerService.java b/src/com/android/bluetooth/avrcp/AvrcpControllerService.java index c42d4cff..66751247 100644 --- a/src/com/android/bluetooth/avrcp/AvrcpControllerService.java +++ b/src/com/android/bluetooth/avrcp/AvrcpControllerService.java @@ -804,7 +804,7 @@ public class AvrcpControllerService extends ProfileService { } }; - 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); } diff --git a/src/com/android/bluetooth/avrcp/AvrcpHelperClasses.java b/src/com/android/bluetooth/avrcp/AvrcpHelperClasses.java new file mode 100644 index 00000000..9855a272 --- /dev/null +++ b/src/com/android/bluetooth/avrcp/AvrcpHelperClasses.java @@ -0,0 +1,341 @@ +/* + * 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 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 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 mNowPlayingItems = null; + + synchronized void setNowPlayingList(List queue) { + mNowPlayingItems = queue; + } + + synchronized List 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 */ + } +} diff --git a/src/com/android/bluetooth/avrcp/AvrcpMediaRspInterface.java b/src/com/android/bluetooth/avrcp/AvrcpMediaRspInterface.java new file mode 100644 index 00000000..9c40a96f --- /dev/null +++ b/src/com/android/bluetooth/avrcp/AvrcpMediaRspInterface.java @@ -0,0 +1,54 @@ +/* + * 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); +} + diff --git a/src/com/android/bluetooth/avrcp/BrowsedMediaPlayer.java b/src/com/android/bluetooth/avrcp/BrowsedMediaPlayer.java new file mode 100644 index 00000000..ab19c838 --- /dev/null +++ b/src/com/android/bluetooth/avrcp/BrowsedMediaPlayer.java @@ -0,0 +1,799 @@ +/* + * 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 mPathStack = null; + + /* Number of items in current folder */ + private int mCurrFolderNumItems = 0; + + /* store mapping between uid(Avrcp) and mediaId(Media Player). */ + private HashMap mHmap = new HashMap(); + + /* command objects from avrcp handler */ + private AvrcpCmd.FolderItemsCmd mFolderItemsReqObj; + + private AvrcpCmd.ItemAttrCmd mItemAttrReqObj; + + /* store result of getfolderitems with scope="vfs" */ + private List 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 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 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(); + /* 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 checkIndexOutofBounds(byte[] bdaddr, + List children, int startItem, int endItem) { + try { + List 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 children, byte scope, int startItem, int endItem) { + if (DEBUG) Log.d(TAG, "getFolderItemsFilterAttr: startItem =" + startItem + + ", endItem = " + endItem); + + List result_items = new ArrayList(); + + 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 attrArray = new ArrayList(); + ArrayList attrId = new ArrayList(); + + 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 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 ""; + } + 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 resultItems = new ArrayList(); + resultItems.add(mediaItem); + /* variables to temperorily add attrs */ + ArrayList attrArray = new ArrayList(); + ArrayList attrId = new ArrayList(); + + if (mediaItem == null) { + Log.e(TAG, "getItemAttrFilterAttr: media item is null"); + mMediaInterface.getItemAttrRsp(mBDAddr, AvrcpConstants.RSP_INTERNAL_ERR, null); + return; + } + + ArrayList attrTempId = new ArrayList(); + + /* 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 queueItem2MediaItem( + List tempItems) { + + List tempMedia = new ArrayList(); + 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; + } +} -- 2.11.0