Merge "OPP: Fix "0%" progress even after file transfer finished"
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 0c71e21..c3ee7e4 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -316,7 +316,7 @@
         </service>
         <service
             android:process="@string/process"
-            android:name = ".avrcp.AvrcpControllerService"
+            android:name = ".avrcpcontroller.AvrcpControllerService"
             android:enabled="@bool/profile_supported_avrcp_controller">
             <intent-filter>
                 <action android:name="android.bluetooth.IBluetoothAvrcpController" />
diff --git a/jni/com_android_bluetooth_avrcp_controller.cpp b/jni/com_android_bluetooth_avrcp_controller.cpp
index c28b15a..457b771 100644
--- a/jni/com_android_bluetooth_avrcp_controller.cpp
+++ b/jni/com_android_bluetooth_avrcp_controller.cpp
@@ -37,12 +37,22 @@
 static jmethodID method_handletrackchanged;
 static jmethodID method_handleplaypositionchanged;
 static jmethodID method_handleplaystatuschanged;
+static jmethodID method_handleGetFolderItemsRsp;
+static jmethodID method_handleGetPlayerItemsRsp;
 static jmethodID method_handleGroupNavigationRsp;
+static jmethodID method_createFromNativeMediaItem;
+static jmethodID method_createFromNativeFolderItem;
+static jmethodID method_createFromNativePlayerItem;
+static jmethodID method_handleChangeFolderRsp;
+static jmethodID method_handleSetBrowsedPlayerRsp;
 
+static jclass class_MediaBrowser_MediaItem;
+static jclass class_AvrcpPlayer;
 
 static const btrc_ctrl_interface_t *sBluetoothAvrcpInterface = NULL;
-static jobject mCallbacksObj = NULL;
+static jobject sCallbacksObj = NULL;
 static JNIEnv *sCallbackEnv = NULL;
+static JNIEnv *sEnv = NULL;
 
 static bool checkCallbackThread() {
     // Always fetch the latest callbackEnv from AdapterService.
@@ -56,7 +66,7 @@
     return true;
 }
 
-static void btavrcp_passthrough_response_callback(int id, int pressed, bt_bdaddr_t* bd_addr) {
+static void btavrcp_passthrough_response_callback(bt_bdaddr_t* bd_addr, int id, int pressed)  {
     jbyteArray addr;
 
     ALOGI("%s", __func__);
@@ -75,7 +85,7 @@
 
     sCallbackEnv->SetByteArrayRegion(addr, 0, sizeof(bt_bdaddr_t), (jbyte*) bd_addr);
 
-    sCallbackEnv->CallVoidMethod(mCallbacksObj, method_handlePassthroughRsp, (jint)id,
+    sCallbackEnv->CallVoidMethod(sCallbacksObj, method_handlePassthroughRsp, (jint)id,
                                                                              (jint)pressed,
                                                                              addr);
     checkAndClearExceptionFromCallback(sCallbackEnv, __func__);
@@ -84,23 +94,24 @@
 }
 
 static void btavrcp_groupnavigation_response_callback(int id, int pressed) {
-    ALOGI("%s", __FUNCTION__);
+    ALOGV("%s", __FUNCTION__);
 
     if (!checkCallbackThread()) {
         ALOGE("Callback: '%s' is not called on the correct thread", __FUNCTION__);
         return;
     }
 
-    sCallbackEnv->CallVoidMethod(mCallbacksObj, method_handleGroupNavigationRsp, (jint)id,
+    sCallbackEnv->CallVoidMethod(sCallbacksObj, method_handleGroupNavigationRsp, (jint)id,
                                                                              (jint)pressed);
     checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
 }
 
-static void btavrcp_connection_state_callback(bool state, bt_bdaddr_t* bd_addr) {
+static void btavrcp_connection_state_callback(
+        bool rc_connect, bool br_connect, bt_bdaddr_t* bd_addr) {
     jbyteArray addr;
 
-    ALOGI("%s", __FUNCTION__);
-    ALOGI("conn state: %d", state);
+    ALOGV("%s", __FUNCTION__);
+    ALOGI("%s conn state rc: %d br: %d", __FUNCTION__, rc_connect, br_connect);
 
     if (!checkCallbackThread()) {                                       \
         ALOGE("Callback: '%s' is not called on the correct thread", __FUNCTION__); \
@@ -115,8 +126,8 @@
     }
 
     sCallbackEnv->SetByteArrayRegion(addr, 0, sizeof(bt_bdaddr_t), (jbyte*) bd_addr);
-    sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onConnectionStateChanged, (jboolean) state,
-                                 addr);
+    sCallbackEnv->CallVoidMethod(sCallbacksObj, method_onConnectionStateChanged,
+                                 (jboolean) rc_connect, (jboolean) br_connect, addr);
     checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
     sCallbackEnv->DeleteLocalRef(addr);
 }
@@ -124,7 +135,7 @@
 static void btavrcp_get_rcfeatures_callback(bt_bdaddr_t *bd_addr, int features) {
     jbyteArray addr;
 
-    ALOGI("%s", __FUNCTION__);
+    ALOGV("%s", __FUNCTION__);
 
     if (!checkCallbackThread()) {                                       \
         ALOGE("Callback: '%s' is not called on the correct thread", __FUNCTION__); \
@@ -139,7 +150,7 @@
     }
 
     sCallbackEnv->SetByteArrayRegion(addr, 0, sizeof(bt_bdaddr_t), (jbyte*) bd_addr);
-    sCallbackEnv->CallVoidMethod(mCallbacksObj, method_getRcFeatures, addr, (jint)features);
+    sCallbackEnv->CallVoidMethod(sCallbacksObj, method_getRcFeatures, addr, (jint)features);
     checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
     sCallbackEnv->DeleteLocalRef(addr);
 }
@@ -148,7 +159,7 @@
                                                                     uint8_t accepted) {
     jbyteArray addr;
 
-    ALOGI("%s", __FUNCTION__);
+    ALOGV("%s", __FUNCTION__);
 
     if (!checkCallbackThread()) {                                       \
         ALOGE("Callback: '%s' is not called on the correct thread", __FUNCTION__); \
@@ -163,7 +174,7 @@
     }
 
     sCallbackEnv->SetByteArrayRegion(addr, 0, sizeof(bt_bdaddr_t), (jbyte*) bd_addr);
-    sCallbackEnv->CallVoidMethod(mCallbacksObj, method_setplayerappsettingrsp, addr, (jint)accepted);
+    sCallbackEnv->CallVoidMethod(sCallbacksObj, method_setplayerappsettingrsp, addr, (jint)accepted);
     checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
     sCallbackEnv->DeleteLocalRef(addr);
 }
@@ -171,7 +182,7 @@
 static void btavrcp_playerapplicationsetting_callback(bt_bdaddr_t *bd_addr, uint8_t num_attr,
         btrc_player_app_attr_t *app_attrs, uint8_t num_ext_attr,
         btrc_player_app_ext_attr_t *ext_attrs) {
-    ALOGI("%s", __FUNCTION__);
+    ALOGV("%s", __FUNCTION__);
     jbyteArray addr;
     jbyteArray playerattribs;
     jint arraylen;
@@ -198,7 +209,7 @@
         /*2 bytes for id and num */
         arraylen += 2 + app_attrs[i].num_val;
     }
-    ALOGI(" arraylen %d", arraylen);
+    ALOGV(" arraylen %d", arraylen);
     playerattribs = sCallbackEnv->NewByteArray(arraylen);
     if(!playerattribs)
     {
@@ -218,7 +229,7 @@
                 (jbyte*)(app_attrs[i].attr_val));
         k = k + app_attrs[i].num_val;
     }
-    sCallbackEnv->CallVoidMethod(mCallbacksObj, method_handleplayerappsetting, addr,
+    sCallbackEnv->CallVoidMethod(sCallbacksObj, method_handleplayerappsetting, addr,
             playerattribs, (jint)arraylen);
     checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
     sCallbackEnv->DeleteLocalRef(addr);
@@ -265,7 +276,7 @@
         sCallbackEnv->SetByteArrayRegion(playerattribs, k, 1, (jbyte*)&(p_vals->attr_values[i]));
         k++;
     }
-    sCallbackEnv->CallVoidMethod(mCallbacksObj, method_handleplayerappsettingchanged, addr,
+    sCallbackEnv->CallVoidMethod(sCallbacksObj, method_handleplayerappsettingchanged, addr,
             playerattribs, (jint)arraylen);
     checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
     sCallbackEnv->DeleteLocalRef(addr);
@@ -291,7 +302,7 @@
     }
 
     sCallbackEnv->SetByteArrayRegion(addr, 0, sizeof(bt_bdaddr_t), (jbyte*)bd_addr);
-    sCallbackEnv->CallVoidMethod(mCallbacksObj, method_handleSetAbsVolume, addr, (jbyte)abs_vol,
+    sCallbackEnv->CallVoidMethod(sCallbacksObj, method_handleSetAbsVolume, addr, (jbyte)abs_vol,
                                  (jbyte)label);
     checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
     sCallbackEnv->DeleteLocalRef(addr);
@@ -315,7 +326,7 @@
     }
 
     sCallbackEnv->SetByteArrayRegion(addr, 0, sizeof(bt_bdaddr_t), (jbyte*)bd_addr);
-    sCallbackEnv->CallVoidMethod(mCallbacksObj, method_handleRegisterNotificationAbsVol, addr,
+    sCallbackEnv->CallVoidMethod(sCallbacksObj, method_handleRegisterNotificationAbsVol, addr,
                                  (jbyte)label);
     checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
     sCallbackEnv->DeleteLocalRef(addr);
@@ -379,7 +390,7 @@
         sCallbackEnv->DeleteLocalRef(str);
     }
 
-    sCallbackEnv->CallVoidMethod(mCallbacksObj, method_handletrackchanged, addr,
+    sCallbackEnv->CallVoidMethod(sCallbacksObj, method_handletrackchanged, addr,
          (jbyte)(num_attr), attribIds, stringArray);
     checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
     sCallbackEnv->DeleteLocalRef(addr);
@@ -402,7 +413,7 @@
         return;
     }
     sCallbackEnv->SetByteArrayRegion(addr, 0, sizeof(bt_bdaddr_t), (jbyte*) bd_addr);
-    sCallbackEnv->CallVoidMethod(mCallbacksObj, method_handleplaypositionchanged, addr,
+    sCallbackEnv->CallVoidMethod(sCallbacksObj, method_handleplaypositionchanged, addr,
          (jint)(song_len), (jint)song_pos);
     sCallbackEnv->DeleteLocalRef(addr);
 }
@@ -419,11 +430,196 @@
         return;
     }
     sCallbackEnv->SetByteArrayRegion(addr, 0, sizeof(bt_bdaddr_t), (jbyte*) bd_addr);
-    sCallbackEnv->CallVoidMethod(mCallbacksObj, method_handleplaystatuschanged, addr,
+    sCallbackEnv->CallVoidMethod(sCallbacksObj, method_handleplaystatuschanged, addr,
              (jbyte)play_status);
     sCallbackEnv->DeleteLocalRef(addr);
 }
 
+static void btavrcp_get_folder_items_callback(bt_bdaddr_t *bd_addr,
+        const btrc_folder_items_t *folder_items, uint8_t count) {
+    /* Folder items are list of items that can be either BTRC_ITEM_PLAYER
+     * BTRC_ITEM_MEDIA, BTRC_ITEM_FOLDER. Here we translate them to their java
+     * counterparts by calling the java constructor for each of the items.
+     */
+    ALOGV("%s count %d", __FUNCTION__, count);
+
+    if (!checkCallbackThread()) {
+        ALOGE("Callback: '%s' is not called on the correct thread", __FUNCTION__);
+        return;
+    }
+
+    // Inspect if the first element is a folder/item or player listing. They are
+    // always exclusive.
+    bool isPlayerListing = count > 0 && (folder_items[0].item_type == BTRC_ITEM_PLAYER);
+
+    // Initialize arrays for Folder OR Player listing.
+    jobjectArray playerItemArray = NULL;
+    jobjectArray folderItemArray = NULL;
+    if (isPlayerListing) {
+        playerItemArray = sCallbackEnv->NewObjectArray((jint) count, class_AvrcpPlayer, 0);
+        if (!playerItemArray) {
+            ALOGE("%s playerItemArray allocation failed.", __FUNCTION__);
+            checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+            return;
+        }
+    } else {
+        folderItemArray = sCallbackEnv->NewObjectArray(
+            (jint) count, class_MediaBrowser_MediaItem, 0);
+        if (!folderItemArray) {
+            ALOGE("%s folderItemArray is empty.", __FUNCTION__);
+            checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+            return;
+        }
+    }
+
+    for (int i = 0; i < count; i++) {
+        const btrc_folder_items_t *item = &(folder_items[i]);
+        ALOGV("%s item type %d", __FUNCTION__, item->item_type);
+        switch (item->item_type) {
+            case BTRC_ITEM_MEDIA:
+            {
+                // Parse name
+                jstring mediaName = sCallbackEnv->NewStringUTF((const char *) item->media.name);
+                // Parse UID
+                jbyteArray uidByteArray = sCallbackEnv->NewByteArray(
+                    sizeof(uint8_t) * BTRC_UID_SIZE);
+                sCallbackEnv->SetByteArrayRegion(
+                    uidByteArray, 0, BTRC_UID_SIZE * sizeof (uint8_t), (jbyte *) item->media.uid);
+
+                // Parse Attrs
+                jintArray attrIdArray = sCallbackEnv->NewIntArray(item->media.num_attrs);
+                jobjectArray attrValArray = sCallbackEnv->NewObjectArray(
+                      item->media.num_attrs,
+                      sCallbackEnv->FindClass("java/lang/String"),
+                      0);
+
+                for (int j = 0; j < item->media.num_attrs; j++) {
+                    sCallbackEnv->SetIntArrayRegion(
+                        attrIdArray, j, 1, (jint *)&(item->media.p_attrs[j].attr_id));
+                    jstring attrValStr = sCallbackEnv->NewStringUTF(
+                        (char *)(item->media.p_attrs[j].text));
+                    sCallbackEnv->SetObjectArrayElement(
+                        attrValArray, j, attrValStr);
+                    sCallbackEnv->DeleteLocalRef(attrValStr);
+                }
+
+                jobject mediaObj = (jobject) sCallbackEnv->CallObjectMethod(
+                    sCallbacksObj, method_createFromNativeMediaItem, uidByteArray,
+                    (jint) item->media.type, mediaName, attrIdArray, attrValArray);
+                sCallbackEnv->DeleteLocalRef(uidByteArray);
+                sCallbackEnv->DeleteLocalRef(mediaName);
+                sCallbackEnv->DeleteLocalRef(attrIdArray);
+                sCallbackEnv->DeleteLocalRef(attrValArray);
+
+                if (!mediaObj) {
+                    ALOGE("%s failed to create MediaItem for type ITEM_MEDIA", __FUNCTION__);
+                    return;
+                }
+                sCallbackEnv->SetObjectArrayElement(folderItemArray, i, mediaObj);
+                sCallbackEnv->DeleteLocalRef(mediaObj);
+                break;
+            }
+
+            case BTRC_ITEM_FOLDER:
+            {
+                // Parse name
+                jstring folderName = sCallbackEnv->NewStringUTF((const char *) item->folder.name);
+                // Parse UID
+                jbyteArray uidByteArray = sCallbackEnv->NewByteArray(
+                    sizeof(uint8_t) * BTRC_UID_SIZE);
+                sCallbackEnv->SetByteArrayRegion(
+                    uidByteArray, 0, BTRC_UID_SIZE * sizeof (uint8_t), (jbyte *) item->folder.uid);
+
+                jobject folderObj = (jobject) sCallbackEnv->CallObjectMethod(
+                    sCallbacksObj, method_createFromNativeFolderItem, uidByteArray,
+                    (jint) item->folder.type, folderName, (jint) item->folder.playable);
+                sCallbackEnv->DeleteLocalRef(uidByteArray);
+                sCallbackEnv->DeleteLocalRef(folderName);
+
+                if (!folderObj) {
+                    ALOGE("%s failed to create MediaItem for type ITEM_MEDIA", __FUNCTION__);
+                    return;
+                }
+                sCallbackEnv->SetObjectArrayElement(folderItemArray, i, folderObj);
+                sCallbackEnv->DeleteLocalRef(folderObj);
+                break;
+            }
+
+            case BTRC_ITEM_PLAYER:
+            {
+                // Parse name
+                isPlayerListing = true;
+                jint id = (jint) item->player.player_id;
+                jint playerType = (jint) item->player.major_type;
+                jint playStatus = (jint) item->player.play_status;
+                jbyteArray featureBitArray = sCallbackEnv->NewByteArray(
+                    BTRC_FEATURE_BIT_MASK_SIZE * sizeof (uint8_t));
+                if (!featureBitArray) {
+                    ALOGE("%s failed to allocate featureBitArray", __FUNCTION__);
+                    sCallbackEnv->DeleteLocalRef(playerItemArray);
+                    sCallbackEnv->DeleteLocalRef(folderItemArray);
+                    checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+                    return;
+                }
+                sCallbackEnv->SetByteArrayRegion(
+                    featureBitArray, 0, sizeof(uint8_t) * BTRC_FEATURE_BIT_MASK_SIZE,
+                    (jbyte *) item->player.features);
+                jstring playerName = sCallbackEnv->NewStringUTF((const char *) item->player.name);
+                jobject playerObj = (jobject) sCallbackEnv->CallObjectMethod(
+                    sCallbacksObj, method_createFromNativePlayerItem, id,
+                    playerName, featureBitArray, playStatus, playerType);
+                sCallbackEnv->SetObjectArrayElement(playerItemArray, i, playerObj);
+
+                sCallbackEnv->DeleteLocalRef(featureBitArray);
+                sCallbackEnv->DeleteLocalRef(playerObj);
+                break;
+            }
+
+            default:
+                ALOGE("%s cannot understand type %d", __FUNCTION__, item->item_type);
+        }
+        ALOGI("%s inserted %d elements uptil now", __FUNCTION__, i);
+    }
+
+    ALOGI("%s returning the complete set now", __FUNCTION__);
+    if (isPlayerListing) {
+        sCallbackEnv->CallVoidMethod(sCallbacksObj, method_handleGetPlayerItemsRsp,
+                                     playerItemArray);
+    } else {
+        sCallbackEnv->CallVoidMethod(sCallbacksObj, method_handleGetFolderItemsRsp,
+                                     folderItemArray);
+    }
+    if (isPlayerListing) {
+        sCallbackEnv->DeleteLocalRef(playerItemArray);
+    } else {
+        sCallbackEnv->DeleteLocalRef(folderItemArray);
+    }
+}
+
+static void btavrcp_change_path_callback(bt_bdaddr_t *bd_addr, uint8_t count) {
+    ALOGI("%s count %d", __FUNCTION__, count);
+
+    if (!checkCallbackThread()) {
+        ALOGE("Callback: '%s' is not called on the correct thread", __FUNCTION__);
+        return;
+    }
+
+    sCallbackEnv->CallVoidMethod(sCallbacksObj, method_handleChangeFolderRsp, (jint) count);
+}
+
+static void btavrcp_set_browsed_player_callback(
+        bt_bdaddr_t *bd_addr, uint8_t num_items, uint8_t depth) {
+    ALOGI("%s items %d depth %d", __FUNCTION__, num_items, depth);
+
+    if (!checkCallbackThread()) {
+        ALOGE("Callback: '%s' is not called on the correct thread", __FUNCTION__);
+        return;
+    }
+
+    sCallbackEnv->CallVoidMethod(
+        sCallbacksObj, method_handleSetBrowsedPlayerRsp, (jint) num_items, (jint) depth);
+}
+
 static btrc_ctrl_callbacks_t sBluetoothAvrcpCallbacks = {
     sizeof(sBluetoothAvrcpCallbacks),
     btavrcp_passthrough_response_callback,
@@ -437,7 +633,10 @@
     btavrcp_register_notification_absvol_callback,
     btavrcp_track_changed_callback,
     btavrcp_play_position_changed_callback,
-    btavrcp_play_status_changed_callback
+    btavrcp_play_status_changed_callback,
+    btavrcp_get_folder_items_callback,
+    btavrcp_change_path_callback,
+    btavrcp_set_browsed_player_callback
 };
 
 static void classInitNative(JNIEnv* env, jclass clazz) {
@@ -448,7 +647,7 @@
         env->GetMethodID(clazz, "handleGroupNavigationRsp", "(II)V");
 
     method_onConnectionStateChanged =
-        env->GetMethodID(clazz, "onConnectionStateChanged", "(Z[B)V");
+        env->GetMethodID(clazz, "onConnectionStateChanged", "(ZZ[B)V");
 
     method_getRcFeatures =
         env->GetMethodID(clazz, "getRcFeatures", "([BI)V");
@@ -475,13 +674,40 @@
         env->GetMethodID(clazz, "onPlayPositionChanged", "([BII)V");
 
     method_handleplaystatuschanged =
-            env->GetMethodID(clazz, "onPlayStatusChanged", "([BB)V");
+        env->GetMethodID(clazz, "onPlayStatusChanged", "([BB)V");
+
+    method_handleGetFolderItemsRsp =
+        env->GetMethodID(clazz, "handleGetFolderItemsRsp", "([Landroid/media/browse/MediaBrowser$MediaItem;)V");
+    method_handleGetPlayerItemsRsp =
+        env->GetMethodID(clazz, "handleGetPlayerItemsRsp",
+                         "([Lcom/android/bluetooth/avrcpcontroller/AvrcpPlayer;)V");
+
+    method_createFromNativeMediaItem =
+        env->GetMethodID(clazz, "createFromNativeMediaItem",
+                         "([BILjava/lang/String;[I[Ljava/lang/String;)Landroid/media/browse/MediaBrowser$MediaItem;");
+    method_createFromNativeFolderItem =
+        env->GetMethodID(clazz, "createFromNativeFolderItem",
+                         "([BILjava/lang/String;I)Landroid/media/browse/MediaBrowser$MediaItem;");
+    method_createFromNativePlayerItem =
+        env->GetMethodID(clazz, "createFromNativePlayerItem",
+                         "(ILjava/lang/String;[BII)Lcom/android/bluetooth/avrcpcontroller/AvrcpPlayer;");
+    method_handleChangeFolderRsp =
+        env->GetMethodID(clazz, "handleChangeFolderRsp", "(I)V");
+    method_handleSetBrowsedPlayerRsp =
+        env->GetMethodID(clazz, "handleSetBrowsedPlayerRsp", "(II)V");
     ALOGI("%s: succeeds", __FUNCTION__);
 }
 
 static void initNative(JNIEnv *env, jobject object) {
     const bt_interface_t* btInf;
     bt_status_t status;
+    sEnv = env;
+
+    jclass tmpMediaItem = env->FindClass("android/media/browse/MediaBrowser$MediaItem");
+    class_MediaBrowser_MediaItem = (jclass) env->NewGlobalRef(tmpMediaItem);
+
+    jclass tmpBtPlayer = env->FindClass("com/android/bluetooth/avrcpcontroller/AvrcpPlayer");
+    class_AvrcpPlayer = (jclass) env->NewGlobalRef(tmpBtPlayer);
 
     if ( (btInf = getBluetoothInterface()) == NULL) {
         ALOGE("Bluetooth module is not loaded");
@@ -494,10 +720,10 @@
          sBluetoothAvrcpInterface = NULL;
     }
 
-    if (mCallbacksObj != NULL) {
+    if (sCallbacksObj != NULL) {
          ALOGW("Cleaning up Avrcp callback object");
-         env->DeleteGlobalRef(mCallbacksObj);
-         mCallbacksObj = NULL;
+         env->DeleteGlobalRef(sCallbacksObj);
+         sCallbacksObj = NULL;
     }
 
     if ( (sBluetoothAvrcpInterface = (btrc_ctrl_interface_t *)
@@ -513,7 +739,7 @@
         return;
     }
 
-    mCallbacksObj = env->NewGlobalRef(object);
+    sCallbacksObj = env->NewGlobalRef(object);
 }
 
 static void cleanupNative(JNIEnv *env, jobject object) {
@@ -529,9 +755,9 @@
         sBluetoothAvrcpInterface = NULL;
     }
 
-    if (mCallbacksObj != NULL) {
-        env->DeleteGlobalRef(mCallbacksObj);
-        mCallbacksObj = NULL;
+    if (sCallbacksObj != NULL) {
+        env->DeleteGlobalRef(sCallbacksObj);
+        sCallbacksObj = NULL;
     }
 }
 
@@ -685,6 +911,137 @@
     env->ReleaseByteArrayElements(address, addr, 0);
 }
 
+static void getNowPlayingListNative(JNIEnv *env, jobject object, jbyteArray address, jbyte start,
+                                    jbyte items) {
+    bt_status_t status;
+    jbyte *addr;
+
+    if (!sBluetoothAvrcpInterface) return;
+    addr = env->GetByteArrayElements(address, NULL);
+    if (!addr) {
+        jniThrowIOException(env, EINVAL);
+        return;
+    }
+    ALOGV("%s: sBluetoothAvrcpInterface: %p", __FUNCTION__, sBluetoothAvrcpInterface);
+    if ((status = sBluetoothAvrcpInterface->get_now_playing_list_cmd(
+        (bt_bdaddr_t *) addr, (uint8_t) start, (uint8_t) items)) != BT_STATUS_SUCCESS) {
+        ALOGE("Failed sending getNowPlayingListNative command, status: %d", status);
+    }
+    env->ReleaseByteArrayElements(address, addr, 0);
+}
+
+static void getFolderListNative(JNIEnv *env, jobject object, jbyteArray address, jbyte start,
+                                    jbyte items) {
+    bt_status_t status;
+    jbyte *addr;
+
+    if (!sBluetoothAvrcpInterface) return;
+    addr = env->GetByteArrayElements(address, NULL);
+    if (!addr) {
+        jniThrowIOException(env, EINVAL);
+        return;
+    }
+    ALOGV("%s: sBluetoothAvrcpInterface: %p", __FUNCTION__, sBluetoothAvrcpInterface);
+    if ((status = sBluetoothAvrcpInterface->get_folder_list_cmd(
+        (bt_bdaddr_t *) addr, (uint8_t) start, (uint8_t) items)) != BT_STATUS_SUCCESS) {
+        ALOGE("Failed sending getFolderListNative command, status: %d", status);
+    }
+    env->ReleaseByteArrayElements(address, addr, 0);
+}
+
+static void getPlayerListNative(JNIEnv *env, jobject object, jbyteArray address, jbyte start,
+                                    jbyte items) {
+    bt_status_t status;
+    jbyte *addr;
+
+    if (!sBluetoothAvrcpInterface) return;
+    addr = env->GetByteArrayElements(address, NULL);
+    if (!addr) {
+        jniThrowIOException(env, EINVAL);
+        return;
+    }
+    ALOGI("%s: sBluetoothAvrcpInterface: %p", __FUNCTION__, sBluetoothAvrcpInterface);
+    if ((status = sBluetoothAvrcpInterface->get_player_list_cmd(
+        (bt_bdaddr_t *) addr, (uint8_t) start, (uint8_t) items)) != BT_STATUS_SUCCESS) {
+        ALOGE("Failed sending getPlayerListNative command, status: %d", status);
+    }
+    env->ReleaseByteArrayElements(address, addr, 0);
+}
+
+static void changeFolderPathNative(JNIEnv *env, jobject object, jbyteArray address, jbyte direction,
+                                   jbyteArray uidarr) {
+    bt_status_t status;
+
+    jbyte *addr;
+    if (!sBluetoothAvrcpInterface) return;
+    addr = env->GetByteArrayElements(address, NULL);
+    if (!addr) {
+        jniThrowIOException(env, EINVAL);
+        return;
+    }
+
+    jbyte *uid;
+    uid = env->GetByteArrayElements(uidarr, NULL);
+    if (!uid) {
+        jniThrowIOException(env, EINVAL);
+        return;
+    }
+
+    ALOGI("%s: sBluetoothAvrcpInterface: %p", __FUNCTION__, sBluetoothAvrcpInterface);
+    if ((status = sBluetoothAvrcpInterface->change_folder_path_cmd(
+            (bt_bdaddr_t *) addr, (uint8_t) direction, (uint8_t *) uid)) != BT_STATUS_SUCCESS) {
+        ALOGE("Failed sending changeFolderPathNative command, status: %d", status);
+    }
+    env->ReleaseByteArrayElements(address, addr, 0);
+}
+
+static void setBrowsedPlayerNative(JNIEnv *env, jobject object, jbyteArray address, jint id) {
+    bt_status_t status;
+
+    jbyte *addr;
+    if (!sBluetoothAvrcpInterface) return;
+    addr = env->GetByteArrayElements(address, NULL);
+    if (!addr) {
+        jniThrowIOException(env, EINVAL);
+        return;
+    }
+
+    ALOGI("%s: sBluetoothAvrcpInterface: %p", __FUNCTION__, sBluetoothAvrcpInterface);
+    if ((status = sBluetoothAvrcpInterface->set_browsed_player_cmd(
+            (bt_bdaddr_t *) addr, (uint16_t) id)) != BT_STATUS_SUCCESS) {
+        ALOGE("Failed sending changeFolderPathNative command, status: %d", status);
+    }
+    env->ReleaseByteArrayElements(address, addr, 0);
+}
+
+static void playItemNative(JNIEnv *env, jobject object, jbyteArray address, jbyte scope,
+                                   jbyteArray uidArr, jint uidCounter) {
+    bt_status_t status;
+
+    jbyte *addr;
+    if (!sBluetoothAvrcpInterface) return;
+    addr = env->GetByteArrayElements(address, NULL);
+    if (!addr) {
+        jniThrowIOException(env, EINVAL);
+        return;
+    }
+
+    jbyte *uid;
+    uid = env->GetByteArrayElements(uidArr, NULL);
+    if (!uid) {
+        jniThrowIOException(env, EINVAL);
+        return;
+    }
+
+    ALOGI("%s: sBluetoothAvrcpInterface: %p", __FUNCTION__, sBluetoothAvrcpInterface);
+    if ((status = sBluetoothAvrcpInterface->play_item_cmd(
+            (bt_bdaddr_t *) addr, (uint8_t) scope, (uint8_t *) uid,
+            (uint16_t) uidCounter)) != BT_STATUS_SUCCESS) {
+        ALOGE("Failed sending playItemNative command, status: %d", status);
+    }
+    env->ReleaseByteArrayElements(address, addr, 0);
+}
+
 static JNINativeMethod sMethods[] = {
     {"classInitNative", "()V", (void *) classInitNative},
     {"initNative", "()V", (void *) initNative},
@@ -695,11 +1052,18 @@
                                (void *) setPlayerApplicationSettingValuesNative},
     {"sendAbsVolRspNative", "([BII)V",(void *) sendAbsVolRspNative},
     {"sendRegisterAbsVolRspNative", "([BBII)V",(void *) sendRegisterAbsVolRspNative},
+    {"getNowPlayingListNative", "([BBB)V", (void *) getNowPlayingListNative},
+    {"getFolderListNative", "([BBB)V", (void *) getFolderListNative},
+    {"getPlayerListNative", "([BBB)V", (void *) getPlayerListNative},
+    {"changeFolderPathNative", "([BB[B)V", (void *) changeFolderPathNative},
+    {"playItemNative", "([BB[BI)V", (void *) playItemNative},
+    {"setBrowsedPlayerNative", "([BI)V", (void *) setBrowsedPlayerNative},
 };
 
 int register_com_android_bluetooth_avrcp_controller(JNIEnv* env)
 {
-    return jniRegisterNativeMethods(env, "com/android/bluetooth/avrcp/AvrcpControllerService",
+    return jniRegisterNativeMethods(env,
+                                    "com/android/bluetooth/avrcpcontroller/AvrcpControllerService",
                                     sMethods, NELEM(sMethods));
 }
 
diff --git a/jni/com_android_bluetooth_gatt.cpp b/jni/com_android_bluetooth_gatt.cpp
index fa57e4f..70dcfea 100644
--- a/jni/com_android_bluetooth_gatt.cpp
+++ b/jni/com_android_bluetooth_gatt.cpp
@@ -1365,7 +1365,6 @@
     if (!sGattIf) return;
 
     set_uuid(uuid.uu, app_uuid_msb, app_uuid_lsb);
-    error("advertiser address is: %d", sGattIf->advertiser);
     sGattIf->advertiser->RegisterAdvertiser(base::Bind(&ble_advertiser_register_cb, uuid));
 }
 
diff --git a/src/com/android/bluetooth/a2dpsink/A2dpSinkService.java b/src/com/android/bluetooth/a2dpsink/A2dpSinkService.java
index 1d84d0f..4d0d2ca 100644
--- a/src/com/android/bluetooth/a2dpsink/A2dpSinkService.java
+++ b/src/com/android/bluetooth/a2dpsink/A2dpSinkService.java
@@ -17,13 +17,14 @@
 package com.android.bluetooth.a2dpsink;
 
 import android.bluetooth.BluetoothAudioConfig;
-import android.bluetooth.BluetoothAvrcpController;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothProfile;
 import android.bluetooth.IBluetoothA2dpSink;
 import android.content.Intent;
 import android.provider.Settings;
 import android.util.Log;
+
+import com.android.bluetooth.avrcpcontroller.AvrcpControllerService;
 import com.android.bluetooth.a2dpsink.mbs.A2dpMediaBrowserService;
 
 import com.android.bluetooth.btservice.ProfileService;
@@ -192,12 +193,12 @@
      */
     public void informAvrcpPassThroughCmd(BluetoothDevice device, int keyCode, int keyState) {
         if (mStateMachine != null) {
-            if (keyCode == BluetoothAvrcpController.PASS_THRU_CMD_ID_PLAY &&
-                keyState == BluetoothAvrcpController.KEY_STATE_RELEASED) {
+            if (keyCode == AvrcpControllerService.PASS_THRU_CMD_ID_PLAY &&
+                keyState == AvrcpControllerService.KEY_STATE_RELEASED) {
                 mStateMachine.sendMessage(A2dpSinkStateMachine.EVENT_AVRCP_CT_PLAY);
-            } else if ((keyCode == BluetoothAvrcpController.PASS_THRU_CMD_ID_PAUSE ||
-                       keyCode == BluetoothAvrcpController.PASS_THRU_CMD_ID_STOP) &&
-                       keyState == BluetoothAvrcpController.KEY_STATE_RELEASED) {
+            } else if ((keyCode == AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE ||
+                       keyCode == AvrcpControllerService.PASS_THRU_CMD_ID_STOP) &&
+                       keyState == AvrcpControllerService.KEY_STATE_RELEASED) {
                 mStateMachine.sendMessage(A2dpSinkStateMachine.EVENT_AVRCP_CT_PAUSE);
             }
         }
diff --git a/src/com/android/bluetooth/a2dpsink/A2dpSinkStateMachine.java b/src/com/android/bluetooth/a2dpsink/A2dpSinkStateMachine.java
index 6e6e2af..c005f27 100644
--- a/src/com/android/bluetooth/a2dpsink/A2dpSinkStateMachine.java
+++ b/src/com/android/bluetooth/a2dpsink/A2dpSinkStateMachine.java
@@ -29,7 +29,6 @@
 package com.android.bluetooth.a2dpsink;
 
 import android.bluetooth.BluetoothA2dpSink;
-import android.bluetooth.BluetoothAvrcpController;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothAudioConfig;
 import android.bluetooth.BluetoothDevice;
@@ -49,7 +48,7 @@
 import com.android.bluetooth.Utils;
 import com.android.bluetooth.btservice.AdapterService;
 import com.android.bluetooth.btservice.ProfileService;
-import com.android.bluetooth.avrcp.AvrcpControllerService;
+import com.android.bluetooth.avrcpcontroller.AvrcpControllerService;
 import com.android.internal.util.IState;
 import com.android.internal.util.State;
 import com.android.internal.util.StateMachine;
@@ -694,16 +693,21 @@
         return false;
     }
 
+    // Utility Functions
     boolean okToConnect(BluetoothDevice device) {
         AdapterService adapterService = AdapterService.getAdapterService();
-        boolean ret = true;
-        //check if this is an incoming connection in Quiet mode.
-        if((adapterService == null) ||
-           ((adapterService.isQuietModeEnabled() == true) &&
-           (mTargetDevice == null))){
-            ret = false;
+        int priority = mService.getPriority(device);
+
+        // check priority and accept or reject the connection. if priority is undefined
+        // it is likely that our SDP has not completed and peer is initiating the
+        // connection. Allow this connection, provided the device is bonded
+        if((BluetoothProfile.PRIORITY_OFF < priority) ||
+                ((BluetoothProfile.PRIORITY_UNDEFINED == priority) &&
+                (device.getBondState() != BluetoothDevice.BOND_NONE))){
+            return true;
         }
-        return ret;
+        logw("okToConnect not OK to connect " + device);
+        return false;
     }
 
     synchronized List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
@@ -802,6 +806,7 @@
             this.type = type;
         }
     }
+
     /** Handles A2DP connection state change intent broadcasts. */
     private class IntentBroadcastHandler extends Handler {
 
@@ -833,11 +838,11 @@
             if ((avrcpCtrlService != null) && (mDevice != null) &&
                 (avrcpCtrlService.getConnectedDevices().contains(mDevice))){
                 avrcpCtrlService.sendPassThroughCmd(mDevice,
-                    BluetoothAvrcpController.PASS_THRU_CMD_ID_PLAY,
-                    BluetoothAvrcpController.KEY_STATE_PRESSED);
+                    AvrcpControllerService.PASS_THRU_CMD_ID_PLAY,
+                    AvrcpControllerService.KEY_STATE_PRESSED);
                 avrcpCtrlService.sendPassThroughCmd(mDevice,
-                    BluetoothAvrcpController.PASS_THRU_CMD_ID_PLAY,
-                    BluetoothAvrcpController.KEY_STATE_RELEASED);
+                    AvrcpControllerService.PASS_THRU_CMD_ID_PLAY,
+                    AvrcpControllerService.KEY_STATE_RELEASED);
                 log(" SendPassThruPlay command sent - ");
                 return true;
             } else {
diff --git a/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamingStateMachine.java b/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamingStateMachine.java
index fb62c95..9a84eb0 100644
--- a/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamingStateMachine.java
+++ b/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamingStateMachine.java
@@ -17,7 +17,6 @@
 package com.android.bluetooth.a2dpsink;
 
 import android.bluetooth.BluetoothA2dpSink;
-import android.bluetooth.BluetoothAvrcpController;
 import android.bluetooth.BluetoothDevice;
 import android.content.Context;
 import android.media.AudioManager;
@@ -25,7 +24,7 @@
 import android.os.Message;
 import android.util.Log;
 
-import com.android.bluetooth.avrcp.AvrcpControllerService;
+import com.android.bluetooth.avrcpcontroller.AvrcpControllerService;
 import com.android.bluetooth.R;
 import com.android.internal.util.IState;
 import com.android.internal.util.State;
@@ -209,12 +208,12 @@
             }
             avrcpService.sendPassThroughCmd(
                 avrcpService.getConnectedDevices().get(0),
-                BluetoothAvrcpController.PASS_THRU_CMD_ID_PAUSE,
-                BluetoothAvrcpController.KEY_STATE_PRESSED);
+                AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE,
+                AvrcpControllerService.KEY_STATE_PRESSED);
             avrcpService.sendPassThroughCmd(
                 avrcpService.getConnectedDevices().get(0),
-                BluetoothAvrcpController.PASS_THRU_CMD_ID_PAUSE,
-                BluetoothAvrcpController.KEY_STATE_RELEASED);
+                AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE,
+                AvrcpControllerService.KEY_STATE_RELEASED);
         } else {
             Log.e(TAG, "Passthrough not sent, connection un-available.");
         }
@@ -233,12 +232,12 @@
             }
             avrcpService.sendPassThroughCmd(
                 avrcpService.getConnectedDevices().get(0),
-                BluetoothAvrcpController.PASS_THRU_CMD_ID_PLAY,
-                BluetoothAvrcpController.KEY_STATE_PRESSED);
+                AvrcpControllerService.PASS_THRU_CMD_ID_PLAY,
+                AvrcpControllerService.KEY_STATE_PRESSED);
             avrcpService.sendPassThroughCmd(
                 avrcpService.getConnectedDevices().get(0),
-                BluetoothAvrcpController.PASS_THRU_CMD_ID_PLAY,
-                BluetoothAvrcpController.KEY_STATE_RELEASED);
+                AvrcpControllerService.PASS_THRU_CMD_ID_PLAY,
+                AvrcpControllerService.KEY_STATE_RELEASED);
         } else {
             Log.e(TAG, "Passthrough not sent, connection un-available.");
         }
diff --git a/src/com/android/bluetooth/a2dpsink/mbs/A2dpMediaBrowserService.java b/src/com/android/bluetooth/a2dpsink/mbs/A2dpMediaBrowserService.java
index 7ee0276..f663571 100644
--- a/src/com/android/bluetooth/a2dpsink/mbs/A2dpMediaBrowserService.java
+++ b/src/com/android/bluetooth/a2dpsink/mbs/A2dpMediaBrowserService.java
@@ -24,8 +24,10 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.media.MediaMetadata;
+import android.media.browse.MediaBrowser;
 import android.media.browse.MediaBrowser.MediaItem;
+import android.media.MediaDescription;
+import android.media.MediaMetadata;
 import android.media.session.MediaController;
 import android.media.session.MediaSession;
 import android.media.session.PlaybackState;
@@ -33,44 +35,54 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
+import android.os.Parcelable;
 import android.os.ResultReceiver;
 import android.service.media.MediaBrowserService;
 import android.util.Pair;
 import android.util.Log;
 
 import com.android.bluetooth.R;
+import com.android.bluetooth.avrcpcontroller.AvrcpControllerService;
+import com.android.bluetooth.avrcpcontroller.BrowseTree;
 
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 public class A2dpMediaBrowserService extends MediaBrowserService {
     private static final String TAG = "A2dpMediaBrowserService";
-    private static final String MEDIA_ID_ROOT = "__ROOT__";
     private static final String UNKNOWN_BT_AUDIO = "__UNKNOWN_BT_AUDIO__";
     private static final float PLAYBACK_SPEED = 1.0f;
 
     // Message sent when A2DP device is disconnected.
     private static final int MSG_DEVICE_DISCONNECT = 0;
-    // Message snet when the AVRCP profile is disconnected = 1;
-    private static final int MSG_PROFILE_DISCONNECT = 1;
     // Message sent when A2DP device is connected.
     private static final int MSG_DEVICE_CONNECT = 2;
-    // Message sent when AVRCP profile is connected (note AVRCP profile may be connected before or
-    // after A2DP device is connected).
-    private static final int MSG_PROFILE_CONNECT = 3;
     // Message sent when we recieve a TRACK update from AVRCP profile over a connected A2DP device.
     private static final int MSG_TRACK = 4;
     // Internal message sent to trigger a AVRCP action.
     private static final int MSG_AVRCP_PASSTHRU = 5;
+    // Message sent when AVRCP browse is connected.
+    private static final int MSG_DEVICE_BROWSE_CONNECT = 6;
+    // Message sent when AVRCP browse is disconnected.
+    private static final int MSG_DEVICE_BROWSE_DISCONNECT = 7;
+    // Message sent when folder list is fetched.
+    private static final int MSG_FOLDER_LIST = 9;
 
     private MediaSession mSession;
     private MediaMetadata mA2dpMetadata;
 
-    private BluetoothAdapter mAdapter;
-    private BluetoothAvrcpController mAvrcpProfile;
+    private AvrcpControllerService mAvrcpCtrlSrvc;
+    private boolean mBrowseConnected = false;
     private BluetoothDevice mA2dpDevice = null;
     private Handler mAvrcpCommandQueue;
+    private final Map<String, Result<List<MediaItem>>> mParentIdToRequestMap = new HashMap<>();
+    private static final List<MediaItem> mEmptyList = new ArrayList<MediaItem>();
+
+    // Browsing related structures.
+    private List<MediaItem> mNowPlayingList = null;
 
     private long mTransportControlFlags = PlaybackState.ACTION_PAUSE | PlaybackState.ACTION_PLAY
             | PlaybackState.ACTION_SKIP_TO_NEXT | PlaybackState.ACTION_SKIP_TO_PREVIOUS;
@@ -95,15 +107,9 @@
                 case MSG_DEVICE_CONNECT:
                     inst.msgDeviceConnect((BluetoothDevice) msg.obj);
                     break;
-                case MSG_PROFILE_CONNECT:
-                    inst.msgProfileConnect((BluetoothProfile) msg.obj);
-                    break;
                 case MSG_DEVICE_DISCONNECT:
                     inst.msgDeviceDisconnect((BluetoothDevice) msg.obj);
                     break;
-                case MSG_PROFILE_DISCONNECT:
-                    inst.msgProfileDisconnect();
-                    break;
                 case MSG_TRACK:
                     Pair<PlaybackState, MediaMetadata> pair =
                         (Pair<PlaybackState, MediaMetadata>) (msg.obj);
@@ -112,6 +118,17 @@
                 case MSG_AVRCP_PASSTHRU:
                     inst.msgPassThru((int) msg.obj);
                     break;
+                case MSG_DEVICE_BROWSE_CONNECT:
+                    inst.msgDeviceBrowseConnect((BluetoothDevice) msg.obj);
+                    break;
+                case MSG_DEVICE_BROWSE_DISCONNECT:
+                    inst.msgDeviceBrowseDisconnect((BluetoothDevice) msg.obj);
+                    break;
+                case MSG_FOLDER_LIST:
+                    inst.msgFolderList((Intent) msg.obj);
+                    break;
+                default:
+                    Log.e(TAG, "Message not handled " + msg);
             }
         }
     }
@@ -120,6 +137,7 @@
     public void onCreate() {
         Log.d(TAG, "onCreate");
         super.onCreate();
+
         mSession = new MediaSession(this, TAG);
         setSessionToken(mSession.getSessionToken());
         mSession.setCallback(mSessionCallbacks);
@@ -127,13 +145,18 @@
                 MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS);
         mAvrcpCommandQueue = new AvrcpCommandQueueHandler(Looper.getMainLooper(), this);
 
-        mAdapter = BluetoothAdapter.getDefaultAdapter();
-        mAdapter.getProfileProxy(this, mServiceListener, BluetoothProfile.AVRCP_CONTROLLER);
+        refreshInitialPlayingState();
 
         IntentFilter filter = new IntentFilter();
         filter.addAction(BluetoothAvrcpController.ACTION_CONNECTION_STATE_CHANGED);
-        filter.addAction(BluetoothAvrcpController.ACTION_TRACK_EVENT);
+        filter.addAction(AvrcpControllerService.ACTION_BROWSE_CONNECTION_STATE_CHANGED);
+        filter.addAction(AvrcpControllerService.ACTION_TRACK_EVENT);
+        filter.addAction(AvrcpControllerService.ACTION_FOLDER_LIST);
         registerReceiver(mBtReceiver, filter);
+
+        synchronized (this) {
+            mParentIdToRequestMap.clear();
+        }
     }
 
     @Override
@@ -146,37 +169,32 @@
 
     @Override
     public BrowserRoot onGetRoot(String clientPackageName, int clientUid, Bundle rootHints) {
-        return new BrowserRoot(MEDIA_ID_ROOT, null);
+        return new BrowserRoot(BrowseTree.ROOT, null);
     }
 
     @Override
-    public void onLoadChildren(final String parentMediaId, final Result<List<MediaItem>> result) {
+    public synchronized void onLoadChildren(
+            final String parentMediaId, final Result<List<MediaItem>> result) {
+        if (mAvrcpCtrlSrvc == null) {
+            Log.e(TAG, "AVRCP not yet connected.");
+            result.sendResult(mEmptyList);
+            return;
+        }
+
         Log.d(TAG, "onLoadChildren parentMediaId=" + parentMediaId);
-        List<MediaItem> items = new ArrayList<MediaItem>();
-        result.sendResult(items);
+        mAvrcpCtrlSrvc.getChildren(mA2dpDevice, parentMediaId, 0, 0xff);
+
+        // Since we are using this thread from a binder thread we should make sure that
+        // we synchronize against other such asynchronous calls.
+        synchronized (this) {
+            mParentIdToRequestMap.put(parentMediaId, result);
+        }
+        result.detach();
     }
 
-    BluetoothProfile.ServiceListener mServiceListener = new BluetoothProfile.ServiceListener() {
-        public void onServiceConnected(int profile, BluetoothProfile proxy) {
-            Log.d(TAG, "onServiceConnected");
-            if (profile == BluetoothProfile.AVRCP_CONTROLLER) {
-                mAvrcpCommandQueue.obtainMessage(MSG_PROFILE_CONNECT, proxy).sendToTarget();
-                List<BluetoothDevice> devices = proxy.getConnectedDevices();
-                if (devices != null && devices.size() > 0) {
-                    BluetoothDevice device = devices.get(0);
-                    Log.d(TAG, "got AVRCP device " + device);
-                }
-            }
-        }
-
-        public void onServiceDisconnected(int profile) {
-            Log.d(TAG, "onServiceDisconnected " + profile);
-            if (profile == BluetoothProfile.AVRCP_CONTROLLER) {
-                mAvrcpProfile = null;
-                mAvrcpCommandQueue.obtainMessage(MSG_PROFILE_DISCONNECT).sendToTarget();
-            }
-        }
-    };
+    @Override
+    public void onLoadItem(String itemId, Result<MediaBrowser.MediaItem> result) {
+    }
 
     // Media Session Stuff.
     private MediaSession.Callback mSessionCallbacks = new MediaSession.Callback() {
@@ -184,7 +202,7 @@
         public void onPlay() {
             Log.d(TAG, "onPlay");
             mAvrcpCommandQueue.obtainMessage(
-                MSG_AVRCP_PASSTHRU, BluetoothAvrcpController.PASS_THRU_CMD_ID_PLAY).sendToTarget();
+                MSG_AVRCP_PASSTHRU, AvrcpControllerService.PASS_THRU_CMD_ID_PLAY).sendToTarget();
             // TRACK_EVENT should be fired eventually and the UI should be hence updated.
         }
 
@@ -192,7 +210,7 @@
         public void onPause() {
             Log.d(TAG, "onPause");
             mAvrcpCommandQueue.obtainMessage(
-                MSG_AVRCP_PASSTHRU, BluetoothAvrcpController.PASS_THRU_CMD_ID_PAUSE).sendToTarget();
+                MSG_AVRCP_PASSTHRU, AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE).sendToTarget();
             // TRACK_EVENT should be fired eventually and the UI should be hence updated.
         }
 
@@ -200,7 +218,7 @@
         public void onSkipToNext() {
             Log.d(TAG, "onSkipToNext");
             mAvrcpCommandQueue.obtainMessage(
-                MSG_AVRCP_PASSTHRU, BluetoothAvrcpController.PASS_THRU_CMD_ID_FORWARD)
+                MSG_AVRCP_PASSTHRU, AvrcpControllerService.PASS_THRU_CMD_ID_FORWARD)
                 .sendToTarget();
             // TRACK_EVENT should be fired eventually and the UI should be hence updated.
         }
@@ -210,7 +228,7 @@
             Log.d(TAG, "onSkipToPrevious");
 
             mAvrcpCommandQueue.obtainMessage(
-                MSG_AVRCP_PASSTHRU, BluetoothAvrcpController.PASS_THRU_CMD_ID_BACKWARD)
+                MSG_AVRCP_PASSTHRU, AvrcpControllerService.PASS_THRU_CMD_ID_BACKWARD)
                 .sendToTarget();
             // TRACK_EVENT should be fired eventually and the UI should be hence updated.
         }
@@ -243,9 +261,16 @@
 
         @Override
         public void onPlayFromMediaId(String mediaId, Bundle extras) {
-            Log.d(TAG, "onPlayFromMediaId mediaId=" + mediaId + " extras=" + extras);
-        }
+            synchronized (A2dpMediaBrowserService.this) {
+                // Play the item if possible.
+                mAvrcpCtrlSrvc.fetchAttrAndPlayItem(mA2dpDevice, mediaId);
 
+                // Since we request explicit playback here we should start the updates to UI.
+                mAvrcpCtrlSrvc.startAvrcpUpdates();
+            }
+
+            // TRACK_EVENT should be fired eventually and the UI should be hence updated.
+        }
     };
 
     private BroadcastReceiver mBtReceiver = new BroadcastReceiver() {
@@ -268,40 +293,49 @@
                     // Set the playback state to unconnected.
                     mAvrcpCommandQueue.obtainMessage(MSG_DEVICE_DISCONNECT, btDev).sendToTarget();
                 }
-            } else if (BluetoothAvrcpController.ACTION_TRACK_EVENT.equals(action)) {
+            } else if (AvrcpControllerService.ACTION_BROWSE_CONNECTION_STATE_CHANGED.equals(
+                action)) {
+                if (state == BluetoothProfile.STATE_CONNECTED) {
+                    mAvrcpCommandQueue.obtainMessage(
+                        MSG_DEVICE_BROWSE_CONNECT, btDev).sendToTarget();
+                } else if (state == BluetoothProfile.STATE_DISCONNECTED) {
+                    mAvrcpCommandQueue.obtainMessage(
+                        MSG_DEVICE_BROWSE_DISCONNECT, btDev).sendToTarget();
+                }
+            } else if (AvrcpControllerService.ACTION_TRACK_EVENT.equals(action)) {
                 PlaybackState pbb =
-                    intent.getParcelableExtra(BluetoothAvrcpController.EXTRA_PLAYBACK);
+                    intent.getParcelableExtra(AvrcpControllerService.EXTRA_PLAYBACK);
                 MediaMetadata mmd =
-                    intent.getParcelableExtra(BluetoothAvrcpController.EXTRA_METADATA);
+                    intent.getParcelableExtra(AvrcpControllerService.EXTRA_METADATA);
                 mAvrcpCommandQueue.obtainMessage(
                     MSG_TRACK, new Pair<PlaybackState, MediaMetadata>(pbb, mmd)).sendToTarget();
+            } else if (AvrcpControllerService.ACTION_FOLDER_LIST.equals(action)) {
+                mAvrcpCommandQueue.obtainMessage(MSG_FOLDER_LIST, intent).sendToTarget();
             }
         }
     };
 
-    private void msgDeviceConnect(BluetoothDevice device) {
+    private synchronized void msgDeviceConnect(BluetoothDevice device) {
         Log.d(TAG, "msgDeviceConnect");
         // We are connected to a new device via A2DP now.
         mA2dpDevice = device;
-        refreshInitialPlayingState();
-    }
-
-    private void msgProfileConnect(BluetoothProfile profile) {
-        Log.d(TAG, "msgProfileConnect");
-        if (profile != null) {
-            mAvrcpProfile = (BluetoothAvrcpController) profile;
+        mAvrcpCtrlSrvc = AvrcpControllerService.getAvrcpControllerService();
+        if (mAvrcpCtrlSrvc == null) {
+            Log.e(TAG, "!!!AVRCP Controller cannot be null");
+            return;
         }
         refreshInitialPlayingState();
     }
 
+
     // Refresh the UI if we have a connected device and AVRCP is initialized.
-    private void refreshInitialPlayingState() {
-        if (mAvrcpProfile == null || mA2dpDevice == null) {
-            Log.d(TAG, "AVRCP Profile " + mAvrcpProfile + " device " + mA2dpDevice);
+    private synchronized void refreshInitialPlayingState() {
+        if (mA2dpDevice == null) {
+            Log.d(TAG, "device " + mA2dpDevice);
             return;
         }
 
-        List<BluetoothDevice> devices = mAvrcpProfile.getConnectedDevices();
+        List<BluetoothDevice> devices = mAvrcpCtrlSrvc.getConnectedDevices();
         if (devices.size() == 0) {
             Log.w(TAG, "No devices connected yet");
             return;
@@ -309,17 +343,18 @@
 
         if (mA2dpDevice != null && !mA2dpDevice.equals(devices.get(0))) {
             Log.e(TAG, "A2dp device : " + mA2dpDevice + " avrcp device " + devices.get(0));
+            return;
         }
         mA2dpDevice = devices.get(0);
 
-        PlaybackState playbackState = mAvrcpProfile.getPlaybackState(mA2dpDevice);
+        PlaybackState playbackState = mAvrcpCtrlSrvc.getPlaybackState(mA2dpDevice);
         // Add actions required for playback and rebuild the object.
         PlaybackState.Builder pbb = new PlaybackState.Builder(playbackState);
         playbackState = pbb.setActions(mTransportControlFlags).build();
 
-        MediaMetadata mediaMetadata = mAvrcpProfile.getMetadata(mA2dpDevice);
+        MediaMetadata mediaMetadata = mAvrcpCtrlSrvc.getMetaData(mA2dpDevice);
         Log.d(TAG, "Media metadata " + mediaMetadata + " playback state " + playbackState);
-        mSession.setMetadata(mAvrcpProfile.getMetadata(mA2dpDevice));
+        mSession.setMetadata(mAvrcpCtrlSrvc.getMetaData(mA2dpDevice));
         mSession.setPlaybackState(playbackState);
     }
 
@@ -341,21 +376,10 @@
                 .setActions(mTransportControlFlags)
                 .setErrorMessage(getString(R.string.bluetooth_disconnected));
         mSession.setPlaybackState(pbb.build());
-    }
 
-    private void msgProfileDisconnect() {
-        Log.d(TAG, "msgProfileDisconnect");
-        // The profile is disconnected - even if the device is still connected we cannot really have
-        // a functioning UI so reset the session.
-        mAvrcpProfile = null;
-
-        // Unset the session.
-        PlaybackState.Builder pbb = new PlaybackState.Builder();
-        pbb = pbb.setState(PlaybackState.STATE_ERROR, PlaybackState.PLAYBACK_POSITION_UNKNOWN,
-                    PLAYBACK_SPEED)
-                .setActions(mTransportControlFlags)
-                .setErrorMessage(getString(R.string.bluetooth_disconnected));
-        mSession.setPlaybackState(pbb.build());
+        // Set device to null.
+        mA2dpDevice = null;
+        mBrowseConnected = false;
     }
 
     private void msgTrack(PlaybackState pb, MediaMetadata mmd) {
@@ -388,7 +412,7 @@
         }
     }
 
-    private void msgPassThru(int cmd) {
+    private synchronized void msgPassThru(int cmd) {
         Log.d(TAG, "msgPassThru " + cmd);
         if (mA2dpDevice == null) {
             // We should have already disconnected - ignore this message.
@@ -396,17 +420,54 @@
             return;
         }
 
-        if (mAvrcpProfile == null) {
-            // We may be disconnected with the profile but there is not much we can do for now but
-            // to wait for the profile to come back up.
-            Log.e(TAG, "Profile disconnected; ignoring.");
+        // Send the pass through.
+        mAvrcpCtrlSrvc.sendPassThroughCmd(
+            mA2dpDevice, cmd, AvrcpControllerService.KEY_STATE_PRESSED);
+        mAvrcpCtrlSrvc.sendPassThroughCmd(
+            mA2dpDevice, cmd, AvrcpControllerService.KEY_STATE_RELEASED);
+    }
+
+    private void msgDeviceBrowseConnect(BluetoothDevice device) {
+        Log.d(TAG, "msgDeviceBrowseConnect device " + device);
+        // We should already be connected to this device over A2DP.
+        if (!device.equals(mA2dpDevice)) {
+            Log.e(TAG, "Browse connected over different device a2dp " + mA2dpDevice +
+                " browse " + device);
             return;
         }
+        mBrowseConnected = true;
+    }
 
-        // Send the pass through.
-        mAvrcpProfile.sendPassThroughCmd(
-            mA2dpDevice, cmd, BluetoothAvrcpController.KEY_STATE_PRESSED);
-        mAvrcpProfile.sendPassThroughCmd(
-            mA2dpDevice, cmd, BluetoothAvrcpController.KEY_STATE_RELEASED);
+    private void msgFolderList(Intent intent) {
+        // Parse the folder list for children list and id.
+        List<Parcelable> extraParcelableList =
+            (ArrayList<Parcelable>) intent.getParcelableArrayListExtra(
+                AvrcpControllerService.EXTRA_FOLDER_LIST);
+        List<MediaItem> folderList = new ArrayList<MediaItem>();
+        for (Parcelable p : extraParcelableList) {
+            folderList.add((MediaItem) p);
+        }
+
+        String id = intent.getStringExtra(AvrcpControllerService.EXTRA_FOLDER_ID);
+        Log.d(TAG, "Parent: " + id + " Folder list: " + folderList);
+        synchronized (this) {
+            Result<List<MediaItem>> results = mParentIdToRequestMap.remove(id);
+            if (results == null) {
+                Log.w(TAG, "Request no longer exists, hence ignoring reply!");
+                return;
+            }
+            results.sendResult(folderList);
+        }
+    }
+
+    private void msgDeviceBrowseDisconnect(BluetoothDevice device) {
+        Log.d(TAG, "msgDeviceBrowseDisconnect device " + device);
+        // Disconnect only if mA2dpDevice is non null
+        if (!device.equals(mA2dpDevice)) {
+            Log.w(TAG, "Browse disconnecting from different device a2dp " + mA2dpDevice +
+                " browse " + device);
+            return;
+        }
+        mBrowseConnected = false;
     }
 }
diff --git a/src/com/android/bluetooth/avrcp/AvrcpControllerService.java b/src/com/android/bluetooth/avrcp/AvrcpControllerService.java
deleted file mode 100644
index 6675124..0000000
--- a/src/com/android/bluetooth/avrcp/AvrcpControllerService.java
+++ /dev/null
@@ -1,964 +0,0 @@
-/*
- * Copyright (C) 2014 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.BluetoothAdapter;
-import android.bluetooth.BluetoothAvrcpController;
-import android.bluetooth.BluetoothAvrcpPlayerSettings;
-import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothProfile;
-import android.bluetooth.IBluetoothAvrcpController;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.media.MediaMetadata;
-import android.media.session.PlaybackState;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Looper;
-import android.os.Message;
-import android.util.Log;
-import android.media.AudioManager;
-
-import com.android.bluetooth.a2dpsink.A2dpSinkService;
-import com.android.bluetooth.btservice.ProfileService;
-import com.android.bluetooth.Utils;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.nio.charset.Charset;
-import java.nio.ByteBuffer;
-
-/**
- * Provides Bluetooth AVRCP Controller profile, as a service in the Bluetooth application.
- * @hide
- */
-public class AvrcpControllerService extends ProfileService {
-    private static final boolean DBG = AvrcpControllerConstants.DBG;
-    private static final boolean VDBG = AvrcpControllerConstants.VDBG;
-    private static final String TAG = "AvrcpControllerService";
-
-/*
- *  Messages handled by mHandler
- */
-
-    RemoteDevice mAvrcpRemoteDevice;
-    RemoteMediaPlayers mRemoteMediaPlayers;
-    NowPlaying mRemoteNowPlayingList;
-
-    private AvrcpMessageHandler mHandler;
-    private static AvrcpControllerService sAvrcpControllerService;
-    private static AudioManager mAudioManager;
-
-    private final ArrayList<BluetoothDevice> mConnectedDevices
-            = new ArrayList<BluetoothDevice>();
-
-    static {
-        classInitNative();
-    }
-
-    public AvrcpControllerService() {
-        initNative();
-    }
-
-    protected String getName() {
-        return TAG;
-    }
-
-    protected IProfileServiceBinder initBinder() {
-        return new BluetoothAvrcpControllerBinder(this);
-    }
-
-    protected boolean start() {
-        HandlerThread thread = new HandlerThread("BluetoothAvrcpHandler");
-        thread.start();
-        Looper looper = thread.getLooper();
-        mHandler = new AvrcpMessageHandler(looper);
-
-        setAvrcpControllerService(this);
-        mAudioManager = (AudioManager)sAvrcpControllerService.
-                                  getSystemService(Context.AUDIO_SERVICE);
-        return true;
-    }
-
-    protected void resetRemoteData() {
-        try {
-            unregisterReceiver(mBroadcastReceiver);
-        }
-        catch (IllegalArgumentException e) {
-            Log.e(TAG,"Receiver not registered");
-        }
-        if(mAvrcpRemoteDevice != null) {
-            mAvrcpRemoteDevice.cleanup();
-            mAvrcpRemoteDevice = null;
-        }
-        if(mRemoteMediaPlayers != null) {
-            mRemoteMediaPlayers.cleanup();
-            mRemoteMediaPlayers = null;
-        }
-        if(mRemoteNowPlayingList != null) {
-            mRemoteNowPlayingList.cleanup();
-            mRemoteNowPlayingList = null;
-        }
-    }
-    protected boolean stop() {
-        if (mHandler != null) {
-            mHandler.removeCallbacksAndMessages(null);
-            Looper looper = mHandler.getLooper();
-            if (looper != null) {
-                looper.quit();
-            }
-        }
-        resetRemoteData();
-        return true;
-    }
-
-    protected boolean cleanup() {
-        if (mHandler != null) {
-            mHandler.removeCallbacksAndMessages(null);
-            Looper looper = mHandler.getLooper();
-            if (looper != null) looper.quit();
-        }
-        resetRemoteData();
-        clearAvrcpControllerService();
-        cleanupNative();
-
-        return true;
-    }
-
-    //API Methods
-
-    public static synchronized AvrcpControllerService getAvrcpControllerService(){
-        if (sAvrcpControllerService != null && sAvrcpControllerService.isAvailable()) {
-            if (DBG) Log.d(TAG, "getAvrcpControllerService(): returning "
-                    + sAvrcpControllerService);
-            return sAvrcpControllerService;
-        }
-        if (DBG)  {
-            if (sAvrcpControllerService == null) {
-                Log.d(TAG, "getAvrcpControllerService(): service is NULL");
-            } else if (!(sAvrcpControllerService.isAvailable())) {
-                Log.d(TAG,"getAvrcpControllerService(): service is not available");
-            }
-        }
-        return null;
-    }
-
-    private static synchronized void setAvrcpControllerService(AvrcpControllerService instance) {
-        if (instance != null && instance.isAvailable()) {
-            if (DBG) Log.d(TAG, "setAvrcpControllerService(): set to: " + sAvrcpControllerService);
-            sAvrcpControllerService = instance;
-        } else {
-            if (DBG)  {
-                if (sAvrcpControllerService == null) {
-                    Log.d(TAG, "setAvrcpControllerService(): service not available");
-                } else if (!sAvrcpControllerService.isAvailable()) {
-                    Log.d(TAG,"setAvrcpControllerService(): service is cleaning up");
-                }
-            }
-        }
-    }
-
-    private static synchronized void clearAvrcpControllerService() {
-        sAvrcpControllerService = null;
-    }
-
-    public List<BluetoothDevice> getConnectedDevices() {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-        return mConnectedDevices;
-    }
-
-    List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-        for (int i = 0; i < states.length; i++) {
-            if (states[i] == BluetoothProfile.STATE_CONNECTED) {
-                return mConnectedDevices;
-            }
-        }
-        return new ArrayList<BluetoothDevice>();
-    }
-
-    int getConnectionState(BluetoothDevice device) {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-        return (mConnectedDevices.contains(device) ? BluetoothProfile.STATE_CONNECTED
-                                                : BluetoothProfile.STATE_DISCONNECTED);
-    }
-
-    public void sendGroupNavigationCmd(BluetoothDevice device, int keyCode, int keyState) {
-        Log.v(TAG, "sendGroupNavigationCmd keyCode: " + keyCode + " keyState: " + keyState);
-        if (device == null) {
-            throw new NullPointerException("device == null");
-        }
-        if (!(mConnectedDevices.contains(device))) {
-            for (BluetoothDevice cdevice : mConnectedDevices) {
-                Log.e(TAG, "Device: " + cdevice);
-            }
-            Log.e(TAG," Device does not match " + device);
-            return;
-        }
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-        Message msg = mHandler.obtainMessage(AvrcpControllerConstants.
-                MESSAGE_SEND_GROUP_NAVIGATION_CMD,keyCode, keyState, device);
-        mHandler.sendMessage(msg);
-    }
-
-    public void sendPassThroughCmd(BluetoothDevice device, int keyCode, int keyState) {
-        Log.v(TAG, "sendPassThroughCmd keyCode: " + keyCode + " keyState: " + keyState);
-        if (device == null) {
-            throw new NullPointerException("device == null");
-        }
-        if (!(mConnectedDevices.contains(device))) {
-            Log.d(TAG," Device does not match");
-            return;
-        }
-        if ((mAvrcpRemoteDevice == null)||
-            (mAvrcpRemoteDevice.mRemoteFeatures == AvrcpControllerConstants.BTRC_FEAT_NONE)||
-            (mRemoteMediaPlayers == null) ||
-            (mRemoteMediaPlayers.getAddressedPlayer() == null)){
-            Log.d(TAG," Device connected but PlayState not present ");
-            enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-            Message msg = mHandler.obtainMessage(AvrcpControllerConstants.MESSAGE_SEND_PASS_THROUGH_CMD,
-                    keyCode, keyState, device);
-            mHandler.sendMessage(msg);
-            return;
-        }
-        boolean sendCommand = false;
-        switch(keyCode) {
-            case BluetoothAvrcpController.PASS_THRU_CMD_ID_PLAY:
-                sendCommand  = (mRemoteMediaPlayers.getPlayStatus() ==
-                                       AvrcpControllerConstants.PLAY_STATUS_STOPPED)||
-                               (mRemoteMediaPlayers.getPlayStatus() ==
-                                       AvrcpControllerConstants.PLAY_STATUS_PAUSED) ||
-                                (mRemoteMediaPlayers.getPlayStatus() ==
-                                       AvrcpControllerConstants.PLAY_STATUS_PLAYING);
-                break;
-            case BluetoothAvrcpController.PASS_THRU_CMD_ID_PAUSE:
-            /*
-             * allowing pause command in pause state to handle A2DP Sink Concurrency
-             * If call is ongoing and Start is initiated from remote, we will send pause again
-             * If acquireFocus fails, we will send Pause again
-             * To Stop sending multiple Pause, check in application.
-             */
-                sendCommand  = (mRemoteMediaPlayers.getPlayStatus() ==
-                                       AvrcpControllerConstants.PLAY_STATUS_PLAYING)||
-                               (mRemoteMediaPlayers.getPlayStatus() ==
-                                       AvrcpControllerConstants.PLAY_STATUS_FWD_SEEK)||
-                               (mRemoteMediaPlayers.getPlayStatus() ==
-                                       AvrcpControllerConstants.PLAY_STATUS_STOPPED)||
-                               (mRemoteMediaPlayers.getPlayStatus() ==
-                                       AvrcpControllerConstants.PLAY_STATUS_PAUSED)||
-                               (mRemoteMediaPlayers.getPlayStatus() ==
-                                       AvrcpControllerConstants.PLAY_STATUS_REV_SEEK);
-                break;
-            case BluetoothAvrcpController.PASS_THRU_CMD_ID_STOP:
-                sendCommand  = (mRemoteMediaPlayers.getPlayStatus() ==
-                                       AvrcpControllerConstants.PLAY_STATUS_PLAYING)||
-                               (mRemoteMediaPlayers.getPlayStatus() ==
-                                       AvrcpControllerConstants.PLAY_STATUS_FWD_SEEK)||
-                               (mRemoteMediaPlayers.getPlayStatus() ==
-                                       AvrcpControllerConstants.PLAY_STATUS_REV_SEEK)||
-                               (mRemoteMediaPlayers.getPlayStatus() ==
-                                       AvrcpControllerConstants.PLAY_STATUS_STOPPED)||
-                               (mRemoteMediaPlayers.getPlayStatus() ==
-                                       AvrcpControllerConstants.PLAY_STATUS_PAUSED);
-                break;
-            case BluetoothAvrcpController.PASS_THRU_CMD_ID_BACKWARD:
-            case BluetoothAvrcpController.PASS_THRU_CMD_ID_FORWARD:
-            case BluetoothAvrcpController.PASS_THRU_CMD_ID_FF:
-            case BluetoothAvrcpController.PASS_THRU_CMD_ID_REWIND:
-                sendCommand = true; // we can send this command in all states
-                break;
-        }
-        if (sendCommand) {
-            enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-            Message msg = mHandler.obtainMessage(AvrcpControllerConstants.MESSAGE_SEND_PASS_THROUGH_CMD,
-                keyCode, keyState, device);
-            mHandler.sendMessage(msg);
-        }
-        else {
-            Log.e(TAG," Not in right state, don't send Pass Thru cmd ");
-        }
-    }
-
-    public void startAvrcpUpdates() {
-        mHandler.obtainMessage(
-            AvrcpControllerConstants.MESSAGE_START_METADATA_BROADCASTS).sendToTarget();
-    }
-
-    public void stopAvrcpUpdates() {
-        mHandler.obtainMessage(
-            AvrcpControllerConstants.MESSAGE_STOP_METADATA_BROADCASTS).sendToTarget();
-    }
-
-    public MediaMetadata getMetaData(BluetoothDevice device) {
-        Log.d(TAG, "getMetaData = ");
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-        if((mRemoteNowPlayingList != null) && (mRemoteNowPlayingList.getCurrentTrack() != null)) {
-            return getCurrentMetaData(AvrcpControllerConstants.AVRCP_SCOPE_NOW_PLAYING, 0);
-        }
-        else
-            return null;
-    }
-    public PlaybackState getPlaybackState(BluetoothDevice device) {
-        if (DBG) Log.d(TAG, "getPlayBackState device = "+ device);
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-        return getCurrentPlayBackState();
-    }
-    public BluetoothAvrcpPlayerSettings getPlayerSettings(BluetoothDevice device) {
-        if (DBG) Log.d(TAG, "getPlayerApplicationSetting ");
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-        return getCurrentPlayerAppSetting();
-    }
-    public boolean setPlayerApplicationSetting(BluetoothAvrcpPlayerSettings plAppSetting) {
-        if ((mAvrcpRemoteDevice == null)||(mRemoteMediaPlayers == null)) {
-            return false;
-        }
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-        /*
-         * We have to extract values from BluetoothAvrcpPlayerSettings
-         */
-        int mSettings = plAppSetting.getSettings();
-        int numAttributes = 0;
-        /* calculate number of attributes in request */
-        while(mSettings > 0) {
-            numAttributes += ((mSettings & 0x01)!= 0)?1: 0;
-            mSettings = mSettings >> 1;
-        }
-        byte[] attribArray = new byte [2*numAttributes];
-        mSettings = plAppSetting.getSettings();
-        /*
-         * Now we will flatten it <id, val>
-         */
-        int i = 0;
-        if((mSettings & BluetoothAvrcpPlayerSettings.SETTING_EQUALIZER) != 0) {
-            attribArray[i++] = AvrcpControllerConstants.ATTRIB_EQUALIZER_STATUS;
-            attribArray[i++] = (byte)AvrcpUtils.mapAvrcpPlayerSettingstoBTAttribVal(
-                    BluetoothAvrcpPlayerSettings.SETTING_EQUALIZER, plAppSetting.
-                    getSettingValue(BluetoothAvrcpPlayerSettings.SETTING_EQUALIZER));
-        }
-        if((mSettings & BluetoothAvrcpPlayerSettings.SETTING_REPEAT) != 0) {
-            attribArray[i++] = AvrcpControllerConstants.ATTRIB_REPEAT_STATUS;
-            attribArray[i++] = (byte)AvrcpUtils.mapAvrcpPlayerSettingstoBTAttribVal(
-                    BluetoothAvrcpPlayerSettings.SETTING_REPEAT, plAppSetting.
-                    getSettingValue(BluetoothAvrcpPlayerSettings.SETTING_REPEAT));
-        }
-        if((mSettings & BluetoothAvrcpPlayerSettings.SETTING_SHUFFLE) != 0) {
-            attribArray[i++] = AvrcpControllerConstants.ATTRIB_SHUFFLE_STATUS;
-            attribArray[i++] = (byte)AvrcpUtils.mapAvrcpPlayerSettingstoBTAttribVal(
-                    BluetoothAvrcpPlayerSettings.SETTING_SHUFFLE, plAppSetting.
-                    getSettingValue(BluetoothAvrcpPlayerSettings.SETTING_SHUFFLE));
-        }
-        if((mSettings & BluetoothAvrcpPlayerSettings.SETTING_SCAN) != 0) {
-            attribArray[i++] = AvrcpControllerConstants.ATTRIB_SCAN_STATUS;
-            attribArray[i++] = (byte)AvrcpUtils.mapAvrcpPlayerSettingstoBTAttribVal(
-                    BluetoothAvrcpPlayerSettings.SETTING_SCAN, plAppSetting.
-                    getSettingValue(BluetoothAvrcpPlayerSettings.SETTING_SCAN));
-        }
-        boolean isSettingSupported = mRemoteMediaPlayers.getAddressedPlayer().
-                                   isPlayerAppSettingSupported((byte)numAttributes, attribArray);
-        if(isSettingSupported) {
-            ByteBuffer bb = ByteBuffer.wrap(attribArray, 0, (2*numAttributes));
-             Message msg = mHandler.obtainMessage(AvrcpControllerConstants.
-                MESSAGE_SEND_SET_CURRENT_PLAYER_APPLICATION_SETTINGS, numAttributes, 0, bb);
-            mHandler.sendMessage(msg);
-        }
-        return isSettingSupported;
-    }
-
-    //Binder object: Must be static class or memory leak may occur
-    private static class BluetoothAvrcpControllerBinder extends IBluetoothAvrcpController.Stub
-        implements IProfileServiceBinder {
-        private AvrcpControllerService mService;
-
-        private AvrcpControllerService getService() {
-            if (!Utils.checkCaller()) {
-                Log.w(TAG,"AVRCP call not allowed for non-active user");
-                return null;
-            }
-
-            if (mService != null && mService.isAvailable()) {
-                return mService;
-            }
-            return null;
-        }
-
-        BluetoothAvrcpControllerBinder(AvrcpControllerService svc) {
-            mService = svc;
-        }
-
-        public boolean cleanup()  {
-            mService = null;
-            return true;
-        }
-
-        public List<BluetoothDevice> getConnectedDevices() {
-            AvrcpControllerService service = getService();
-            if (service == null) return new ArrayList<BluetoothDevice>(0);
-            return service.getConnectedDevices();
-        }
-
-        public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
-            AvrcpControllerService service = getService();
-            if (service == null) return new ArrayList<BluetoothDevice>(0);
-            return service.getDevicesMatchingConnectionStates(states);
-        }
-
-        public int getConnectionState(BluetoothDevice device) {
-            AvrcpControllerService service = getService();
-            if (service == null) return BluetoothProfile.STATE_DISCONNECTED;
-            return service.getConnectionState(device);
-        }
-
-        public void sendPassThroughCmd(BluetoothDevice device, int keyCode, int keyState) {
-            Log.v(TAG,"Binder Call: sendPassThroughCmd");
-            AvrcpControllerService service = getService();
-            if (service == null) return;
-            service.sendPassThroughCmd(device, keyCode, keyState);
-        }
-
-        @Override
-        public void sendGroupNavigationCmd(BluetoothDevice device, int keyCode, int keyState) {
-            Log.v(TAG,"Binder Call: sendGroupNavigationCmd");
-            AvrcpControllerService service = getService();
-            if (service == null) return;
-            service.sendGroupNavigationCmd(device, keyCode, keyState);
-        }
-
-        @Override
-        public BluetoothAvrcpPlayerSettings getPlayerSettings(BluetoothDevice device) {
-            Log.v(TAG,"Binder Call: getPlayerApplicationSetting ");
-            AvrcpControllerService service = getService();
-            if (service == null) return null;
-            return service.getPlayerSettings(device);
-        }
-
-        @Override
-        public MediaMetadata getMetadata(BluetoothDevice device) {
-            Log.v(TAG,"Binder Call: getMetaData ");
-            AvrcpControllerService service = getService();
-            if (service == null) return null;
-            return service.getMetaData(device);
-        }
-
-        @Override
-        public PlaybackState getPlaybackState(BluetoothDevice device) {
-            Log.v(TAG,"Binder Call: getPlaybackState");
-            AvrcpControllerService service = getService();
-            if (service == null) return null;
-            return service.getPlaybackState(device);
-        }
-
-        @Override
-        public boolean setPlayerApplicationSetting(BluetoothAvrcpPlayerSettings plAppSetting) {
-            Log.v(TAG,"Binder Call: setPlayerApplicationSetting " );
-            AvrcpControllerService service = getService();
-            if (service == null) return false;
-            return service.setPlayerApplicationSetting(plAppSetting);
-        }
-    };
-
-    private String utf8ToString(byte[] input)
-    {
-        Charset UTF8_CHARSET = Charset.forName("UTF-8");
-        return new String(input,UTF8_CHARSET);
-    }
-    private int asciiToInt(int len, byte[] array)
-    {
-        return Integer.parseInt(utf8ToString(array));
-    }
-    private BluetoothAvrcpPlayerSettings getCurrentPlayerAppSetting() {
-        if((mRemoteMediaPlayers == null) || (mRemoteMediaPlayers.getAddressedPlayer() == null))
-            return null;
-        return mRemoteMediaPlayers.getAddressedPlayer().getSupportedPlayerAppSetting();
-    }
-    private PlaybackState getCurrentPlayBackState() {
-        if ((mRemoteMediaPlayers == null) || (mRemoteMediaPlayers.getAddressedPlayer() == null)) {
-            return new PlaybackState.Builder().setState(PlaybackState.STATE_ERROR,
-                                                        PlaybackState.PLAYBACK_POSITION_UNKNOWN,
-                                                        0).build();
-        }
-        return AvrcpUtils.mapBtPlayStatustoPlayBackState(
-                mRemoteMediaPlayers.getAddressedPlayer().mPlayStatus,
-                mRemoteMediaPlayers.getAddressedPlayer().mPlayTime);
-    }
-    private MediaMetadata getCurrentMetaData(int scope, int trackId) {
-        /* if scope is now playing */
-        if(scope == AvrcpControllerConstants.AVRCP_SCOPE_NOW_PLAYING) {
-            if((mRemoteNowPlayingList == null) || (mRemoteNowPlayingList.
-                                                           getTrackFromId(trackId) == null))
-                return null;
-            TrackInfo mNowPlayingTrack = mRemoteNowPlayingList.getTrackFromId(trackId);
-            return AvrcpUtils.getMediaMetaData(mNowPlayingTrack);
-        }
-        /* if scope is now playing */
-        else if(scope == AvrcpControllerConstants.AVRCP_SCOPE_VFS) {
-            /* TODO for browsing */
-        }
-        return null;
-    }
-    private void broadcastMetaDataChanged(MediaMetadata mMetaData) {
-        Intent intent = new Intent(BluetoothAvrcpController.ACTION_TRACK_EVENT);
-        intent.putExtra(BluetoothAvrcpController.EXTRA_METADATA, mMetaData);
-        if(DBG) Log.d(TAG," broadcastMetaDataChanged = " +
-                                                   AvrcpUtils.displayMetaData(mMetaData));
-        sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
-    }
-    private void broadcastPlayBackStateChanged(PlaybackState state) {
-        Intent intent = new Intent(BluetoothAvrcpController.ACTION_TRACK_EVENT);
-        intent.putExtra(BluetoothAvrcpController.EXTRA_PLAYBACK, state);
-        if(DBG) Log.d(TAG," broadcastPlayBackStateChanged = " + state.toString());
-        sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
-    }
-    private void broadcastPlayerAppSettingChanged(BluetoothAvrcpPlayerSettings mPlAppSetting) {
-        Intent intent = new Intent(BluetoothAvrcpController.ACTION_PLAYER_SETTING);
-        intent.putExtra(BluetoothAvrcpController.EXTRA_PLAYER_SETTING, mPlAppSetting);
-        if(DBG) Log.d(TAG," broadcastPlayerAppSettingChanged = " +
-                AvrcpUtils.displayBluetoothAvrcpSettings(mPlAppSetting));
-        sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
-    }
-    /** Handles Avrcp messages. */
-    private final class AvrcpMessageHandler extends Handler {
-        private boolean mBroadcastMetadata = false;
-
-        private AvrcpMessageHandler(Looper looper) {
-            super(looper);
-        }
-
-        @Override
-        public void handleMessage(Message msg) {
-            Log.d(TAG," HandleMessage: "+ AvrcpControllerConstants.dumpMessageString(msg.what) +
-                  " Remote Connected " + !mConnectedDevices.isEmpty());
-            A2dpSinkService a2dpSinkService = A2dpSinkService.getA2dpSinkService();
-            switch (msg.what) {
-            case AvrcpControllerConstants.MESSAGE_STOP_METADATA_BROADCASTS:
-                // Any messages hence forth about play pos/play status/song info will be ignored.
-                if(mRemoteMediaPlayers != null) {
-                    // Mock the current state to *look like* it is paused. The remote play state is
-                    // still cached in mRemoteMediaPlayers and will be restored when the
-                    // startAvrcpUpdates is called again.
-                    broadcastPlayBackStateChanged(AvrcpUtils.mapBtPlayStatustoPlayBackState
-                            ((byte) AvrcpControllerConstants.PLAY_STATUS_PAUSED,
-                             mRemoteMediaPlayers.getAddressedPlayer().mPlayTime));
-                }
-                mBroadcastMetadata = false;
-                break;
-            case AvrcpControllerConstants.MESSAGE_START_METADATA_BROADCASTS:
-                // Any messages hence forth about play pos/play status/song info will be sent.
-                if(mRemoteMediaPlayers != null) {
-                    broadcastPlayBackStateChanged(getCurrentPlayBackState());
-                    broadcastMetaDataChanged(
-                        getCurrentMetaData(AvrcpControllerConstants.AVRCP_SCOPE_NOW_PLAYING, 0));
-                }
-                mBroadcastMetadata = true;
-                break;
-            case AvrcpControllerConstants.MESSAGE_SEND_PASS_THROUGH_CMD:
-                BluetoothDevice device = (BluetoothDevice)msg.obj;
-                sendPassThroughCommandNative(getByteAddress(device), msg.arg1, msg.arg2);
-                if((a2dpSinkService != null)&&(!mConnectedDevices.isEmpty())) {
-                    Log.d(TAG," inform AVRCP Commands to A2DP Sink ");
-                    a2dpSinkService.informAvrcpPassThroughCmd(device, msg.arg1, msg.arg2);
-                }
-                break;
-            case AvrcpControllerConstants.MESSAGE_SEND_GROUP_NAVIGATION_CMD:
-                BluetoothDevice peerDevice = (BluetoothDevice)msg.obj;
-                sendGroupNavigationCommandNative(getByteAddress(peerDevice), msg.arg1, msg.arg2);
-                break;
-            case AvrcpControllerConstants.MESSAGE_SEND_SET_CURRENT_PLAYER_APPLICATION_SETTINGS:
-                byte numAttributes = (byte)msg.arg1;
-                ByteBuffer bbRsp = (ByteBuffer)msg.obj;
-                byte[] attributeIds = new byte [numAttributes];
-                byte[] attributeVals = new byte [numAttributes];
-                for(int i = 0; (bbRsp.hasRemaining())&&(i < numAttributes); i++) {
-                    attributeIds[i] = bbRsp.get();
-                    attributeVals[i] = bbRsp.get();
-                }
-                setPlayerApplicationSettingValuesNative(getByteAddress(mAvrcpRemoteDevice.mBTDevice),
-                        numAttributes, attributeIds, attributeVals);
-                break;
-
-            case AvrcpControllerConstants.MESSAGE_PROCESS_CONNECTION_CHANGE:
-                int newState = msg.arg1;
-                int oldState = msg.arg2;
-                BluetoothDevice rtDevice =  (BluetoothDevice)msg.obj;
-                if ((newState == BluetoothProfile.STATE_CONNECTED) &&
-                    (oldState == BluetoothProfile.STATE_DISCONNECTED)) {
-                    /* We create RemoteDevice and MediaPlayerList here
-                     * Now playing list after RC features
-                     */
-                    if(mAvrcpRemoteDevice == null){
-                        mAvrcpRemoteDevice =  new RemoteDevice(rtDevice);
-                        /* Remote will have a player irrespective of AVRCP Version
-                         * Create a Default player, we will add entries in case Browsing
-                         * is supported by remote
-                         */
-                        if(mRemoteMediaPlayers == null) {
-                            mRemoteMediaPlayers = new RemoteMediaPlayers(mAvrcpRemoteDevice);
-                            PlayerInfo mPlayer = new PlayerInfo();
-                            mPlayer.mPlayerId = 0;
-                            mRemoteMediaPlayers.addPlayer(mPlayer);
-                            mRemoteMediaPlayers.setAddressedPlayer(mPlayer);
-                        }
-                    }
-                }
-                else if ((newState == BluetoothProfile.STATE_DISCONNECTED) &&
-                        (oldState == BluetoothProfile.STATE_CONNECTED)) /* connection down */
-                {
-                    resetRemoteData();
-                    mHandler.removeCallbacksAndMessages(null);
-                }
-                /*
-                 * Send intent now
-                 */
-                Intent intent = new Intent(BluetoothAvrcpController.ACTION_CONNECTION_STATE_CHANGED);
-                intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, oldState);
-                intent.putExtra(BluetoothProfile.EXTRA_STATE, newState);
-                intent.putExtra(BluetoothDevice.EXTRA_DEVICE, rtDevice);
-//              intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
-                sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
-                break;
-            case AvrcpControllerConstants.MESSAGE_PROCESS_RC_FEATURES:
-                if(mAvrcpRemoteDevice == null)
-                    break;
-                mAvrcpRemoteDevice.mRemoteFeatures = msg.arg1;
-                /* in case of AVRCP version < 1.3, no need to add track info */
-                if(mAvrcpRemoteDevice.isMetaDataSupported()) {
-                    if(mRemoteNowPlayingList == null)
-                        mRemoteNowPlayingList = new NowPlaying(mAvrcpRemoteDevice);
-                    TrackInfo mTrack = new TrackInfo();
-                    /* First element of NowPlayingList will be current Track
-                     * for 1.3 this will be the only song
-                     * for >= 1.4, others songs will have non-zero UID
-                     */
-                    mTrack.mItemUid = 0;
-                    mRemoteNowPlayingList.addTrack(mTrack);
-                    mRemoteNowPlayingList.setCurrTrack(mTrack);
-                }
-                break;
-            case AvrcpControllerConstants.MESSAGE_PROCESS_SET_ABS_VOL_CMD:
-                mAvrcpRemoteDevice.mAbsVolNotificationState =
-                                         AvrcpControllerConstants.DEFER_VOLUME_CHANGE_RSP;
-                setAbsVolume(msg.arg1, msg.arg2);
-                break;
-            case AvrcpControllerConstants.MESSAGE_PROCESS_REGISTER_ABS_VOL_NOTIFICATION:
-                /* start BroadcastReceiver now */
-                IntentFilter filter = new IntentFilter(AudioManager.VOLUME_CHANGED_ACTION);
-                mAvrcpRemoteDevice.mNotificationLabel = msg.arg1;
-                mAvrcpRemoteDevice.mAbsVolNotificationState =
-                        AvrcpControllerConstants.SEND_VOLUME_CHANGE_RSP;
-                registerReceiver(mBroadcastReceiver, filter);
-                int maxVolume = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
-                int currIndex = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
-                int percentageVol = ((currIndex* AvrcpControllerConstants.ABS_VOL_BASE)/maxVolume);
-                Log.d(TAG," Sending Interim Response = "+ percentageVol + " label " + msg.arg1);
-                sendRegisterAbsVolRspNative(getByteAddress(mAvrcpRemoteDevice.mBTDevice),
-                        (byte)AvrcpControllerConstants.NOTIFICATION_RSP_TYPE_INTERIM, percentageVol,
-                        mAvrcpRemoteDevice.mNotificationLabel);
-                break;
-            case AvrcpControllerConstants.MESSAGE_PROCESS_TRACK_CHANGED:
-                if(mRemoteNowPlayingList != null) {
-                    mRemoteNowPlayingList.updateCurrentTrack((TrackInfo)msg.obj);
-
-                    if (!mBroadcastMetadata) {
-                        Log.d(TAG, "Metadata is not broadcasted, ignoring.");
-                        return;
-                    }
-
-                    broadcastMetaDataChanged(AvrcpUtils.getMediaMetaData
-                                       (mRemoteNowPlayingList.getCurrentTrack()));
-                }
-                break;
-            case AvrcpControllerConstants.MESSAGE_PROCESS_PLAY_POS_CHANGED:
-                if(mRemoteMediaPlayers != null) {
-                    mRemoteMediaPlayers.getAddressedPlayer().mPlayTime = msg.arg2;
-
-                    if (!mBroadcastMetadata) {
-                        Log.d(TAG, "Metadata is not broadcasted, ignoring.");
-                        return;
-                    }
-
-                    broadcastPlayBackStateChanged(AvrcpUtils.mapBtPlayStatustoPlayBackState
-                            (mRemoteMediaPlayers.getAddressedPlayer().mPlayStatus,
-                                    mRemoteMediaPlayers.getAddressedPlayer().mPlayTime));
-                }
-                if(mRemoteNowPlayingList != null) {
-                    mRemoteNowPlayingList.getCurrentTrack().mTrackLen = msg.arg1;
-                }
-                break;
-            case AvrcpControllerConstants.MESSAGE_PROCESS_PLAY_STATUS_CHANGED:
-                if(mRemoteMediaPlayers != null) {
-                    int status = msg.arg1;
-                    mRemoteMediaPlayers.getAddressedPlayer().mPlayStatus = (byte) status;
-                    if (status == AvrcpControllerConstants.PLAY_STATUS_PLAYING) {
-                        a2dpSinkService.informTGStatePlaying(mConnectedDevices.get(0), true);
-                    } else if (status == AvrcpControllerConstants.PLAY_STATUS_PAUSED ||
-                               status == AvrcpControllerConstants.PLAY_STATUS_STOPPED) {
-                        a2dpSinkService.informTGStatePlaying(mConnectedDevices.get(0), false);
-                    }
-
-                    if (mBroadcastMetadata) {
-                        broadcastPlayBackStateChanged(AvrcpUtils.mapBtPlayStatustoPlayBackState
-                                (mRemoteMediaPlayers.getAddressedPlayer().mPlayStatus,
-                                 mRemoteMediaPlayers.getAddressedPlayer().mPlayTime));
-                    } else {
-                        Log.d(TAG, "Metadata is not broadcasted, ignoring.");
-                        return;
-                    }
-                }
-                break;
-            case AvrcpControllerConstants.MESSAGE_PROCESS_SUPPORTED_PLAYER_APP_SETTING:
-                if(mRemoteMediaPlayers != null)
-                    mRemoteMediaPlayers.getAddressedPlayer().
-                                           setSupportedPlayerAppSetting((ByteBuffer)msg.obj);
-                break;
-            case AvrcpControllerConstants.MESSAGE_PROCESS_PLAYER_APP_SETTING_CHANGED:
-                if(mRemoteMediaPlayers != null) {
-                    mRemoteMediaPlayers.getAddressedPlayer().
-                                           updatePlayerAppSetting((ByteBuffer)msg.obj);
-                    broadcastPlayerAppSettingChanged(getCurrentPlayerAppSetting());
-                }
-                break;
-            }
-        }
-    }
-
-    private void setAbsVolume(int absVol, int label)
-    {
-        int maxVolume = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
-        int currIndex = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
-        if (mAvrcpRemoteDevice.mFirstAbsVolCmdRecvd) {
-            int newIndex = (maxVolume*absVol)/AvrcpControllerConstants.ABS_VOL_BASE;
-            Log.d(TAG," setAbsVolume ="+absVol + " maxVol = " + maxVolume + " cur = " + currIndex +
-                                              " new = "+newIndex);
-            /*
-             * In some cases change in percentage is not sufficient enough to warrant
-             * change in index values which are in range of 0-15. For such cases
-             * no action is required
-             */
-            if (newIndex != currIndex) {
-                mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, newIndex,
-                                                     AudioManager.FLAG_SHOW_UI);
-            }
-        }
-        else {
-            mAvrcpRemoteDevice.mFirstAbsVolCmdRecvd = true;
-            absVol = (currIndex*AvrcpControllerConstants.ABS_VOL_BASE)/maxVolume;
-            Log.d(TAG," SetAbsVol recvd for first time, respond with " + absVol);
-        }
-        sendAbsVolRspNative(getByteAddress(mAvrcpRemoteDevice.mBTDevice), absVol, label);
-    }
-
-    private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            String action = intent.getAction();
-            if (action.equals(AudioManager.VOLUME_CHANGED_ACTION)) {
-                int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
-                if (streamType == AudioManager.STREAM_MUSIC) {
-                    int streamValue = intent
-                            .getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, -1);
-                    int streamPrevValue = intent.getIntExtra(
-                            AudioManager.EXTRA_PREV_VOLUME_STREAM_VALUE, -1);
-                    if (streamValue != -1 && streamValue != streamPrevValue) {
-                        if ((mAvrcpRemoteDevice == null)
-                            ||((mAvrcpRemoteDevice.mRemoteFeatures &
-                                    AvrcpControllerConstants.BTRC_FEAT_ABSOLUTE_VOLUME) == 0)
-                            ||(mConnectedDevices.isEmpty()))
-                            return;
-                        if(mAvrcpRemoteDevice.mAbsVolNotificationState ==
-                                AvrcpControllerConstants.SEND_VOLUME_CHANGE_RSP) {
-                            int maxVol = mAudioManager.
-                                                  getStreamMaxVolume(AudioManager.STREAM_MUSIC);
-                            int currIndex = mAudioManager.
-                                                  getStreamVolume(AudioManager.STREAM_MUSIC);
-                            int percentageVol = ((currIndex*
-                                            AvrcpControllerConstants.ABS_VOL_BASE)/maxVol);
-                            Log.d(TAG," Abs Vol Notify Rsp Changed vol = "+ percentageVol);
-                            sendRegisterAbsVolRspNative(getByteAddress(mAvrcpRemoteDevice.mBTDevice),
-                                (byte)AvrcpControllerConstants.NOTIFICATION_RSP_TYPE_CHANGED,
-                                    percentageVol, mAvrcpRemoteDevice.mNotificationLabel);
-                        }
-                        else if (mAvrcpRemoteDevice.mAbsVolNotificationState ==
-                                AvrcpControllerConstants.DEFER_VOLUME_CHANGE_RSP) {
-                            Log.d(TAG," Don't Complete Notification Rsp. ");
-                            mAvrcpRemoteDevice.mAbsVolNotificationState =
-                                              AvrcpControllerConstants.SEND_VOLUME_CHANGE_RSP;
-                        }
-                    }
-                }
-            }
-        }
-    };
-
-    private void handlePassthroughRsp(int id, int keyState, byte[] address) {
-        Log.d(TAG, "passthrough response received as: key: " + id + " state: " + keyState);
-    }
-
-    private void onConnectionStateChanged(boolean connected, byte[] address) {
-        BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice
-            (Utils.getAddressStringFromByte(address));
-        Log.d(TAG, "onConnectionStateChanged " + connected + " " + device+ " size "+
-                    mConnectedDevices.size());
-        if (device == null)
-            return;
-        int oldState = (mConnectedDevices.contains(device) ? BluetoothProfile.STATE_CONNECTED
-                                                        : BluetoothProfile.STATE_DISCONNECTED);
-        int newState = (connected ? BluetoothProfile.STATE_CONNECTED
-                                  : BluetoothProfile.STATE_DISCONNECTED);
-
-        if (connected && oldState == BluetoothProfile.STATE_DISCONNECTED) {
-            /* AVRCPControllerService supports single connection */
-            if(mConnectedDevices.size() > 0) {
-                Log.d(TAG,"A Connection already exists, returning");
-                return;
-            }
-            mConnectedDevices.add(device);
-            Message msg =  mHandler.obtainMessage(
-                    AvrcpControllerConstants.MESSAGE_PROCESS_CONNECTION_CHANGE, newState,
-                        oldState, device);
-            mHandler.sendMessage(msg);
-        } else if (!connected && oldState == BluetoothProfile.STATE_CONNECTED) {
-            mConnectedDevices.remove(device);
-            Message msg =  mHandler.obtainMessage(
-                    AvrcpControllerConstants.MESSAGE_PROCESS_CONNECTION_CHANGE, newState,
-                        oldState, device);
-            mHandler.sendMessage(msg);
-        }
-    }
-
-    private void getRcFeatures(byte[] address, int features) {
-        BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice
-                (Utils.getAddressStringFromByte(address));
-        Message msg = mHandler.obtainMessage(
-                AvrcpControllerConstants.MESSAGE_PROCESS_RC_FEATURES, features, 0, device);
-        mHandler.sendMessage(msg);
-    }
-    private void setPlayerAppSettingRsp(byte[] address, byte accepted) {
-              /* TODO do we need to do anything here */
-    }
-    private void handleRegisterNotificationAbsVol(byte[] address, byte label)
-    {
-        Log.d(TAG,"handleRegisterNotificationAbsVol ");
-        BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice
-                (Utils.getAddressStringFromByte(address));
-        if (!mConnectedDevices.contains(device))
-            return;
-        Message msg = mHandler.obtainMessage(AvrcpControllerConstants.
-                MESSAGE_PROCESS_REGISTER_ABS_VOL_NOTIFICATION, label, 0);
-        mHandler.sendMessage(msg);
-    }
-
-    private void handleSetAbsVolume(byte[] address, byte absVol, byte label)
-    {
-        Log.d(TAG,"handleSetAbsVolume ");
-        BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice
-                (Utils.getAddressStringFromByte(address));
-        if (!mConnectedDevices.contains(device))
-            return;
-        Message msg = mHandler.obtainMessage(
-                AvrcpControllerConstants.MESSAGE_PROCESS_SET_ABS_VOL_CMD, absVol, label);
-        mHandler.sendMessage(msg);
-    }
-
-    private void onTrackChanged(byte[] address, byte numAttributes, int[] attributes,
-                                               String[] attribVals)
-    {
-        Log.d(TAG,"onTrackChanged ");
-        BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice
-                (Utils.getAddressStringFromByte(address));
-        if (!mConnectedDevices.contains(device))
-            return;
-        TrackInfo mTrack = new TrackInfo(0, numAttributes, attributes, attribVals);
-        Message msg = mHandler.obtainMessage(AvrcpControllerConstants.
-                MESSAGE_PROCESS_TRACK_CHANGED, numAttributes, 0, mTrack);
-        mHandler.sendMessage(msg);
-    }
-
-    private void onPlayPositionChanged(byte[] address, int songLen, int currSongPosition) {
-        Log.d(TAG,"onPlayPositionChanged ");
-        BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice
-                (Utils.getAddressStringFromByte(address));
-        if (!mConnectedDevices.contains(device))
-            return;
-        Message msg = mHandler.obtainMessage(AvrcpControllerConstants.
-                MESSAGE_PROCESS_PLAY_POS_CHANGED, songLen, currSongPosition);
-        mHandler.sendMessage(msg);
-    }
-
-    private void onPlayStatusChanged(byte[] address, byte playStatus) {
-        if(DBG) Log.d(TAG,"onPlayStatusChanged " + playStatus);
-        BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice
-                (Utils.getAddressStringFromByte(address));
-        if (!mConnectedDevices.contains(device))
-            return;
-        Message msg = mHandler.obtainMessage(AvrcpControllerConstants.
-                MESSAGE_PROCESS_PLAY_STATUS_CHANGED, playStatus, 0);
-        mHandler.sendMessage(msg);
-    }
-
-    private void handlePlayerAppSetting(byte[] address, byte[] playerAttribRsp, int rspLen) {
-        Log.d(TAG,"handlePlayerAppSetting rspLen = " + rspLen);
-        BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice
-                (Utils.getAddressStringFromByte(address));
-        if (!mConnectedDevices.contains(device))
-            return;
-        ByteBuffer bb = ByteBuffer.wrap(playerAttribRsp, 0, rspLen);
-        Message msg = mHandler.obtainMessage(AvrcpControllerConstants.
-                MESSAGE_PROCESS_SUPPORTED_PLAYER_APP_SETTING, 0, 0, bb);
-        mHandler.sendMessage(msg);
-    }
-
-    private void onPlayerAppSettingChanged(byte[] address, byte[] playerAttribRsp, int rspLen) {
-        Log.d(TAG,"onPlayerAppSettingChanged ");
-        BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice
-                (Utils.getAddressStringFromByte(address));
-        if (!mConnectedDevices.contains(device))
-            return;
-        ByteBuffer bb = ByteBuffer.wrap(playerAttribRsp, 0, rspLen);
-        Message msg = mHandler.obtainMessage(AvrcpControllerConstants.
-                MESSAGE_PROCESS_PLAYER_APP_SETTING_CHANGED, 0, 0, bb);
-        mHandler.sendMessage(msg);
-    }
-
-    private void handleGroupNavigationRsp(int id, int keyState) {
-        Log.d(TAG, "group navigation response received as: key: "
-                                + id + " state: " + keyState);
-    }
-
-    private byte[] getByteAddress(BluetoothDevice device) {
-        return Utils.getBytesFromAddress(device.getAddress());
-    }
-
-    @Override
-    public void dump(StringBuilder sb) {
-        super.dump(sb);
-    }
-
-    private native static void classInitNative();
-    private native void initNative();
-    private native void cleanupNative();
-    private native boolean sendPassThroughCommandNative(byte[] address, int keyCode, int keyState);
-    private native boolean sendGroupNavigationCommandNative(byte[] address, int keyCode,
-                                                                                     int keyState);
-    private native void setPlayerApplicationSettingValuesNative(byte[] address, byte numAttrib,
-                                                    byte[] atttibIds, byte[]attribVal);
-    /* This api is used to send response to SET_ABS_VOL_CMD */
-    private native void sendAbsVolRspNative(byte[] address, int absVol, int label);
-    /* This api is used to inform remote for any volume level changes */
-    private native void sendRegisterAbsVolRspNative(byte[] address, byte rspType, int absVol,
-                                                    int label);
-}
diff --git a/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerService.java b/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerService.java
new file mode 100644
index 0000000..4142e63
--- /dev/null
+++ b/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerService.java
@@ -0,0 +1,1095 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.avrcpcontroller;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothAvrcpPlayerSettings;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.IBluetoothAvrcpController;
+import android.content.Context;
+import android.media.AudioManager;
+import android.media.browse.MediaBrowser;
+import android.media.browse.MediaBrowser.MediaItem;
+import android.media.MediaDescription;
+import android.media.MediaMetadata;
+import android.media.session.PlaybackState;
+import android.os.Bundle;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+import android.util.Log;
+
+import com.android.bluetooth.Utils;
+import com.android.bluetooth.btservice.ProfileService;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+/**
+ * Provides Bluetooth AVRCP Controller profile, as a service in the Bluetooth application.
+ */
+public class AvrcpControllerService extends ProfileService {
+    static final String TAG = "AvrcpControllerService";
+    static final boolean DBG = true;
+    static final boolean VDBG = Log.isLoggable(TAG, Log.VERBOSE);
+    /*
+     *  Play State Values from JNI
+     */
+    private static final byte JNI_PLAY_STATUS_STOPPED = 0x00;
+    private static final byte JNI_PLAY_STATUS_PLAYING = 0x01;
+    private static final byte JNI_PLAY_STATUS_PAUSED  = 0x02;
+    private static final byte JNI_PLAY_STATUS_FWD_SEEK = 0x03;
+    private static final byte JNI_PLAY_STATUS_REV_SEEK = 0x04;
+    private static final byte JNI_PLAY_STATUS_ERROR    = -1;
+
+    /*
+     * Browsing Media Item Attribute IDs
+     * This should be kept in sync with BTRC_MEDIA_ATTR_ID_* in bt_rc.h
+     */
+    private static final int JNI_MEDIA_ATTR_ID_INVALID = -1;
+    private static final int JNI_MEDIA_ATTR_ID_TITLE = 0x00000001;
+    private static final int JNI_MEDIA_ATTR_ID_ARTIST = 0x00000002;
+    private static final int JNI_MEDIA_ATTR_ID_ALBUM = 0x00000003;
+    private static final int JNI_MEDIA_ATTR_ID_TRACK_NUM = 0x00000004;
+    private static final int JNI_MEDIA_ATTR_ID_NUM_TRACKS = 0x00000005;
+    private static final int JNI_MEDIA_ATTR_ID_GENRE = 0x00000006;
+    private static final int JNI_MEDIA_ATTR_ID_PLAYING_TIME = 0x00000007;
+
+    /*
+     * Browsing folder types
+     * This should be kept in sync with BTRC_FOLDER_TYPE_* in bt_rc.h
+     */
+    private static final int JNI_FOLDER_TYPE_TITLES = 0x01;
+    private static final int JNI_FOLDER_TYPE_ALBUMS = 0x02;
+    private static final int JNI_FOLDER_TYPE_ARTISTS = 0x03;
+    private static final int JNI_FOLDER_TYPE_GENRES = 0x04;
+    private static final int JNI_FOLDER_TYPE_PLAYLISTS = 0x05;
+    private static final int JNI_FOLDER_TYPE_YEARS = 0x06;
+
+    /**
+     * Intent used to broadcast the change in browse connection state of the AVRCP Controller
+     * profile.
+     *
+     * <p>This intent will have 2 extras:
+     * <ul>
+     *   <li> {@link BluetoothProfile#EXTRA_STATE} - The current state of the profile. </li>
+     *   <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li>
+     * </ul>
+     *
+     * <p>{@link #EXTRA_STATE} can be any of
+     * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING},
+     * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
+     * receive.
+     */
+    public static final String ACTION_BROWSE_CONNECTION_STATE_CHANGED =
+        "android.bluetooth.avrcp-controller.profile.action.BROWSE_CONNECTION_STATE_CHANGED";
+
+    /**
+     * intent used to broadcast the change in metadata state of playing track on the avrcp
+     * ag.
+     *
+     * <p>this intent will have the two extras:
+     * <ul>
+     *    <li> {@link #extra_metadata} - {@link mediametadata} containing the current metadata.</li>
+     *    <li> {@link #extra_playback} - {@link playbackstate} containing the current playback
+     *    state. </li>
+     * </ul>
+     */
+    public static final String ACTION_TRACK_EVENT =
+        "android.bluetooth.avrcp-controller.profile.action.TRACK_EVENT";
+
+    /**
+     * Intent used to broadcast the change of folder list.
+     *
+     * <p>This intent will have the one extra:
+     * <ul>
+     *    <li> {@link #EXTRA_FOLDER_LIST} - array of {@link MediaBrowser#MediaItem}
+     *    containing the folder listing of currently selected folder.
+     * </ul>
+     */
+    public static final String ACTION_FOLDER_LIST =
+        "android.bluetooth.avrcp-controller.profile.action.FOLDER_LIST";
+
+    public static final String EXTRA_FOLDER_LIST =
+            "android.bluetooth.avrcp-controller.profile.extra.FOLDER_LIST";
+
+    public static final String EXTRA_FOLDER_ID = "com.android.bluetooth.avrcp.EXTRA_FOLDER_ID";
+    public static final String EXTRA_FOLDER_BT_ID =
+        "com.android.bluetooth.avrcp-controller.EXTRA_FOLDER_BT_ID";
+
+    public static final String EXTRA_METADATA =
+            "android.bluetooth.avrcp-controller.profile.extra.METADATA";
+
+    public static final String EXTRA_PLAYBACK =
+            "android.bluetooth.avrcp-controller.profile.extra.PLAYBACK";
+
+    public static final String MEDIA_ITEM_UID_KEY = "media-item-uid-key";
+
+    /*
+     * KeyCoded for Pass Through Commands
+     */
+    public static final int PASS_THRU_CMD_ID_PLAY = 0x44;
+    public static final int PASS_THRU_CMD_ID_PAUSE = 0x46;
+    public static final int PASS_THRU_CMD_ID_VOL_UP = 0x41;
+    public static final int PASS_THRU_CMD_ID_VOL_DOWN = 0x42;
+    public static final int PASS_THRU_CMD_ID_STOP = 0x45;
+    public static final int PASS_THRU_CMD_ID_FF = 0x49;
+    public static final int PASS_THRU_CMD_ID_REWIND = 0x48;
+    public static final int PASS_THRU_CMD_ID_FORWARD = 0x4B;
+    public static final int PASS_THRU_CMD_ID_BACKWARD = 0x4C;
+
+    /* Key State Variables */
+    public static final int KEY_STATE_PRESSED = 0;
+    public static final int KEY_STATE_RELEASED = 1;
+
+    /* Group Navigation Key Codes */
+    public static final int PASS_THRU_CMD_ID_NEXT_GRP = 0x00;
+    public static final int PASS_THRU_CMD_ID_PREV_GRP = 0x01;
+
+    /* Folder navigation directions
+     * This is borrowed from AVRCP 1.6 spec and must be kept with same values
+     */
+    public static final int FOLDER_NAVIGATION_DIRECTION_UP = 0x00;
+    public static final int FOLDER_NAVIGATION_DIRECTION_DOWN = 0x01;
+
+    /* Folder/Media Item scopes.
+     * Keep in sync with AVRCP 1.6 sec. 6.10.1
+     */
+    public static final int BROWSE_SCOPE_PLAYER_LIST = 0x00;
+    public static final int BROWSE_SCOPE_VFS = 0x01;
+    public static final int BROWSE_SCOPE_SEARCH = 0x02;
+    public static final int BROWSE_SCOPE_NOW_PLAYING = 0x03;
+
+    private AvrcpControllerStateMachine mAvrcpCtSm;
+    private static AvrcpControllerService sAvrcpControllerService;
+    // UID size is 8 bytes (AVRCP 1.6 spec)
+    private static final byte[] EMPTY_UID = {0, 0, 0, 0, 0, 0, 0, 0};
+
+    // We only support one device.
+    private BluetoothDevice mConnectedDevice = null;
+    // If browse is supported (only valid if mConnectedDevice != null).
+    private boolean mBrowseConnected = false;
+    // Caches the current browse folder. If this is null then root is the currently browsed folder
+    // (which also has no UID).
+    private String mCurrentBrowseFolderUID = null;
+
+    static {
+        classInitNative();
+    }
+
+    public AvrcpControllerService() {
+        initNative();
+    }
+
+    protected String getName() {
+        return TAG;
+    }
+
+    protected IProfileServiceBinder initBinder() {
+        return new BluetoothAvrcpControllerBinder(this);
+    }
+
+    protected boolean start() {
+        HandlerThread thread = new HandlerThread("BluetoothAvrcpHandler");
+        thread.start();
+        mAvrcpCtSm = new AvrcpControllerStateMachine(this);
+        mAvrcpCtSm.start();
+
+        setAvrcpControllerService(this);
+        return true;
+    }
+
+    protected boolean stop() {
+        if (mAvrcpCtSm != null) {
+            mAvrcpCtSm.doQuit();
+        }
+        return true;
+    }
+
+    //API Methods
+
+    public static synchronized AvrcpControllerService getAvrcpControllerService() {
+        if (sAvrcpControllerService != null && sAvrcpControllerService.isAvailable()) {
+            if (DBG) {
+                Log.d(TAG, "getAvrcpControllerService(): returning "
+                    + sAvrcpControllerService);
+            }
+            return sAvrcpControllerService;
+        }
+        if (DBG) {
+            if (sAvrcpControllerService == null) {
+                Log.d(TAG, "getAvrcpControllerService(): service is NULL");
+            } else if (!(sAvrcpControllerService.isAvailable())) {
+                Log.d(TAG, "getAvrcpControllerService(): service is not available");
+            }
+        }
+        return null;
+    }
+
+    private static synchronized void setAvrcpControllerService(AvrcpControllerService instance) {
+        if (instance != null && instance.isAvailable()) {
+            if (DBG) {
+                Log.d(TAG, "setAvrcpControllerService(): set to: " + sAvrcpControllerService);
+            }
+            sAvrcpControllerService = instance;
+        } else {
+            if (DBG) {
+                if (instance == null) {
+                    Log.d(TAG, "setAvrcpControllerService(): service not available");
+                } else if (!instance.isAvailable()) {
+                    Log.d(TAG, "setAvrcpControllerService(): service is cleaning up");
+                }
+            }
+        }
+    }
+
+    private static synchronized void clearAvrcpControllerService() {
+        sAvrcpControllerService = null;
+    }
+
+    public synchronized List<BluetoothDevice> getConnectedDevices() {
+        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        List<BluetoothDevice> devices = new ArrayList<BluetoothDevice>();
+        if (mConnectedDevice != null) {
+            devices.add(mConnectedDevice);
+        }
+        return devices;
+    }
+
+    /**
+     * This function only supports STATE_CONNECTED
+     */
+    public synchronized List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
+        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        List<BluetoothDevice> devices = new ArrayList<BluetoothDevice>();
+        for (int i = 0; i < states.length; i++) {
+            if (states[i] == BluetoothProfile.STATE_CONNECTED && mConnectedDevice != null) {
+                devices.add(mConnectedDevice);
+            }
+        }
+        return devices;
+    }
+
+    public synchronized int getConnectionState(BluetoothDevice device) {
+        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        return (mConnectedDevice != null ? BluetoothProfile.STATE_CONNECTED :
+            BluetoothProfile.STATE_DISCONNECTED);
+    }
+
+    public synchronized void sendGroupNavigationCmd(BluetoothDevice device, int keyCode, int keyState) {
+        Log.v(TAG, "sendGroupNavigationCmd keyCode: " + keyCode + " keyState: " + keyState);
+        if (device == null) {
+            Log.e(TAG, "sendGroupNavigationCmd device is null");
+        }
+
+        if (!(device.equals(mConnectedDevice))) {
+            Log.e(TAG, " Device does not match " + device + " connected " + mConnectedDevice);
+            return;
+        }
+        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        Message msg = mAvrcpCtSm.obtainMessage(AvrcpControllerStateMachine.
+            MESSAGE_SEND_GROUP_NAVIGATION_CMD, keyCode, keyState, device);
+        mAvrcpCtSm.sendMessage(msg);
+    }
+
+    public synchronized void sendPassThroughCmd(BluetoothDevice device, int keyCode, int keyState) {
+        Log.v(TAG, "sendPassThroughCmd keyCode: " + keyCode + " keyState: " + keyState);
+        if (device == null) {
+            Log.e(TAG, "sendPassThroughCmd Device is null");
+            return;
+        }
+
+        if (!device.equals(mConnectedDevice)) {
+            Log.w(TAG, " Device does not match device " + device + " conn " + mConnectedDevice);
+            return;
+        }
+
+        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        Message msg = mAvrcpCtSm
+            .obtainMessage(AvrcpControllerStateMachine.MESSAGE_SEND_PASS_THROUGH_CMD,
+                keyCode, keyState, device);
+        mAvrcpCtSm.sendMessage(msg);
+    }
+
+    public void startAvrcpUpdates() {
+        mAvrcpCtSm.obtainMessage(
+            AvrcpControllerStateMachine.MESSAGE_START_METADATA_BROADCASTS).sendToTarget();
+    }
+
+    public void stopAvrcpUpdates() {
+        mAvrcpCtSm.obtainMessage(
+            AvrcpControllerStateMachine.MESSAGE_STOP_METADATA_BROADCASTS).sendToTarget();
+    }
+
+    public synchronized MediaMetadata getMetaData(BluetoothDevice device) {
+        if (DBG) {
+            Log.d(TAG, "getMetaData");
+        }
+        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        if (device == null) {
+            Log.e(TAG, "getMetadata device is null");
+            return null;
+        }
+
+        if (!device.equals(mConnectedDevice)) {
+            return null;
+        }
+        return mAvrcpCtSm.getCurrentMetaData();
+    }
+
+    public synchronized PlaybackState getPlaybackState(BluetoothDevice device) {
+        if (DBG) {
+            Log.d(TAG, "getPlayBackState device = " + device);
+        }
+
+        if (device == null) {
+            Log.e(TAG, "getPlaybackState device is null");
+            return null;
+        }
+
+        if (!device.equals(mConnectedDevice)) {
+            Log.e(TAG, "Device " + device + " does not match connected deivce " + mConnectedDevice);
+            return null;
+
+        }
+        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        return mAvrcpCtSm.getCurrentPlayBackState();
+    }
+
+    public synchronized BluetoothAvrcpPlayerSettings getPlayerSettings(BluetoothDevice device) {
+        if (DBG) {
+            Log.d(TAG, "getPlayerApplicationSetting ");
+        }
+
+        if (device == null) {
+            Log.e(TAG, "getPlayerSettings device is null");
+            return null;
+        }
+
+        if (!device.equals(mConnectedDevice)) {
+            Log.e(TAG, "device " + device + " does not match connected device " + mConnectedDevice);
+            return null;
+        }
+
+        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+
+        /* Do nothing */
+        return null;
+    }
+
+    public boolean setPlayerApplicationSetting(BluetoothAvrcpPlayerSettings plAppSetting) {
+        if (DBG) {
+            Log.d(TAG, "getPlayerApplicationSetting");
+        }
+
+        /* Do nothing */
+        return false;
+    }
+
+    /**
+     * Fetches the list of children for the parentID node.
+     *
+     * This function manages the overall tree for browsing structure.
+     *
+     * Arguments:
+     * device - Device to browse content for.
+     * parentMediaId - ID of the parent that we need to browse content for. Since most
+     * of the players are database unware, fetching a root invalidates all the children.
+     * start - number of item to start scanning from
+     * items - number of items to fetch
+     */
+    public synchronized void getChildren(BluetoothDevice device, String parentMediaId, int start, int items) {
+        if (DBG) {
+            Log.d(TAG, "getChildrent device = " + device + " parent " + parentMediaId);
+        }
+
+        if (device == null) {
+            Log.e(TAG, "getChildren device is null");
+            return;
+        }
+
+        if (!device.equals(mConnectedDevice)) {
+            Log.e(TAG, "getChildren device " + device + " does not match " +
+                mConnectedDevice);
+            return;
+        }
+
+        if (!mBrowseConnected) {
+            Log.e(TAG, "getChildren browse not yet connected");
+            return;
+        }
+
+        if (!mAvrcpCtSm.isConnected()) {
+            return;
+        }
+        mAvrcpCtSm.getChildren(parentMediaId, start, items);
+    }
+
+    public synchronized void getNowPlayingList(BluetoothDevice device, String id, int start, int items) {
+        if (DBG) {
+            Log.d(TAG, "getNowPlayingList device = " + device + " start = " + start +
+                "items = " + items);
+        }
+
+        if (device == null) {
+            Log.e(TAG, "getNowPlayingList device is null");
+            return;
+        }
+
+        if (!device.equals(mConnectedDevice)) {
+            Log.e(TAG, "getNowPlayingList device " + device + " does not match " + mConnectedDevice);
+            return;
+        }
+
+        if (!mBrowseConnected) {
+            Log.e(TAG, "getNowPlayingList browse not yet connected");
+        }
+
+        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+
+        Message msg = mAvrcpCtSm.obtainMessage(
+            AvrcpControllerStateMachine.MESSAGE_GET_NOW_PLAYING_LIST, start, items, id);
+        mAvrcpCtSm.sendMessage(msg);
+    }
+
+    public synchronized void getFolderList(BluetoothDevice device, String id, int start, int items) {
+        if (DBG) {
+            Log.d(TAG, "getFolderListing device = " + device + " start = " + start +
+                "items = " + items);
+        }
+
+        if (device == null) {
+            Log.e(TAG, "getFolderListing device is null");
+            return;
+        }
+
+        if (!device.equals(mConnectedDevice)) {
+            Log.e(TAG, "getFolderListing device " + device + " does not match " + mConnectedDevice);
+            return;
+        }
+
+        if (!mBrowseConnected) {
+            Log.e(TAG, "getFolderListing browse not yet connected");
+            return;
+        }
+
+        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+
+        Message msg = mAvrcpCtSm.obtainMessage(
+            AvrcpControllerStateMachine.MESSAGE_GET_FOLDER_LIST, start, items, id);
+        mAvrcpCtSm.sendMessage(msg);
+    }
+
+    public synchronized void getPlayerList(BluetoothDevice device, int start, int items) {
+        if (DBG) {
+            Log.d(TAG, "getPlayerList device = " + device + " start = " + start +
+                "items = " + items);
+        }
+
+        if (device == null) {
+            Log.e(TAG, "getPlayerList device is null");
+            return;
+        }
+
+        if (!device.equals(mConnectedDevice)) {
+            Log.e(TAG, "getPlayerList device " + device + " does not match " + mConnectedDevice);
+            return;
+        }
+
+        if (!mBrowseConnected) {
+            Log.e(TAG, "getPlayerList browse not yet connected");
+            return;
+        }
+
+        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+
+        Message msg = mAvrcpCtSm.obtainMessage(
+            AvrcpControllerStateMachine.MESSAGE_GET_PLAYER_LIST, start, items);
+        mAvrcpCtSm.sendMessage(msg);
+    }
+
+    public synchronized void changeFolderPath(
+            BluetoothDevice device, int direction, String uid, String fid) {
+        if (DBG) {
+            Log.d(TAG, "changeFolderPath device = " + device + " direction " +
+                direction + " uid " + uid);
+        }
+
+        if (device == null) {
+            Log.e(TAG, "changeFolderPath device is null");
+            return;
+        }
+
+        if (!device.equals(mConnectedDevice)) {
+            Log.e(TAG, "changeFolderPath device " + device + " does not match " +
+                mConnectedDevice);
+            return;
+        }
+
+        if (!mBrowseConnected) {
+            Log.e(TAG, "changeFolderPath browse not yet connected");
+            return;
+        }
+
+        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+
+        Bundle b = new Bundle();
+        b.putString(EXTRA_FOLDER_ID, fid);
+        b.putString(EXTRA_FOLDER_BT_ID, uid);
+        Message msg = mAvrcpCtSm.obtainMessage(
+            AvrcpControllerStateMachine.MESSAGE_CHANGE_FOLDER_PATH, direction, 0, b);
+        mAvrcpCtSm.sendMessage(msg);
+    }
+
+    public void setBrowsedPlayer(BluetoothDevice device, int id, String fid) {
+        if (DBG) {
+            Log.d(TAG, "setBrowsedPlayer device = " + device + " id" + id + " fid " + fid);
+        }
+
+        if (device == null) {
+            Log.e(TAG, "setBrowsedPlayer device is null");
+            return;
+        }
+
+
+        if (!mBrowseConnected) {
+            Log.e(TAG, "setBrowsedPlayer browse not yet connected");
+            return;
+        }
+
+        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+
+        Message msg = mAvrcpCtSm.obtainMessage(
+            AvrcpControllerStateMachine.MESSAGE_SET_BROWSED_PLAYER, id, 0, fid);
+        mAvrcpCtSm.sendMessage(msg);
+    }
+
+    public synchronized void fetchAttrAndPlayItem(BluetoothDevice device, String uid) {
+        if (DBG) {
+            Log.d(TAG, "fetchAttrAndPlayItem device = " + device + " uid " + uid);
+        }
+
+        if (device == null) {
+            Log.e(TAG, "fetchAttrAndPlayItem device is null");
+            return;
+        }
+
+        if (!device.equals(mConnectedDevice)) {
+            Log.e(TAG, "fetchAttrAndPlayItem device " + device + " does not match " +
+                mConnectedDevice);
+            return;
+        }
+
+        if (!mBrowseConnected) {
+            Log.e(TAG, "fetchAttrAndPlayItem browse not yet connected");
+            return;
+        }
+        mAvrcpCtSm.fetchAttrAndPlayItem(uid);
+    }
+
+    //Binder object: Must be static class or memory leak may occur
+    private static class BluetoothAvrcpControllerBinder extends IBluetoothAvrcpController.Stub
+        implements IProfileServiceBinder {
+
+        private AvrcpControllerService mService;
+
+        private AvrcpControllerService getService() {
+            if (!Utils.checkCaller()) {
+                Log.w(TAG, "AVRCP call not allowed for non-active user");
+                return null;
+            }
+
+            if (mService != null && mService.isAvailable()) {
+                return mService;
+            }
+            return null;
+        }
+
+        BluetoothAvrcpControllerBinder(AvrcpControllerService svc) {
+            mService = svc;
+        }
+
+        public boolean cleanup() {
+            mService = null;
+            return true;
+        }
+
+        @Override
+        public List<BluetoothDevice> getConnectedDevices() {
+            AvrcpControllerService service = getService();
+            if (service == null) {
+                return new ArrayList<BluetoothDevice>(0);
+            }
+            return service.getConnectedDevices();
+        }
+
+        @Override
+        public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
+            AvrcpControllerService service = getService();
+            if (service == null) {
+                return new ArrayList<BluetoothDevice>(0);
+            }
+            return service.getDevicesMatchingConnectionStates(states);
+        }
+
+        @Override
+        public int getConnectionState(BluetoothDevice device) {
+            AvrcpControllerService service = getService();
+            if (service == null) {
+                return BluetoothProfile.STATE_DISCONNECTED;
+            }
+
+            if (device == null) {
+              throw new IllegalStateException("Device cannot be null!");
+            }
+
+            return service.getConnectionState(device);
+        }
+
+        @Override
+        public void sendGroupNavigationCmd(BluetoothDevice device, int keyCode, int keyState) {
+            Log.v(TAG, "Binder Call: sendGroupNavigationCmd");
+            AvrcpControllerService service = getService();
+            if (service == null) {
+                return;
+            }
+
+            if (device == null) {
+              throw new IllegalStateException("Device cannot be null!");
+            }
+
+            service.sendGroupNavigationCmd(device, keyCode, keyState);
+        }
+
+        @Override
+        public BluetoothAvrcpPlayerSettings getPlayerSettings(BluetoothDevice device) {
+            Log.v(TAG, "Binder Call: getPlayerApplicationSetting ");
+            AvrcpControllerService service = getService();
+            if (service == null) {
+                return null;
+            }
+
+            if (device == null) {
+              throw new IllegalStateException("Device cannot be null!");
+            }
+
+            return service.getPlayerSettings(device);
+        }
+
+        @Override
+        public boolean setPlayerApplicationSetting(BluetoothAvrcpPlayerSettings plAppSetting) {
+            Log.v(TAG, "Binder Call: setPlayerApplicationSetting ");
+            AvrcpControllerService service = getService();
+            if (service == null) {
+                return false;
+            }
+            return service.setPlayerApplicationSetting(plAppSetting);
+        }
+    }
+
+    // Called by JNI when a passthrough key was received.
+    private void handlePassthroughRsp(int id, int keyState) {
+        Log.d(TAG, "passthrough response received as: key: " + id + " state: " +
+            keyState);
+    }
+
+    private void handleGroupNavigationRsp(int id, int keyState) {
+        Log.d(TAG, "group navigation response received as: key: " + id + " state: " +
+            keyState);
+    }
+
+    // Called by JNI when a device has connected or disconnected.
+    private synchronized void onConnectionStateChanged(
+            boolean rc_connected, boolean br_connected, byte[] address) {
+        BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
+        Log.d(TAG, "onConnectionStateChanged " + rc_connected + " " + br_connected +
+            device + " conn device " + mConnectedDevice);
+        if (device == null) {
+            Log.e(TAG, "onConnectionStateChanged Device is null");
+            return;
+        }
+
+        // Adjust the AVRCP connection state.
+        int oldState = (device.equals(mConnectedDevice) ? BluetoothProfile.STATE_CONNECTED :
+            BluetoothProfile.STATE_DISCONNECTED);
+        int newState = (rc_connected ? BluetoothProfile.STATE_CONNECTED :
+            BluetoothProfile.STATE_DISCONNECTED);
+
+        if (rc_connected && oldState == BluetoothProfile.STATE_DISCONNECTED) {
+            /* AVRCPControllerService supports single connection */
+            if (mConnectedDevice != null) {
+                Log.d(TAG, "A Connection already exists, returning");
+                return;
+            }
+            mConnectedDevice = device;
+            Message msg = mAvrcpCtSm.obtainMessage(
+                AvrcpControllerStateMachine.MESSAGE_PROCESS_CONNECTION_CHANGE, newState,
+                oldState, device);
+            mAvrcpCtSm.sendMessage(msg);
+        } else if (!rc_connected && oldState == BluetoothProfile.STATE_CONNECTED) {
+            mConnectedDevice = null;
+            Message msg = mAvrcpCtSm.obtainMessage(
+                AvrcpControllerStateMachine.MESSAGE_PROCESS_CONNECTION_CHANGE, newState,
+                oldState, device);
+            mAvrcpCtSm.sendMessage(msg);
+        }
+
+        // Adjust the browse connection state. If RC is connected we should have already sent the
+        // connection status out.
+        if (rc_connected && br_connected) {
+            mBrowseConnected = true;
+            Message msg = mAvrcpCtSm.obtainMessage(
+               AvrcpControllerStateMachine.MESSAGE_PROCESS_BROWSE_CONNECTION_CHANGE);
+            msg.arg1 = 1;
+            msg.obj = device;
+            mAvrcpCtSm.sendMessage(msg);
+        }
+    }
+
+    // Called by JNI to notify Avrcp of features supported by the Remote device.
+    private void getRcFeatures(byte[] address, int features) {
+        BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
+        Message msg = mAvrcpCtSm.obtainMessage(
+            AvrcpControllerStateMachine.MESSAGE_PROCESS_RC_FEATURES, features, 0, device);
+        mAvrcpCtSm.sendMessage(msg);
+    }
+
+    // Called by JNI
+    private void setPlayerAppSettingRsp(byte[] address, byte accepted) {
+              /* Do Nothing. */
+    }
+
+    // Called by JNI when remote wants to receive absolute volume notifications.
+    private synchronized void handleRegisterNotificationAbsVol(byte[] address, byte label) {
+        Log.d(TAG, "handleRegisterNotificationAbsVol ");
+        BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
+        if (device != null && !device.equals(mConnectedDevice)) {
+            Log.e(TAG, "handleRegisterNotificationAbsVol device not found " + address);
+            return;
+        }
+        Message msg = mAvrcpCtSm.obtainMessage(AvrcpControllerStateMachine.
+            MESSAGE_PROCESS_REGISTER_ABS_VOL_NOTIFICATION, (int) label, 0);
+        mAvrcpCtSm.sendMessage(msg);
+    }
+
+    // Called by JNI when remote wants to set absolute volume.
+    private synchronized void handleSetAbsVolume(byte[] address, byte absVol, byte label) {
+        Log.d(TAG, "handleSetAbsVolume ");
+        BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
+        if (device != null && !device.equals(mConnectedDevice)) {
+            Log.e(TAG, "handleSetAbsVolume device not found " + address);
+            return;
+        }
+        Message msg = mAvrcpCtSm.obtainMessage(
+            AvrcpControllerStateMachine.MESSAGE_PROCESS_SET_ABS_VOL_CMD, absVol, label);
+        mAvrcpCtSm.sendMessage(msg);
+    }
+
+    // Called by JNI when a track changes and local AvrcpController is registered for updates.
+    private synchronized void onTrackChanged(byte[] address, byte numAttributes, int[] attributes,
+        String[] attribVals) {
+        if (DBG) {
+            Log.d(TAG, "onTrackChanged");
+        }
+        BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
+        if (device != null && !device.equals(mConnectedDevice)) {
+            Log.e(TAG, "onTrackChanged device not found " + address);
+            return;
+        }
+
+        List<Integer> attrList = new ArrayList<>();
+        for (int attr : attributes) {
+            attrList.add(attr);
+        }
+        List<String> attrValList = Arrays.asList(attribVals);
+        TrackInfo trackInfo = new TrackInfo(attrList, attrValList);
+        if (DBG) {
+            Log.d(TAG, "onTrackChanged " + trackInfo);
+        }
+        Message msg = mAvrcpCtSm.obtainMessage(AvrcpControllerStateMachine.
+            MESSAGE_PROCESS_TRACK_CHANGED, trackInfo);
+        mAvrcpCtSm.sendMessage(msg);
+    }
+
+    // Called by JNI periodically based upon timer to update play position
+    private synchronized void onPlayPositionChanged(byte[] address, int songLen, int currSongPosition) {
+        if (DBG) {
+            Log.d(TAG, "onPlayPositionChanged pos " + currSongPosition);
+        }
+        BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
+        if (device != null && !device.equals(mConnectedDevice)) {
+            Log.e(TAG, "onPlayPositionChanged not found device not found " + address);
+            return;
+        }
+        Message msg = mAvrcpCtSm.obtainMessage(AvrcpControllerStateMachine.
+            MESSAGE_PROCESS_PLAY_POS_CHANGED, songLen, currSongPosition);
+        mAvrcpCtSm.sendMessage(msg);
+    }
+
+    // Called by JNI on changes of play status
+    private synchronized void onPlayStatusChanged(byte[] address, byte playStatus) {
+        if (DBG) {
+            Log.d(TAG, "onPlayStatusChanged " + playStatus);
+        }
+        BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
+        if (device != null && !device.equals(mConnectedDevice)) {
+            Log.e(TAG, "onPlayStatusChanged not found device not found " + address);
+            return;
+        }
+        int playbackState = PlaybackState.STATE_NONE;
+        switch (playStatus) {
+            case JNI_PLAY_STATUS_STOPPED:
+                playbackState =  PlaybackState.STATE_STOPPED;
+                break;
+            case JNI_PLAY_STATUS_PLAYING:
+                playbackState =  PlaybackState.STATE_PLAYING;
+                break;
+            case JNI_PLAY_STATUS_PAUSED:
+                playbackState = PlaybackState.STATE_PAUSED;
+                break;
+            case JNI_PLAY_STATUS_FWD_SEEK:
+                playbackState = PlaybackState.STATE_FAST_FORWARDING;
+                break;
+            case JNI_PLAY_STATUS_REV_SEEK:
+                playbackState = PlaybackState.STATE_FAST_FORWARDING;
+                break;
+            default:
+                playbackState = PlaybackState.STATE_NONE;
+        }
+        Message msg = mAvrcpCtSm.obtainMessage(AvrcpControllerStateMachine.
+            MESSAGE_PROCESS_PLAY_STATUS_CHANGED, playbackState);
+        mAvrcpCtSm.sendMessage(msg);
+    }
+
+    // Called by JNI to report remote Player's capabilities
+    private synchronized void handlePlayerAppSetting(byte[] address, byte[] playerAttribRsp, int rspLen) {
+        if (DBG) {
+            Log.d(TAG, "handlePlayerAppSetting rspLen = " + rspLen);
+        }
+        BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
+        if (device != null && !device.equals(mConnectedDevice)) {
+            Log.e(TAG, "handlePlayerAppSetting not found device not found " + address);
+            return;
+        }
+        PlayerApplicationSettings supportedSettings = PlayerApplicationSettings.
+            makeSupportedSettings(playerAttribRsp);
+        /* Do nothing */
+    }
+
+    private synchronized void onPlayerAppSettingChanged(byte[] address, byte[] playerAttribRsp, int rspLen) {
+        if (DBG) {
+            Log.d(TAG, "onPlayerAppSettingChanged ");
+        }
+        BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
+        if (device != null && !device.equals(mConnectedDevice)) {
+            Log.e(TAG, "onPlayerAppSettingChanged not found device not found " + address);
+            return;
+        }
+        PlayerApplicationSettings desiredSettings = PlayerApplicationSettings.
+            makeSettings(playerAttribRsp);
+        /* Do nothing */
+    }
+
+    // Browsing related JNI callbacks.
+    void handleGetFolderItemsRsp(MediaItem[] items) {
+        if (DBG) {
+            Log.d(TAG, "handleGetFolderItemsRsp called with " + items.length + " items.");
+        }
+        for (MediaItem item : items) {
+            if (DBG) {
+                Log.d(TAG, "media item: " + item + " uid: " + item.getDescription().getMediaId());
+            }
+        }
+        ArrayList<MediaItem> itemsList = new ArrayList<>();
+        for (MediaItem item : items) {
+            itemsList.add(item);
+        }
+        Message msg = mAvrcpCtSm.obtainMessage(AvrcpControllerStateMachine.
+            MESSAGE_PROCESS_GET_FOLDER_ITEMS, itemsList);
+        mAvrcpCtSm.sendMessage(msg);
+    }
+
+    void handleGetPlayerItemsRsp(AvrcpPlayer[] items) {
+        if (DBG) {
+            Log.d(TAG, "handleGetFolderItemsRsp called with " + items.length + " items.");
+        }
+        for (AvrcpPlayer item : items) {
+            if (DBG) {
+                Log.d(TAG, "bt player item: " + item);
+            }
+        }
+        List<AvrcpPlayer> itemsList = new ArrayList<>();
+        for (AvrcpPlayer p : items) {
+            itemsList.add(p);
+        }
+
+        Message msg = mAvrcpCtSm.obtainMessage(AvrcpControllerStateMachine.
+            MESSAGE_PROCESS_GET_PLAYER_ITEMS, itemsList);
+        mAvrcpCtSm.sendMessage(msg);
+    }
+
+    // JNI Helper functions to convert native objects to java.
+    MediaItem createFromNativeMediaItem(
+            byte[] uid, int type, String name, int[] attrIds, String[] attrVals) {
+        if (DBG) {
+            Log.d(TAG, "createFromNativeMediaItem uid: " + uid + " type " + type + " name " +
+                name + " attrids " + attrIds + " attrVals " + attrVals);
+        }
+        MediaDescription.Builder mdb = new MediaDescription.Builder();
+
+        Bundle mdExtra = new Bundle();
+        mdExtra.putString(MEDIA_ITEM_UID_KEY, byteUIDToHexString(uid));
+        mdb.setExtras(mdExtra);
+
+        // Generate a random UUID. We do this since database unaware TGs can send multiple
+        // items with same MEDIA_ITEM_UID_KEY.
+        mdb.setMediaId(UUID.randomUUID().toString());
+
+        // Concise readable name.
+        mdb.setTitle(name);
+
+        // We skip the attributes since we can query them using UID for the item above
+        // Also MediaDescription does not give an easy way to provide this unless we pass
+        // it as an MediaMetadata which is put inside the extras.
+        return new MediaItem(mdb.build(), MediaItem.FLAG_PLAYABLE);
+    }
+
+    MediaItem createFromNativeFolderItem(
+            byte[] uid, int type, String name, int playable) {
+        if (DBG) {
+            Log.d(TAG, "createFromNativeFolderItem uid: " + uid + " type " + type +
+                " name " + name + " playable " + playable);
+        }
+        MediaDescription.Builder mdb = new MediaDescription.Builder();
+
+        // Covert the byte to a hex string. The coversion can be done back here to a
+        // byte array when needed.
+        Bundle mdExtra = new Bundle();
+        mdExtra.putString(MEDIA_ITEM_UID_KEY, byteUIDToHexString(uid));
+        mdb.setExtras(mdExtra);
+
+        // Generate a random UUID. We do this since database unaware TGs can send multiple
+        // items with same MEDIA_ITEM_UID_KEY.
+        mdb.setMediaId(UUID.randomUUID().toString());
+
+        // Concise readable name.
+        mdb.setTitle(name);
+
+        return new MediaItem(mdb.build(), MediaItem.FLAG_BROWSABLE);
+    }
+
+    AvrcpPlayer createFromNativePlayerItem(
+            int id, String name, byte[] transportFlags, int playStatus, int playerType) {
+        if (DBG) {
+            Log.d(TAG, "createFromNativePlayerItem name: " + name + " transportFlags " +
+                transportFlags + " play status " + playStatus + " player type " +
+                playerType);
+        }
+        AvrcpPlayer player = new AvrcpPlayer(id, name, 0, playStatus, playerType);
+        return player;
+    }
+
+    private void handleChangeFolderRsp(int count) {
+        if (DBG) {
+            Log.d(TAG, "handleChangeFolderRsp count: " + count);
+        }
+        Message msg = mAvrcpCtSm.obtainMessage(
+            AvrcpControllerStateMachine.MESSAGE_PROCESS_FOLDER_PATH, count);
+        mAvrcpCtSm.sendMessage(msg);
+    }
+
+    private void handleSetBrowsedPlayerRsp(int items, int depth) {
+        if (DBG) {
+            Log.d(TAG, "handleSetBrowsedPlayerRsp depth: " + depth);
+        }
+        Message msg = mAvrcpCtSm.obtainMessage(
+            AvrcpControllerStateMachine.MESSAGE_PROCESS_SET_BROWSED_PLAYER, items, depth);
+        mAvrcpCtSm.sendMessage(msg);
+    }
+
+    @Override
+    public void dump(StringBuilder sb) {
+        super.dump(sb);
+        mAvrcpCtSm.dump(sb);
+    }
+
+    public static String byteUIDToHexString(byte[] uid) {
+        StringBuilder sb = new StringBuilder();
+        for (byte b : uid) {
+            sb.append(String.format("%02X", b));
+        }
+        return sb.toString();
+    }
+
+    public static byte[] hexStringToByteUID(String uidStr) {
+        if (uidStr == null) {
+            Log.e(TAG, "Null hex string.");
+            return EMPTY_UID;
+        } else if (uidStr.length() % 2 == 1) {
+            // Odd length strings should not be possible.
+            Log.e(TAG, "Odd length hex string " + uidStr);
+            return EMPTY_UID;
+        }
+        int len = uidStr.length();
+        byte[] data = new byte[len / 2];
+        for (int i = 0; i < len; i += 2) {
+          data[i / 2] = (byte) ((Character.digit(uidStr.charAt(i), 16) << 4)
+              + Character.digit(uidStr.charAt(i + 1), 16));
+        }
+        return data;
+    }
+
+    private native static void classInitNative();
+
+    private native void initNative();
+
+    private native void cleanupNative();
+
+    native static boolean sendPassThroughCommandNative(byte[] address, int keyCode, int keyState);
+
+    native static boolean sendGroupNavigationCommandNative(byte[] address, int keyCode,
+        int keyState);
+
+    native static void setPlayerApplicationSettingValuesNative(byte[] address, byte numAttrib,
+        byte[] atttibIds, byte[] attribVal);
+
+    /* This api is used to send response to SET_ABS_VOL_CMD */
+    native static void sendAbsVolRspNative(byte[] address, int absVol, int label);
+
+    /* This api is used to inform remote for any volume level changes */
+    native static void sendRegisterAbsVolRspNative(byte[] address, byte rspType, int absVol,
+        int label);
+
+    /* API used to fetch the current now playing list */
+    native static void getNowPlayingListNative(byte[] address, byte start, byte items);
+    /* API used to fetch the current folder's listing */
+    native static void getFolderListNative(byte[] address, byte start, byte items);
+    /* API used to fetch the listing of players */
+    native static void getPlayerListNative(byte[] address, byte start, byte items);
+    /* API used to change the folder */
+    native static void changeFolderPathNative(byte[] address, byte direction, byte[] uid);
+    native static void playItemNative(
+        byte[] address, byte scope, byte[] uid, int uidCounter);
+    native static void setBrowsedPlayerNative(byte[] address, int playerId);
+}
diff --git a/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java b/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java
new file mode 100644
index 0000000..874512b
--- /dev/null
+++ b/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java
@@ -0,0 +1,1019 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.avrcpcontroller;
+
+import android.bluetooth.BluetoothAvrcpController;
+import android.bluetooth.BluetoothAvrcpPlayerSettings;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.media.AudioManager;
+import android.media.browse.MediaBrowser;
+import android.media.browse.MediaBrowser.MediaItem;
+import android.media.MediaDescription;
+import android.media.MediaMetadata;
+import android.media.session.PlaybackState;
+import android.os.Bundle;
+import android.os.Message;
+import android.util.Log;
+
+import com.android.bluetooth.Utils;
+import com.android.bluetooth.a2dpsink.A2dpSinkService;
+import com.android.bluetooth.btservice.ProfileService;
+import com.android.internal.util.State;
+import com.android.internal.util.StateMachine;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Queue;
+
+/**
+ * Provides Bluetooth AVRCP Controller State Machine responsible for all remote control connections
+ * and interactions with a remote controlable device.
+ */
+class AvrcpControllerStateMachine extends StateMachine {
+
+    // commands from Binder service
+    static final int MESSAGE_SEND_PASS_THROUGH_CMD = 1;
+    static final int MESSAGE_SEND_GROUP_NAVIGATION_CMD = 3;
+    static final int MESSAGE_GET_NOW_PLAYING_LIST = 5;
+    static final int MESSAGE_GET_FOLDER_LIST = 6;
+    static final int MESSAGE_GET_PLAYER_LIST = 7;
+    static final int MESSAGE_CHANGE_FOLDER_PATH = 8;
+    static final int MESSAGE_FETCH_ATTR_AND_PLAY_ITEM = 9;
+    static final int MESSAGE_SET_BROWSED_PLAYER = 10;
+
+    // commands from native layer
+    static final int MESSAGE_PROCESS_SET_ABS_VOL_CMD = 103;
+    static final int MESSAGE_PROCESS_REGISTER_ABS_VOL_NOTIFICATION = 104;
+    static final int MESSAGE_PROCESS_TRACK_CHANGED = 105;
+    static final int MESSAGE_PROCESS_PLAY_POS_CHANGED = 106;
+    static final int MESSAGE_PROCESS_PLAY_STATUS_CHANGED = 107;
+    static final int MESSAGE_PROCESS_VOLUME_CHANGED_NOTIFICATION = 108;
+    static final int MESSAGE_PROCESS_GET_FOLDER_ITEMS = 109;
+    static final int MESSAGE_PROCESS_GET_PLAYER_ITEMS = 110;
+    static final int MESSAGE_PROCESS_FOLDER_PATH = 111;
+    static final int MESSAGE_PROCESS_SET_BROWSED_PLAYER = 113;
+
+    // commands from A2DP sink
+    static final int MESSAGE_STOP_METADATA_BROADCASTS = 201;
+    static final int MESSAGE_START_METADATA_BROADCASTS = 202;
+
+    // commands for connection
+    static final int MESSAGE_PROCESS_RC_FEATURES = 301;
+    static final int MESSAGE_PROCESS_CONNECTION_CHANGE = 302;
+    static final int MESSAGE_PROCESS_BROWSE_CONNECTION_CHANGE = 303;
+
+    // Interal messages
+    static final int MESSAGE_INTERNAL_BROWSE_DEPTH_INCREMENT = 401;
+    static final int MESSAGE_INTERNAL_MOVE_N_LEVELS_UP = 402;
+    static final int MESSAGE_INTERNAL_CMD_TIMEOUT = 403;
+
+    static final int CMD_TIMEOUT_MILLIS = 5000; // 5s
+
+    /*
+     * Base value for absolute volume from JNI
+     */
+    private static final int ABS_VOL_BASE = 127;
+
+    /*
+     * Notification types for Avrcp protocol JNI.
+     */
+    private static final byte NOTIFICATION_RSP_TYPE_INTERIM = 0x00;
+    private static final byte NOTIFICATION_RSP_TYPE_CHANGED = 0x01;
+
+
+    private static final String TAG = "AvrcpControllerSM";
+    private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
+    private static final boolean VDBG = Log.isLoggable(TAG, Log.VERBOSE);
+
+    private final Context mContext;
+    private final AudioManager mAudioManager;
+
+    private final State mDisconnected;
+    private final State mConnected;
+    private final SetBrowsedPlayer mSetBrowsedPlayer;
+    private final ChangeFolderPath mChangeFolderPath;
+    private final GetFolderList mGetFolderListing;
+    private final GetPlayerListing mGetPlayerListing;
+    private final GetNowPlayingList mGetNowPlayingList;
+    private final MoveToRoot mMoveToRoot;
+
+    private final Object mLock = new Object();
+    private static final ArrayList<MediaItem> mEmptyMediaItemList = new ArrayList<>();
+    private static final MediaMetadata mEmptyMMD = new MediaMetadata.Builder().build();
+
+    // APIs exist to access these so they must be thread safe
+    private Boolean mIsConnected = false;
+    private RemoteDevice mRemoteDevice;
+    private AvrcpPlayer mAddressedPlayer;
+
+    // Only accessed from State Machine processMessage
+    private boolean mAbsoluteVolumeChangeInProgress = false;
+    private boolean mBroadcastMetadata = false;
+    private int previousPercentageVol = -1;
+
+    // New addressed player.
+    private String mCurrentPlayer = null;
+
+    // Depth from root of current browsing. This can be used to move to root directly.
+    // Only valid if mCurrentPlayer != null.
+    private int mBrowseDepth = 0;
+
+    // Browse tree.
+    private BrowseTree mBrowseTree = new BrowseTree();
+
+    AvrcpControllerStateMachine(Context context) {
+        super(TAG);
+        mContext = context;
+
+        mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
+        IntentFilter filter = new IntentFilter(AudioManager.VOLUME_CHANGED_ACTION);
+        mContext.registerReceiver(mBroadcastReceiver, filter);
+
+        mDisconnected = new Disconnected();
+        mConnected = new Connected();
+
+        // Used to change folder path and fetch the new folder listing.
+        mSetBrowsedPlayer = new SetBrowsedPlayer();
+        mChangeFolderPath = new ChangeFolderPath();
+        mGetFolderListing = new GetFolderList();
+        mGetPlayerListing = new GetPlayerListing();
+        mGetNowPlayingList = new GetNowPlayingList();
+        mMoveToRoot = new MoveToRoot();
+
+        addState(mDisconnected);
+        addState(mConnected);
+
+        // Any action that needs blocking other requests to the state machine will be implemented as
+        // a separate substate of the mConnected state. Once transtition to the sub-state we should
+        // only handle the messages that are relevant to the sub-action. Everything else should be
+        // deferred so that once we transition to the mConnected we can process them hence.
+        addState(mSetBrowsedPlayer, mConnected);
+        addState(mChangeFolderPath, mConnected);
+        addState(mGetFolderListing, mConnected);
+        addState(mGetPlayerListing, mConnected);
+        addState(mGetNowPlayingList, mConnected);
+        addState(mMoveToRoot, mConnected);
+
+        setInitialState(mDisconnected);
+    }
+
+    class Disconnected extends State {
+
+        @Override
+        public boolean processMessage(Message msg) {
+            Log.d(TAG, " HandleMessage: " + dumpMessageString(msg.what));
+            switch (msg.what) {
+                case MESSAGE_PROCESS_CONNECTION_CHANGE:
+                    if (msg.arg1 == BluetoothProfile.STATE_CONNECTED) {
+                        transitionTo(mConnected);
+                        BluetoothDevice rtDevice = (BluetoothDevice) msg.obj;
+                        synchronized(mLock) {
+                            mRemoteDevice = new RemoteDevice(rtDevice);
+                            mAddressedPlayer = new AvrcpPlayer();
+                            mIsConnected = true;
+                        }
+                        Intent intent = new Intent(
+                            BluetoothAvrcpController.ACTION_CONNECTION_STATE_CHANGED);
+                        intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE,
+                            BluetoothProfile.STATE_DISCONNECTED);
+                        intent.putExtra(BluetoothProfile.EXTRA_STATE,
+                            BluetoothProfile.STATE_CONNECTED);
+                        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, rtDevice);
+                        mContext.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
+                    }
+                    break;
+
+                default:
+                    Log.w(TAG,"Currently Disconnected not handling " + dumpMessageString(msg.what));
+                    return false;
+            }
+            return true;
+        }
+    }
+
+    class Connected extends State {
+
+        @Override
+        public boolean processMessage(Message msg) {
+            Log.d(TAG, " HandleMessage: " + dumpMessageString(msg.what));
+            A2dpSinkService a2dpSinkService = A2dpSinkService.getA2dpSinkService();
+            synchronized (mLock) {
+                switch (msg.what) {
+                    case MESSAGE_STOP_METADATA_BROADCASTS:
+                        mBroadcastMetadata = false;
+                        broadcastPlayBackStateChanged(new PlaybackState.Builder().setState(
+                            PlaybackState.STATE_PAUSED, mAddressedPlayer.getPlayTime(),
+                            0).build());
+                        break;
+
+                    case MESSAGE_START_METADATA_BROADCASTS:
+                        mBroadcastMetadata = true;
+                        broadcastPlayBackStateChanged(mAddressedPlayer.getPlaybackState());
+                        if (mAddressedPlayer.getCurrentTrack() != null) {
+                            broadcastMetaDataChanged(
+                                mAddressedPlayer.getCurrentTrack().getMediaMetaData());
+                        }
+                        break;
+
+                    case MESSAGE_SEND_PASS_THROUGH_CMD:
+                        BluetoothDevice device = (BluetoothDevice) msg.obj;
+                        AvrcpControllerService
+                            .sendPassThroughCommandNative(Utils.getByteAddress(device), msg.arg1,
+                                msg.arg2);
+                        if (a2dpSinkService != null) {
+                            Log.d(TAG, " inform AVRCP Commands to A2DP Sink ");
+                            a2dpSinkService.informAvrcpPassThroughCmd(device, msg.arg1, msg.arg2);
+                        }
+                        break;
+
+                    case MESSAGE_SEND_GROUP_NAVIGATION_CMD:
+                        AvrcpControllerService.sendGroupNavigationCommandNative(
+                            mRemoteDevice.getBluetoothAddress(), msg.arg1, msg.arg2);
+                        break;
+
+                    case MESSAGE_GET_NOW_PLAYING_LIST:
+                        AvrcpControllerService.getNowPlayingListNative(
+                            mRemoteDevice.getBluetoothAddress(), (byte) msg.arg1,
+                            (byte) msg.arg2);
+                        mGetNowPlayingList.setFolder((String) msg.obj);
+                        transitionTo(mGetNowPlayingList);
+                        break;
+
+                    case MESSAGE_GET_FOLDER_LIST:
+                        AvrcpControllerService.getFolderListNative(
+                            mRemoteDevice.getBluetoothAddress(), (byte) msg.arg1,
+                            (byte) msg.arg2);
+
+                        // Whenever we transition we set the information for folder we need to
+                        // return result.
+                        mGetFolderListing.setFolder((String) msg.obj);
+                        transitionTo(mGetFolderListing);
+                        sendMessageDelayed(MESSAGE_INTERNAL_CMD_TIMEOUT, CMD_TIMEOUT_MILLIS);
+                        break;
+
+                    case MESSAGE_GET_PLAYER_LIST:
+                        AvrcpControllerService.getPlayerListNative(
+                            mRemoteDevice.getBluetoothAddress(), (byte) msg.arg1,
+                            (byte) msg.arg2);
+                        transitionTo(mGetPlayerListing);
+                        sendMessageDelayed(MESSAGE_INTERNAL_CMD_TIMEOUT, CMD_TIMEOUT_MILLIS);
+                        break;
+
+                    case MESSAGE_CHANGE_FOLDER_PATH: {
+                        int direction = msg.arg1;
+                        Bundle b = (Bundle) msg.obj;
+                        String uid = b.getString(AvrcpControllerService.EXTRA_FOLDER_BT_ID);
+                        String fid = b.getString(AvrcpControllerService.EXTRA_FOLDER_ID);
+
+                        // String is encoded as a Hex String (mostly for display purposes)
+                        // hence convert this back to real byte string.
+                        AvrcpControllerService.changeFolderPathNative(
+                            mRemoteDevice.getBluetoothAddress(), (byte) msg.arg1,
+                            AvrcpControllerService.hexStringToByteUID(uid));
+                        mChangeFolderPath.setFolder(fid);
+                        transitionTo(mChangeFolderPath);
+                        sendMessage(MESSAGE_INTERNAL_BROWSE_DEPTH_INCREMENT, (byte) msg.arg1);
+                        sendMessageDelayed(MESSAGE_INTERNAL_CMD_TIMEOUT, CMD_TIMEOUT_MILLIS);
+                        break;
+                    }
+
+                    case MESSAGE_FETCH_ATTR_AND_PLAY_ITEM: {
+                        int scope = msg.arg1;
+                        String uid = (String) msg.obj;
+                        // String is encoded as a Hex String (mostly for display purposes)
+                        // hence convert this back to real byte string.
+                        AvrcpControllerService.playItemNative(
+                            mRemoteDevice.getBluetoothAddress(), (byte) msg.arg1,
+                            AvrcpControllerService.hexStringToByteUID(uid), (int) 0);
+                        break;
+                    }
+
+                    case MESSAGE_SET_BROWSED_PLAYER: {
+                        AvrcpControllerService.setBrowsedPlayerNative(
+                            mRemoteDevice.getBluetoothAddress(), (int) msg.arg1);
+                        mSetBrowsedPlayer.setFolder((String) msg.obj);
+                        transitionTo(mSetBrowsedPlayer);
+                        break;
+                    }
+
+                    case MESSAGE_PROCESS_CONNECTION_CHANGE:
+                        if (msg.arg1 == BluetoothProfile.STATE_DISCONNECTED) {
+                            synchronized (mLock) {
+                                mIsConnected = false;
+                                mRemoteDevice = null;
+                            }
+                            transitionTo(mDisconnected);
+                            BluetoothDevice rtDevice = (BluetoothDevice) msg.obj;
+                            Intent intent = new Intent(
+                                BluetoothAvrcpController.ACTION_CONNECTION_STATE_CHANGED);
+                            intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE,
+                                BluetoothProfile.STATE_CONNECTED);
+                            intent.putExtra(BluetoothProfile.EXTRA_STATE,
+                                BluetoothProfile.STATE_DISCONNECTED);
+                            intent.putExtra(BluetoothDevice.EXTRA_DEVICE, rtDevice);
+                            mContext.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
+                        }
+                        break;
+
+                    case MESSAGE_PROCESS_BROWSE_CONNECTION_CHANGE:
+                        // Service tells us if the browse is connected or disconnected.
+                        // This is useful only for deciding whether to send browse commands rest of
+                        // the connection state handling should be done via the message
+                        // MESSAGE_PROCESS_CONNECTION_CHANGE.
+                        Intent intent = new Intent(
+                            AvrcpControllerService.ACTION_BROWSE_CONNECTION_STATE_CHANGED);
+                        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, (BluetoothDevice) msg.obj);
+                        if (DBG) {
+                            Log.d(TAG, "Browse connection state " + msg.arg1);
+                        }
+                        if (msg.arg1 == 1) {
+                            intent.putExtra(
+                                BluetoothProfile.EXTRA_STATE, BluetoothProfile.STATE_CONNECTED);
+                        } else if (msg.arg1 == 0) {
+                            intent.putExtra(
+                                BluetoothProfile.EXTRA_STATE, BluetoothProfile.STATE_DISCONNECTED);
+                            // If browse is disconnected, the next time we connect we should
+                            // be at the ROOT.
+                            mBrowseDepth = 0;
+                        } else {
+                            Log.w(TAG, "Incorrect browse state " + msg.arg1);
+                        }
+
+                        mContext.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
+                        break;
+
+                    case MESSAGE_PROCESS_RC_FEATURES:
+                        mRemoteDevice.setRemoteFeatures(msg.arg1);
+                        break;
+
+                    case MESSAGE_PROCESS_SET_ABS_VOL_CMD:
+                        mAbsoluteVolumeChangeInProgress = true;
+                        setAbsVolume(msg.arg1, msg.arg2);
+                        break;
+
+                    case MESSAGE_PROCESS_REGISTER_ABS_VOL_NOTIFICATION: {
+                        mRemoteDevice.setNotificationLabel(msg.arg1);
+                        mRemoteDevice.setAbsVolNotificationRequested(true);
+                        int percentageVol = getVolumePercentage();
+                        Log.d(TAG,
+                            " Sending Interim Response = " + percentageVol + " label " + msg.arg1);
+                        AvrcpControllerService
+                            .sendRegisterAbsVolRspNative(mRemoteDevice.getBluetoothAddress(),
+                                NOTIFICATION_RSP_TYPE_INTERIM,
+                                percentageVol,
+                                mRemoteDevice.getNotificationLabel());
+                    }
+                    break;
+
+                    case MESSAGE_PROCESS_VOLUME_CHANGED_NOTIFICATION: {
+                        if (mAbsoluteVolumeChangeInProgress) {
+                            mAbsoluteVolumeChangeInProgress = false;
+                        } else {
+                            if (mRemoteDevice.getAbsVolNotificationRequested()) {
+                                int percentageVol = getVolumePercentage();
+                                if (percentageVol != previousPercentageVol) {
+                                    AvrcpControllerService.sendRegisterAbsVolRspNative(
+                                        mRemoteDevice.getBluetoothAddress(),
+                                        NOTIFICATION_RSP_TYPE_CHANGED,
+                                        percentageVol, mRemoteDevice.getNotificationLabel());
+                                    previousPercentageVol = percentageVol;
+                                    mRemoteDevice.setAbsVolNotificationRequested(false);
+                                }
+                            }
+                        }
+                    }
+                    break;
+
+                    case MESSAGE_PROCESS_TRACK_CHANGED:
+                        mAddressedPlayer.updateCurrentTrack((TrackInfo) msg.obj);
+                        if (mBroadcastMetadata) {
+                            broadcastMetaDataChanged(mAddressedPlayer.getCurrentTrack().
+                                getMediaMetaData());
+                        }
+                        break;
+
+                    case MESSAGE_PROCESS_PLAY_POS_CHANGED:
+                        mAddressedPlayer.setPlayTime(msg.arg2);
+                        if (mBroadcastMetadata) {
+                            broadcastPlayBackStateChanged(getCurrentPlayBackState());
+                        }
+                        break;
+
+                    case MESSAGE_PROCESS_PLAY_STATUS_CHANGED:
+                        int status = msg.arg1;
+                        mAddressedPlayer.setPlayStatus(status);
+                        if (status == PlaybackState.STATE_PLAYING) {
+                            a2dpSinkService.informTGStatePlaying(mRemoteDevice.mBTDevice, true);
+                        } else if (status == PlaybackState.STATE_PAUSED ||
+                            status == PlaybackState.STATE_STOPPED) {
+                            a2dpSinkService.informTGStatePlaying(mRemoteDevice.mBTDevice, false);
+                        }
+                        break;
+
+                    default:
+                        return false;
+                }
+            }
+            return true;
+        }
+    }
+
+    // Handle the change folder path meta-action.
+    // a) Send Change folder command
+    // b) Once successful transition to folder fetch state.
+    class ChangeFolderPath extends CmdState {
+        private String STATE_TAG = "AVRCPSM.ChangeFolderPath";
+        private int mTmpIncrDirection;
+        private String mID = "";
+
+        public void setFolder(String id) {
+            mID = id;
+        }
+
+        @Override
+        public void enter() {
+            super.enter();
+            mTmpIncrDirection = -1;
+        }
+
+        @Override
+        public boolean processMessage(Message msg) {
+            Log.d(STATE_TAG, "processMessage " + msg);
+            switch (msg.what) {
+                case MESSAGE_INTERNAL_BROWSE_DEPTH_INCREMENT:
+                    mTmpIncrDirection = msg.arg1;
+                    break;
+
+                case MESSAGE_PROCESS_FOLDER_PATH: {
+                    // Fetch the listing of objects in this folder.
+                    Log.d(STATE_TAG, "MESSAGE_PROCESS_FOLDER_PATH returned " + msg.arg1 +
+                        " elements");
+
+                    // Update the folder depth.
+                    if (mTmpIncrDirection ==
+                        AvrcpControllerService.FOLDER_NAVIGATION_DIRECTION_UP) {
+                        mBrowseDepth -= 1;;
+                    } else if (mTmpIncrDirection ==
+                        AvrcpControllerService.FOLDER_NAVIGATION_DIRECTION_DOWN) {
+                        mBrowseDepth += 1;
+                    } else {
+                        throw new IllegalStateException("incorrect nav " + mTmpIncrDirection);
+                    }
+                    Log.d(STATE_TAG, "New browse depth " + mBrowseDepth);
+
+                    if (msg.arg1 > 0) {
+                        sendMessage(MESSAGE_GET_FOLDER_LIST, 0, 0xff, mID);
+                    } else {
+                        // Return an empty response to the upper layer.
+                        broadcastFolderList(mID, mEmptyMediaItemList);
+                    }
+                    mBrowseTree.setCurrentBrowsedFolder(mID);
+                    transitionTo(mConnected);
+                    break;
+                }
+
+                case MESSAGE_INTERNAL_CMD_TIMEOUT:
+                    // We timed out changing folders. It is imperative we tell
+                    // the upper layers that we failed by giving them an empty list.
+                    Log.e(STATE_TAG, "change folder failed, sending empty list.");
+                    broadcastFolderList(mID, mEmptyMediaItemList);
+                    transitionTo(mConnected);
+                    break;
+
+                default:
+                    Log.d(STATE_TAG, "deferring message " + msg + " to Connected state.");
+                    deferMessage(msg);
+            }
+            return true;
+        }
+    }
+
+    // Handle the get folder listing action
+    // a) Fetch the listing of folders
+    // b) Once completed return the object listing
+    class GetFolderList extends CmdState {
+        private String STATE_TAG = "AVRCPSM.GetFolderList";
+
+        String mID = "";
+
+        public void setFolder(String id) {
+            Log.d(STATE_TAG, "Setting folder to " + id);
+            mID = id;
+        }
+
+        @Override
+        public boolean processMessage(Message msg) {
+            Log.d(STATE_TAG, "processMessage " + msg);
+            switch (msg.what) {
+                case MESSAGE_PROCESS_GET_FOLDER_ITEMS:
+                    ArrayList<MediaItem> folderList = (ArrayList<MediaItem>) msg.obj;
+                    BrowseTree.BrowseNode bn = mBrowseTree.findBrowseNodeByID(mID);
+                    if (bn.isPlayer()) {
+                        // Add the now playing folder.
+                        MediaDescription.Builder mdb = new MediaDescription.Builder();
+                        mdb.setMediaId(BrowseTree.NOW_PLAYING_PREFIX + ":" +
+                            bn.getPlayerID());
+                        mdb.setTitle(BrowseTree.NOW_PLAYING_PREFIX);
+                        Bundle mdBundle = new Bundle();
+                        mdBundle.putString(
+                            AvrcpControllerService.MEDIA_ITEM_UID_KEY,
+                            BrowseTree.NOW_PLAYING_PREFIX + ":" + bn.getID());
+                        mdb.setExtras(mdBundle);
+                        folderList.add(new MediaItem(mdb.build(), MediaItem.FLAG_BROWSABLE));
+                    }
+
+                    mBrowseTree.refreshChildren(bn, folderList);
+                    broadcastFolderList(mID, folderList);
+
+                    transitionTo(mConnected);
+                    break;
+
+                case MESSAGE_INTERNAL_CMD_TIMEOUT:
+                    // We have timed out to execute the request.
+                    broadcastFolderList(mID, mEmptyMediaItemList);
+                    transitionTo(mConnected);
+                    break;
+
+                default:
+                    Log.d(STATE_TAG, "deferring message " + msg + " to connected!");
+                    deferMessage(msg);
+            }
+            return true;
+        }
+    }
+
+    // Handle the get player listing action
+    // a) Fetch the listing of players
+    // b) Once completed return the object listing
+    class GetPlayerListing extends CmdState {
+        private String STATE_TAG = "AVRCPSM.GetPlayerList";
+
+        @Override
+        public boolean processMessage(Message msg) {
+            Log.d(STATE_TAG, "processMessage " + msg);
+            switch (msg.what) {
+                case MESSAGE_PROCESS_GET_PLAYER_ITEMS:
+                    List<AvrcpPlayer> playerList =
+                        (List<AvrcpPlayer>) msg.obj;
+                    mBrowseTree.refreshChildren(BrowseTree.ROOT, playerList);
+                    ArrayList<MediaItem> mediaItemList = new ArrayList<>();
+                    for (BrowseTree.BrowseNode c :
+                            mBrowseTree.findBrowseNodeByID(BrowseTree.ROOT).getChildren()) {
+                        mediaItemList.add(c.getMediaItem());
+                    }
+                    broadcastFolderList(BrowseTree.ROOT, mediaItemList);
+                    mBrowseTree.setCurrentBrowsedFolder(BrowseTree.ROOT);
+                    transitionTo(mConnected);
+                    break;
+
+                case MESSAGE_INTERNAL_CMD_TIMEOUT:
+                    // We have timed out to execute the request.
+                    // Send an empty list here.
+                    broadcastFolderList(BrowseTree.ROOT, mEmptyMediaItemList);
+                    transitionTo(mConnected);
+                    break;
+
+                default:
+                    Log.d(STATE_TAG, "deferring message " + msg + " to connected!");
+                    deferMessage(msg);
+            }
+            return true;
+        }
+    }
+
+    class GetNowPlayingList extends CmdState {
+        private String STATE_TAG = "AVRCPSM.NowPlayingList";
+        private String mID = "";
+
+        public void setFolder(String id) {
+            mID = id;
+        }
+
+        @Override
+        public boolean processMessage(Message msg) {
+            Log.d(STATE_TAG, "processMessage " + msg);
+            switch (msg.what) {
+                case MESSAGE_PROCESS_GET_FOLDER_ITEMS:
+                    ArrayList<MediaItem> folderList = (ArrayList<MediaItem>) msg.obj;
+                    broadcastFolderList(mID, folderList);
+
+                    BrowseTree.BrowseNode bn = mBrowseTree.findBrowseNodeByID(mID);
+                    mBrowseTree.refreshChildren(bn, folderList);
+                    mBrowseTree.setCurrentBrowsedFolder(mID);
+                    transitionTo(mConnected);
+                    break;
+
+                case MESSAGE_INTERNAL_CMD_TIMEOUT:
+                    broadcastFolderList(mID, mEmptyMediaItemList);
+                    transitionTo(mConnected);
+                    break;
+
+                default:
+                    Log.d(STATE_TAG, "deferring message " + msg + " to connected!");
+                    deferMessage(msg);
+            }
+            return true;
+        }
+    }
+
+    class MoveToRoot extends CmdState {
+        private String STATE_TAG = "AVRCPSM.MoveToRoot";
+        private String mID = "";
+
+        public void setFolder(String id) {
+            Log.d(STATE_TAG, "setFolder " + id);
+            mID = id;
+        }
+
+        @Override
+        public void enter() {
+            // Setup the timeouts.
+            super.enter();
+
+            // We need to move mBrowseDepth levels up. The following message is
+            // completely internal to this state.
+            sendMessage(MESSAGE_INTERNAL_MOVE_N_LEVELS_UP);
+        }
+
+        @Override
+        public boolean processMessage(Message msg) {
+            Log.d(STATE_TAG, "processMessage " + msg + " browse depth " + mBrowseDepth);
+            switch (msg.what) {
+                case MESSAGE_INTERNAL_MOVE_N_LEVELS_UP:
+                    if (mBrowseDepth == 0) {
+                        Log.w(STATE_TAG, "Already in root!");
+                        transitionTo(mConnected);
+                        sendMessage(MESSAGE_GET_FOLDER_LIST, 0, 0xff, mID);
+                    } else {
+                        AvrcpControllerService.changeFolderPathNative(
+                            mRemoteDevice.getBluetoothAddress(),
+                            (byte) AvrcpControllerService.FOLDER_NAVIGATION_DIRECTION_UP,
+                            AvrcpControllerService.hexStringToByteUID(null));
+                    }
+                    break;
+
+                case MESSAGE_PROCESS_FOLDER_PATH:
+                    mBrowseDepth -= 1;
+                    Log.d(STATE_TAG, "New browse depth " + mBrowseDepth);
+                    if (mBrowseDepth < 0) {
+                        throw new IllegalArgumentException("Browse depth negative!");
+                    }
+
+                    sendMessage(MESSAGE_INTERNAL_MOVE_N_LEVELS_UP);
+                    break;
+
+                default:
+                    Log.d(STATE_TAG, "deferring message " + msg + " to connected!");
+                    deferMessage(msg);
+            }
+            return true;
+        }
+    }
+
+    class SetBrowsedPlayer extends CmdState {
+        private String STATE_TAG = "AVRCPSM.SetBrowsedPlayer";
+        String mID = "";
+
+        public void setFolder(String id) {
+            mID = id;
+        }
+
+        @Override
+        public boolean processMessage(Message msg) {
+            Log.d(STATE_TAG, "processMessage " + msg);
+            switch (msg.what) {
+                case MESSAGE_PROCESS_SET_BROWSED_PLAYER:
+                    // Set the new depth.
+                    Log.d(STATE_TAG, "player depth " + msg.arg2);
+                    mBrowseDepth = msg.arg2;
+
+                    // If we already on top of player and there is no content.
+                    // This should very rarely happen.
+                    if (mBrowseDepth == 0 && msg.arg1 == 0) {
+                        broadcastFolderList(mID, mEmptyMediaItemList);
+                        transitionTo(mConnected);
+                    } else {
+                        // Otherwise move to root and fetch the listing.
+                        // the MoveToRoot#enter() function takes care of fetch.
+                        mMoveToRoot.setFolder(mID);
+                        transitionTo(mMoveToRoot);
+                    }
+                    mBrowseTree.setCurrentBrowsedFolder(mID);
+                    break;
+
+                case MESSAGE_INTERNAL_CMD_TIMEOUT:
+                    broadcastFolderList(mID, mEmptyMediaItemList);
+                    transitionTo(mConnected);
+                    break;
+
+                default:
+                    Log.d(STATE_TAG, "deferring message " + msg + " to connected!");
+                    deferMessage(msg);
+            }
+            return true;
+        }
+    }
+
+    // Class template for commands. Each state should do the following:
+    // (a) In enter() send a timeout message which could be tracked in the
+    // processMessage() stage.
+    // (b) In exit() remove all the timeouts.
+    //
+    // Essentially the lifecycle of a timeout should be bounded to a CmdState always.
+    abstract class CmdState extends State {
+        @Override
+        public void enter() {
+            sendMessageDelayed(MESSAGE_INTERNAL_CMD_TIMEOUT, CMD_TIMEOUT_MILLIS);
+        }
+
+        @Override
+        public void exit() {
+            removeMessages(MESSAGE_INTERNAL_CMD_TIMEOUT);
+        }
+    }
+
+    // Interface APIs
+    boolean isConnected() {
+        synchronized (mLock) {
+            return mIsConnected;
+        }
+    }
+
+    void doQuit() {
+        try {
+            mContext.unregisterReceiver(mBroadcastReceiver);
+        } catch (IllegalArgumentException expected) {
+            // If the receiver was never registered unregister will throw an
+            // IllegalArgumentException.
+        }
+        quit();
+    }
+
+    void dump(StringBuilder sb) {
+        ProfileService.println(sb, "StateMachine: " + this.toString());
+    }
+
+    MediaMetadata getCurrentMetaData() {
+        synchronized (mLock) {
+            if (mAddressedPlayer != null && mAddressedPlayer.getCurrentTrack() != null) {
+                MediaMetadata mmd = mAddressedPlayer.getCurrentTrack().getMediaMetaData();
+                if (DBG) {
+                    Log.d(TAG, "getCurrentMetaData mmd " + mmd);
+                }
+            }
+            return mEmptyMMD;
+        }
+    }
+
+    PlaybackState getCurrentPlayBackState() {
+        synchronized (mLock) {
+            if (mAddressedPlayer == null) {
+                return new PlaybackState.Builder().setState(PlaybackState.STATE_ERROR,
+                    PlaybackState.PLAYBACK_POSITION_UNKNOWN,0).build();
+            }
+            return mAddressedPlayer.getPlaybackState();
+        }
+    }
+
+    // Browsing related functions.
+    void getChildren(String parentMediaId, int start, int items) {
+        BrowseTree.BrowseNode bn = mBrowseTree.findBrowseNodeByID(parentMediaId);
+        if (bn == null) {
+            Log.e(TAG, "Invalid folder to browse " + mBrowseTree);
+            broadcastFolderList(parentMediaId, mEmptyMediaItemList);
+            return;
+        }
+
+        Message msg = null;
+        if (BrowseTree.ROOT.equals(parentMediaId)) {
+            // Root contains the list of players.
+            msg = obtainMessage(AvrcpControllerStateMachine.MESSAGE_GET_PLAYER_LIST, start, items);
+        } else if (bn.isPlayer()) {
+            // Set browsed (and addressed player) as the new player.
+            // This should fetch the list of folders.
+            msg = obtainMessage(AvrcpControllerStateMachine.MESSAGE_SET_BROWSED_PLAYER,
+                bn.getPlayerID(), 0, bn.getID());
+        } else if (bn.isNowPlaying()) {
+            // Get all songs in the list.
+            msg = obtainMessage(
+                AvrcpControllerStateMachine.MESSAGE_GET_NOW_PLAYING_LIST,
+                start, items, parentMediaId);
+        } else {
+            // Only change folder if desired. If an app refreshes a folder
+            // (because it resumed etc) and current folder does not change
+            // then we can simply fetch list.
+            BrowseTree.BrowseNode currFol = mBrowseTree.getCurrentBrowsedFolder();
+
+            // We exempt two conditions from change folder:
+            // a) If the new folder is the same as current folder (refresh of UI)
+            // b) If the new folder is ROOT and current folder is NOW_PLAYING. In this
+            // condition we 'fake' child-parent hierarchy but it does not exist in
+            // bluetooth world.
+            boolean isNowPlayingToRoot =
+                currFol.isNowPlaying() && bn.getID().equals(BrowseTree.ROOT);
+            if (!bn.equals(currFol) && !isNowPlayingToRoot) {
+                // Find the direction of traversal.
+                int direction;
+                int btDirection = mBrowseTree.getDirection(parentMediaId);
+                Log.d(TAG, "Browse direction " + currFol + " " + bn + " = " + btDirection);
+                if (btDirection == BrowseTree.DIRECTION_DOWN) {
+                    direction = AvrcpControllerService.FOLDER_NAVIGATION_DIRECTION_DOWN;
+                } else if (btDirection == BrowseTree.DIRECTION_UP) {
+                    direction = AvrcpControllerService.FOLDER_NAVIGATION_DIRECTION_UP;
+                } else {
+                    Log.w(TAG, "parent " + bn + " is not a direct " +
+                        "successor or predeccessor of current folder " + currFol);
+                    broadcastFolderList(parentMediaId, mEmptyMediaItemList);
+                    return;
+                }
+                Bundle b = new Bundle();
+                b.putString(AvrcpControllerService.EXTRA_FOLDER_ID, bn.getID());
+                b.putString(AvrcpControllerService.EXTRA_FOLDER_BT_ID, bn.getFolderUID());
+                msg = obtainMessage(
+                    AvrcpControllerStateMachine.MESSAGE_CHANGE_FOLDER_PATH, direction, 0, b);
+            } else {
+                // Fetch the listing without changing paths.
+                msg = obtainMessage(
+                    AvrcpControllerStateMachine.MESSAGE_GET_FOLDER_LIST,
+                    start, items, bn.getFolderUID());
+            }
+        }
+        if (msg != null) {
+            sendMessage(msg);
+        }
+    }
+
+    public void fetchAttrAndPlayItem(String uid) {
+        BrowseTree.BrowseNode currItem = mBrowseTree.findFolderByIDLocked(uid);
+        BrowseTree.BrowseNode currFolder = mBrowseTree.getCurrentBrowsedFolder();
+        Log.d(TAG, "fetchAttrAndPlayItem mediaId=" + uid + " node=" + currItem);
+        if (currItem != null) {
+            int scope = currFolder.isNowPlaying() ?
+                AvrcpControllerService.BROWSE_SCOPE_NOW_PLAYING :
+                AvrcpControllerService.BROWSE_SCOPE_VFS;
+            Message msg = obtainMessage(
+                AvrcpControllerStateMachine.MESSAGE_FETCH_ATTR_AND_PLAY_ITEM,
+                scope, 0, currItem.getFolderUID());
+            sendMessage(msg);
+        }
+    }
+
+    private void broadcastMetaDataChanged(MediaMetadata metadata) {
+        Intent intent = new Intent(AvrcpControllerService.ACTION_TRACK_EVENT);
+        intent.putExtra(AvrcpControllerService.EXTRA_METADATA, metadata);
+        if (DBG) {
+            Log.d(TAG, " broadcastMetaDataChanged = " + metadata.getDescription());
+        }
+        mContext.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
+    }
+
+    private void broadcastFolderList(String id, ArrayList<MediaItem> items) {
+        Intent intent = new Intent(AvrcpControllerService.ACTION_FOLDER_LIST);
+        Log.d(TAG, "broadcastFolderList id " + id + " items " + items);
+        intent.putExtra(AvrcpControllerService.EXTRA_FOLDER_ID, id);
+        intent.putParcelableArrayListExtra(
+            AvrcpControllerService.EXTRA_FOLDER_LIST, items);
+        mContext.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
+    }
+
+    private void broadcastPlayBackStateChanged(PlaybackState state) {
+        Intent intent = new Intent(AvrcpControllerService.ACTION_TRACK_EVENT);
+        intent.putExtra(AvrcpControllerService.EXTRA_PLAYBACK, state);
+        if (DBG) {
+            Log.d(TAG, " broadcastPlayBackStateChanged = " + state.toString());
+        }
+        mContext.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
+    }
+
+    private void setAbsVolume(int absVol, int label) {
+        int maxVolume = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
+        int currIndex = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
+        // Ignore first volume command since phone may not know difference between stream volume
+        // and amplifier volume.
+        if (mRemoteDevice.getFirstAbsVolCmdRecvd()) {
+            int newIndex = (maxVolume * absVol) / ABS_VOL_BASE;
+            Log.d(TAG,
+                " setAbsVolume =" + absVol + " maxVol = " + maxVolume + " cur = " + currIndex +
+                    " new = " + newIndex);
+            /*
+             * In some cases change in percentage is not sufficient enough to warrant
+             * change in index values which are in range of 0-15. For such cases
+             * no action is required
+             */
+            if (newIndex != currIndex) {
+                mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, newIndex,
+                    AudioManager.FLAG_SHOW_UI);
+            }
+        } else {
+            mRemoteDevice.setFirstAbsVolCmdRecvd();
+            absVol = (currIndex * ABS_VOL_BASE) / maxVolume;
+            Log.d(TAG, " SetAbsVol recvd for first time, respond with " + absVol);
+        }
+        AvrcpControllerService.sendAbsVolRspNative(
+            mRemoteDevice.getBluetoothAddress(), absVol, label);
+    }
+
+    private int getVolumePercentage() {
+        int maxVolume = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
+        int currIndex = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
+        int percentageVol = ((currIndex * ABS_VOL_BASE) / maxVolume);
+        return percentageVol;
+    }
+
+    private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            if (action.equals(AudioManager.VOLUME_CHANGED_ACTION)) {
+                int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
+                if (streamType == AudioManager.STREAM_MUSIC) {
+                    sendMessage(MESSAGE_PROCESS_VOLUME_CHANGED_NOTIFICATION);
+                }
+            }
+        }
+    };
+
+    public static String dumpMessageString(int message) {
+        String str = "UNKNOWN";
+        switch (message) {
+            case MESSAGE_SEND_PASS_THROUGH_CMD:
+                str = "REQ_PASS_THROUGH_CMD";
+                break;
+            case MESSAGE_SEND_GROUP_NAVIGATION_CMD:
+                str = "REQ_GRP_NAV_CMD";
+                break;
+            case MESSAGE_PROCESS_SET_ABS_VOL_CMD:
+                str = "CB_SET_ABS_VOL_CMD";
+                break;
+            case MESSAGE_PROCESS_REGISTER_ABS_VOL_NOTIFICATION:
+                str = "CB_REGISTER_ABS_VOL";
+                break;
+            case MESSAGE_PROCESS_TRACK_CHANGED:
+                str = "CB_TRACK_CHANGED";
+                break;
+            case MESSAGE_PROCESS_PLAY_POS_CHANGED:
+                str = "CB_PLAY_POS_CHANGED";
+                break;
+            case MESSAGE_PROCESS_PLAY_STATUS_CHANGED:
+                str = "CB_PLAY_STATUS_CHANGED";
+                break;
+            case MESSAGE_PROCESS_RC_FEATURES:
+                str = "CB_RC_FEATURES";
+                break;
+            case MESSAGE_PROCESS_CONNECTION_CHANGE:
+                str = "CB_CONN_CHANGED";
+                break;
+            default:
+                str = Integer.toString(message);
+                break;
+        }
+        return str;
+    }
+
+    public static String displayBluetoothAvrcpSettings(BluetoothAvrcpPlayerSettings mSett) {
+        StringBuffer sb =  new StringBuffer();
+        int supportedSetting = mSett.getSettings();
+        if(VDBG) Log.d(TAG," setting: " + supportedSetting);
+        if((supportedSetting & BluetoothAvrcpPlayerSettings.SETTING_EQUALIZER) != 0) {
+            sb.append(" EQ : ");
+            sb.append(Integer.toString(mSett.getSettingValue(BluetoothAvrcpPlayerSettings.
+                                                             SETTING_EQUALIZER)));
+        }
+        if((supportedSetting & BluetoothAvrcpPlayerSettings.SETTING_REPEAT) != 0) {
+            sb.append(" REPEAT : ");
+            sb.append(Integer.toString(mSett.getSettingValue(BluetoothAvrcpPlayerSettings.
+                                                             SETTING_REPEAT)));
+        }
+        if((supportedSetting & BluetoothAvrcpPlayerSettings.SETTING_SHUFFLE) != 0) {
+            sb.append(" SHUFFLE : ");
+            sb.append(Integer.toString(mSett.getSettingValue(BluetoothAvrcpPlayerSettings.
+                                                             SETTING_SHUFFLE)));
+        }
+        if((supportedSetting & BluetoothAvrcpPlayerSettings.SETTING_SCAN) != 0) {
+            sb.append(" SCAN : ");
+            sb.append(Integer.toString(mSett.getSettingValue(BluetoothAvrcpPlayerSettings.
+                                                             SETTING_SCAN)));
+        }
+        return sb.toString();
+    }
+}
diff --git a/src/com/android/bluetooth/avrcpcontroller/AvrcpPlayer.java b/src/com/android/bluetooth/avrcpcontroller/AvrcpPlayer.java
new file mode 100644
index 0000000..3f037c2
--- /dev/null
+++ b/src/com/android/bluetooth/avrcpcontroller/AvrcpPlayer.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.avrcpcontroller;
+
+import android.bluetooth.BluetoothAvrcpPlayerSettings;
+import android.media.session.PlaybackState;
+import android.util.Log;
+
+import java.util.ArrayList;
+
+/*
+ * Contains information about remote player
+ */
+class AvrcpPlayer {
+    private static final String TAG = "AvrcpPlayer";
+    private static final boolean DBG = true;
+
+    public static final int INVALID_ID = -1;
+
+    private int mPlayStatus = PlaybackState.STATE_NONE;
+    private long mPlayTime   = PlaybackState.PLAYBACK_POSITION_UNKNOWN;
+    private int mId;
+    private String mName = "";
+    private int mPlayerType;
+    private TrackInfo mCurrentTrack = new TrackInfo();
+
+    AvrcpPlayer() {
+        mId = INVALID_ID;
+    }
+
+    AvrcpPlayer(int id, String name, int transportFlags, int playStatus, int playerType) {
+        mId = id;
+        mName = name;
+        mPlayerType = playerType;
+    }
+
+    public int getId() {
+        return mId;
+    }
+
+    public String getName() {
+      return mName;
+    }
+
+    public void setPlayTime(int playTime) {
+        mPlayTime = playTime;
+    }
+
+    public long getPlayTime() {
+        return mPlayTime;
+    }
+
+    public void setPlayStatus(int playStatus) {
+        mPlayStatus = playStatus;
+    }
+
+    public PlaybackState getPlaybackState() {
+        if (DBG) {
+            Log.d(TAG, "getPlayBackState state " + mPlayStatus + " time " + mPlayTime);
+        }
+
+        long position = mPlayTime;
+        float speed = 1;
+        switch (mPlayStatus) {
+            case PlaybackState.STATE_STOPPED:
+                position = 0;
+                speed = 0;
+                break;
+            case PlaybackState.STATE_PAUSED:
+                speed = 0;
+                break;
+            case PlaybackState.STATE_FAST_FORWARDING:
+                speed = 3;
+                break;
+            case PlaybackState.STATE_REWINDING:
+                speed = -3;
+                break;
+        }
+        return new PlaybackState.Builder().setState(mPlayStatus, position, speed).build();
+    }
+
+    synchronized public void updateCurrentTrack(TrackInfo update) {
+        mCurrentTrack = update;
+    }
+
+    synchronized public TrackInfo getCurrentTrack() {
+        return mCurrentTrack;
+    }
+}
diff --git a/src/com/android/bluetooth/avrcpcontroller/BrowseTree.java b/src/com/android/bluetooth/avrcpcontroller/BrowseTree.java
new file mode 100644
index 0000000..a11988d
--- /dev/null
+++ b/src/com/android/bluetooth/avrcpcontroller/BrowseTree.java
@@ -0,0 +1,267 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.avrcpcontroller;
+
+import android.media.browse.MediaBrowser;
+import android.media.browse.MediaBrowser.MediaItem;
+import android.media.MediaDescription;
+import android.os.Bundle;
+import android.os.ResultReceiver;
+import android.service.media.MediaBrowserService.Result;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Stack;
+
+// Browsing hierarchy.
+// Root:
+//      Player1:
+//        Now_Playing:
+//          MediaItem1
+//          MediaItem2
+//        Folder1
+//        Folder2
+//        ....
+//      Player2
+//      ....
+public class BrowseTree {
+    private static final String TAG = "BrowseTree";
+    private static final boolean DBG = true;
+
+    public static final int DIRECTION_DOWN = 0;
+    public static final int DIRECTION_UP = 1;
+    public static final int DIRECTION_UNKNOWN = -1;
+
+    public static final String ROOT = "__ROOT__";
+    public static final String NOW_PLAYING_PREFIX = "NOW_PLAYING";
+    public static final String PLAYER_PREFIX = "PLAYER";
+
+    // Static instance of Folder ID <-> Folder Instance (for navigation purposes)
+    private final HashMap<String, BrowseNode> mBrowseMap = new HashMap<String, BrowseNode>();
+    private BrowseNode mCurrentBrowseNode;
+
+    BrowseTree() {
+        MediaDescription.Builder mdb = new MediaDescription.Builder();
+        mdb.setMediaId(ROOT);
+        mdb.setTitle(ROOT);
+        Bundle mdBundle = new Bundle();
+        mdBundle.putString(AvrcpControllerService.MEDIA_ITEM_UID_KEY, ROOT);
+        mdb.setExtras(mdBundle);
+        mBrowseMap.put(ROOT, new BrowseNode(new MediaItem(mdb.build(), MediaItem.FLAG_BROWSABLE)));
+        mCurrentBrowseNode = mBrowseMap.get(ROOT);
+    }
+
+    // Each node of the tree is represented by Folder ID, Folder Name and the children.
+    class BrowseNode {
+        // MediaItem to store the media related details.
+        MediaItem mItem;
+
+        // Type of this browse node.
+        // Since Media APIs do not define the player separately we define that
+        // distinction here.
+        boolean mIsPlayer = false;
+
+        // Result object if this node is not loaded yet. This result object will be used
+        // once loading is finished.
+        Result<List<MediaItem>> mResult = null;
+
+        // List of children.
+        final List<BrowseNode> mChildren = new ArrayList<BrowseNode>();
+
+        BrowseNode(MediaItem item) {
+            mItem = item;
+        }
+
+        BrowseNode(AvrcpPlayer player) {
+            mIsPlayer = true;
+
+            // Transform the player into a item.
+            MediaDescription.Builder mdb = new MediaDescription.Builder();
+            Bundle mdExtra = new Bundle();
+            String playerKey = PLAYER_PREFIX + player.getId();
+            mdExtra.putString(AvrcpControllerService.MEDIA_ITEM_UID_KEY, playerKey);
+            mdb.setExtras(mdExtra);
+            mdb.setMediaId(playerKey);
+            mdb.setTitle(player.getName());
+            mItem = new MediaBrowser.MediaItem(mdb.build(), MediaBrowser.MediaItem.FLAG_BROWSABLE);
+        }
+
+        synchronized List<BrowseNode> getChildren() {
+            return mChildren;
+        }
+
+        synchronized boolean isChild(BrowseNode node) {
+            for (BrowseNode bn : mChildren) {
+                if (bn.equals(node)) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        // Fetch the Unique UID for this item, this is unique across all elements in the tree.
+        synchronized String getID() {
+            return mItem.getDescription().getMediaId();
+        }
+
+        // Get the BT Player ID associated with this node.
+        synchronized int getPlayerID() {
+            return Integer.parseInt(getID().replace(PLAYER_PREFIX, ""));
+        }
+
+        // Fetch the Folder UID that can be used to fetch folder listing via bluetooth.
+        // This may not be unique hence this combined with direction will define the
+        // browsing here.
+        synchronized String getFolderUID() {
+            return mItem.getDescription().getExtras().getString(
+                AvrcpControllerService.MEDIA_ITEM_UID_KEY);
+        }
+
+        synchronized MediaItem getMediaItem() {
+            return mItem;
+        }
+
+        synchronized boolean isPlayer() {
+            return mIsPlayer;
+        }
+
+        synchronized boolean isNowPlaying() {
+            return getID().startsWith(NOW_PLAYING_PREFIX);
+        }
+
+        @Override
+        public boolean equals(Object other) {
+            if (!(other instanceof BrowseNode)) {
+                return false;
+            }
+            BrowseNode otherNode = (BrowseNode) other;
+            return getID().equals(otherNode.getID());
+        }
+
+        @Override
+        public String toString() {
+            return "ID: " + getID() + " desc: " + mItem;
+        }
+    }
+
+    synchronized <E> void refreshChildren(String parentID, List<E> children) {
+        BrowseNode parent = findFolderByIDLocked(parentID);
+        if (parent == null) {
+            Log.w(TAG, "parent not found for parentID " + parentID);
+            return;
+        }
+        refreshChildren(parent, children);
+    }
+
+    synchronized <E> void refreshChildren(BrowseNode parent, List<E> children) {
+        if (children == null) {
+            Log.e(TAG, "children cannot be null ");
+            return;
+        }
+
+        List<BrowseNode> bnList = new ArrayList<BrowseNode>();
+        for (E child : children) {
+            if (child instanceof MediaItem) {
+                bnList.add(new BrowseNode((MediaItem) child));
+            } else if (child instanceof AvrcpPlayer) {
+                bnList.add(new BrowseNode((AvrcpPlayer) child));
+            }
+        }
+
+        String parentID = parent.getID();
+        // Make sure that the child list is clean.
+        if (DBG) {
+            Log.d(TAG, "parent " + parentID + " child list " + parent.getChildren());
+        }
+
+        addChildrenLocked(parent, bnList);
+        List<MediaItem> childrenList = new ArrayList<MediaItem>();
+        for (BrowseNode bn : parent.getChildren()) {
+            childrenList.add(bn.getMediaItem());
+        }
+    }
+
+    synchronized BrowseNode findBrowseNodeByID(String parentID) {
+        BrowseNode bn = mBrowseMap.get(parentID);
+        if (bn == null) {
+            Log.e(TAG, "folder " + parentID + " not found!");
+            return null;
+        }
+        if (DBG) {
+            Log.d(TAG, "Browse map: " + mBrowseMap);
+        }
+        return bn;
+    }
+
+    BrowseNode findFolderByIDLocked(String parentID) {
+        return mBrowseMap.get(parentID);
+    }
+
+    void addChildrenLocked(BrowseNode parent, List<BrowseNode> items) {
+        // Remove existing children and then add the new children.
+        for (BrowseNode c : parent.getChildren()) {
+            mBrowseMap.remove(c.getID());
+        }
+        parent.getChildren().clear();
+
+        for (BrowseNode bn : items) {
+            parent.getChildren().add(bn);
+            mBrowseMap.put(bn.getID(), bn);
+        }
+    }
+
+    synchronized int getDirection(String toUID) {
+        BrowseNode fromFolder = mCurrentBrowseNode;
+        BrowseNode toFolder = findFolderByIDLocked(toUID);
+        if (fromFolder == null || toFolder == null) {
+            Log.e(TAG, "from folder " + mCurrentBrowseNode + " or to folder " + toUID + " null!");
+        }
+
+        // Check the relationship.
+        if (fromFolder.isChild(toFolder)) {
+            return DIRECTION_DOWN;
+        } else if (toFolder.isChild(fromFolder)) {
+            return DIRECTION_UP;
+        } else {
+            Log.w(TAG, "from folder " + mCurrentBrowseNode + " children " +
+                fromFolder.getChildren() + "to folder " + toUID + " children " +
+                toFolder.getChildren());
+            return DIRECTION_UNKNOWN;
+        }
+    }
+
+    synchronized boolean setCurrentBrowsedFolder(String uid) {
+        BrowseNode bn = findFolderByIDLocked(uid);
+        if (bn == null) {
+            Log.e(TAG, "Setting an unknown browsed folder, ignoring bn " + uid);
+            return false;
+        }
+        mCurrentBrowseNode = bn;
+        return true;
+    }
+
+    synchronized BrowseNode getCurrentBrowsedFolder() {
+        return mCurrentBrowseNode;
+    }
+
+    @Override
+    public String toString() {
+        return mBrowseMap.toString();
+    }
+}
diff --git a/src/com/android/bluetooth/avrcpcontroller/PlayerApplicationSettings.java b/src/com/android/bluetooth/avrcpcontroller/PlayerApplicationSettings.java
new file mode 100644
index 0000000..65d9966
--- /dev/null
+++ b/src/com/android/bluetooth/avrcpcontroller/PlayerApplicationSettings.java
@@ -0,0 +1,302 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.avrcpcontroller;
+
+import android.bluetooth.BluetoothAvrcpPlayerSettings;
+
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+
+/*
+ * Contains information Player Application Setting extended from BluetootAvrcpPlayerSettings
+ */
+class PlayerApplicationSettings {
+    private static final String TAG = "PlayerApplicationSettings";
+
+    /*
+     * Values for SetPlayerApplicationSettings from AVRCP Spec V1.6 Appendix F.
+     */
+    private static final byte JNI_ATTRIB_EQUALIZER_STATUS = 0x01;
+    private static final byte JNI_ATTRIB_REPEAT_STATUS = 0x02;
+    private static final byte JNI_ATTRIB_SHUFFLE_STATUS = 0x03;
+    private static final byte JNI_ATTRIB_SCAN_STATUS = 0x04;
+
+    private static final byte JNI_EQUALIZER_STATUS_OFF = 0x01;
+    private static final byte JNI_EQUALIZER_STATUS_ON = 0x02;
+
+    private static final byte JNI_REPEAT_STATUS_OFF = 0x01;
+    private static final byte JNI_REPEAT_STATUS_SINGLE_TRACK_REPEAT = 0x02;
+    private static final byte JNI_REPEAT_STATUS_ALL_TRACK_REPEAT = 0x03;
+    private static final byte JNI_REPEAT_STATUS_GROUP_REPEAT = 0x04;
+
+    private static final byte JNI_SHUFFLE_STATUS_OFF = 0x01;
+    private static final byte JNI_SHUFFLE_STATUS_ALL_TRACK_SHUFFLE = 0x02;
+    private static final byte JNI_SHUFFLE_STATUS_GROUP_SHUFFLE = 0x03;
+
+    private static final byte JNI_SCAN_STATUS_OFF = 0x01;
+    private static final byte JNI_SCAN_STATUS_ALL_TRACK_SCAN = 0x02;
+    private static final byte JNI_SCAN_STATUS_GROUP_SCAN = 0x03;
+
+    private static final byte JNI_STATUS_INVALID = -1;
+
+
+    /*
+     * Hash map of current settings.
+     */
+    private Map<Integer, Integer> mSettings = new HashMap<Integer, Integer>();
+
+    /*
+     * Hash map of supported values, a setting should be supported by the remote in order to enable
+     * in mSettings.
+     */
+    private Map<Integer, ArrayList<Integer>> mSupportedValues =
+        new HashMap<Integer, ArrayList<Integer>>();
+
+    /* Convert from JNI array to Java classes. */
+    static PlayerApplicationSettings makeSupportedSettings(byte[] btAvrcpAttributeList) {
+        PlayerApplicationSettings newObj = new PlayerApplicationSettings();
+        try {
+            for (int i = 0; i < btAvrcpAttributeList.length; ) {
+                byte attrId = btAvrcpAttributeList[i++];
+                byte numSupportedVals = btAvrcpAttributeList[i++];
+                ArrayList<Integer> supportedValues = new ArrayList<Integer>();
+
+                for (int j = 0; j < numSupportedVals; j++) {
+                    // Yes, keep using i for array indexing.
+                    supportedValues.add(mapAttribIdValtoAvrcpPlayerSetting(attrId,
+                        btAvrcpAttributeList[i++]));
+                }
+                newObj.mSupportedValues.put(mapBTAttribIdToAvrcpPlayerSettings(attrId),
+                    supportedValues);
+            }
+        } catch (ArrayIndexOutOfBoundsException exception) {
+            Log.e(TAG,"makeSupportedSettings attributeList index error.");
+        }
+        return newObj;
+    }
+
+    public BluetoothAvrcpPlayerSettings getAvrcpSettings() {
+        int supportedSettings = 0;
+        for (Integer setting : mSettings.keySet()) {
+            supportedSettings |= setting;
+        }
+        BluetoothAvrcpPlayerSettings result = new BluetoothAvrcpPlayerSettings(supportedSettings);
+        for (Integer setting : mSettings.keySet()) {
+            result.addSettingValue(setting, mSettings.get(setting));
+        }
+        return result;
+    }
+
+    static PlayerApplicationSettings makeSettings(byte[] btAvrcpAttributeList) {
+        PlayerApplicationSettings newObj = new PlayerApplicationSettings();
+        try {
+            for (int i = 0; i < btAvrcpAttributeList.length; ) {
+                byte attrId = btAvrcpAttributeList[i++];
+
+                newObj.mSettings.put(mapBTAttribIdToAvrcpPlayerSettings(attrId),
+                    mapAttribIdValtoAvrcpPlayerSetting(attrId,
+                        btAvrcpAttributeList[i++]));
+            }
+        } catch (ArrayIndexOutOfBoundsException exception) {
+            Log.e(TAG,"makeSettings JNI_ATTRIButeList index error.");
+        }
+        return newObj;
+    }
+
+    public void setSupport(PlayerApplicationSettings updates) {
+        mSettings = updates.mSettings;
+        mSupportedValues = updates.mSupportedValues;
+    }
+
+    public void setValues(BluetoothAvrcpPlayerSettings updates) {
+        int supportedSettings = updates.getSettings();
+        for (int i = 1; i <= BluetoothAvrcpPlayerSettings.SETTING_SCAN; i++) {
+            if ((i & supportedSettings) > 0) {
+                mSettings.put(i, updates.getSettingValue(i));
+            }
+        }
+    }
+
+    /*
+     * Check through all settings to ensure that they are all available to be set and then check
+     * that the desired value is in fact supported by our remote player.
+     */
+    public boolean supportsSettings(BluetoothAvrcpPlayerSettings settingsToCheck) {
+        int settingSubset = settingsToCheck.getSettings();
+        int supportedSettings = 0;
+        for (Integer setting : mSupportedValues.keySet()) {
+            supportedSettings |= setting;
+        }
+        try {
+            if ((supportedSettings & settingSubset) == settingSubset) {
+                for (Integer settingId : mSettings.keySet()) {
+                    if ((settingId & settingSubset )== settingId &&
+                        (!mSupportedValues.get(settingId).contains(settingsToCheck.
+                            getSettingValue(settingId))))
+                        // The setting is in both settings to check and supported settings but the
+                        // value is not supported.
+                        return false;
+                }
+                return true;
+            }
+        } catch (NullPointerException e) {
+            Log.e(TAG,
+                "supportsSettings received a supported setting that has no supported values.");
+        }
+        return false;
+    }
+
+    // Convert currently desired settings into an attribute array to pass to the native layer to
+    // enable them.
+    public ArrayList<Byte> getNativeSettings() {
+        int i = 0;
+        ArrayList<Byte> attribArray = new ArrayList<Byte>();
+        for (Integer settingId : mSettings.keySet()) {
+            switch (settingId)
+            {
+                case BluetoothAvrcpPlayerSettings.SETTING_EQUALIZER:
+                    attribArray.add(JNI_ATTRIB_EQUALIZER_STATUS);
+                    attribArray.add(mapAvrcpPlayerSettingstoBTattribVal(
+                        settingId, mSettings.get(settingId)));
+                    break;
+                case BluetoothAvrcpPlayerSettings.SETTING_REPEAT:
+                    attribArray.add(JNI_ATTRIB_REPEAT_STATUS);
+                    attribArray.add(mapAvrcpPlayerSettingstoBTattribVal(
+                        settingId, mSettings.get(settingId)));
+                    break;
+                case BluetoothAvrcpPlayerSettings.SETTING_SHUFFLE:
+                    attribArray.add(JNI_ATTRIB_SHUFFLE_STATUS);
+                    attribArray.add(mapAvrcpPlayerSettingstoBTattribVal(
+                        settingId, mSettings.get(settingId)));
+                    break;
+                case BluetoothAvrcpPlayerSettings.SETTING_SCAN:
+                    attribArray.add(JNI_ATTRIB_SCAN_STATUS);
+                    attribArray.add(mapAvrcpPlayerSettingstoBTattribVal(
+                        settingId, mSettings.get(settingId)));
+                    break;
+                default:
+                    Log.w(TAG,"Unknown setting found in getNativeSettings: " + settingId);
+            }
+        }
+        return attribArray;
+    }
+
+    // Convert a native Attribute Id/Value pair into the AVRCP equivalent value.
+    private static int mapAttribIdValtoAvrcpPlayerSetting(byte attribId, byte attribVal) {
+        if (attribId == JNI_ATTRIB_EQUALIZER_STATUS) {
+            switch(attribVal) {
+                case JNI_EQUALIZER_STATUS_OFF:
+                    return BluetoothAvrcpPlayerSettings.STATE_OFF;
+                case JNI_EQUALIZER_STATUS_ON:
+                    return BluetoothAvrcpPlayerSettings.STATE_ON;
+            }
+        } else if (attribId == JNI_ATTRIB_REPEAT_STATUS) {
+            switch(attribVal) {
+                case JNI_REPEAT_STATUS_ALL_TRACK_REPEAT:
+                    return BluetoothAvrcpPlayerSettings.STATE_ALL_TRACK;
+                case JNI_REPEAT_STATUS_GROUP_REPEAT:
+                    return BluetoothAvrcpPlayerSettings.STATE_GROUP;
+                case JNI_REPEAT_STATUS_OFF:
+                    return BluetoothAvrcpPlayerSettings.STATE_OFF;
+                case JNI_REPEAT_STATUS_SINGLE_TRACK_REPEAT:
+                    return BluetoothAvrcpPlayerSettings.STATE_SINGLE_TRACK;
+            }
+        } else if (attribId == JNI_ATTRIB_SCAN_STATUS) {
+            switch(attribVal) {
+                case JNI_SCAN_STATUS_ALL_TRACK_SCAN:
+                    return BluetoothAvrcpPlayerSettings.STATE_ALL_TRACK;
+                case JNI_SCAN_STATUS_GROUP_SCAN:
+                    return BluetoothAvrcpPlayerSettings.STATE_GROUP;
+                case JNI_SCAN_STATUS_OFF:
+                    return BluetoothAvrcpPlayerSettings.STATE_OFF;
+            }
+        } else if (attribId == JNI_ATTRIB_SHUFFLE_STATUS) {
+            switch(attribVal) {
+                case JNI_SHUFFLE_STATUS_ALL_TRACK_SHUFFLE:
+                    return BluetoothAvrcpPlayerSettings.STATE_ALL_TRACK;
+                case JNI_SHUFFLE_STATUS_GROUP_SHUFFLE:
+                    return BluetoothAvrcpPlayerSettings.STATE_GROUP;
+                case JNI_SHUFFLE_STATUS_OFF:
+                    return BluetoothAvrcpPlayerSettings.STATE_OFF;
+            }
+        }
+        return BluetoothAvrcpPlayerSettings.STATE_INVALID;
+    }
+
+    // Convert an AVRCP Setting/Value pair into the native equivalent value;
+    private static byte mapAvrcpPlayerSettingstoBTattribVal(int mSetting, int mSettingVal) {
+        if (mSetting == BluetoothAvrcpPlayerSettings.SETTING_EQUALIZER) {
+            switch(mSettingVal) {
+                case BluetoothAvrcpPlayerSettings.STATE_OFF:
+                    return JNI_EQUALIZER_STATUS_OFF;
+                case BluetoothAvrcpPlayerSettings.STATE_ON:
+                    return JNI_EQUALIZER_STATUS_ON;
+            }
+        } else if (mSetting == BluetoothAvrcpPlayerSettings.SETTING_REPEAT) {
+            switch(mSettingVal) {
+                case BluetoothAvrcpPlayerSettings.STATE_OFF:
+                    return JNI_REPEAT_STATUS_OFF;
+                case BluetoothAvrcpPlayerSettings.STATE_SINGLE_TRACK:
+                    return JNI_REPEAT_STATUS_SINGLE_TRACK_REPEAT;
+                case BluetoothAvrcpPlayerSettings.STATE_ALL_TRACK:
+                    return JNI_REPEAT_STATUS_ALL_TRACK_REPEAT;
+                case BluetoothAvrcpPlayerSettings.STATE_GROUP:
+                    return JNI_REPEAT_STATUS_GROUP_REPEAT;
+            }
+        } else if (mSetting == BluetoothAvrcpPlayerSettings.SETTING_SHUFFLE) {
+            switch(mSettingVal) {
+                case BluetoothAvrcpPlayerSettings.STATE_OFF:
+                    return JNI_SHUFFLE_STATUS_OFF;
+                case BluetoothAvrcpPlayerSettings.STATE_ALL_TRACK:
+                    return JNI_SHUFFLE_STATUS_ALL_TRACK_SHUFFLE;
+                case BluetoothAvrcpPlayerSettings.STATE_GROUP:
+                    return JNI_SHUFFLE_STATUS_GROUP_SHUFFLE;
+            }
+        } else if (mSetting == BluetoothAvrcpPlayerSettings.SETTING_SCAN) {
+            switch(mSettingVal) {
+                case BluetoothAvrcpPlayerSettings.STATE_OFF:
+                    return JNI_SCAN_STATUS_OFF;
+                case BluetoothAvrcpPlayerSettings.STATE_ALL_TRACK:
+                    return JNI_SCAN_STATUS_ALL_TRACK_SCAN;
+                case BluetoothAvrcpPlayerSettings.STATE_GROUP:
+                    return JNI_SCAN_STATUS_GROUP_SCAN;
+            }
+        }
+        return JNI_STATUS_INVALID;
+    }
+
+    // convert a native Attribute Id into the AVRCP Setting equivalent value;
+    private static int mapBTAttribIdToAvrcpPlayerSettings(byte attribId) {
+        switch(attribId) {
+            case JNI_ATTRIB_EQUALIZER_STATUS:
+                return BluetoothAvrcpPlayerSettings.SETTING_EQUALIZER;
+            case JNI_ATTRIB_REPEAT_STATUS:
+                return BluetoothAvrcpPlayerSettings.SETTING_REPEAT;
+            case JNI_ATTRIB_SHUFFLE_STATUS:
+                return BluetoothAvrcpPlayerSettings.SETTING_SHUFFLE;
+            case JNI_ATTRIB_SCAN_STATUS:
+                return BluetoothAvrcpPlayerSettings.SETTING_SCAN;
+            default:
+                return BluetoothAvrcpPlayerSettings.STATE_INVALID;
+        }
+    }
+
+}
+
diff --git a/src/com/android/bluetooth/avrcpcontroller/RemoteDevice.java b/src/com/android/bluetooth/avrcpcontroller/RemoteDevice.java
new file mode 100644
index 0000000..e535f92
--- /dev/null
+++ b/src/com/android/bluetooth/avrcpcontroller/RemoteDevice.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.avrcpcontroller;
+
+import android.bluetooth.BluetoothAvrcpPlayerSettings;
+import android.bluetooth.BluetoothDevice;
+import android.media.session.PlaybackState;
+
+import com.android.bluetooth.Utils;
+
+import java.util.ArrayList;
+import java.nio.ByteBuffer;
+
+/*
+ * Contains information about remote device specifically the player and features enabled on it along
+ * with an encapsulation of the current track and playlist information.
+ */
+class RemoteDevice {
+
+    /*
+     * Remote features from JNI
+     */
+    private static final int FEAT_NONE = 0;
+    private static final int FEAT_METADATA = 1;
+    private static final int FEAT_ABSOLUTE_VOLUME = 2;
+    private static final int FEAT_BROWSE = 4;
+
+    private static final int VOLUME_LABEL_UNDEFINED = -1;
+
+    final BluetoothDevice mBTDevice;
+    private int mRemoteFeatures;
+    private boolean mAbsVolNotificationRequested;
+    private boolean mFirstAbsVolCmdRecvd;
+    private int mNotificationLabel;
+
+    RemoteDevice(BluetoothDevice mDevice) {
+        mBTDevice = mDevice;
+        mRemoteFeatures = FEAT_NONE;
+        mAbsVolNotificationRequested = false;
+        mNotificationLabel = VOLUME_LABEL_UNDEFINED;
+        mFirstAbsVolCmdRecvd = false;
+    }
+
+    synchronized void setRemoteFeatures(int remoteFeatures) {
+        mRemoteFeatures = remoteFeatures;
+    }
+
+    synchronized public byte[] getBluetoothAddress() {
+        return Utils.getByteAddress(mBTDevice);
+    }
+
+    synchronized public void setNotificationLabel(int label) {
+        mNotificationLabel = label;
+    }
+
+    synchronized public int getNotificationLabel() {
+        return mNotificationLabel;
+    }
+
+    synchronized public void setAbsVolNotificationRequested(boolean request) {
+        mAbsVolNotificationRequested = request;
+    }
+
+    synchronized public boolean getAbsVolNotificationRequested() {
+        return mAbsVolNotificationRequested;
+    }
+
+    synchronized public void setFirstAbsVolCmdRecvd() {
+        mFirstAbsVolCmdRecvd = true;
+    }
+
+    synchronized public boolean getFirstAbsVolCmdRecvd() {
+        return mFirstAbsVolCmdRecvd;
+    }
+}
diff --git a/src/com/android/bluetooth/avrcpcontroller/TrackInfo.java b/src/com/android/bluetooth/avrcpcontroller/TrackInfo.java
new file mode 100644
index 0000000..033ea04
--- /dev/null
+++ b/src/com/android/bluetooth/avrcpcontroller/TrackInfo.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package com.android.bluetooth.avrcpcontroller;
+
+import android.media.MediaMetadata;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.HashMap;
+
+/*
+ * Contains information about tracks that either currently playing or maintained in playlist
+ * This is used as a local repository for information that will be passed on as MediaMetadata to the
+ * MediaSessionServicve
+ */
+class TrackInfo {
+    private static final String TAG = "AvrcpTrackInfo";
+    private static final boolean DBG = true;
+
+    /*
+     * Default values for each of the items from JNI
+     */
+    private static final int TRACK_NUM_INVALID = -1;
+    private static final int TOTAL_TRACKS_INVALID = -1;
+    private static final int TOTAL_TRACK_TIME_INVALID = -1;
+    private static final String UNPOPULATED_ATTRIBUTE = "";
+
+    /*
+     *Element Id Values for GetMetaData  from JNI
+     */
+    private static final int MEDIA_ATTRIBUTE_TITLE = 0x01;
+    private static final int MEDIA_ATTRIBUTE_ARTIST_NAME = 0x02;
+    private static final int MEDIA_ATTRIBUTE_ALBUM_NAME = 0x03;
+    private static final int MEDIA_ATTRIBUTE_TRACK_NUMBER = 0x04;
+    private static final int MEDIA_ATTRIBUTE_TOTAL_TRACK_NUMBER = 0x05;
+    private static final int MEDIA_ATTRIBUTE_GENRE = 0x06;
+    private static final int MEDIA_ATTRIBUTE_PLAYING_TIME = 0x07;
+
+
+    private final String mArtistName;
+    private final String mTrackTitle;
+    private final String mAlbumTitle;
+    private final String mGenre;
+    private final long mTrackNum; // number of audio file on original recording.
+    private final long mTotalTracks;// total number of tracks on original recording
+    private final long mTrackLen;// full length of AudioFile.
+
+    public TrackInfo() {
+        this(new ArrayList<Integer>(), new ArrayList<String>());
+    }
+
+    public TrackInfo(List<Integer> attrIds, List<String> attrMap) {
+        Map<Integer, String> attributeMap = new HashMap<>();
+        for (int i = 0; i < attrIds.size(); i++) {
+            attributeMap.put(attrIds.get(i), attrMap.get(i));
+        }
+
+        String attribute;
+        mTrackTitle = attributeMap.getOrDefault(MEDIA_ATTRIBUTE_TITLE, UNPOPULATED_ATTRIBUTE);
+
+        mArtistName = attributeMap.getOrDefault(MEDIA_ATTRIBUTE_ARTIST_NAME, UNPOPULATED_ATTRIBUTE);
+
+        mAlbumTitle = attributeMap.getOrDefault(MEDIA_ATTRIBUTE_ALBUM_NAME, UNPOPULATED_ATTRIBUTE);
+
+        attribute = attributeMap.get(MEDIA_ATTRIBUTE_TRACK_NUMBER);
+        mTrackNum = (attribute != null && !attribute.isEmpty()) ? Long.valueOf(attribute) : TRACK_NUM_INVALID;
+
+        attribute = attributeMap.get(MEDIA_ATTRIBUTE_TOTAL_TRACK_NUMBER);
+        mTotalTracks = (attribute != null && !attribute.isEmpty()) ? Long.valueOf(attribute) : TOTAL_TRACKS_INVALID;
+
+        mGenre = attributeMap.getOrDefault(MEDIA_ATTRIBUTE_GENRE, UNPOPULATED_ATTRIBUTE);
+
+        attribute = attributeMap.get(MEDIA_ATTRIBUTE_PLAYING_TIME);
+        mTrackLen = (attribute != null && !attribute.isEmpty()) ? Long.valueOf(attribute) : TOTAL_TRACK_TIME_INVALID;
+    }
+
+    public String toString() {
+        return "Metadata [artist=" + mArtistName + " trackTitle= " + mTrackTitle +
+                " albumTitle= " + mAlbumTitle + " genre= " +mGenre+" trackNum= "+
+                Long.toString(mTrackNum) + " track_len : "+ Long.toString(mTrackLen) +
+                " TotalTracks " + Long.toString(mTotalTracks) + "]";
+    }
+
+    public MediaMetadata getMediaMetaData() {
+        if (DBG) Log.d(TAG, " TrackInfo " + toString());
+        MediaMetadata.Builder mMetaDataBuilder = new MediaMetadata.Builder();
+        mMetaDataBuilder.putString(MediaMetadata.METADATA_KEY_ARTIST,
+            mArtistName);
+        mMetaDataBuilder.putString(MediaMetadata.METADATA_KEY_TITLE,
+            mTrackTitle);
+        mMetaDataBuilder.putString(MediaMetadata.METADATA_KEY_ALBUM,
+            mAlbumTitle);
+        mMetaDataBuilder.putString(MediaMetadata.METADATA_KEY_GENRE,
+            mGenre);
+        mMetaDataBuilder.putLong(MediaMetadata.METADATA_KEY_TRACK_NUMBER,
+            mTrackNum);
+        mMetaDataBuilder.putLong(MediaMetadata.METADATA_KEY_NUM_TRACKS,
+            mTotalTracks);
+        mMetaDataBuilder.putLong(MediaMetadata.METADATA_KEY_DURATION,
+            mTrackLen);
+        return mMetaDataBuilder.build();
+    }
+
+
+    public String displayMetaData() {
+        MediaMetadata metaData = getMediaMetaData();
+        StringBuffer sb = new StringBuffer();
+        /* getDescription only contains artist, title and album */
+        sb.append(metaData.getDescription().toString() + " ");
+        if(metaData.containsKey(MediaMetadata.METADATA_KEY_GENRE))
+            sb.append(metaData.getString(MediaMetadata.METADATA_KEY_GENRE) + " ");
+        if(metaData.containsKey(MediaMetadata.METADATA_KEY_MEDIA_ID))
+            sb.append(metaData.getString(MediaMetadata.METADATA_KEY_MEDIA_ID) + " ");
+        if(metaData.containsKey(MediaMetadata.METADATA_KEY_TRACK_NUMBER))
+            sb.append(Long.toString(metaData.getLong(MediaMetadata.METADATA_KEY_TRACK_NUMBER)) + " ");
+        if(metaData.containsKey(MediaMetadata.METADATA_KEY_NUM_TRACKS))
+            sb.append(Long.toString(metaData.getLong(MediaMetadata.METADATA_KEY_NUM_TRACKS)) + " ");
+        if(metaData.containsKey(MediaMetadata.METADATA_KEY_TRACK_NUMBER))
+            sb.append(Long.toString(metaData.getLong(MediaMetadata.METADATA_KEY_DURATION)) + " ");
+        if(metaData.containsKey(MediaMetadata.METADATA_KEY_TRACK_NUMBER))
+            sb.append(Long.toString(metaData.getLong(MediaMetadata.METADATA_KEY_DURATION)) + " ");
+        return sb.toString();
+    }
+}
diff --git a/src/com/android/bluetooth/btservice/Config.java b/src/com/android/bluetooth/btservice/Config.java
index afb9d8d..6bae460 100644
--- a/src/com/android/bluetooth/btservice/Config.java
+++ b/src/com/android/bluetooth/btservice/Config.java
@@ -28,7 +28,7 @@
 import com.android.bluetooth.R;
 import com.android.bluetooth.a2dp.A2dpService;
 import com.android.bluetooth.a2dpsink.A2dpSinkService;
-import com.android.bluetooth.avrcp.AvrcpControllerService;
+import com.android.bluetooth.avrcpcontroller.AvrcpControllerService;
 import com.android.bluetooth.hdp.HealthService;
 import com.android.bluetooth.hfp.HeadsetService;
 import com.android.bluetooth.hfpclient.HeadsetClientService;