OSDN Git Service

AVRCP 1.6: media browsing support on Target(3/3)
authorAvish Shah <avish.shah@broadcom.com>
Wed, 22 Jun 2016 01:23:35 +0000 (06:53 +0530)
committerAjay Panicker <apanicke@google.com>
Fri, 2 Sep 2016 19:52:09 +0000 (19:52 +0000)
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
jni/com_android_bluetooth_avrcp_controller.cpp
src/com/android/bluetooth/avrcp/AddressedMediaPlayer.java [new file with mode: 0644]
src/com/android/bluetooth/avrcp/Avrcp.java [changed mode: 0755->0644]
src/com/android/bluetooth/avrcp/AvrcpConstants.java [new file with mode: 0644]
src/com/android/bluetooth/avrcp/AvrcpControllerService.java
src/com/android/bluetooth/avrcp/AvrcpHelperClasses.java [new file with mode: 0644]
src/com/android/bluetooth/avrcp/AvrcpMediaRspInterface.java [new file with mode: 0644]
src/com/android/bluetooth/avrcp/BrowsedMediaPlayer.java [new file with mode: 0644]

index ba1d78b..7fda163 100644 (file)
@@ -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.
 
 #include <string.h>
 
+#define CHECK_CALLBACK_ENV()\
+do { \
+   if (!checkCallbackThread()) { \
+       ALOGE("Callback: '%s' is not called on the correct thread", __func__); \
+       return; \
+   } \
+} while (0)
+
+
 namespace android {
 static jmethodID method_getRcFeatures;
 static jmethodID method_getPlayStatus;
@@ -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, &param)) != 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,&param)) != 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, &param)) != 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, &param)) != 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;
+    }
+}
+
 }
index 2c07a57..c28b15a 100644 (file)
@@ -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 (file)
index 0000000..bb3136a
--- /dev/null
@@ -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<QueueItem> tempItems;
+        List<MediaSession.QueueItem> mNowPlayingItems = mNowPlayingListManager.getNowPlayingList();
+        if (DEBUG) Log.v(TAG, "getFolderItemsNowPlaying");
+
+        if (mNowPlayingItems != null) {
+            // We already have the cached list sending the response to remote
+            if (DEBUG) Log.i(TAG, "sending cached now playing list");
+            /* Filter attributes from cached NowPlayingList and send response */
+            getFolderItemsFilterAttr(bdaddr, reqObj, mNowPlayingItems,
+                    AvrcpConstants.BTRC_SCOPE_FILE_SYSTEM, reqObj.mStartItem, reqObj.mEndItem);
+        } else if (mediaController == null) {
+            Log.e(TAG, "mediaController = null, sending internal error response");
+            mMediaInterface.folderItemsRsp(bdaddr, AvrcpConstants.RSP_INTERNAL_ERR, null);
+        } else {
+            // We don't have the cached list, fetching it from Media Controller
+            mNowPlayingItems = mediaController.getQueue();
+            if (mNowPlayingItems == null) {
+                Log.w(TAG, "Received Now playing list is null from: " +
+                        mediaController.getPackageName() + ", sending internal error response");
+                mMediaInterface.folderItemsRsp(bdaddr, AvrcpConstants.RSP_INTERNAL_ERR, null);
+            } else {
+                mNowPlayingListManager.setNowPlayingList(mNowPlayingItems);
+                getFolderItemsFilterAttr(bdaddr, reqObj, mNowPlayingItems,
+                        AvrcpConstants.BTRC_SCOPE_NOW_PLAYING, reqObj.mStartItem,
+                        reqObj.mEndItem);
+            }
+        }
+    }
+
+    /* get item attributes for item in now playing list */
+    void getItemAttr(byte[] bdaddr, AvrcpCmd.ItemAttrCmd itemAttr,
+            MediaController mediaController) {
+        int status = AvrcpConstants.RSP_NO_ERROR;
+        int idx;
+        long mediaID = ByteBuffer.wrap(itemAttr.mUid).getLong();
+        List<MediaSession.QueueItem> mNowPlayingItems = mNowPlayingListManager.getNowPlayingList();
+
+        /* checking if item attributes has been asked for now playing item or
+         * some other item with specific media id */
+        if (Arrays.equals(itemAttr.mUid, NOW_PLAYING_UID)) {
+            if (DEBUG) Log.d(TAG, "getItemAttr: Remote requests for now playing contents:");
+
+            // get the current playing song metadata and sending the queueitem.
+            if (mediaController != null) {
+                MediaMetadata metadata = mediaController.getMetadata();
+                if (metadata != null) {
+                    getItemAttrFilterAttr(bdaddr, itemAttr, getQueueItem(metadata));
+                } else {
+                    Log.e(TAG, "getItemAttr: metadata = null");
+                    status = AvrcpConstants.RSP_INTERNAL_ERR;
+                }
+            } else {
+                Log.e(TAG, "getItemAttr: mediaController = null");
+                status = AvrcpConstants.RSP_INTERNAL_ERR;
+            }
+        } else if (mNowPlayingItems != null) {
+            if(DEBUG) printByteArray("getItemAttr-UID", itemAttr.mUid);
+            for (idx = 0; idx < mNowPlayingItems.size(); idx++) {
+                if (mediaID == mNowPlayingItems.get(idx).getQueueId()) {
+                    getItemAttrFilterAttr(bdaddr, itemAttr, mNowPlayingItems.get(idx));
+                    break;
+                }
+            }
+            if (idx >= mNowPlayingItems.size()) {
+                Log.e(TAG, "getItemAttr: idx is more than now playing list: idx = " + idx
+                        + ", now playing list size = " + mNowPlayingItems.size());
+                status = AvrcpConstants.RSP_INV_ITEM;
+            }
+        } else {
+            Log.e(TAG, "getItemAttr: mNowPlayingItems is null!");
+            status = AvrcpConstants.RSP_INTERNAL_ERR;
+        }
+
+        // sending error status in case of error
+        if (status != AvrcpConstants.RSP_NO_ERROR) {
+            mMediaInterface.getItemAttrRsp(bdaddr, status, null);
+        }
+    }
+
+    private MediaSession.QueueItem getQueueItem(MediaMetadata metadata) {
+        MediaDescription.Builder builder = new MediaDescription.Builder();
+
+        // getting the media id
+        String mediaId = metadata.getDescription().getMediaId();
+        if (mediaId != null) {
+            builder.setMediaId(mediaId);
+            if(DEBUG) Log.d(TAG, "Item mediaId = " + mediaId);
+        }
+
+        // getting the title
+        if (metadata.getDescription().getTitle() != null) {
+            String title = metadata.getDescription().getTitle().toString();
+            builder.setTitle(title);
+            if(DEBUG) Log.d(TAG, "Item title = " + title);
+        }
+
+        // getting the metadata from the key-value pairs and filling to bundle
+        String artist = metadata.getString(MediaMetadata.METADATA_KEY_ARTIST);
+        String album = metadata.getString(MediaMetadata.METADATA_KEY_ALBUM);
+        String track_num = metadata.getLong(MediaMetadata.METADATA_KEY_TRACK_NUMBER) + "";
+        String num_tracks = metadata.getLong(MediaMetadata.METADATA_KEY_NUM_TRACKS) + "";
+        String genre = metadata.getString(MediaMetadata.METADATA_KEY_GENRE);
+        String duration = metadata.getLong(MediaMetadata.METADATA_KEY_DURATION) + "";
+
+        Bundle bundle = fillBundle(artist, album, track_num, num_tracks, genre, duration);
+        builder.setExtras(bundle);
+
+        // building a queue item from the above metadata
+        MediaDescription desc = builder.build();
+        return new QueueItem((desc), ByteBuffer.wrap(NOW_PLAYING_UID).getLong());
+    }
+
+    private Bundle fillBundle(String artist, String album, String trackNum, String numTracks,
+            String genre, String playTime) {
+
+        Bundle bundle = new Bundle();
+
+        bundle.putString(MediaMetadata.METADATA_KEY_ARTIST, artist);
+        bundle.putString(MediaMetadata.METADATA_KEY_ALBUM, album);
+        bundle.putString(MediaMetadata.METADATA_KEY_GENRE, genre);
+        bundle.putString(MediaMetadata.METADATA_KEY_NUM_TRACKS, numTracks);
+        bundle.putString(MediaMetadata.METADATA_KEY_DURATION, playTime);
+        bundle.putString(MediaMetadata.METADATA_KEY_TRACK_NUMBER, trackNum);
+        return bundle;
+    }
+
+    void updateNowPlayingList(List<MediaSession.QueueItem> queue){
+        mNowPlayingListManager.setNowPlayingList(queue);
+    }
+
+    /* Instructs media player to play particular media item */
+    void playItem(byte[] bdaddr, byte[] uid, byte scope, MediaController mediaController) {
+        long qid = ByteBuffer.wrap(uid).getLong();
+        List<MediaSession.QueueItem> mNowPlayingItems = mNowPlayingListManager.getNowPlayingList();
+
+        if (mediaController != null) {
+            MediaController.TransportControls mediaControllerCntrl =
+                    mediaController.getTransportControls();
+            if (DEBUG) Log.d(TAG, "Sending playID");
+
+            if (scope == AvrcpConstants.BTRC_SCOPE_NOW_PLAYING) {
+                int idx;
+                /* find the queueId of the mediaId to play */
+                if (mNowPlayingItems != null) {
+                    for (idx = 0; idx < mNowPlayingItems.size(); idx++) {
+                        if (qid == mNowPlayingItems.get(idx).getQueueId()) {
+                            mediaControllerCntrl.skipToQueueItem(qid);
+                            break;
+                        }
+                    }
+                    /* if mediaId is not found in nowplaying list */
+                    if (idx >= mNowPlayingItems.size()) {
+                        Log.w(TAG, "item is not present in queue");
+                        mMediaInterface.playItemRsp(bdaddr, AvrcpConstants.RSP_INV_ITEM);
+                    }
+                } else {
+                    Log.w(TAG, "nowPlayingItems is null");
+                    mMediaInterface.playItemRsp(bdaddr, AvrcpConstants.RSP_INTERNAL_ERR);
+                }
+            }
+            mMediaInterface.playItemRsp(bdaddr, AvrcpConstants.RSP_NO_ERROR);
+        } else {
+            Log.e(TAG, "mediaController is null");
+            mMediaInterface.playItemRsp(bdaddr, AvrcpConstants.RSP_INTERNAL_ERR);
+        }
+    }
+
+    void getTotalNumOfItems(byte[] bdaddr, byte scope, MediaController mediaController) {
+        if (DEBUG) Log.d(TAG, "getTotalNumOfItems scope = " + scope);
+        List<MediaSession.QueueItem> mNowPlayingItems = mNowPlayingListManager.getNowPlayingList();
+        if (mNowPlayingItems != null) {
+            // We already have the cached list sending the response to remote
+            mMediaInterface.getTotalNumOfItemsRsp(bdaddr, AvrcpConstants.RSP_NO_ERROR, 0,
+                    mNowPlayingItems.size());
+        } else if (mediaController == null) {
+            Log.e(TAG, "mediaController is null");
+            mMediaInterface.getTotalNumOfItemsRsp(bdaddr,
+                    AvrcpConstants.RSP_INTERNAL_ERR, 0, 0);
+        } else {
+            // We don't have the cached list, fetching it from Media Controller
+            mNowPlayingItems = mediaController.getQueue();
+            if (mNowPlayingItems == null) {
+                Log.e(TAG, "mNowPlayingItems is null");
+                mMediaInterface.getTotalNumOfItemsRsp(bdaddr,
+                        AvrcpConstants.RSP_INV_ITEM, 0, 0);
+            } else {
+                mNowPlayingListManager.setNowPlayingList(mediaController.getQueue());
+                mMediaInterface.getTotalNumOfItemsRsp(bdaddr,
+                        AvrcpConstants.RSP_NO_ERROR, 0, mNowPlayingItems.size());
+            }
+        }
+    }
+
+    void sendTrackChangeWithId(int trackChangedNT, long trackNumber,
+            MediaController mediaController) {
+        if (DEBUG) Log.d(TAG, "sendTrackChangeWithId");
+        try {
+            String mediaId = mediaController.getMetadata().getDescription().getMediaId();
+            long qid = 0;
+            List<MediaSession.QueueItem> mNowPlayingItems = mNowPlayingListManager.getNowPlayingList();
+            /* traverse now playing list for current playing item */
+            for (QueueItem qitem : mNowPlayingItems) {
+                if (qitem.getDescription().getMediaId().equals(mediaId)) {
+                    qid = qitem.getQueueId();
+                    if (DEBUG) Log.d(TAG, "sendTrackChangeWithId: Found matching qid= " + qid);
+                    break;
+                }
+            }
+            /* for any item associated with NowPlaying, uid is queueId */
+            byte[] uid = ByteBuffer.allocate(AvrcpConstants.UID_SIZE).putLong(qid).array();
+            if (DEBUG) printByteArray("trackChangedRsp", uid);
+            mMediaInterface.trackChangedRsp(trackChangedNT, uid);
+        } catch (NullPointerException e) {
+            Log.w(TAG, "Null Pointer while getting current track Uid from media player");
+            sendTrackChangeRsp(trackChangedNT, trackNumber);
+        }
+    }
+
+    /*
+     * utility function to respond for track change when failed to get current track UID
+     * from media controller
+     */
+    private void sendTrackChangeRsp(int trackChangedNT, long trackNumber) {
+        byte[] track = new byte[AvrcpConstants.TRACK_ID_SIZE];
+        /* track is stored in big endian format */
+        for (int idx = 0; idx < AvrcpConstants.TRACK_ID_SIZE; ++idx) {
+            if (trackChangedNT == AvrcpConstants.NOTIFICATION_TYPE_INTERIM
+                    && trackNumber == -1) {
+                 /* if no track is currently selected then return 0xFF in interim response */
+                track[idx] = AvrcpConstants.NO_TRACK_SELECTED;
+            } else {
+                /* if Browsing is not supported and a track is selected, then return 0x00 */
+                track[idx] = AvrcpConstants.TRACK_IS_SELECTED;
+            }
+        }
+        if (DEBUG) printByteArray("sendTrackChangeRsp", track);
+        mMediaInterface.trackChangedRsp(trackChangedNT, track);
+    }
+
+    /*
+     * helper method to check if startItem and endItem index is with range of
+     * MediaItem list. (Resultset containing all items in current path)
+     */
+    private List<MediaSession.QueueItem> checkIndexOutofBounds(byte[] bdaddr,
+            List<MediaSession.QueueItem> children, int startItem, int endItem) {
+        try {
+            List<MediaSession.QueueItem> childrenSubList =
+                children.subList(startItem, Math.min(children.size(), endItem + 1));
+            if (childrenSubList.isEmpty()) {
+                Log.i(TAG, "childrenSubList is empty.");
+                throw new IndexOutOfBoundsException();
+            }
+            return childrenSubList;
+        } catch (IndexOutOfBoundsException ex) {
+            Log.i(TAG, "Index out of bounds start item =" + startItem + " end item = "
+                    + Math.min(children.size(), endItem + 1));
+            mMediaInterface.folderItemsRsp(bdaddr, AvrcpConstants.RSP_INV_RANGE, null);
+            return null;
+        } catch (IllegalArgumentException ex) {
+            Log.i(TAG, "Index out of bounds start item =" + startItem + " > size");
+            mMediaInterface.folderItemsRsp(bdaddr, AvrcpConstants.RSP_INV_RANGE, null);
+            return null;
+        }
+    }
+
+    /*
+     * helper method to filter required attibutes before sending GetFolderItems
+     * response
+     */
+    private void getFolderItemsFilterAttr(byte[] bdaddr,
+            AvrcpCmd.FolderItemsCmd mFolderItemsReqObj,
+            List<MediaSession.QueueItem> children, byte scope, int startItem, int endItem) {
+        if (DEBUG) Log.d(TAG, "getFolderItemsFilterAttr: startItem =" + startItem + ", endItem = "
+                + endItem);
+
+        List<MediaSession.QueueItem> result_items = new ArrayList<MediaSession.QueueItem>();
+
+        if (children != null) {
+            /* check for index out of bound errors */
+            if ((result_items = checkIndexOutofBounds(bdaddr, children, startItem, endItem))
+                    == null) {
+                Log.w(TAG, "result_items is null.");
+                mMediaInterface.folderItemsRsp(bdaddr, AvrcpConstants.RSP_INV_RANGE, null);
+                return;
+            }
+            FolderItemsData folderDataNative = new FolderItemsData(result_items.size());
+
+            /* variables to temperorily add attrs */
+            ArrayList<String> attrArray = new ArrayList<String>();
+            ArrayList<Integer> attrId = new ArrayList<Integer>();
+
+            for (int itemIndex = 0; itemIndex < result_items.size(); itemIndex++) {
+                // get the queue id
+                long qid = result_items.get(itemIndex).getQueueId();
+                byte[] uid = ByteBuffer.allocate(AvrcpConstants.UID_SIZE).putLong(qid).array();
+
+                // get the array of uid from 2d to array 1D array
+                for (int idx = 0; idx < AvrcpConstants.UID_SIZE; idx++) {
+                    folderDataNative.mItemUid[itemIndex * AvrcpConstants.UID_SIZE + idx] = uid[idx];
+                }
+
+                /* Set display name for current item */
+                folderDataNative.mDisplayNames[itemIndex] = result_items.get(itemIndex)
+                        .getDescription().getTitle().toString();
+
+                int maxAttributesRequested = 0;
+                boolean isAllAttribRequested = false;
+                /* check if remote requested for attributes */
+                if (mFolderItemsReqObj.mNumAttr != AvrcpConstants.NUM_ATTR_NONE) {
+                    int attrCnt = 0;
+
+                    /* add requested attr ids to a temp array */
+                    if (mFolderItemsReqObj.mNumAttr == AvrcpConstants.NUM_ATTR_ALL) {
+                        isAllAttribRequested = true;
+                        maxAttributesRequested = AvrcpConstants.MAX_NUM_ATTR;
+                    } else {
+                        /* get only the requested attribute ids from the request */
+                        maxAttributesRequested = mFolderItemsReqObj.mNumAttr;
+                    }
+
+                    /* lookup and copy values of attributes for ids requested above */
+                    for (int idx = 0; idx < maxAttributesRequested; idx++) {
+                        /* check if media player provided requested attributes */
+                        String value = null;
+
+                        int attribId = isAllAttribRequested ? (idx + 1)
+                                : mFolderItemsReqObj.mAttrIDs[idx];
+                        if (attribId >= AvrcpConstants.ATTRID_TITLE
+                                && attribId <= AvrcpConstants.ATTRID_PLAY_TIME) {
+                            if ((value = getAttrValue(attribId, result_items, itemIndex))
+                                    != null) {
+                                attrArray.add(value);
+                                attrId.add(attribId);
+                                attrCnt++;
+                            }
+                        } else {
+                            Log.w(TAG, "invalid attributed id is requested: " + attribId);
+                        }
+                    }
+                     /* add num attr actually received from media player for a particular item */
+                    folderDataNative.mAttributesNum[itemIndex] = attrCnt;
+                }
+            }
+
+            /* copy filtered attr ids and attr values to response parameters */
+            if (mFolderItemsReqObj.mNumAttr != AvrcpConstants.NUM_ATTR_NONE) {
+                folderDataNative.mAttrIds = new int[attrId.size()];
+                for (int attrIndex = 0; attrIndex < attrId.size(); attrIndex++)
+                    folderDataNative.mAttrIds[attrIndex] = attrId.get(attrIndex);
+                folderDataNative.mAttrValues = attrArray.toArray(new String[attrArray.size()]);
+            }
+            for (int attrIndex = 0; attrIndex < folderDataNative.mAttributesNum.length; attrIndex++)
+                if (DEBUG) Log.d(TAG, "folderDataNative.mAttributesNum"
+                        + folderDataNative.mAttributesNum[attrIndex] + " attrIndex " + attrIndex);
+
+            /* create rsp object and send response to remote device */
+            FolderItemsRsp rspObj = new FolderItemsRsp(AvrcpConstants.RSP_NO_ERROR,
+                    Avrcp.sUIDCounter, scope, folderDataNative.mNumItems,
+                    folderDataNative.mFolderTypes, folderDataNative.mPlayable,
+                    folderDataNative.mItemTypes, folderDataNative.mItemUid,
+                    folderDataNative.mDisplayNames, folderDataNative.mAttributesNum,
+                    folderDataNative.mAttrIds, folderDataNative.mAttrValues);
+            mMediaInterface.folderItemsRsp(bdaddr, AvrcpConstants.RSP_NO_ERROR, rspObj);
+        } else {
+            Log.e(TAG, "Error: children are null in getFolderItemsFilterAttr");
+            mMediaInterface.folderItemsRsp(bdaddr, AvrcpConstants.RSP_INV_RANGE, null);
+            return;
+        }
+    }
+
+    private String getAttrValue(int attr, List<MediaSession.QueueItem> resultItems,
+            int itemIndex) {
+        String attrValue = null;
+        try {
+            switch (attr) {
+            /* Title is mandatory attribute */
+                case AvrcpConstants.ATTRID_TITLE:
+                    attrValue = resultItems.get(itemIndex).getDescription().getTitle().toString();
+                    break;
+
+                case AvrcpConstants.ATTRID_ARTIST:
+                    attrValue = resultItems.get(itemIndex).getDescription().getExtras()
+                            .getString(MediaMetadata.METADATA_KEY_ARTIST);
+                    break;
+
+                case AvrcpConstants.ATTRID_ALBUM:
+                    attrValue = resultItems.get(itemIndex).getDescription().getExtras()
+                            .getString(MediaMetadata.METADATA_KEY_ALBUM);
+                    break;
+
+                case AvrcpConstants.ATTRID_TRACK_NUM:
+                    attrValue = resultItems.get(itemIndex).getDescription().getExtras()
+                            .getString(MediaMetadata.METADATA_KEY_TRACK_NUMBER);
+                    break;
+
+                case AvrcpConstants.ATTRID_NUM_TRACKS:
+                    attrValue = resultItems.get(itemIndex).getDescription().getExtras()
+                            .getString(MediaMetadata.METADATA_KEY_NUM_TRACKS);
+                    break;
+
+                case AvrcpConstants.ATTRID_GENRE:
+                    attrValue = resultItems.get(itemIndex).getDescription().getExtras()
+                            .getString(MediaMetadata.METADATA_KEY_GENRE);
+                    break;
+
+                case AvrcpConstants.ATTRID_PLAY_TIME:
+                    attrValue = resultItems.get(itemIndex).getDescription().getExtras()
+                            .getString(MediaMetadata.METADATA_KEY_DURATION);
+                    break;
+
+                default:
+                    Log.e(TAG, "Unknown attribute ID");
+            }
+        } catch (IndexOutOfBoundsException ex) {
+            Log.w(TAG, "getAttrValue: requested item index out of bounds");
+            return null;
+        } catch (NullPointerException ex) {
+            Log.w(TAG, "getAttrValue: attr id not found in result");
+            /* checking if attribute is title, then it is mandatory and cannot send null */
+            if (attr == AvrcpConstants.ATTRID_TITLE) {
+                return "<Unknown Title>";
+            }
+            return null;
+        }
+        if (DEBUG) Log.d(TAG, "getAttrValue: attrvalue = " + attrValue + ", attr id:" + attr);
+        return attrValue;
+    }
+
+    private void getItemAttrFilterAttr(byte[] bdaddr, AvrcpCmd.ItemAttrCmd mItemAttrReqObj,
+            MediaSession.QueueItem mediaItem) {
+        /* Response parameters */
+        int[] attrIds = null; /* array of attr ids */
+        String[] attrValues = null; /* array of attr values */
+        int attrCounter = 0; /* num attributes for each item */
+        List<MediaSession.QueueItem> resultItems = new ArrayList<MediaSession.QueueItem>();
+        resultItems.add(mediaItem);
+        /* variables to temperorily add attrs */
+        ArrayList<String> attrArray = new ArrayList<String>();
+        ArrayList<Integer> attrId = new ArrayList<Integer>();
+
+        ArrayList<Integer> attrTempId = new ArrayList<Integer>();
+
+        /* check if remote device has requested for attributes */
+        if (mItemAttrReqObj.mNumAttr != AvrcpConstants.NUM_ATTR_NONE) {
+            if (mItemAttrReqObj.mNumAttr == AvrcpConstants.NUM_ATTR_ALL) {
+                for (int idx = 1; idx < AvrcpConstants.MAX_NUM_ATTR; idx++) {
+                    attrTempId.add(idx); /* attr id 0x00 is unused */
+                }
+            } else {
+                /* get only the requested attribute ids from the request */
+                for (int idx = 0; idx < mItemAttrReqObj.mNumAttr; idx++) {
+                    if (DEBUG) Log.d(TAG, "getAttrValue: attr id[" + idx + "] :" +
+                        mItemAttrReqObj.mAttrIDs[idx]);
+                    attrTempId.add(mItemAttrReqObj.mAttrIDs[idx]);
+                }
+            }
+
+            if (DEBUG) Log.d(TAG, "getAttrValue: attr id list size:" + attrTempId.size());
+            /* lookup and copy values of attributes for ids requested above */
+            for (int idx = 0; idx < attrTempId.size(); idx++) {
+                /* check if media player provided requested attributes */
+                String value = null;
+                if ((value = getAttrValue(attrTempId.get(idx), resultItems, 0)) != null) {
+                    attrArray.add(value);
+                    attrId.add(attrTempId.get(idx));
+                    attrCounter++;
+                }
+            }
+            attrTempId = null;
+        }
+
+        /* copy filtered attr ids and attr values to response parameters */
+        if (mItemAttrReqObj.mNumAttr != AvrcpConstants.NUM_ATTR_NONE) {
+            attrIds = new int[attrId.size()];
+
+            for (int attrIndex = 0; attrIndex < attrId.size(); attrIndex++)
+                attrIds[attrIndex] = attrId.get(attrIndex);
+
+            attrValues = attrArray.toArray(new String[attrId.size()]);
+
+            /* create rsp object and send response */
+            ItemAttrRsp rspObj = new ItemAttrRsp(AvrcpConstants.RSP_NO_ERROR,
+                    (byte)attrCounter, attrIds, attrValues);
+            mMediaInterface.getItemAttrRsp(bdaddr, AvrcpConstants.RSP_NO_ERROR, rspObj);
+            return;
+        }
+    }
+
+    void handlePassthroughCmd(int id, int keyState, byte[] bdAddr,
+            MediaController mediaController) {
+
+        if (mediaController != null) {
+            MediaController.TransportControls mediaControllerCntrl =
+                mediaController.getTransportControls();
+            if (DEBUG) Log.v(TAG, "handlePassthroughCmd - id:" + id + " keyState:" + keyState);
+            if (keyState == AvrcpConstants.KEY_STATE_PRESS) {
+                switch (id) {
+                    case BluetoothAvrcp.PASSTHROUGH_ID_REWIND:
+                        mediaControllerCntrl.rewind();
+                        break;
+                    case BluetoothAvrcp.PASSTHROUGH_ID_FAST_FOR:
+                        mediaControllerCntrl.fastForward();
+                        break;
+                    case BluetoothAvrcp.PASSTHROUGH_ID_PLAY:
+                        mediaControllerCntrl.play();
+                        break;
+                    case BluetoothAvrcp.PASSTHROUGH_ID_PAUSE:
+                        mediaControllerCntrl.pause();
+                        break;
+                    case BluetoothAvrcp.PASSTHROUGH_ID_STOP:
+                        mediaControllerCntrl.stop();
+                        break;
+                    case BluetoothAvrcp.PASSTHROUGH_ID_FORWARD:
+                        mediaControllerCntrl.skipToNext();
+                        break;
+                    case BluetoothAvrcp.PASSTHROUGH_ID_BACKWARD:
+                        mediaControllerCntrl.skipToPrevious();
+                        break;
+                    default:
+                        Log.w(TAG, "unknown id:" + id + " keyState:" + keyState);
+                }
+            } else {
+                Log.i(TAG, "ignoring the release event for id:" + id + " keyState:" + keyState);
+            }
+        } else {
+            Log.e(TAG, "Unable to handlePassthroughCmd, mediaController is null!");
+        }
+    }
+
+    private void printByteArray(String arrName, byte[] array) {
+        StringBuilder byteArray = new StringBuilder(arrName + ": 0x");
+
+        for (int idx = 0; idx < array.length; idx++) {
+            byteArray.append(String.format(" %02x", array[idx]));
+        }
+        Log.d(TAG, byteArray + "");
+    }
+
+}
old mode 100755 (executable)
new mode 100644 (file)
index 308dcdc..8952b79
@@ -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<MediaPlayerInfo> mMediaPlayerInfoList;
+
+    /* List of media players which supports browse */
+    private ArrayList<BrowsePlayerInfo> mBrowsePlayerInfoList;
+
+    /* Manage browsed players */
+    private AvrcpBrowseManager mAvrcpBrowseManager;
+
+    /* Broadcast receiver for device connections intent broadcasts */
+    private final BroadcastReceiver mAvrcpReceiver = new AvrcpServiceBroadcastReceiver();
+
     static {
         classInitNative();
     }
@@ -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<Integer, Integer>();
-
+        sUIDCounter = AvrcpConstants.DEFAULT_UID_COUNTER;
+        mCurrAddrPlayerID = -1;
+        mCurrBrowsePlayerID = -1;
         mContext = context;
+        mMPLObj = null;
+        mAddressedMediaPlayer = null;
 
         initNative();
 
-        mMediaSessionManager = (MediaSessionManager) context.getSystemService(Context.MEDIA_SESSION_SERVICE);
+        mMediaSessionManager = (MediaSessionManager) context.getSystemService(
+            Context.MEDIA_SESSION_SERVICE);
         mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
         mAudioStreamMax = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
         mVolumeStep = Math.max(AVRCP_BASE_VOLUME_STEP, AVRCP_MAX_VOL/mAudioStreamMax);
+
         Resources resources = context.getResources();
         if (resources != null) {
             mAbsVolThreshold = resources.getInteger(R.integer.a2dp_absolute_volume_initial_threshold);
         }
+
+        // Register for package removal intent broadcasts for media button receiver persistence
+        IntentFilter pkgFilter = new IntentFilter();
+        pkgFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+        pkgFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
+        pkgFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
+        pkgFilter.addAction(Intent.ACTION_PACKAGE_DATA_CLEARED);
+        pkgFilter.addDataScheme("package");
+        context.registerReceiver(mAvrcpReceiver, pkgFilter);
     }
 
     private void start() {
@@ -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<MediaPlayerInfo>();
+        mBrowsePlayerInfoList = new ArrayList<BrowsePlayerInfo>();
+        mMediaSessionManager.addOnActiveSessionsChangedListener(mActiveSessionListener, null,
+                mHandler);
+        mPackageManager = mContext.getApplicationContext().getPackageManager();
+
+        /* create object to communicate with addressed player */
+        mAddressedMediaPlayer = new AddressedMediaPlayer(mAvrcpMediaRsp);
+
+        /* initializing media player's list */
+        buildBrowsablePlayersList();
+        buildMediaPlayersList();
+
+        /* initialize BrowseMananger which manages Browse commands and response */
+        mAvrcpBrowseManager = new AvrcpBrowseManager(mContext, mAvrcpMediaRsp);
     }
 
     public static Avrcp make(Context context) {
@@ -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<MediaController> controllers) {
-            Log.v(TAG, "Active sessions changed, " + controllers.size() + " sessions");
-            updateCurrentMediaControllers(controllers);
-        }
-    }
-
-    private void updateCurrentMediaControllers(List<MediaController> controllers) {
-        MediaController controller = null;
-        for (MediaController c : controllers) {
-          controller = c;
-          if (c.getMetadata() != null)
-            break; // We found a suitable controller
-        }
+        public void onQueueChanged(List<MediaSession.QueueItem> queue) {
+            if (queue == null) {
+                Log.v(TAG, "onQueueChanged: received null queue");
+                return;
+            }
 
-        String name = (controller == null) ? "null" : controller.getPackageName();
-        if (mMediaController == controller) {
-          Log.v(TAG, "MediaController still " + name);
-          return;
-        }
+            Log.v(TAG, "onQueueChanged: NowPlaying list changed, Queue Size = "+ queue.size());
+            mAddressedMediaPlayer.updateNowPlayingList(queue);
 
-        Log.v(TAG, "MediaController changed to " + name);
-        if (mMediaController != null) {
-            mMediaController.unregisterCallback(mMediaControllerCb);
-        }
-        mMediaController = controller;
-        if (mMediaController == null) {
-            updateMetadata(null);
-            return;
+            /* sent notification to remote for NowPlayingList changed */
+            if(!registerNotificationRspNowPlayingChangedNative(
+                    AvrcpConstants.NOTIFICATION_TYPE_CHANGED)){
+                Log.e(TAG, "onQueueChanged-registerNotificationRspNowPlayingChangedNative failed");
+            }
         }
-        mMediaController.registerCallback(mMediaControllerCb, mHandler);
-        updateMetadata(mMediaController.getMetadata());
     }
 
     /** Handles Avrcp messages. */
@@ -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<Integer> attrList = (ArrayList<Integer>) msg.obj;
-                Log.v(TAG, "MESSAGE_GET_ELEM_ATTRS:numAttr=" + numAttr);
-                attrIds = new int[numAttr];
+                AvrcpCmd.ElementAttrCmd elem = (AvrcpCmd.ElementAttrCmd) msg.obj;
+                byte numAttr = elem.mNumAttr;
+                int[] attrIds = elem.mAttrIDs;
+                if (DEBUG) Log.v(TAG, "MSG_NATIVE_REQ_GET_ELEM_ATTRS:numAttr=" + numAttr);
                 textArray = new String[numAttr];
                 for (int i = 0; i < numAttr; ++i) {
-                    attrIds[i] = attrList.get(i).intValue();
                     textArray[i] = mMediaAttributes.getString(attrIds[i]);
                     Log.v(TAG, "getAttributeString:attrId=" + attrIds[i] +
-                               " str=" + textArray[i]);
+                            " str=" + textArray[i]);
                 }
-                getElementAttrRspNative(numAttr, attrIds, textArray);
+                byte[] bdaddr = elem.mAddress;
+                getElementAttrRspNative(bdaddr, numAttr, attrIds, textArray);
                 break;
+            }
 
-            case MESSAGE_REGISTER_NOTIFICATION:
-                if (DEBUG) Log.v(TAG, "MESSAGE_REGISTER_NOTIFICATION:event=" + msg.arg1 +
-                                      " param=" + msg.arg2);
-                processRegisterNotification(msg.arg1, msg.arg2);
+            case MSG_NATIVE_REQ_REGISTER_NOTIFICATION:
+                if (DEBUG) Log.v(TAG, "MSG_NATIVE_REQ_REGISTER_NOTIFICATION:event=" + msg.arg1 +
+                        " param=" + msg.arg2);
+                processRegisterNotification((byte[]) msg.obj, msg.arg1, msg.arg2);
                 break;
 
-            case MESSAGE_PLAY_INTERVAL_TIMEOUT:
-                if (DEBUG) Log.v(TAG, "MESSAGE_PLAY_INTERVAL_TIMEOUT");
+            case MSG_PLAY_INTERVAL_TIMEOUT:
+                if (DEBUG) Log.v(TAG, "MSG_PLAY_INTERVAL_TIMEOUT");
                 sendPlayPosNotificationRsp(false);
                 break;
 
-            case MESSAGE_VOLUME_CHANGED:
+            case MSG_NATIVE_REQ_VOLUME_CHANGE:
                 if (!isAbsoluteVolumeSupported()) {
-                    if (DEBUG) Log.v(TAG, "ignore MESSAGE_VOLUME_CHANGED");
+                    if (DEBUG) Log.v(TAG, "ignore MSG_NATIVE_REQ_VOLUME_CHANGE");
                     break;
                 }
 
-                if (DEBUG) Log.v(TAG, "MESSAGE_VOLUME_CHANGED: volume=" + ((byte)msg.arg1 & 0x7f)
-                                                        + " ctype=" + msg.arg2);
-
+                if (DEBUG) Log.v(TAG, "MSG_NATIVE_REQ_VOLUME_CHANGE: volume=" + ((byte) msg.arg1 & 0x7f)
+                        + " ctype=" + msg.arg2);
 
                 boolean volAdj = false;
                 if (msg.arg2 == AVRC_RSP_ACCEPT || msg.arg2 == AVRC_RSP_REJ) {
@@ -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<Integer> attrList = new ArrayList<Integer>();
-        for (i = 0; i < numAttr; ++i) {
-            attrList.add(attrs[i]);
-        }
-        Message msg = mHandler.obtainMessage(MESSAGE_GET_ELEM_ATTRS, numAttr, 0, attrList);
+    private void getElementAttrRequestFromNative(byte[] address,byte numAttr, int[] attrs) {
+        if (DEBUG) Log.v(TAG, "getElementAttrRequestFromNative: numAttr=" + numAttr);
+        AvrcpCmd avrcpCmdobj = new AvrcpCmd();
+        AvrcpCmd.ElementAttrCmd elemAttr = avrcpCmdobj.new ElementAttrCmd(address, numAttr, attrs);
+        Message msg = mHandler.obtainMessage(MSG_NATIVE_REQ_GET_ELEM_ATTRS);
+        msg.obj = elemAttr;
         mHandler.sendMessage(msg);
     }
 
-    private void registerNotification(int eventId, int param) {
-        Message msg = mHandler.obtainMessage(MESSAGE_REGISTER_NOTIFICATION, eventId, param);
+    private void registerNotificationRequestFromNative(byte[] address,int eventId, int param) {
+        if (DEBUG) Log.v(TAG, "registerNotificationRequestFromNative: eventId=" + eventId);
+        Message msg = mHandler.obtainMessage(MSG_NATIVE_REQ_REGISTER_NOTIFICATION, eventId, param);
+        msg.obj = address;
         mHandler.sendMessage(msg);
     }
 
-    private void processRegisterNotification(int eventId, int param) {
+    private void processRegisterNotification(byte[] address, int eventId, int param) {
         switch (eventId) {
             case EVT_PLAY_STATUS_CHANGED:
-                mPlayStatusChangedNT = NOTIFICATION_TYPE_INTERIM;
+                mPlayStatusChangedNT = AvrcpConstants.NOTIFICATION_TYPE_INTERIM;
                 registerNotificationRspPlayStatusNative(mPlayStatusChangedNT,
                         convertPlayStateToPlayStatus(mCurrentPlayState));
                 break;
 
             case EVT_TRACK_CHANGED:
                 Log.v(TAG, "Track changed notification enabled");
-                mTrackChangedNT = NOTIFICATION_TYPE_INTERIM;
+                mTrackChangedNT = AvrcpConstants.NOTIFICATION_TYPE_INTERIM;
                 sendTrackChangedRsp();
                 break;
 
             case EVT_PLAY_POS_CHANGED:
-                mPlayPosChangedNT = NOTIFICATION_TYPE_INTERIM;
-                mPlaybackIntervalMs = (long)param * 1000L;
+                mPlayPosChangedNT = AvrcpConstants.NOTIFICATION_TYPE_INTERIM;
+                mPlaybackIntervalMs = (long) param * 1000L;
                 sendPlayPosNotificationRsp(true);
                 break;
 
+            case EVT_AVBL_PLAYERS_CHANGED:
+                /* Notify remote available players changed */
+                if (DEBUG) Log.d (TAG, "sending availablePlayersChanged to remote ");
+                registerNotificationRspAvalPlayerChangedNative(
+                        AvrcpConstants.NOTIFICATION_TYPE_INTERIM);
+                break;
+
+            case EVT_ADDR_PLAYER_CHANGED:
+                /* Notify remote addressed players changed */
+                if (DEBUG) Log.d (TAG, "sending addressedPlayersChanged to remote ");
+                registerNotificationRspAddrPlayerChangedNative(
+                        AvrcpConstants.NOTIFICATION_TYPE_INTERIM,
+                        mCurrAddrPlayerID, sUIDCounter);
+                break;
+
+            case EVENT_UIDS_CHANGED:
+                if (DEBUG) Log.d(TAG, "sending UIDs changed to remote");
+                registerNotificationRspUIDsChangedNative(
+                        AvrcpConstants.NOTIFICATION_TYPE_INTERIM, sUIDCounter);
+                break;
+
+            case EVENT_NOW_PLAYING_CONTENT_CHANGED:
+                if (DEBUG) Log.d(TAG, "sending NowPlayingList changed to remote");
+                /* send interim response to remote device */
+                if (!registerNotificationRspNowPlayingChangedNative(
+                        AvrcpConstants.NOTIFICATION_TYPE_INTERIM)) {
+                    Log.e(TAG, "EVENT_NOW_PLAYING_CONTENT_CHANGED: " +
+                            "registerNotificationRspNowPlayingChangedNative for Interim rsp failed!");
+                }
+                break;
         }
     }
 
-    private void handlePassthroughCmd(int id, int keyState) {
+    private void handlePassthroughCmdRequestFromNative(byte[] address, int id, int keyState) {
         switch (id) {
             case BluetoothAvrcp.PASSTHROUGH_ID_REWIND:
-                rewind(keyState);
-                break;
+                rewind(address, keyState);
+                return;
             case BluetoothAvrcp.PASSTHROUGH_ID_FAST_FOR:
-                fastForward(keyState);
-                break;
+                fastForward(address, keyState);
+                return;
         }
+
+        /* For all other pass through commands other than fast forward and backward
+         * (like play, pause, next, previous, stop, etc.); sending to current addressed player.
+         */
+        Message msg = mHandler.obtainMessage(MSG_NATIVE_REQ_PASS_THROUGH, id, keyState, address);
+        mHandler.sendMessage(msg);
     }
 
-    private void fastForward(int keyState) {
-        Message msg = mHandler.obtainMessage(MESSAGE_FAST_FORWARD, keyState, 0);
+    private void fastForward(byte[] address, int keyState) {
+        Message msg = mHandler.obtainMessage(MSG_FAST_FORWARD, keyState, 0);
+        Bundle data = new Bundle();
+        data.putByteArray("BdAddress" , address);
+        msg.setData(data);
         mHandler.sendMessage(msg);
     }
 
-    private void rewind(int keyState) {
-        Message msg = mHandler.obtainMessage(MESSAGE_REWIND, keyState, 0);
+    private void rewind(byte[] address, int keyState) {
+        Message msg = mHandler.obtainMessage(MSG_REWIND, keyState, 0);
+        Bundle data = new Bundle();
+        data.putByteArray("BdAddress" , address);
+        msg.setData(data);
         mHandler.sendMessage(msg);
     }
 
@@ -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<String> browsePlayersList = new ArrayList<String>();
+        Intent intent = new Intent("android.media.browse.MediaBrowserService");
+        List<ResolveInfo> resInfos = mPackageManager.queryIntentServices(intent, 0);
+        for (ResolveInfo resolveInfo : resInfos) {
+            browsePlayersList.add(resolveInfo.serviceInfo.packageName);
+        }
 
-    // match up with btrc_notification_type_t enum of bt_rc.h
-    final static int NOTIFICATION_TYPE_INTERIM = 0;
-    final static int NOTIFICATION_TYPE_CHANGED = 1;
+        // if new added package is browsable or list has been updated from the global object
+        if (browsePlayersList.contains(newPackageName)
+                || browsePlayersList.size() != getBrowsePlayersListSize()) {
+            isUpdated = true;
+        }
+        if (DEBUG) Log.d(TAG, "isBrowsableListUpdated " + newPackageName +
+                " isUpdated:" +  isUpdated);
+        return isUpdated;
+    }
+
+    private synchronized void removePackageFromBrowseList(String packageName) {
+        if (DEBUG) Log.d(TAG, "removePackageFromBrowseList: " + packageName);
+        int browseInfoID = getBrowseId(packageName);
+        if (browseInfoID != -1) {
+            mBrowsePlayerInfoList.remove(browseInfoID);
+        }
+    }
+
+    /*
+     * utility function to get the browse player index from global browsable
+     * list. It may return -1 if specified package name is not in the list.
+     */
+    private synchronized int getBrowseId(String packageName) {
+
+        boolean response = false;
+        int browseInfoID = 0;
+
+        for (BrowsePlayerInfo info : mBrowsePlayerInfoList) {
+            if (info.packageName.equals(packageName)) {
+                response = true;
+                break;
+            }
+            browseInfoID++;
+        }
+
+        if (!response) {
+            browseInfoID = -1;
+        }
+
+        if (DEBUG) Log.d(TAG, "getBrowseId for packageName: " + packageName +
+                " , browseInfoID: " + browseInfoID);
+        return browseInfoID;
+    }
+
+    private void setAddressedPlayer(byte[] bdaddr, int selectedId) {
+        int status = AvrcpConstants.RSP_NO_ERROR;
+
+        if (isCurrentMediaPlayerListEmpty()) {
+            status = AvrcpConstants.RSP_NO_AVBL_PLAY;
+            Log.w(TAG, " No Available Players to set, sending response back ");
+        } else if (!isIdValid(selectedId)) {
+            status = AvrcpConstants.RSP_INV_PLAYER;
+            Log.w(TAG, " Invalid Player id: " + selectedId + " to set, sending response back ");
+        } else if (!isPlayerAlreadyAddressed(selectedId)) {
+            // register new Media Controller Callback and update the current Ids
+            if (!updateCurrentController(selectedId, mCurrBrowsePlayerID)) {
+                status = AvrcpConstants.RSP_INTERNAL_ERR;
+                Log.e(TAG, "register for new Address player failed: " + mCurrAddrPlayerID);
+            }
+        } else {
+            Log.i(TAG, "requested addressPlayer is already focused:" + getCurrentAddrPlayer());
+        }
+
+        if (DEBUG) Log.d(TAG, "setAddressedPlayer for selectedId: " + selectedId +
+                " , status: " + status);
+        // Sending address player response to remote
+        setAddressedPlayerRspNative(bdaddr, status);
+    }
+
+    private void setBrowsedPlayer(byte[] bdaddr, int selectedId) {
+        int status = AvrcpConstants.RSP_NO_ERROR;
+
+        // checking for error cases
+        if (isCurrentMediaPlayerListEmpty()) {
+            status = AvrcpConstants.RSP_NO_AVBL_PLAY;
+            Log.w(TAG, " No Available Players to set, sending response back ");
+        } else {
+            // update current browse player id and start browsing service
+            updateNewIds(mCurrAddrPlayerID, selectedId);
+            String browsedPackage = getPackageName(selectedId);
+
+            if (!isPackageNameValid(browsedPackage)) {
+                Log.w(TAG, " Invalid package for id:" + mCurrBrowsePlayerID);
+                status = AvrcpConstants.RSP_INV_PLAYER;
+            } else if (!isBrowseSupported(browsedPackage)) {
+                Log.w(TAG, "Browse unsupported for id:" + mCurrBrowsePlayerID
+                        + ", packagename : " + browsedPackage);
+                status = AvrcpConstants.RSP_PLAY_NOT_BROW;
+            } else if (!startBrowseService(bdaddr, browsedPackage)) {
+                Log.e(TAG, "service cannot be started for browse player id:" + mCurrBrowsePlayerID
+                        + ", packagename : " + browsedPackage);
+                status = AvrcpConstants.RSP_INTERNAL_ERR;
+            }
+        }
+
+        if (status != AvrcpConstants.RSP_NO_ERROR) {
+            setBrowsedPlayerRspNative(bdaddr, status, (byte) 0x00, 0, null);
+        }
+
+        if (DEBUG) Log.d(TAG, "setBrowsedPlayer for selectedId: " + selectedId +
+                " , status: " + status);
+    }
+
+    private MediaSessionManager.OnActiveSessionsChangedListener mActiveSessionListener =
+            new MediaSessionManager.OnActiveSessionsChangedListener() {
+
+        @Override
+        public void onActiveSessionsChanged(List<MediaController> mediaControllerList) {
+            if (DEBUG) Log.v(TAG, "received onActiveSessionsChanged");
+
+            if (isAvailablePlayersChanged(mediaControllerList)) {
+                // rebuild the list cached locally in this file
+                buildMediaPlayersList();
+
+                // inform the remote device that the player list has changed
+                sendAvailablePlayersChanged();
+            } else if (isAddressedPlayerChanged(mediaControllerList)) {
+                int newAddrPlayerID = getNewAddrPlayerID(mediaControllerList.get(0)
+                        .getPackageName());
+                // inform the remote device that the addressed player has changed
+                sendAddressedPlayerChanged(newAddrPlayerID);
+
+                if (!updateCurrentController(newAddrPlayerID, mCurrBrowsePlayerID)) {
+                    Log.e(TAG, "register for new Address player failed. id: " + newAddrPlayerID);
+                }
+            } else {
+                if (DEBUG) Log.d(TAG, "Active sessions same, ignoring onActiveSessionsChanged.");
+            }
+        }
+
+        private void sendAddressedPlayerChanged(int newAddrPlayerID) {
+            if (DEBUG) Log.d(TAG, "sendAddressedPlayerChanged: new PlayerID=" + newAddrPlayerID);
+
+            /* notify remote addressed player changed */
+            registerNotificationRspAddrPlayerChangedNative(
+                    AvrcpConstants.NOTIFICATION_TYPE_CHANGED, newAddrPlayerID, sUIDCounter);
+        }
+
+        private void sendAvailablePlayersChanged() {
+            if (DEBUG) Log.d(TAG, "sendAvailablePlayersChanged");
+
+            /* Notify remote available players changed */
+            registerNotificationRspAvalPlayerChangedNative(
+                    AvrcpConstants.NOTIFICATION_TYPE_CHANGED);
+        }
+
+        private boolean isAddressedPlayerChanged(List<MediaController> mediaControllerList) {
+            boolean isAddrPlayerChanged = false;
+
+            // checking top of the controller's list with current addressed player
+            if (mediaControllerList != null && !mediaControllerList.isEmpty()) {
+                if (!mediaControllerList.get(0).getPackageName().equals(getCurrentAddrPlayer())) {
+                    isAddrPlayerChanged = true;
+                }
+            }
+
+            if (DEBUG) Log.d(TAG, "isAddressedPlayerChanged: " + isAddrPlayerChanged);
+            return isAddrPlayerChanged;
+        }
+
+        private boolean isAvailablePlayersChanged(List<MediaController> mediaControllerList) {
+            boolean isListModified = false;
+
+            /* comparing media controller list from framework and from local cached list */
+            if (mediaControllerList == null && isCurrentMediaPlayerListEmpty()) {
+                if (DEBUG) Log.d(TAG,
+                        "both player list, received from framework and local are empty");
+                return isListModified;
+            }
+
+            if (mediaControllerList == null && !isCurrentMediaPlayerListEmpty()) {
+                isListModified = true;
+                if (DEBUG) Log.d(TAG, "players list is empty, local player list is not empty");
+            } else if (isCurrentMediaPlayerListEmpty() && mediaControllerList != null) {
+                if (DEBUG) Log.d(TAG, "players list is not empty, but local player list is empty");
+                isListModified = true;
+            } else if (isCtrlListChanged(mediaControllerList, mMPLObj.mControllersList)) {
+                isListModified = true;
+            }
+
+            if (DEBUG) Log.d(TAG, "isAvailablePlayersChanged: " + isListModified);
+            return isListModified;
+        }
+
+        private int getNewAddrPlayerID(String newAddressedPlayer) {
+            int newAddrPlayerId = -1;
+
+            for (int id = 0; id < mMPLObj.mPackageNameList.length; id++) {
+                if (mMPLObj.mPackageNameList[id].equals(newAddressedPlayer)) {
+                    // increment Id by one, because list Ids starts from 1.
+                    newAddrPlayerId = id + 1;
+                    break;
+                }
+            }
+
+            if (DEBUG) Log.d(TAG, "getNewAddrPlayerID: " + newAddrPlayerId);
+            return newAddrPlayerId;
+        }
 
-    // match up with BTRC_UID_SIZE of bt_rc.h
-    final static int TRACK_ID_SIZE = 8;
+        private boolean isCtrlListChanged(List<MediaController> mediaControllerList,
+                List<MediaController> mControllersList) {
+            boolean isListChanged = false;
+
+            if (mControllersList.size() != mediaControllerList.size()) {
+                if (DEBUG) Log.d(TAG, "size of new list and old list are different");
+                isListChanged = true;
+            } else {
+                // loop through both the list and check if any new entry found
+                for (MediaController newCtrller : mediaControllerList) {
+                    boolean isPackageExist = false;
+
+                    for (MediaController oldCtrller : mControllersList) {
+                        if (oldCtrller.getPackageName().equals(newCtrller.getPackageName())) {
+                            isPackageExist = true;
+                            break;
+                        }
+                    }
+
+                    if (!isPackageExist) {
+                        if (DEBUG) Log.d(TAG, "no match found for " + newCtrller.getPackageName());
+                        isListChanged =  true;
+                        break;
+                    }
+                }
+            }
+
+            if (DEBUG) Log.d(TAG, "isCtrlListChanged: " + isListChanged);
+            return isListChanged;
+        }
+
+    };
+
+    private boolean startBrowseService(byte[] bdaddr, String packageName) {
+        boolean status = true;
+
+        /* creating new instance for Browse Media Player */
+        String browseService = getBrowseServiceName(packageName);
+        if (!browseService.isEmpty()) {
+            mAvrcpBrowseManager.getBrowsedMediaPlayer(bdaddr).setBrowsed(
+                    packageName, browseService);
+        } else {
+            Log.w(TAG, "No Browser service available for " + packageName);
+            status = false;
+        }
+
+        if (DEBUG) Log.d(TAG, "startBrowseService for packageName: " + packageName +
+                ", status = " + status);
+        return status;
+    }
+
+    private synchronized String getBrowseServiceName(String packageName) {
+        String browseServiceName = "";
+
+        // getting the browse service name from browse player info
+        int browseInfoID = getBrowseId(packageName);
+        if (browseInfoID != -1) {
+            browseServiceName = mBrowsePlayerInfoList.get(browseInfoID).serviceClass;
+        }
+
+        if (DEBUG) Log.d(TAG, "getBrowseServiceName for packageName: " + packageName +
+                ", browseServiceName = " + browseServiceName);
+        return browseServiceName;
+    }
+
+    /*
+     * utility function to build list of browsable players identified from
+     * browse service implementation.
+     */
+    private synchronized void buildBrowsablePlayersList() {
+        if (DEBUG) Log.i(TAG, "buildBrowsablePlayersList()");
+
+        // Clearing old browsable player's list
+        mBrowsePlayerInfoList.clear();
+
+        Intent intent = new Intent(android.service.media.MediaBrowserService.SERVICE_INTERFACE);
+        List<ResolveInfo> resInfos = mPackageManager.queryIntentServices(intent, 0);
+
+        for (ResolveInfo resolveInfo : resInfos) {
+            String displayableName = resolveInfo.loadLabel(mPackageManager).toString();
+            String serviceName = resolveInfo.serviceInfo.name;
+            String packageName = resolveInfo.serviceInfo.packageName;
+
+            BrowsePlayerInfo infoObj = new BrowsePlayerInfo(packageName, displayableName,
+                    serviceName);
+            if (DEBUG)
+                Log.d(TAG, infoObj.toString());
+            mBrowsePlayerInfoList.add(infoObj);
+        }
+
+        if (DEBUG) Log.i(TAG, "buildBrowsablePlayersList: found " + resInfos.size() + " players");
+    }
+
+    /* initializing media player info list and prepare media player response object */
+    private void buildMediaPlayersList() {
+
+        initMediaPlayersInfoList();
+        mMPLObj = prepareMediaPlayerRspObj();
+
+        if (mMPLObj.mNumItems > 0) {
+            // Setting player which is on the Top (id=1) of the list as an Addressed player
+            updateCurrentController(1, -1);
+        } else {
+            Log.i(TAG, "No players available in the media players list");
+            /* If there are no players available in the media players list, meaning none of the
+             * players are yet open, so no active players are in the list. But in this case none
+             * of the AVRCP player related commands can be satisfied. So, launching first available
+             * browsable player service to avail atleast one player to do AVRCP operations. */
+            /* Starting media player service */
+            if ((mBrowsePlayerInfoList != null) && (mBrowsePlayerInfoList.size()!=0)) {
+                BrowsePlayerInfo player = mBrowsePlayerInfoList.get(0);
+                Intent intent = new Intent();
+                intent.setComponent(new ComponentName(player.packageName, player.serviceClass));
+                Log.i(TAG, "Starting service:" + player.packageName + ", " + player.serviceClass);
+                mContext.startService(intent);
+            } else {
+                Log.e(TAG, "Opening player to support AVRCP operations failed, " +
+                        "No browsable players available!");
+            }
+        }
+
+    }
+
+    /*
+     * utility function to build list of active media players identified from
+     * session manager by getting the active sessions
+     */
+    private synchronized void initMediaPlayersInfoList() {
+        if (DEBUG) Log.v(TAG, "initMediaPlayersInfoList");
+
+        // Clearing old browsable player's list
+        mMediaPlayerInfoList.clear();
+
+        /* Initializing all media players */
+        for (MediaController mediaController : getActiveControllersList()) {
+            initMediaPlayer(mediaController);
+        }
+    }
+
+    /* Using session manager apis, getting the list of active media controllers */
+    private List<MediaController> getActiveControllersList() {
+        List<MediaController> controllersList = new ArrayList<MediaController>();
+        controllersList = mMediaSessionManager.getActiveSessions(null);
+        Log.i(TAG, "getActiveControllersList: " + controllersList.size() + " controllers");
+        return controllersList;
+    }
+
+    /*
+     * utility function to initialize media players info and add them to global
+     * media player info list
+     */
+    private synchronized void initMediaPlayer(MediaController mediaController) {
+
+        String packageName = mediaController.getPackageName();
+
+        MediaPlayerInfo mMediaPlayerInfo = new MediaPlayerInfo(packageName,
+                AvrcpConstants.PLAYER_TYPE_AUDIO, AvrcpConstants.PLAYER_SUBTYPE_NONE,
+                getPlayBackState(mediaController), getFeatureBitMask(packageName),
+                getAppLabel(packageName), mediaController);
+
+        if (DEBUG) Log.d(TAG, mMediaPlayerInfo.toString());
+
+        mMediaPlayerInfoList.add(mMediaPlayerInfo);
+    }
+
+    /*
+     * utility function to get the playback state of any media player through
+     * media controller APIs.
+     */
+    private byte getPlayBackState(MediaController mediaController) {
+        PlaybackState pbState = mediaController.getPlaybackState();
+        byte playStateBytes = PLAYSTATUS_STOPPED;
+
+        if (pbState != null) {
+            playStateBytes = (byte)convertPlayStateToBytes(pbState.getState());
+            Log.v(TAG, "getPlayBackState: playStateBytes = " + playStateBytes);
+        } else {
+            Log.w(TAG, "playState object null, sending playStateBytes = " + playStateBytes);
+        }
+
+        return playStateBytes;
+    }
+
+    /*
+     * utility function to map framework's play state values to AVRCP spec
+     * defined play status values
+     */
+    private int convertPlayStateToBytes(int playState) {
+        switch (playState) {
+            case PlaybackState.STATE_PLAYING:
+            case PlaybackState.STATE_BUFFERING:
+                return PLAYSTATUS_PLAYING;
+
+            case PlaybackState.STATE_STOPPED:
+            case PlaybackState.STATE_NONE:
+            case PlaybackState.STATE_CONNECTING:
+                return PLAYSTATUS_STOPPED;
+
+            case PlaybackState.STATE_PAUSED:
+                return PLAYSTATUS_PAUSED;
+
+            case PlaybackState.STATE_FAST_FORWARDING:
+            case PlaybackState.STATE_SKIPPING_TO_NEXT:
+            case PlaybackState.STATE_SKIPPING_TO_QUEUE_ITEM:
+                return PLAYSTATUS_FWD_SEEK;
+
+            case PlaybackState.STATE_REWINDING:
+            case PlaybackState.STATE_SKIPPING_TO_PREVIOUS:
+                return PLAYSTATUS_REV_SEEK;
+
+            case PlaybackState.STATE_ERROR:
+            default:
+                return PLAYSTATUS_ERROR;
+        }
+    }
+
+    /*
+     * utility function to get the feature bit mask of any media player through
+     * package name
+     */
+    private short[] getFeatureBitMask(String packageName) {
+
+        ArrayList<Short> featureBitsList = new ArrayList<Short>();
+
+        /* adding default feature bits */
+        featureBitsList.add(AvrcpConstants.AVRC_PF_PLAY_BIT_NO);
+        featureBitsList.add(AvrcpConstants.AVRC_PF_STOP_BIT_NO);
+        featureBitsList.add(AvrcpConstants.AVRC_PF_PAUSE_BIT_NO);
+        featureBitsList.add(AvrcpConstants.AVRC_PF_REWIND_BIT_NO);
+        featureBitsList.add(AvrcpConstants.AVRC_PF_FAST_FWD_BIT_NO);
+        featureBitsList.add(AvrcpConstants.AVRC_PF_FORWARD_BIT_NO);
+        featureBitsList.add(AvrcpConstants.AVRC_PF_BACKWARD_BIT_NO);
+        featureBitsList.add(AvrcpConstants.AVRC_PF_ADV_CTRL_BIT_NO);
+
+        /* Add/Modify browse player supported features. */
+        if (isBrowseSupported(packageName)) {
+            featureBitsList.add(AvrcpConstants.AVRC_PF_BROWSE_BIT_NO);
+            featureBitsList.add(AvrcpConstants.AVRC_PF_UID_UNIQUE_BIT_NO);
+            featureBitsList.add(AvrcpConstants.AVRC_PF_NOW_PLAY_BIT_NO);
+            featureBitsList.add(AvrcpConstants.AVRC_PF_GET_NUM_OF_ITEMS_BIT_NO);
+        }
+
+        // converting arraylist to array for response
+        short[] featureBitsArray = new short[featureBitsList.size()];
+
+        for (int i = 0; i < featureBitsList.size(); i++) {
+            featureBitsArray[i] = featureBitsList.get(i).shortValue();
+        }
+
+        return featureBitsArray;
+    }
+
+    /**
+     * Checks the Package name if it supports Browsing or not.
+     *
+     * @param packageName - name of the package to get the Id.
+     * @return true if it supports browsing, else false.
+     */
+    private synchronized boolean isBrowseSupported(String packageName) {
+        boolean response = false;
+
+        /* check if Browsable Player's list contains this package name */
+        for (BrowsePlayerInfo info : mBrowsePlayerInfoList) {
+            if (info.packageName.equals(packageName)) {
+                // TODO: (apanicke) Currently browsing isn't implemented
+                // properly and causes metadata to break. Fix browsing
+                // interface and change this to true.
+                response = false;
+            }
+        }
+
+        if (DEBUG) Log.v(TAG, "isBrowseSupported for " + packageName + ": " + response);
+        return response;
+    }
+
+    /* from the global object, getting the current addressed player's package name */
+    private String getCurrentAddrPlayer() {
+        String addrPlayerPackage = "";
+
+        if (!isCurrentMediaPlayerListEmpty() && isIdValid(mCurrAddrPlayerID)) {
+            addrPlayerPackage = mMPLObj.mPackageNameList[mCurrAddrPlayerID - 1];
+            if (DEBUG) Log.v(TAG, "Current Addressed Player's Package: " + addrPlayerPackage);
+        } else {
+            Log.w(TAG, "current addressed player is not yet set.");
+        }
+        return addrPlayerPackage;
+    }
+
+    private String getPackageName(int id) {
+        String packageName = "";
+
+        if (!isCurrentMediaPlayerListEmpty() && isIdValid(id)) {
+            packageName = mMPLObj.mPackageNameList[id - 1];
+            if (DEBUG) Log.v(TAG, "Current Player's Package: " + packageName);
+        } else {
+            Log.w(TAG, "Current media player is empty or id is invalid");
+        }
+        return packageName;
+    }
+
+    /* from the global object, getting the current browsed player's package name */
+    private String getCurrentBrowsedPlayer(byte[] bdaddr) {
+        String browsedPlayerPackage = "";
+
+        Map<String, BrowsedMediaPlayer> connList = mAvrcpBrowseManager.getConnList();
+        String bdaddrStr = new String(bdaddr);
+        if(connList.containsKey(bdaddrStr)){
+            browsedPlayerPackage = connList.get(bdaddrStr).getPackageName();
+        }
+        if (DEBUG) Log.v(TAG, "getCurrentBrowsedPlayerPackage: " + browsedPlayerPackage);
+        return browsedPlayerPackage;
+    }
+
+    /*
+     * utility function to get the media controller from the current addressed
+     * player id, can return null in error cases
+     */
+    private synchronized MediaController getCurrentMediaController() {
+        MediaController mediaController = null;
+
+        if (mMediaPlayerInfoList == null || mMediaPlayerInfoList.isEmpty()) {
+            Log.w(TAG, " No available players , sending response back ");
+            return mediaController;
+        }
+
+        if (!isIdValid(mCurrAddrPlayerID)) {
+            Log.w(TAG, "CurrPlayerID is not yet set:" + mCurrAddrPlayerID + ", PlayerList length="
+                    + mMediaPlayerInfoList.size() + " , sending response back");
+            return mediaController;
+        }
+
+        mediaController = mMediaPlayerInfoList.get(mCurrAddrPlayerID - 1).getMediaController();
+
+        if (mediaController != null) {
+            if (DEBUG)
+                Log.v(TAG, "getCurrentMediaController: " + mediaController.getPackageName());
+        }
+
+        return mediaController;
+    }
+
+    /*
+     * Utility function to get the Media player info from package name returns
+     * null if package name not found in media players list
+     */
+    private synchronized MediaPlayerInfo getMediaPlayerInfo(String packageName) {
+        if (DEBUG) Log.v(TAG, "getMediaPlayerInfo: " + packageName);
+        if (mMediaPlayerInfoList.size() > 0) {
+            for (MediaPlayerInfo info : mMediaPlayerInfoList) {
+                if (packageName.equals(info.getPackageName())) {
+                    if (DEBUG) Log.v(TAG, "Found " + info.getPackageName());
+                    return info;
+                }
+            }
+        } else {
+            if (DEBUG) Log.v(TAG, "Media players list empty");
+        }
+        return null;
+    }
+
+    /* prepare media list & return the media player list response object */
+    private synchronized MediaPlayerListRsp prepareMediaPlayerRspObj() {
+
+        /* Forming player list -- */
+        int numPlayers = mMediaPlayerInfoList.size();
+
+        byte[] playerTypes = new byte[numPlayers];
+        int[] playerSubTypes = new int[numPlayers];
+        String[] displayableNameArray = new String[numPlayers];
+        String[] packageNameArray = new String[numPlayers];
+        byte[] playStatusValues = new byte[numPlayers];
+        short[] featureBitMaskValues = new short[numPlayers
+                * AvrcpConstants.AVRC_FEATURE_MASK_SIZE];
+        List<MediaController> mediaControllerList = new ArrayList<MediaController>();
+
+        int playerId = 0;
+        for (MediaPlayerInfo info : mMediaPlayerInfoList) {
+            playerTypes[playerId] = info.getMajorType();
+            playerSubTypes[playerId] = info.getSubType();
+            packageNameArray[playerId] = info.getPackageName();
+            displayableNameArray[playerId] = info.getDisplayableName();
+            playStatusValues[playerId] = info.getPlayStatus();
+            mediaControllerList.add(info.getMediaController());
+
+            for (int numBit = 0; numBit < info.getFeatureBitMask().length; numBit++) {
+                /* gives which octet this belongs to */
+                byte octet = (byte) (info.getFeatureBitMask()[numBit] / 8);
+                /* gives the bit position within the octet */
+                byte bit = (byte) (info.getFeatureBitMask()[numBit] % 8);
+                featureBitMaskValues[(playerId * AvrcpConstants.AVRC_FEATURE_MASK_SIZE) + octet] |=
+                        (1 << bit);
+            }
+
+            /* printLogs */
+            if (DEBUG) {
+                Log.d(TAG, "\n   +++ Player " + playerId + " +++   ");
+                Log.d(TAG, "display Name[" + playerId + "]: " + displayableNameArray[playerId]);
+                Log.d(TAG, "Package Name[" + playerId + "]: " + packageNameArray[playerId]);
+                Log.d(TAG, "player Types[" + playerId + "]: " + playerTypes[playerId]);
+                Log.d(TAG, "Play Status Value[" + playerId + "]: " + playStatusValues[playerId]);
+                Log.d(TAG, "\n");
+            }
+
+            playerId++;
+        }
+
+        if (DEBUG) Log.d(TAG, "prepareMediaPlayerRspObj: numPlayers = " + numPlayers);
+
+        return new MediaPlayerListRsp(AvrcpConstants.RSP_NO_ERROR, sUIDCounter,
+                numPlayers, AvrcpConstants.BTRC_ITEM_PLAYER, playerTypes, playerSubTypes,
+                playStatusValues, featureBitMaskValues,
+                displayableNameArray, packageNameArray, mediaControllerList);
+
+    }
+
+     /* build media player list and send it to remote. */
+    private void handleMediaPlayerListRsp(AvrcpCmd.FolderItemsCmd folderObj) {
+        if (folderObj.mStartItem >= mMPLObj.mNumItems) {
+            Log.i(TAG, "handleMediaPlayerListRsp: start item = " + folderObj.mStartItem +
+                    ", but available num of items = " + mMPLObj.mNumItems);
+            mediaPlayerListRspNative(folderObj.mAddress, AvrcpConstants.RSP_INV_RANGE,
+                    (short) 0, (byte) 0, 0, null, null, null, null, null);
+        } else {
+            if (DEBUG) Log.d(TAG, "handleMediaPlayerListRsp: num items = " + mMPLObj.mNumItems);
+            sendFolderItems(mMPLObj, folderObj.mAddress);
+        }
+    }
+
+    /* unregister to the old controller, update new IDs and register to the new controller */
+    private boolean updateCurrentController(int addrId, int browseId) {
+        boolean registerRsp = true;
+
+        if (!unregOldMediaControllerCb()) {
+            Log.d(TAG, "unregisterOldMediaControllerCallback return false");
+        }
+
+        updateNewIds(addrId, browseId);
+        if (!regNewMediaControllerCb()) {
+            Log.d(TAG, "registerOldMediaControllerCallback return false");
+            registerRsp = false;
+        }
+
+        if (DEBUG) Log.d(TAG, "updateCurrentController: registerRsp = " + registerRsp);
+        return registerRsp;
+    }
+
+    /* get the current media controller and unregister for the media controller callback */
+    private boolean unregOldMediaControllerCb() {
+        boolean isUnregistered = false;
+
+        // unregistering callback for old media controller.
+        MediaController oldController = getCurrentMediaController();
+        if (oldController != null) {
+            oldController.unregisterCallback(mMediaControllerCb);
+            isUnregistered = true;
+        } else {
+            Log.i(TAG, "old controller is null, addressPlayerId:" + mCurrAddrPlayerID);
+        }
+
+        if (DEBUG) Log.d(TAG, "unregOldMediaControllerCb: isUnregistered = " + isUnregistered);
+        return isUnregistered;
+    }
+
+    /* get the current media controller and register for the media controller callback */
+    private boolean regNewMediaControllerCb() {
+        // registering callback for new media controller.
+        MediaController newController = getCurrentMediaController();
+        mMediaController = newController;
+
+        String name = (mMediaController == null) ? "null" : mMediaController.getPackageName();
+        Log.v(TAG, "MediaController changed to " + name);
+
+        if (mMediaController == null) {
+            Log.i(TAG, "new controller is null, addressPlayerId:" + mCurrAddrPlayerID);
+            updateMetadata(null);
+            mAddressedMediaPlayer.updateNowPlayingList(null);
+            return false;
+        }
+
+        mMediaController.registerCallback(mMediaControllerCb, mHandler);
+        updateMetadata(mMediaController.getMetadata());
+        mAddressedMediaPlayer.updateNowPlayingList(mMediaController.getQueue());
+        return true;
+    }
+
+    /* Handle getfolderitems for scope = VFS, Search, NowPlayingList */
+    private void handleGetFolderItemBrowseResponse(AvrcpCmd.FolderItemsCmd folderObj, byte[] bdaddr) {
+        int status = AvrcpConstants.RSP_NO_ERROR;
+
+        /* Browsed player is already set */
+        switch (folderObj.mScope) {
+            case AvrcpConstants.BTRC_SCOPE_FILE_SYSTEM:
+                if (mAvrcpBrowseManager.getBrowsedMediaPlayer(bdaddr) != null) {
+                    mAvrcpBrowseManager.getBrowsedMediaPlayer(bdaddr).getFolderItemsVFS(folderObj);
+                } else {
+                    /* No browsed player set. Browsed player should be set by CT before performing browse.*/
+                    Log.e(TAG, "handleGetFolderItemBrowseResponse: mBrowsedMediaPlayer is null");
+                    status = AvrcpConstants.RSP_INTERNAL_ERR;
+                }
+                break;
+
+            case AvrcpConstants.BTRC_SCOPE_NOW_PLAYING:
+                mAddressedMediaPlayer.getFolderItemsNowPlaying(bdaddr, folderObj, mMediaController);
+                break;
+
+            default:
+                /* invalid scope */
+                Log.e(TAG, "handleGetFolderItemBrowseResponse:invalid scope");
+                status = AvrcpConstants.RSP_INV_SCOPE;
+        }
+
+
+        if (status != AvrcpConstants.RSP_NO_ERROR) {
+            getFolderItemsRspNative(bdaddr, status, (short) 0, (byte) 0x00, 0, null, null, null,
+                null, null, null, null, null);
+        }
+
+    }
+
+    /* utility function to update the global values of current Addressed and browsed player */
+    private synchronized void updateNewIds(int addrId, int browseId) {
+        mCurrAddrPlayerID = addrId;
+        mCurrBrowsePlayerID = browseId;
+
+        if (DEBUG) Log.v(TAG, "Updated CurrentIds: AddrPlayerID:" + mCurrAddrPlayerID + " to "
+                + addrId + ", BrowsePlayerID:" + mCurrBrowsePlayerID + " to " + browseId);
+    }
+
+    /* Getting the application's displayable name from package name */
+    private String getAppLabel(String packageName) {
+        ApplicationInfo appInfo = null;
+        try {
+            appInfo = mPackageManager.getApplicationInfo(packageName, 0);
+        } catch (NameNotFoundException e) {
+            e.printStackTrace();
+        }
+
+        return (String) (appInfo != null ? mPackageManager
+                .getApplicationLabel(appInfo) : "Unknown");
+    }
+
+    private void sendFolderItems(MediaPlayerListRsp rspObj, byte[] bdaddr) {
+        mediaPlayerListRspNative(bdaddr, rspObj.mStatus, rspObj.mUIDCounter, rspObj.itemType,
+                rspObj.mNumItems, rspObj.mPlayerTypes, rspObj.mPlayerSubTypes,
+                rspObj.mPlayStatusValues, rspObj.mFeatureBitMaskValues, rspObj.mPlayerNameList);
+    }
+
+    private void handlePlayItemResponse(byte[] bdaddr, byte[] uid, byte scope) {
+
+        if(scope == AvrcpConstants.BTRC_SCOPE_NOW_PLAYING) {
+            mAddressedMediaPlayer.playItem(bdaddr, uid, scope, mMediaController);
+        }
+        else {
+            if(!isAddrPlayerSameAsBrowsed(bdaddr)) {
+                Log.w(TAG, "Remote requesting play item on uid which may not be recognized by" +
+                        "current addressed player");
+                playItemRspNative(bdaddr, AvrcpConstants.RSP_INV_ITEM);
+            }
+
+            if (mAvrcpBrowseManager.getBrowsedMediaPlayer(bdaddr) != null) {
+                mAvrcpBrowseManager.getBrowsedMediaPlayer(bdaddr).playItem(uid, scope);
+            } else {
+                Log.e(TAG, "handlePlayItemResponse: Remote requested playitem " +
+                        "before setbrowsedplayer");
+                playItemRspNative(bdaddr, AvrcpConstants.RSP_INTERNAL_ERR);
+            }
+        }
+    }
+
+    private void handleGetItemAttr(AvrcpCmd.ItemAttrCmd itemAttr) {
+        if(itemAttr.mScope == AvrcpConstants.BTRC_SCOPE_NOW_PLAYING) {
+            mAddressedMediaPlayer.getItemAttr(itemAttr.mAddress, itemAttr, mMediaController);
+        }
+        else {
+            if (mAvrcpBrowseManager.getBrowsedMediaPlayer(itemAttr.mAddress) != null)
+                mAvrcpBrowseManager.getBrowsedMediaPlayer(itemAttr.mAddress).getItemAttr(itemAttr);
+            else {
+                Log.e(TAG, "Could not get attributes. mBrowsedMediaPlayer is null");
+                getItemAttrRspNative(itemAttr.mAddress, AvrcpConstants.RSP_INTERNAL_ERR,
+                        (byte) 0, null, null);
+            }
+        }
+    }
+
+    private void handleGetTotalNumOfItemsResponse(byte[] bdaddr, byte scope) {
+        // for scope as media player list
+        if (scope == AvrcpConstants.BTRC_SCOPE_PLAYER_LIST) {
+            int numPlayers = getPlayerListSize();
+            if (DEBUG) Log.d(TAG, "handleGetTotalNumOfItemsResponse: sending total " + numPlayers +
+                    " media players.");
+            getTotalNumOfItemsRspNative(bdaddr, AvrcpConstants.RSP_NO_ERROR, 0,
+                    numPlayers);
+        } else if(scope == AvrcpConstants.BTRC_SCOPE_NOW_PLAYING) {
+            mAddressedMediaPlayer.getTotalNumOfItems(bdaddr, scope, mMediaController);
+        } else {
+            // for FileSystem browsing scopes as VFS, Now Playing
+            if (mAvrcpBrowseManager.getBrowsedMediaPlayer(bdaddr) != null) {
+                mAvrcpBrowseManager.getBrowsedMediaPlayer(bdaddr).getTotalNumOfItems(scope);
+            } else {
+                Log.e(TAG, "Could not get Total NumOfItems. mBrowsedMediaPlayer is null");
+                getTotalNumOfItemsRspNative(bdaddr, AvrcpConstants.RSP_INTERNAL_ERR, 0, 0);
+            }
+        }
+
+    }
+
+    private synchronized int getPlayerListSize() {
+        return mMediaPlayerInfoList.size();
+    }
+
+    private synchronized int getBrowsePlayersListSize() {
+        return mBrowsePlayerInfoList.size();
+    }
+
+    /* check if browsed player and addressed player are same */
+    private boolean isAddrPlayerSameAsBrowsed(byte[] bdaddr) {
+        boolean isSame = true;
+        String browsedPlayer = getCurrentBrowsedPlayer(bdaddr);
+        String addressedPlayer = getCurrentAddrPlayer();
+
+        if (!isPackageNameValid(browsedPlayer)) {
+            Log.w(TAG, "Browsed player name empty");
+            isSame = false;
+        } else if (!addressedPlayer.equals(browsedPlayer)) {
+            Log.w(TAG, browsedPlayer + " is not current Addressed Player : "
+                    + addressedPlayer);
+            isSame = false;
+        }
+
+        if (DEBUG) Log.d(TAG, "isAddrPlayerSameAsBrowsed: isSame = " + isSame);
+        return isSame;
+    }
+
+    /* checks if global object containing media player list is empty */
+    private boolean isCurrentMediaPlayerListEmpty() {
+        boolean isEmpty = (mMPLObj == null || mMPLObj.mPackageNameList == null
+                || mMPLObj.mPackageNameList.length == 0 || mMediaPlayerInfoList.isEmpty());
+        if (DEBUG) Log.d(TAG, "Current MediaPlayer List Empty.= " + isEmpty);
+        return isEmpty;
+    }
+
+    /* checks if the id is within the range of global object containing media player list */
+    private boolean isIdValid(int id) {
+        boolean isValid = (id > 0 && id <= mMPLObj.mPackageNameList.length);
+        if (DEBUG) Log.d(TAG, "Id = " + id + "isIdValid = " + isValid);
+        return isValid;
+    }
+
+    /* checks if package name is not null or empty */
+    private boolean isPackageNameValid(String browsedPackage) {
+        boolean isValid = (browsedPackage != null && browsedPackage.length() > 0);
+        if (DEBUG) Log.d(TAG, "isPackageNameValid: browsedPackage = " + browsedPackage +
+                "isValid = " + isValid);
+        return isValid;
+    }
+
+    /* checks if selected addressed player is already addressed */
+    private boolean isPlayerAlreadyAddressed(int selectedId) {
+        // checking if selected ID is same as the current addressed player id
+        boolean isAddressed = (mCurrAddrPlayerID == selectedId);
+        if (DEBUG) Log.d(TAG, "isPlayerAlreadyAddressed: isAddressed = " + isAddressed);
+        return isAddressed;
+    }
+
+    public void dump(StringBuilder sb) {
+        sb.append("AVRCP:\n");
+        ProfileService.println(sb, "mMediaAttributes: " + mMediaAttributes);
+        ProfileService.println(sb, "mTransportControlFlags: " + mTransportControlFlags);
+        ProfileService.println(sb, "mCurrentPlayState: " + mCurrentPlayState);
+        ProfileService.println(sb, "mLastStateUpdate: " + mLastStateUpdate);
+        ProfileService.println(sb, "mPlayStatusChangedNT: " + mPlayStatusChangedNT);
+        ProfileService.println(sb, "mTrackChangedNT: " + mTrackChangedNT);
+        ProfileService.println(sb, "mTrackNumber: " + mTrackNumber);
+        ProfileService.println(sb, "mSongLengthMs: " + mSongLengthMs);
+        ProfileService.println(sb, "mPlaybackIntervalMs: " + mPlaybackIntervalMs);
+        ProfileService.println(sb, "mPlayPosChangedNT: " + mPlayPosChangedNT);
+        ProfileService.println(sb, "mNextPosMs: " + mNextPosMs);
+        ProfileService.println(sb, "mPrevPosMs: " + mPrevPosMs);
+        ProfileService.println(sb, "mSkipStartTime: " + mSkipStartTime);
+        ProfileService.println(sb, "mFeatures: " + mFeatures);
+        ProfileService.println(sb, "mRemoteVolume: " + mRemoteVolume);
+        ProfileService.println(sb, "mLastRemoteVolume: " + mLastRemoteVolume);
+        ProfileService.println(sb, "mLastDirection: " + mLastDirection);
+        ProfileService.println(sb, "mVolumeStep: " + mVolumeStep);
+        ProfileService.println(sb, "mAudioStreamMax: " + mAudioStreamMax);
+        ProfileService.println(sb, "mVolCmdAdjustInProgress: " + mVolCmdAdjustInProgress);
+        ProfileService.println(sb, "mVolCmdSetInProgress: " + mVolCmdSetInProgress);
+        ProfileService.println(sb, "mAbsVolRetryTimes: " + mAbsVolRetryTimes);
+        ProfileService.println(sb, "mSkipAmount: " + mSkipAmount);
+        ProfileService.println(sb, "mVolumeMapping: " + mVolumeMapping.toString());
+        if (mMediaController != null)
+            ProfileService.println(sb, "mMediaSession pkg: " + mMediaController.getPackageName());
+    }
+
+    public class AvrcpBrowseManager {
+        Map<String, BrowsedMediaPlayer> connList = new HashMap<String, BrowsedMediaPlayer>();
+        private AvrcpMediaRspInterface mMediaInterface;
+        private Context mContext;
+
+        public AvrcpBrowseManager(Context context, AvrcpMediaRspInterface mediaInterface) {
+            mContext = context;
+            mMediaInterface = mediaInterface;
+        }
+
+        public void cleanup() {
+            Iterator entries = connList.entrySet().iterator();
+            while (entries.hasNext()) {
+                Map.Entry entry = (Map.Entry) entries.next();
+                BrowsedMediaPlayer browsedMediaPlayer = (BrowsedMediaPlayer) entry.getValue();
+                if (browsedMediaPlayer != null) {
+                    browsedMediaPlayer.cleanup();
+                }
+            }
+            // clean up the map
+            connList.clear();
+        }
+
+        public void cleanupConn(BluetoothDevice device) {
+            if(null == device)
+                return;
+            String bdaddr = new String(hexStringToByteArray(device.getAddress().replace(":","")));
+            /* check to see remote device performed setBrowsedPlayer */
+            if(connList.containsKey(bdaddr)) {
+                BrowsedMediaPlayer browsedMediaPlayer = connList.get(bdaddr);
+                /* cleanup browsing connection to media player for disconnected remote device */
+                if(browsedMediaPlayer != null)
+                    browsedMediaPlayer.cleanup();
+                /* remove bdaddr of disconnected device */
+                connList.remove(bdaddr);
+            }
+        }
+
+        // get the a free media player interface based on the passed bd address
+        // if the no items is found for the passed media player then it assignes a
+        // available media player interface
+        public BrowsedMediaPlayer getBrowsedMediaPlayer(byte[] bdaddr) {
+            BrowsedMediaPlayer mediaPlayer;
+            String bdaddrStr = new String(bdaddr);
+            if(connList.containsKey(bdaddrStr)){
+                mediaPlayer = connList.get(bdaddrStr);
+            } else {
+                mediaPlayer = new BrowsedMediaPlayer(bdaddr, mContext, mMediaInterface);
+                connList.put(bdaddrStr, mediaPlayer);
+            }
+            return mediaPlayer;
+        }
+
+        // clears the details pertaining to passed bdaddres
+        public boolean clearBrowsedMediaPlayer(byte[] bdaddr) {
+            String bdaddrStr = new String(bdaddr);
+            if(connList.containsKey(bdaddrStr)) {
+                connList.remove(bdaddrStr);
+                return true;
+            }
+            return false;
+        }
+
+        public Map<String, BrowsedMediaPlayer> getConnList() {
+            return connList;
+        }
+
+        /* Helper function to convert colon separated bdaddr to byte string */
+        private byte[] hexStringToByteArray(String s) {
+            int len = s.length();
+            byte[] data = new byte[len / 2];
+            for (int i = 0; i < len; i += 2) {
+                data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
+                        + Character.digit(s.charAt(i+1), 16));
+            }
+            return data;
+        }
+    }
+
+    /*
+     * private class which handles responses from AvrcpMediaManager. Maps responses to native
+     * responses. This class implements the AvrcpMediaRspInterface interface.
+     */
+    private class AvrcpMediaRsp implements AvrcpMediaRspInterface {
+        private static final String TAG = "AvrcpMediaRsp";
+
+        public void setAddrPlayerRsp(byte[] address, int rspStatus) {
+            if (!setAddressedPlayerRspNative(address, rspStatus)) {
+                Log.e(TAG, "setAddrPlayerRsp failed!");
+            }
+        }
+
+        public void setBrowsedPlayerRsp(byte[] address, int rspStatus, byte depth, int numItems,
+                String[] textArray) {
+            if (!setBrowsedPlayerRspNative(address, rspStatus, depth, numItems, textArray)) {
+                Log.e(TAG, "setBrowsedPlayerRsp failed!");
+            }
+        }
+
+        public void mediaPlayerListRsp(byte[] address, int rspStatus, MediaPlayerListRsp rspObj) {
+            if (rspObj != null && rspStatus == AvrcpConstants.RSP_NO_ERROR) {
+                if (!mediaPlayerListRspNative(address, rspStatus, sUIDCounter, rspObj.itemType,
+                        rspObj.mNumItems, rspObj.mPlayerTypes, rspObj.mPlayerSubTypes,
+                        rspObj.mPlayStatusValues, rspObj.mFeatureBitMaskValues,
+                        rspObj.mPlayerNameList))
+                        Log.e(TAG, "mediaPlayerListRsp failed!");
+            } else {
+                Log.e(TAG, "mediaPlayerListRsp: rspObj is null");
+                if (!mediaPlayerListRspNative(address, rspStatus, sUIDCounter,
+                        (byte)0x00, 0, null, null, null, null, null))
+                        Log.e(TAG, "mediaPlayerListRsp failed!");
+            }
+        }
+
+        public void folderItemsRsp(byte[] address, int rspStatus, FolderItemsRsp rspObj) {
+            if (rspObj != null && rspStatus == AvrcpConstants.RSP_NO_ERROR) {
+                if (!getFolderItemsRspNative(address, rspStatus, sUIDCounter, rspObj.mScope,
+                        rspObj.mNumItems, rspObj.mFolderTypes, rspObj.mPlayable, rspObj.mItemTypes,
+                        rspObj.mItemUid, rspObj.mDisplayNames, rspObj.mAttributesNum,
+                        rspObj.mAttrIds, rspObj.mAttrValues))
+                    Log.e(TAG, "getFolderItemsRspNative failed!");
+            } else {
+                Log.e(TAG, "folderItemsRsp: rspObj is null or rspStatus is error:" + rspStatus);
+                if (!getFolderItemsRspNative(address, rspStatus, sUIDCounter, (byte) 0x00, 0,
+                        null, null, null, null, null, null, null, null))
+                    Log.e(TAG, "getFolderItemsRspNative failed!");
+            }
+
+        }
+
+        public void changePathRsp(byte[] address, int rspStatus, int numItems) {
+            if (!changePathRspNative(address, rspStatus, numItems))
+                Log.e(TAG, "changePathRspNative failed!");
+        }
+
+        public void getItemAttrRsp(byte[] address, int rspStatus, ItemAttrRsp rspObj) {
+            if (rspObj != null && rspStatus == AvrcpConstants.RSP_NO_ERROR) {
+                if (!getItemAttrRspNative(address, rspStatus, rspObj.mNumAttr,
+                        rspObj.mAttributesIds, rspObj.mAttributesArray))
+                    Log.e(TAG, "getItemAttrRspNative failed!");
+            } else {
+                Log.e(TAG, "getItemAttrRsp: rspObj is null or rspStatus is error:" + rspStatus);
+                if (!getItemAttrRspNative(address, rspStatus, (byte) 0x00, null, null))
+                    Log.e(TAG, "getItemAttrRspNative failed!");
+            }
+        }
+
+        public void playItemRsp(byte[] address, int rspStatus) {
+            if (!playItemRspNative(address, rspStatus)) {
+                Log.e(TAG, "playItemRspNative failed!");
+            }
+        }
+
+        public void getTotalNumOfItemsRsp(byte[] address, int rspStatus, int uidCounter,
+                int numItems) {
+            if (!getTotalNumOfItemsRspNative(address, rspStatus, sUIDCounter, numItems)) {
+                Log.e(TAG, "getTotalNumOfItemsRspNative failed!");
+            }
+        }
+
+        public void addrPlayerChangedRsp(byte[] address, int type, int playerId, int uidCounter) {
+            if (!registerNotificationRspAddrPlayerChangedNative(type, playerId, sUIDCounter)) {
+                Log.e(TAG, "registerNotificationRspAddrPlayerChangedNative failed!");
+            }
+        }
+
+        public void avalPlayerChangedRsp(byte[] address, int type) {
+            if (!registerNotificationRspAvalPlayerChangedNative(type)) {
+                Log.e(TAG, "registerNotificationRspAvalPlayerChangedNative failed!");
+            }
+        }
+
+        public void uidsChangedRsp(byte[] address, int type, int uidCounter) {
+            if (!registerNotificationRspUIDsChangedNative(type, sUIDCounter)) {
+                Log.e(TAG, "registerNotificationRspUIDsChangedNative failed!");
+            }
+        }
+
+        public void nowPlayingChangedRsp(int type) {
+            if (!registerNotificationRspNowPlayingChangedNative(type)) {
+                Log.e(TAG, "registerNotificationRspNowPlayingChangedNative failed!");
+            }
+        }
+
+        public void trackChangedRsp(int type, byte[] uid) {
+            if (!registerNotificationRspTrackChangeNative(type, uid)) {
+                Log.e(TAG, "registerNotificationRspTrackChangeNative failed!");
+            }
+        }
+    }
+
+    /* getters for some private variables */
+    public AvrcpBrowseManager getAvrcpBrowseManager() {
+        return mAvrcpBrowseManager;
+    }
+
+    // Do not modify without updating the HAL bt_rc.h files.
+
+    // match up with btrc_play_status_t enum of bt_rc.h
+    final static int PLAYSTATUS_STOPPED = 0;
+    final static int PLAYSTATUS_PLAYING = 1;
+    final static int PLAYSTATUS_PAUSED = 2;
+    final static int PLAYSTATUS_FWD_SEEK = 3;
+    final static int PLAYSTATUS_REV_SEEK = 4;
+    final static int PLAYSTATUS_ERROR = 255;
+
+    // match up with btrc_media_attr_t enum of bt_rc.h
+    final static int MEDIA_ATTR_TITLE = 1;
+    final static int MEDIA_ATTR_ARTIST = 2;
+    final static int MEDIA_ATTR_ALBUM = 3;
+    final static int MEDIA_ATTR_TRACK_NUM = 4;
+    final static int MEDIA_ATTR_NUM_TRACKS = 5;
+    final static int MEDIA_ATTR_GENRE = 6;
+    final static int MEDIA_ATTR_PLAYING_TIME = 7;
+
+    // match up with btrc_event_id_t enum of bt_rc.h
+    final static int EVT_PLAY_STATUS_CHANGED = 1;
+    final static int EVT_TRACK_CHANGED = 2;
+    final static int EVT_TRACK_REACHED_END = 3;
+    final static int EVT_TRACK_REACHED_START = 4;
+    final static int EVT_PLAY_POS_CHANGED = 5;
+    final static int EVT_BATT_STATUS_CHANGED = 6;
+    final static int EVT_SYSTEM_STATUS_CHANGED = 7;
+    final static int EVT_APP_SETTINGS_CHANGED = 8;
+    final static int EVENT_NOW_PLAYING_CONTENT_CHANGED = 9;
+    final static int EVT_AVBL_PLAYERS_CHANGED = 0xa;
+    final static int EVT_ADDR_PLAYER_CHANGED = 0xb;
+    final static int EVENT_UIDS_CHANGED = 0x0c;
 
     private native static void classInitNative();
     private native void initNative();
     private native void cleanupNative();
-    private native boolean getPlayStatusRspNative(int playStatus, int songLen, int songPos);
-    private native boolean getElementAttrRspNative(byte numAttr, int[] attrIds, String[] textArray);
+    private native boolean getPlayStatusRspNative(byte[] address, int playStatus, int songLen,
+            int songPos);
+    private native boolean getElementAttrRspNative(byte[] address, byte numAttr, int[] attrIds,
+            String[] textArray);
     private native boolean registerNotificationRspPlayStatusNative(int type, int playStatus);
     private native boolean registerNotificationRspTrackChangeNative(int type, byte[] track);
     private native boolean registerNotificationRspPlayPosNative(int type, int playPos);
     private native boolean setVolumeNative(int volume);
     private native boolean sendPassThroughCommandNative(int keyCode, int keyState);
+    private native boolean setAddressedPlayerRspNative(byte[] address, int rspStatus);
+    private native boolean setBrowsedPlayerRspNative(byte[] address, int rspStatus, byte depth,
+            int numItems, String[] textArray);
+    private native boolean mediaPlayerListRspNative(byte[] address, int rsStatus, int uidCounter,
+            byte item_type, int numItems, byte[] PlayerTypes, int[] PlayerSubTypes,
+            byte[] playStatusValues, short[] FeatureBitMaskValues, String[] textArray);
+    private native boolean getFolderItemsRspNative(byte[] address, int rspStatus, short uidCounter,
+            byte scope, int numItems, byte[] folderTypes, byte[] playable, byte[] itemTypes,
+            byte[] itemUidArray, String[] textArray, int[] AttributesNum, int[] AttributesIds,
+            String[] attributesArray);
+    private native boolean changePathRspNative(byte[] address, int rspStatus, int numItems);
+    private native boolean getItemAttrRspNative(byte[] address, int rspStatus, byte numAttr,
+            int[] attrIds, String[] textArray);
+    private native boolean playItemRspNative(byte[] address, int rspStatus);
+    private native boolean getTotalNumOfItemsRspNative(byte[] address, int rspStatus,
+            int uidCounter, int numItems);
+    private native boolean searchRspNative(byte[] address, int rspStatus, int uidCounter,
+            int numItems);
+    private native boolean addToNowPlayingRspNative(byte[] address, int rspStatus);
+    private native boolean registerNotificationRspAddrPlayerChangedNative(int type,
+        int playerId, int uidCounter);
+    private native boolean registerNotificationRspAvalPlayerChangedNative(int type);
+    private native boolean registerNotificationRspUIDsChangedNative(int type, int uidCounter);
+    private native boolean registerNotificationRspNowPlayingChangedNative(int type);
 
 }
diff --git a/src/com/android/bluetooth/avrcp/AvrcpConstants.java b/src/com/android/bluetooth/avrcp/AvrcpConstants.java
new file mode 100644 (file)
index 0000000..e0a5ff4
--- /dev/null
@@ -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;
+}
index c42d4cf..6675124 100644 (file)
@@ -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 (file)
index 0000000..9855a27
--- /dev/null
@@ -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<MediaController> mControllersList;
+    int mNumItems;
+
+    public MediaPlayerListRsp(byte status, short UIDCounter, int numItems, byte itemType,
+            byte[] playerTypes, int[] playerSubTypes, byte[] playStatusValues,
+            short[] featureBitMaskValues, String[] playerNameList, String packageNameList[],
+            List<MediaController> mediaControllerList) {
+        this.mStatus = status;
+        this.mUIDCounter = UIDCounter;
+        this.mNumItems = numItems;
+        this.itemType = itemType;
+        this.mPlayerTypes = playerTypes;
+        this.mPlayerSubTypes = new int[numItems];
+        this.mPlayerSubTypes = playerSubTypes;
+        this.mPlayStatusValues = new byte[numItems];
+        this.mPlayStatusValues = playStatusValues;
+        int bitMaskSize = AvrcpConstants.AVRC_FEATURE_MASK_SIZE;
+        this.mFeatureBitMaskValues = new short[numItems * bitMaskSize];
+        for (int bitMaskIndex = 0; bitMaskIndex < (numItems * bitMaskSize); bitMaskIndex++) {
+            this.mFeatureBitMaskValues[bitMaskIndex] = featureBitMaskValues[bitMaskIndex];
+        }
+        this.mPlayerNameList = playerNameList;
+        this.mPackageNameList = packageNameList;
+        this.mControllersList = mediaControllerList;
+    }
+}
+
+class FolderItemsRsp {
+    byte mStatus;
+    short mUIDCounter;
+    byte mScope;
+    int mNumItems;
+    byte[] mFolderTypes;
+    byte[] mPlayable;
+    byte[] mItemTypes;
+    byte[] mItemUid;
+    String[] mDisplayNames; /* display name of the item. Eg: Folder name or song name */
+    int[] mAttributesNum;
+    int[] mAttrIds;
+    String[] mAttrValues;
+
+    public FolderItemsRsp(byte Status, short UIDCounter, byte scope, int numItems,
+            byte[] folderTypes, byte[] playable, byte[] ItemTypes, byte[] ItemsUid,
+            String[] displayNameArray, int[] AttributesNum, int[] AttrIds, String[] attrValues) {
+        this.mStatus = Status;
+        this.mUIDCounter = UIDCounter;
+        this.mScope = scope;
+        this.mNumItems = numItems;
+        this.mFolderTypes = folderTypes;
+        this.mPlayable = playable;
+        this.mItemTypes = ItemTypes;
+        this.mItemUid = ItemsUid;
+        this.mDisplayNames = displayNameArray;
+        this.mAttributesNum = AttributesNum;
+        this.mAttrIds = AttrIds;
+        this.mAttrValues = attrValues;
+    }
+}
+
+class ItemAttrRsp {
+    byte mStatus;
+    byte mNumAttr;
+    int[] mAttributesIds;
+    String[] mAttributesArray;
+
+    public ItemAttrRsp(byte status, byte numAttr, int[] attributesIds, String[] attributesArray) {
+        this.mStatus = status;
+        this.mNumAttr = numAttr;
+        this.mAttributesIds = attributesIds;
+        this.mAttributesArray = attributesArray;
+    }
+}
+
+/* Helps managing the NowPlayingList */
+class NowPlayingListManager {
+    private List<MediaSession.QueueItem> mNowPlayingItems = null;
+
+    synchronized void setNowPlayingList(List<MediaSession.QueueItem> queue) {
+        mNowPlayingItems = queue;
+    }
+
+    synchronized List<MediaSession.QueueItem> getNowPlayingList() {
+        return mNowPlayingItems;
+    }
+}
+
+/* stores information of Media Players in the system */
+class MediaPlayerInfo {
+
+    private String packageName;
+    private byte majorType;
+    private int subType;
+    private byte playStatus;
+    private short[] featureBitMask;
+    private String displayableName;
+    private MediaController mediaController;
+
+    MediaPlayerInfo(String packageName, byte majorType, int subType, byte playStatus,
+            short[] featureBitMask, String displayableName, MediaController mediaController) {
+        this.setPackageName(packageName);
+        this.setMajorType(majorType);
+        this.setSubType(subType);
+        this.playStatus = playStatus;
+
+        // copying the FeatureBitMask array
+        this.setFeatureBitMask(new short[featureBitMask.length]);
+        for (int count = 0; count < featureBitMask.length; count++) {
+            this.getFeatureBitMask()[count] = featureBitMask[count];
+        }
+
+        this.setDisplayableName(displayableName);
+        this.setMediaController(mediaController);
+    }
+
+    /* getters and setters */
+    byte getPlayStatus() {
+        return playStatus;
+    }
+
+    void setPlayStatus(byte playStatus) {
+        this.playStatus = playStatus;
+    }
+
+    MediaController getMediaController() {
+        return mediaController;
+    }
+
+    void setMediaController(MediaController mediaController) {
+        this.mediaController = mediaController;
+    }
+
+    String getPackageName() {
+        return packageName;
+    }
+
+    void setPackageName(String packageName) {
+        this.packageName = packageName;
+    }
+
+    byte getMajorType() {
+        return majorType;
+    }
+
+    void setMajorType(byte majorType) {
+        this.majorType = majorType;
+    }
+
+    int getSubType() {
+        return subType;
+    }
+
+    void setSubType(int subType) {
+        this.subType = subType;
+    }
+
+    String getDisplayableName() {
+        return displayableName;
+    }
+
+    void setDisplayableName(String displayableName) {
+        this.displayableName = displayableName;
+    }
+
+    short[] getFeatureBitMask() {
+        return featureBitMask;
+    }
+
+    void setFeatureBitMask(short[] featureBitMask) {
+        this.featureBitMask = featureBitMask;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append("\n+++ MediaPlayerInfo: +++");
+        sb.append("\nPlayer Package Name = " + getPackageName());
+        sb.append("\nMajor Player Type = " + getMajorType());
+        sb.append("\nPlayer SubType = " + getSubType());
+        sb.append("\nPlay Status = " + playStatus);
+        sb.append("\nFeatureBitMask:\n ");
+        for (int count = 0; count < getFeatureBitMask().length; count++) {
+            sb.append("\nFeature BitMask[" + count + "] = " + getFeatureBitMask()[count]);
+        }
+        sb.append("\nDisplayable Name = " + getDisplayableName());
+        sb.append("\nMedia Controller = " + getMediaController().toString());
+
+        return sb.toString();
+    }
+}
+
+/* stores information for browsable Media Players available in the system */
+class BrowsePlayerInfo {
+    String packageName;
+    String displayableName;
+    String serviceClass;
+
+    public BrowsePlayerInfo(String packageName, String displayableName, String serviceClass) {
+        this.packageName = packageName;
+        this.displayableName = displayableName;
+        this.serviceClass = serviceClass;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append("\n+++ BrowsePlayerInfo: +++");
+        sb.append("\nPackage Name = " + packageName);
+        sb.append("\nDisplayable Name = " + displayableName);
+        sb.append("\nService Class = " + serviceClass);
+        return sb.toString();
+    }
+}
+
+class FolderItemsData {
+
+    /* initialize sizes for rsp parameters */
+    int mNumItems;
+    int[] mAttributesNum;
+    byte[] mFolderTypes ;
+    byte[] mItemTypes;
+    byte[] mPlayable;
+    byte[] mItemUid;
+    String[] mDisplayNames;
+    int[] mAttrIds;
+    String[] mAttrValues;
+    int attrCounter;
+
+    public FolderItemsData(int size) {
+        mNumItems = size;
+        mAttributesNum = new int[size];
+
+        mFolderTypes = new byte[size]; /* folderTypes */
+        mItemTypes = new byte[size]; /* folder or media item */
+        mPlayable = new byte[size];
+        Arrays.fill(mFolderTypes, AvrcpConstants.FOLDER_TYPE_MIXED);
+        Arrays.fill(mItemTypes, AvrcpConstants.BTRC_ITEM_MEDIA);
+        Arrays.fill(mPlayable, AvrcpConstants.ITEM_PLAYABLE);
+
+        mItemUid = new byte[size * AvrcpConstants.UID_SIZE];
+        mDisplayNames = new String[size];
+
+        mAttrIds = null; /* array of attr ids */
+        mAttrValues = null; /* array of attr values */
+    }
+}
diff --git a/src/com/android/bluetooth/avrcp/AvrcpMediaRspInterface.java b/src/com/android/bluetooth/avrcp/AvrcpMediaRspInterface.java
new file mode 100644 (file)
index 0000000..9c40a96
--- /dev/null
@@ -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 (file)
index 0000000..ab19c83
--- /dev/null
@@ -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<String> mPathStack = null;
+
+    /* Number of items in current folder */
+    private int mCurrFolderNumItems = 0;
+
+    /* store mapping between uid(Avrcp) and mediaId(Media Player). */
+    private HashMap<Integer, String> mHmap = new HashMap<Integer, String>();
+
+    /* command objects from avrcp handler */
+    private AvrcpCmd.FolderItemsCmd mFolderItemsReqObj;
+
+    private AvrcpCmd.ItemAttrCmd mItemAttrReqObj;
+
+    /* store result of getfolderitems with scope="vfs" */
+    private List<MediaBrowser.MediaItem> mFolderItems = null;
+
+    /* Connection state callback handler */
+    private MediaBrowser.ConnectionCallback browseMediaConnectionCallback =
+            new MediaBrowser.ConnectionCallback() {
+
+        @Override
+        public void onConnected() {
+            mConnState = CONNECTED;
+            if (DEBUG) Log.d(TAG, "mediaBrowser CONNECTED to " + mPackageName);
+            /* perform init tasks and set player as browsed player on successful connection */
+            onBrowseConnect();
+        }
+
+        @Override
+        public void onConnectionFailed() {
+            mConnState = DISCONNECTED;
+            Log.e(TAG, "mediaBrowser Connection failed with " + mPackageName
+                    + ", Sending fail response!");
+            mMediaInterface.setBrowsedPlayerRsp(mBDAddr, AvrcpConstants.RSP_INTERNAL_ERR,
+                (byte)0x00, 0, null);
+        }
+
+        @Override
+        public void onConnectionSuspended() {
+            mConnState = SUSPENDED;
+            Log.e(TAG, "mediaBrowser SUSPENDED connection with " + mPackageName);
+        }
+    };
+
+    /* Subscription callback handler. Subscribe to a folder to get its contents */
+    private MediaBrowser.SubscriptionCallback folderItemsCb =
+            new MediaBrowser.SubscriptionCallback() {
+
+        @Override
+        public void onChildrenLoaded(String parentId, List<MediaBrowser.MediaItem> children) {
+            if (DEBUG) Log.d(TAG, "OnChildren Loaded folder items: childrens= " + children.size());
+
+            /*
+             * cache current folder items and send as rsp when remote requests
+             * get_folder_items (scope = vfs)
+             */
+            if (mFolderItems == null) {
+                if (DEBUG) Log.d(TAG, "sending setbrowsed player rsp");
+                mFolderItems = children;
+                mMediaInterface.setBrowsedPlayerRsp(mBDAddr, AvrcpConstants.RSP_NO_ERROR,
+                        (byte)0x01, children.size(), ROOT_FOLDER);
+            } else {
+                mFolderItems = children;
+                mCurrFolderNumItems = mFolderItems.size();
+                mMediaInterface.changePathRsp(mBDAddr, AvrcpConstants.RSP_NO_ERROR,
+                        mCurrFolderNumItems);
+            }
+            mMediaBrowser.unsubscribe(parentId);
+        }
+
+        /* UID is invalid */
+        @Override
+        public void onError(String id) {
+            Log.e(TAG, "set browsed player rsp. Could not get root folder items");
+            mMediaInterface.setBrowsedPlayerRsp(mBDAddr, AvrcpConstants.RSP_INTERNAL_ERR,
+                    (byte)0x00, 0, null);
+        }
+    };
+
+    /* callback from media player in response to getitemAttr request */
+    private MediaBrowser.SubscriptionCallback itemAttrCb =
+            new MediaBrowser.SubscriptionCallback() {
+        @Override
+        public void onChildrenLoaded(String parentId, List<MediaBrowser.MediaItem> children) {
+            if (DEBUG) Log.d(TAG, "itemAttrCb OnChildren Loaded");
+            int status = AvrcpConstants.RSP_NO_ERROR;
+
+            if (children != null) {
+                boolean isChildrenFound = false;
+                /* some players may return all items in folder containing requested media item */
+                for (int itemIndex = 0; itemIndex < children.size(); itemIndex++) {
+                    if (children.get(itemIndex).getMediaId().equals(parentId)) {
+                        if (DEBUG) Log.d(TAG, "found an item " + itemIndex +
+                                children.get(itemIndex).getMediaId());
+                        getItemAttrFilterAttr(children.get(itemIndex));
+                        isChildrenFound = true;
+                        break;
+                    }
+                }
+
+                if (!isChildrenFound) {
+                    Log.e(TAG, "not able to find the item:" + parentId);
+                    status = AvrcpConstants.RSP_INV_ITEM;
+                }
+            } else {
+                Log.e(TAG, "children list is null for parent id:" + parentId);
+                status = AvrcpConstants.RSP_INV_ITEM;
+            }
+            //  send only error from here, in case of success it will sent the attributes from getItemAttrFilterAttr
+            if (status != AvrcpConstants.RSP_NO_ERROR) {
+                /* send invalid uid rsp to remote device */
+                mMediaInterface.getItemAttrRsp(mBDAddr, status, null);
+            }
+        }
+        @Override
+        public void onError(String id) {
+            Log.e(TAG, "Could not get attributes from media player. id: " + id);
+            mMediaInterface.getItemAttrRsp(mBDAddr, AvrcpConstants.RSP_INTERNAL_ERR, null);
+        }
+    };
+
+    /* Constructor */
+    public BrowsedMediaPlayer(byte[] address, Context context,
+            AvrcpMediaRspInterface mAvrcpMediaRspInterface) {
+        mContext = context;
+        mMediaInterface = mAvrcpMediaRspInterface;
+        mBDAddr = address;
+    }
+
+    /* initialize mediacontroller in order to communicate with media player. */
+    private void onBrowseConnect() {
+        boolean isError = false;
+        MediaSession.Token token = null;
+        try {
+            /* get rootfolder uid from media player */
+            if (mMediaId == null) {
+                mMediaId = mMediaBrowser.getRoot();
+                /*
+                 * assuming that root folder uid will not change on uids changed
+                 */
+                mRootFolderUid = mMediaId;
+                /* store root folder uid to stack */
+                mPathStack.push(mMediaId);
+            }
+
+            if (!mMediaBrowser.isConnected()) {
+                isError = true;
+                Log.e(TAG, "setBrowsedPlayer : Not connected");
+            }
+
+            if ((token = mMediaBrowser.getSessionToken()) == null) {
+                isError = true;
+                Log.e(TAG, "setBrowsedPlayer : No Session token");
+            }
+
+            if (isError == false) {
+                mMediaController = new MediaController(mContext, token);
+                /* get root folder items */
+                mMediaBrowser.subscribe(mRootFolderUid, folderItemsCb);
+            }
+        } catch (NullPointerException ex) {
+            isError = true;
+            Log.e(TAG, "setBrowsedPlayer : Null pointer during init");
+            ex.printStackTrace();
+        }
+
+        if (isError) {
+            mMediaInterface.setBrowsedPlayerRsp(mBDAddr, AvrcpConstants.RSP_INTERNAL_ERR,
+                    (byte)0x00, 0, null);
+        }
+    }
+
+    public void setBrowsed(String packageName, String cls) {
+        mPackageName = packageName;
+        mClassName = cls;
+        /* cleanup variables from previous browsed calls */
+        mFolderItems = null;
+        mMediaId = null;
+        mRootFolderUid = null;
+        /*
+         * create stack to store the navigation trail (current folder ID). This
+         * will be required while navigating up the folder
+         */
+        mPathStack = new Stack<String>();
+        /* Bind to MediaBrowseService of MediaPlayer */
+        mMediaBrowser = new MediaBrowser(mContext, new ComponentName(mPackageName, mClassName),
+                browseMediaConnectionCallback, null);
+        connectToPlayer();
+    }
+
+    /* called when connection to media player is closed */
+    public void cleanup() {
+        if (DEBUG)
+            Log.d(TAG, "cleanup");
+        if (mConnState != DISCONNECTED) {
+            disconnectFromPlayer();
+        }
+
+        mHmap = null;
+        mMediaController = null;
+        mMediaBrowser = null;
+    }
+
+    private void connectToPlayer() {
+        if (DEBUG) Log.d(TAG, "connectToPlayer");
+        mMediaBrowser.connect();
+    }
+
+    public void disconnectFromPlayer() {
+        if (DEBUG) Log.d(TAG, "disconnectFromPlayer");
+        mMediaBrowser.disconnect();
+    }
+
+    public boolean isPlayerConnected() {
+        if (mMediaBrowser != null) {
+            return mMediaBrowser.isConnected();
+        } else {
+            if (DEBUG) Log.d(TAG, "isPlayerConnected: mMediaBrowser = null!");
+            return false;
+        }
+    }
+
+    /* returns number of items in new path as reponse */
+    public void changePath(byte[] folderUid, byte direction) {
+        if (DEBUG) Log.d(TAG, "changePath.direction = " + direction);
+        String newPath = "";
+
+        if (isPlayerConnected() == false) {
+            Log.w(TAG, "changePath:disconnected from player service, sending internal error");
+            mMediaInterface.changePathRsp(mBDAddr, AvrcpConstants.RSP_INTERNAL_ERR, 0);
+            return;
+        }
+
+        if (mMediaBrowser == null) {
+            Log.e(TAG, "mediaController is null, sending internal error");
+            mMediaInterface.changePathRsp(mBDAddr, AvrcpConstants.RSP_INTERNAL_ERR, 0);
+            return;
+        }
+
+        /* check direction and change the path */
+        if (direction == AvrcpConstants.DIR_DOWN) { /* move down */
+            if ((newPath = byteToString(folderUid)) == null) {
+                Log.e(TAG, "Could not get media item from folder Uid, sending err response");
+                mMediaInterface.changePathRsp(mBDAddr, AvrcpConstants.RSP_INV_ITEM, 0);
+            } else if (isBrowsableFolderDn(newPath) == false) {
+                /* new path is not browsable */
+                Log.e(TAG, "ItemUid received from changePath cmd is not browsable");
+                mMediaInterface.changePathRsp(mBDAddr, AvrcpConstants.RSP_INV_DIRECTORY, 0);
+            } else if (mPathStack.peek().equals(newPath) == true) {
+                /* new_folder is same as current folder */
+                Log.e(TAG, "new_folder is same as current folder, Invalid direction!");
+                mMediaInterface.changePathRsp(mBDAddr, AvrcpConstants.RSP_INV_DIRN, 0);
+            } else {
+                mMediaBrowser.subscribe(newPath, folderItemsCb);
+                /* assume that call is success and update stack with new folder path */
+                mPathStack.push(newPath);
+            }
+        } else if (direction == AvrcpConstants.DIR_UP) { /* move up */
+            if (isBrowsableFolderUp() == false) {
+                /* Already on the root, cannot allow up: PTS: test case TC_TG_MCN_CB_BI_02_C
+                 * This is required, otherwise some CT will keep on sending change path up
+                 * until they receive error */
+                Log.w(TAG, "Cannot go up from now, already in the root, Invalid direction!");
+                mMediaInterface.changePathRsp(mBDAddr, AvrcpConstants.RSP_INV_DIRN, 0);
+            } else {
+                /* move folder up */
+                mPathStack.pop();
+                newPath = mPathStack.peek();
+                mMediaBrowser.subscribe(newPath, folderItemsCb);
+            }
+        } else { /* invalid direction */
+            Log.w(TAG, "changePath : Invalid direction " + direction);
+            mMediaInterface.changePathRsp(mBDAddr, AvrcpConstants.RSP_INV_DIRN, 0);
+        }
+    }
+
+    public void getItemAttr(AvrcpCmd.ItemAttrCmd itemAttr) {
+        String mediaID;
+        if (DEBUG) Log.d(TAG, "getItemAttr");
+
+        /*
+         * store request parameters for reference. To be used to filter
+         * attributes when sending response
+         */
+        mItemAttrReqObj = itemAttr;
+
+        /* check if uid is valid by doing a lookup in hashmap */
+        if ((mediaID = byteToString(itemAttr.mUid)) == null) {
+            Log.e(TAG, "uid is invalid");
+            mMediaInterface.getItemAttrRsp(mBDAddr, AvrcpConstants.RSP_INV_ITEM, null);
+            return;
+        }
+
+        /* check scope */
+        if (itemAttr.mScope == AvrcpConstants.BTRC_SCOPE_FILE_SYSTEM) {
+            if (mMediaBrowser != null) {
+                mMediaBrowser.subscribe(mediaID, itemAttrCb);
+            } else {
+                Log.e(TAG, "mMediaBrowser is null");
+                mMediaInterface.getItemAttrRsp(mBDAddr, AvrcpConstants.RSP_INTERNAL_ERR, null);
+            }
+        } else {
+            Log.e(TAG, "invalid scope");
+            mMediaInterface.getItemAttrRsp(mBDAddr, AvrcpConstants.RSP_INV_SCOPE, null);
+        }
+    }
+
+    public void getTotalNumOfItems(byte scope) {
+        if (DEBUG) Log.d(TAG, "getTotalNumOfItems scope = " + scope);
+        switch (scope) {
+            case AvrcpConstants.BTRC_SCOPE_FILE_SYSTEM:
+                if (mFolderItems != null) {
+                     /* find num items using size of already cached folder items */
+                    mMediaInterface.getTotalNumOfItemsRsp(mBDAddr,
+                            AvrcpConstants.RSP_NO_ERROR, 0, mFolderItems.size());
+                } else {
+                    Log.e(TAG, "mFolderItems is null, sending internal error");
+                    /* folderitems were not fetched during change path */
+                    mMediaInterface.getTotalNumOfItemsRsp(mBDAddr,
+                            AvrcpConstants.RSP_INTERNAL_ERR, 0, 0);
+                }
+                break;
+            default:
+                Log.e(TAG, "getTotalNumOfItems error" + scope);
+                mMediaInterface.getTotalNumOfItemsRsp(mBDAddr, AvrcpConstants.RSP_INV_SCOPE, 0, 0);
+                break;
+        }
+    }
+
+    public void getFolderItemsVFS(AvrcpCmd.FolderItemsCmd reqObj) {
+        if (isPlayerConnected()) {
+            if (DEBUG) Log.d(TAG, "getFolderItemsVFS");
+            mFolderItemsReqObj = reqObj;
+
+            if (mFolderItems == null) {
+                /* Failed to fetch folder items from media player. Send error to remote device */
+                Log.e(TAG, "Failed to fetch folder items during getFolderItemsVFS");
+                mMediaInterface.folderItemsRsp(mBDAddr, AvrcpConstants.RSP_INTERNAL_ERR, null);
+            } else {
+                /* Filter attributes based on the request and send response to remote device */
+                getFolderItemsFilterAttr(mBDAddr, reqObj, mFolderItems,
+                        AvrcpConstants.BTRC_SCOPE_FILE_SYSTEM,
+                        mFolderItemsReqObj.mStartItem, mFolderItemsReqObj.mEndItem);
+            }
+        } else {
+            Log.e(TAG, "unable to connect to media player, sending internal error");
+            /* unable to connect to media player. Send error response to remote device */
+            mMediaInterface.folderItemsRsp(mBDAddr, AvrcpConstants.RSP_INTERNAL_ERR, null);
+        }
+    }
+
+    /* Instructs media player to play particular media item */
+    public void playItem(byte[] uid, byte scope) {
+        String folderUid;
+
+        if (isPlayerConnected()) {
+            /* check if uid is valid */
+            if ((folderUid = byteToString(uid)) == null) {
+                Log.e(TAG, "uid is invalid!");
+                mMediaInterface.playItemRsp(mBDAddr, AvrcpConstants.RSP_INV_ITEM);
+                return;
+            }
+
+            if (mMediaController != null) {
+                MediaController.TransportControls mediaControllerCntrl =
+                        mMediaController.getTransportControls();
+                if (DEBUG) Log.d(TAG, "Sending playID: " + folderUid);
+
+                if (scope == AvrcpConstants.BTRC_SCOPE_FILE_SYSTEM) {
+                    mediaControllerCntrl.playFromMediaId(folderUid, null);
+                    mMediaInterface.playItemRsp(mBDAddr, AvrcpConstants.RSP_NO_ERROR);
+                } else {
+                    Log.e(TAG, "playItem received for invalid scope!");
+                    mMediaInterface.playItemRsp(mBDAddr, AvrcpConstants.RSP_INV_SCOPE);
+                }
+            } else {
+                Log.e(TAG, "mediaController is null");
+                mMediaInterface.playItemRsp(mBDAddr, AvrcpConstants.RSP_INTERNAL_ERR);
+            }
+        } else {
+            Log.e(TAG, "playItem: Not connected to media player");
+            mMediaInterface.playItemRsp(mBDAddr, AvrcpConstants.RSP_INTERNAL_ERR);
+        }
+    }
+
+    /*
+     * helper method to check if startItem and endItem index is with range of
+     * MediaItem list. (Resultset containing all items in current path)
+     */
+    private List<MediaBrowser.MediaItem> checkIndexOutofBounds(byte[] bdaddr,
+        List<MediaBrowser.MediaItem> children, int startItem, int endItem) {
+        try {
+            List<MediaBrowser.MediaItem> childrenSubList =
+                children.subList(startItem, Math.min(children.size(), endItem + 1));
+            if (childrenSubList.isEmpty()) {
+                Log.i(TAG, "childrenSubList is empty.");
+                throw new IndexOutOfBoundsException();
+            }
+            return childrenSubList;
+        } catch (IndexOutOfBoundsException ex) {
+            Log.w(TAG, "Index out of bounds start item ="+ startItem + " end item = "+
+                    Math.min(children.size(), endItem + 1));
+            mMediaInterface.folderItemsRsp(bdaddr, AvrcpConstants.RSP_INV_RANGE, null);
+            return null;
+        } catch (IllegalArgumentException ex) {
+            Log.i(TAG, "Index out of bounds start item =" + startItem + " > size");
+            mMediaInterface.folderItemsRsp(bdaddr, AvrcpConstants.RSP_INV_RANGE, null);
+            return null;
+        }
+    }
+
+
+    /*
+     * helper method to filter required attibutes before sending GetFolderItems response
+     */
+    public void getFolderItemsFilterAttr(byte[] bdaddr, AvrcpCmd.FolderItemsCmd mFolderItemsReqObj,
+        List<MediaBrowser.MediaItem> children, byte scope, int startItem, int endItem) {
+        if (DEBUG) Log.d(TAG, "getFolderItemsFilterAttr: startItem =" + startItem +
+            ", endItem = " + endItem);
+
+        List<MediaBrowser.MediaItem> result_items = new ArrayList<MediaBrowser.MediaItem>();
+
+        if (children != null) {
+            /* check for index out of bound errors */
+            if ((result_items = checkIndexOutofBounds(bdaddr, children, startItem, endItem)) == null) {
+               Log.w(TAG, "result_items is null.");
+               mMediaInterface.folderItemsRsp(bdaddr, AvrcpConstants.RSP_INV_RANGE, null);
+               return;
+        }
+        FolderItemsData folderDataNative = new FolderItemsData(result_items.size());
+
+       /* variables to temperorily add attrs */
+        ArrayList<String> attrArray = new ArrayList<String>();
+        ArrayList<Integer> attrId = new ArrayList<Integer>();
+
+        for (int itemIndex = 0; itemIndex < result_items.size(); itemIndex++) {
+            /* item type. Needs to be set by media player */
+            if ((result_items.get(itemIndex).getFlags() &
+                MediaBrowser.MediaItem.FLAG_BROWSABLE) != 0) {
+                    folderDataNative.mItemTypes[itemIndex] = AvrcpConstants.BTRC_ITEM_FOLDER;
+            } else {
+                folderDataNative.mItemTypes[itemIndex] = AvrcpConstants.BTRC_ITEM_MEDIA;
+            }
+
+            /* set playable */
+            if ((result_items.get(itemIndex).getFlags()
+                & MediaBrowser.MediaItem.FLAG_PLAYABLE) != 0) {
+                folderDataNative.mPlayable[itemIndex] = AvrcpConstants.ITEM_PLAYABLE;
+            } else {
+                folderDataNative.mPlayable[itemIndex] = AvrcpConstants.ITEM_NOT_PLAYABLE;
+            }
+            /* set uid for current item */
+            byte[] uid =
+                    stringToByte(result_items.get(itemIndex).getDescription().getMediaId());
+            for (int idx = 0; idx < AvrcpConstants.UID_SIZE; idx++) {
+                folderDataNative.mItemUid[itemIndex * AvrcpConstants.UID_SIZE + idx] = uid[idx];
+            }
+
+            /* Set display name for current item */
+            folderDataNative.mDisplayNames[itemIndex] =
+                    result_items.get(itemIndex).getDescription().getTitle().toString();
+
+            int maxAttributesRequested = 0;
+            boolean isAllAttribRequested = false;
+            /* check if remote requested for attributes */
+            if (mFolderItemsReqObj.mNumAttr != AvrcpConstants.NUM_ATTR_NONE) {
+                int attrCnt = 0;
+
+                /* add requested attr ids to a temp array */
+                if (mFolderItemsReqObj.mNumAttr == AvrcpConstants.NUM_ATTR_ALL) {
+                    isAllAttribRequested = true;
+                    maxAttributesRequested = AvrcpConstants.MAX_NUM_ATTR;
+                } else {
+                /* get only the requested attribute ids from the request */
+                    maxAttributesRequested = mFolderItemsReqObj.mNumAttr;
+                }
+
+                /* lookup and copy values of attributes for ids requested above */
+                for (int idx = 0; idx < maxAttributesRequested; idx++) {
+                /* check if media player provided requested attributes */
+                    String value = null;
+
+                    int attribId = isAllAttribRequested ? (idx + 1) :
+                            mFolderItemsReqObj.mAttrIDs[idx];
+                    if(attribId >= AvrcpConstants.ATTRID_TITLE &&
+                        attribId <= AvrcpConstants.ATTRID_PLAY_TIME) {
+                        if ((value = getAttrValue(attribId, result_items,
+                            itemIndex)) != null) {
+                            attrArray.add(value);
+                            attrId.add(attribId);
+                            attrCnt++;
+                        }
+                    } else {
+                        Log.d(TAG, "invalid attributed id is requested: " + attribId);
+                    }
+                }
+                /* add num attr actually received from media player for a particular item */
+                folderDataNative.mAttributesNum[itemIndex] = attrCnt;
+                }
+            }
+
+            /* copy filtered attr ids and attr values to response parameters */
+            if (mFolderItemsReqObj.mNumAttr != AvrcpConstants.NUM_ATTR_NONE) {
+                folderDataNative.mAttrIds = new int[attrId.size()];
+                for (int attrIndex = 0; attrIndex < attrId.size(); attrIndex++)
+                    folderDataNative.mAttrIds[attrIndex] = attrId.get(attrIndex);
+                folderDataNative.mAttrValues = attrArray.toArray(new String[attrArray.size()]);
+            }
+
+            /* create rsp object and send response to remote device */
+            FolderItemsRsp rspObj = new FolderItemsRsp(AvrcpConstants.RSP_NO_ERROR,
+                    Avrcp.sUIDCounter, scope, folderDataNative.mNumItems,
+                    folderDataNative.mFolderTypes, folderDataNative.mPlayable,
+                    folderDataNative.mItemTypes,folderDataNative.mItemUid,
+                    folderDataNative.mDisplayNames, folderDataNative.mAttributesNum,
+                    folderDataNative.mAttrIds, folderDataNative.mAttrValues);
+            mMediaInterface.folderItemsRsp(bdaddr, AvrcpConstants.RSP_NO_ERROR, rspObj);
+        } else {
+            Log.e(TAG, "Error: children are null in getFolderItemsFilterAttr");
+            mMediaInterface.folderItemsRsp(bdaddr, AvrcpConstants.RSP_INV_RANGE, null);
+            return;
+        }
+    }
+
+    public static String getAttrValue(int attr, List<MediaBrowser.MediaItem> resultItems,
+            int itemIndex) {
+
+        String attrValue = null;
+        try {
+            switch (attr) {
+                /* Title is mandatory attribute */
+                case AvrcpConstants.ATTRID_TITLE:
+                    attrValue = resultItems.get(itemIndex).getDescription().getTitle().toString();
+                    break;
+                case AvrcpConstants.ATTRID_ARTIST:
+                    attrValue = resultItems.get(itemIndex).getDescription().getExtras()
+                            .getString(MediaMetadata.METADATA_KEY_ARTIST);
+                    break;
+
+                case AvrcpConstants.ATTRID_ALBUM:
+                    attrValue = resultItems.get(itemIndex).getDescription().getExtras()
+                            .getString(MediaMetadata.METADATA_KEY_ALBUM);
+                    break;
+
+                case AvrcpConstants.ATTRID_TRACK_NUM:
+                    attrValue = resultItems.get(itemIndex).getDescription().getExtras()
+                            .getString(MediaMetadata.METADATA_KEY_TRACK_NUMBER);
+                    break;
+
+                case AvrcpConstants.ATTRID_NUM_TRACKS:
+                    attrValue = resultItems.get(itemIndex).getDescription().getExtras()
+                            .getString(MediaMetadata.METADATA_KEY_NUM_TRACKS);
+                    break;
+
+                case AvrcpConstants.ATTRID_GENRE:
+                    attrValue = resultItems.get(itemIndex).getDescription().getExtras()
+                            .getString(MediaMetadata.METADATA_KEY_GENRE);
+
+                case AvrcpConstants.ATTRID_PLAY_TIME:
+                    attrValue = resultItems.get(itemIndex).getDescription().getExtras()
+                            .getString(MediaMetadata.METADATA_KEY_DURATION);
+                    break;
+
+                default:
+                    Log.e(TAG, "Unknown attribute ID");
+            }
+        } catch (IndexOutOfBoundsException ex) {
+            Log.w(TAG, "getAttrValue: requested item index out of bounds");
+            return null;
+        } catch (NullPointerException ex) {
+            Log.w(TAG, "getAttrValue: attr id not found in result");
+            /* checking if attribute is title, then it is mandatory and cannot send null */
+            if (attr == AvrcpConstants.ATTRID_TITLE) {
+                return "<Unknown Title>";
+            }
+            return null;
+        }
+        if(DEBUG) Log.d(TAG, "getAttrValue: attrvalue = "+ attrValue + "attr id:" + attr);
+        return attrValue;
+    }
+
+    /* helper method to filter required attibutes before sending getItemAttrdg response */
+    private void getItemAttrFilterAttr(MediaBrowser.MediaItem mediaItem) {
+        /* Response parameters */
+        int[] attrIds = null; /* array of attr ids */
+        String[] attrValues = null; /* array of attr values */
+        int attrCounter = 0; /* num attributes for each item */
+        List<MediaBrowser.MediaItem> resultItems = new ArrayList<MediaBrowser.MediaItem>();
+        resultItems.add(mediaItem);
+        /* variables to temperorily add attrs */
+        ArrayList<String> attrArray = new ArrayList<String>();
+        ArrayList<Integer> attrId = new ArrayList<Integer>();
+
+        if (mediaItem == null) {
+            Log.e(TAG, "getItemAttrFilterAttr: media item is null");
+            mMediaInterface.getItemAttrRsp(mBDAddr, AvrcpConstants.RSP_INTERNAL_ERR, null);
+            return;
+        }
+
+        ArrayList<Integer> attrTempId = new ArrayList<Integer>();
+
+        /* check if remote device has requested for attributes */
+        if (mItemAttrReqObj.mNumAttr != AvrcpConstants.NUM_ATTR_NONE) {
+            if (mItemAttrReqObj.mNumAttr == AvrcpConstants.NUM_ATTR_ALL ||
+                    mItemAttrReqObj.mNumAttr == AvrcpConstants.MAX_NUM_ATTR) {
+                for (int idx = 1; idx <= AvrcpConstants.MAX_NUM_ATTR; idx++) {
+                    attrTempId.add(idx); /* attr id 0x00 is unused */
+                }
+            } else {
+                /* get only the requested attribute ids from the request */
+                for (int idx = 0; idx < mItemAttrReqObj.mNumAttr; idx++) {
+                    attrTempId.add(mItemAttrReqObj.mAttrIDs[idx]);
+                }
+            }
+
+            /* lookup and copy values of attributes for ids requested above */
+            for (int idx = 0; idx < attrTempId.size(); idx++) {
+                /* check if media player provided requested attributes */
+                String value = null;
+                if ((value = getAttrValue(attrTempId.get(idx), resultItems, 0)) != null) {
+                    attrArray.add(value);
+                    attrId.add(attrTempId.get(idx));
+                    attrCounter++;
+                }
+            }
+            attrTempId = null;
+        } else {
+            Log.i(TAG, "getItemAttrFilterAttr: No attributes requested");
+        }
+
+        /* copy filtered attr ids and attr values to response parameters */
+        if (this.mItemAttrReqObj.mNumAttr != AvrcpConstants.NUM_ATTR_NONE) {
+            attrIds = new int[attrId.size()];
+
+            for (int attrIndex = 0; attrIndex < attrId.size(); attrIndex++)
+                attrIds[attrIndex] = attrId.get(attrIndex);
+
+            attrValues = attrArray.toArray(new String[attrId.size()]);
+
+            /* create rsp object and send response */
+            ItemAttrRsp rspObj = new ItemAttrRsp(AvrcpConstants.RSP_NO_ERROR,
+                    (byte)attrCounter, attrIds, attrValues);
+            mMediaInterface.getItemAttrRsp(mBDAddr, AvrcpConstants.RSP_NO_ERROR, rspObj);
+        }
+    }
+
+    public String getPackageName() {
+        return mPackageName;
+    }
+
+    /* Helper methods */
+
+    /* check if item is browsable Down*/
+    private boolean isBrowsableFolderDn(String uid) {
+        for (MediaBrowser.MediaItem item : mFolderItems) {
+            if (item.getMediaId().equals(uid) &&
+                ((item.getFlags() & MediaBrowser.MediaItem.FLAG_BROWSABLE) ==
+                    MediaBrowser.MediaItem.FLAG_BROWSABLE))
+                return true;
+        }
+        return false;
+    }
+
+    /* check if browsable Up*/
+    private boolean isBrowsableFolderUp() {
+        if (mPathStack.peek().equals(mRootFolderUid)) {
+            /* Already on the root, cannot go up */
+            return false;
+        }
+        return true;
+    }
+
+    /* convert uid to mediaId */
+    private String byteToString(byte[] byteArray) {
+        int key = new BigInteger(byteArray).intValue();
+        String value = mHmap.get(key);
+        return value;
+    }
+
+    /* convert mediaId to uid */
+    private byte[] stringToByte(String mediaId) {
+        /* check if this mediaId already exists in hashmap */
+        if (!mHmap.containsValue(mediaId)) { /* add to hashmap */
+            int key = mHmap.size();
+            mHmap.put(key, mediaId);
+            return intToByteArray(key);
+        } else { /* search key for give mediaId */
+            for (int key : mHmap.keySet()) {
+                if (mHmap.get(key).equals(mediaId)) {
+                    return intToByteArray(key);
+                }
+            }
+        }
+        return null;
+    }
+
+    /* converts queue item received from getQueue call, to MediaItem used by FilterAttr method */
+    private List<MediaBrowser.MediaItem> queueItem2MediaItem(
+            List<MediaSession.QueueItem> tempItems) {
+
+        List<MediaBrowser.MediaItem> tempMedia = new ArrayList<MediaBrowser.MediaItem>();
+        for (int itemCount = 0; itemCount < tempItems.size(); itemCount++) {
+            MediaDescription.Builder build = new MediaDescription.Builder();
+            build.setMediaId(Long.toString(tempItems.get(itemCount).getQueueId()));
+            build.setTitle(tempItems.get(itemCount).getDescription().getTitle());
+            build.setExtras(tempItems.get(itemCount).getDescription().getExtras());
+            MediaDescription des = build.build();
+            MediaItem item = new MediaItem((des), MediaItem.FLAG_PLAYABLE);
+            tempMedia.add(item);
+        }
+        return tempMedia;
+    }
+
+    /* convert integer to byte array of size 8 bytes */
+    public byte[] intToByteArray(int value) {
+        int index = 0;
+        byte[] encodedValue = new byte[AvrcpConstants.UID_SIZE];
+
+        encodedValue[index++] = (byte)0x00;
+        encodedValue[index++] = (byte)0x00;
+        encodedValue[index++] = (byte)0x00;
+        encodedValue[index++] = (byte)0x00;
+        encodedValue[index++] = (byte)(value >> 24);
+        encodedValue[index++] = (byte)(value >> 16);
+        encodedValue[index++] = (byte)(value >> 8);
+        encodedValue[index++] = (byte)value;
+
+        return encodedValue;
+    }
+}