House all AVRCP metadata in custom objects instead of MediaMetadata

Lots of metadata comes over AVRCP and we're only saving a subset of it
into Media Framework objects (mostly because the framework objects don't
have anything in them that relates well to the AVRCP metadata). This
change migrates us to an AvrcpItem that can hold any extra metadata we
need and knows how to convert itself into the correct framework objects
such that we can still share the data with our MediaBrowserService.

Bug: b/132812696
Test: Build, Flash, interopt tests with devices, atest
Change-Id: I86f78de60bbe95229f63c5d282494453f920c6a6
diff --git a/android/app/jni/com_android_bluetooth_avrcp_controller.cpp b/android/app/jni/com_android_bluetooth_avrcp_controller.cpp
index 9f47431..818a38f 100755
--- a/android/app/jni/com_android_bluetooth_avrcp_controller.cpp
+++ b/android/app/jni/com_android_bluetooth_avrcp_controller.cpp
@@ -50,7 +50,7 @@
 static jmethodID method_handleNowPlayingContentChanged;
 static jmethodID method_onAvailablePlayerChanged;
 
-static jclass class_MediaBrowser_MediaItem;
+static jclass class_AvrcpItem;
 static jclass class_AvrcpPlayer;
 
 static const btrc_ctrl_interface_t* sBluetoothAvrcpInterface = NULL;
@@ -458,7 +458,7 @@
         sCallbackEnv->NewObjectArray((jint)count, class_AvrcpPlayer, 0));
   } else {
     itemArray.reset(sCallbackEnv->NewObjectArray(
-        (jint)count, class_MediaBrowser_MediaItem, 0));
+        (jint)count, class_AvrcpItem, 0));
   }
   if (!itemArray.get()) {
     ALOGE("%s itemArray allocation failed.", __func__);
@@ -511,11 +511,11 @@
         ScopedLocalRef<jobject> mediaObj(
             sCallbackEnv.get(),
             (jobject)sCallbackEnv->CallObjectMethod(
-                sCallbacksObj, method_createFromNativeMediaItem, uid,
-                (jint)item->media.type, mediaName.get(), attrIdArray.get(),
-                attrValArray.get()));
+                sCallbacksObj, method_createFromNativeMediaItem, addr.get(),
+                uid, (jint)item->media.type, mediaName.get(),
+                attrIdArray.get(), attrValArray.get()));
         if (!mediaObj.get()) {
-          ALOGE("%s failed to creae MediaItem for type ITEM_MEDIA", __func__);
+          ALOGE("%s failed to create AvrcpItem for type ITEM_MEDIA", __func__);
           return;
         }
         sCallbackEnv->SetObjectArrayElement(itemArray.get(), i, mediaObj.get());
@@ -536,11 +536,11 @@
         ScopedLocalRef<jobject> folderObj(
             sCallbackEnv.get(),
             (jobject)sCallbackEnv->CallObjectMethod(
-                sCallbacksObj, method_createFromNativeFolderItem, uid,
-                (jint)item->folder.type, folderName.get(),
+                sCallbacksObj, method_createFromNativeFolderItem, addr.get(),
+                uid, (jint)item->folder.type, folderName.get(),
                 (jint)item->folder.playable));
         if (!folderObj.get()) {
-          ALOGE("%s failed to create MediaItem for type ITEM_FOLDER", __func__);
+          ALOGE("%s failed to create AvrcpItem for type ITEM_FOLDER", __func__);
           return;
         }
         sCallbackEnv->SetObjectArrayElement(itemArray.get(), i,
@@ -576,8 +576,8 @@
         ScopedLocalRef<jobject> playerObj(
             sCallbackEnv.get(),
             (jobject)sCallbackEnv->CallObjectMethod(
-                sCallbacksObj, method_createFromNativePlayerItem, id,
-                playerName.get(), featureBitArray.get(), playStatus,
+                sCallbacksObj, method_createFromNativePlayerItem, addr.get(),
+                id, playerName.get(), featureBitArray.get(), playStatus,
                 playerType));
         if (!playerObj.get()) {
           ALOGE("%s failed to create AvrcpPlayer from ITEM_PLAYER", __func__);
@@ -805,21 +805,23 @@
 
   method_handleGetFolderItemsRsp =
       env->GetMethodID(clazz, "handleGetFolderItemsRsp",
-                       "([BI[Landroid/support/v4/media/MediaBrowserCompat$MediaItem;)V");
+                       "([BI[Lcom/android/bluetooth/avrcpcontroller/"
+                       "AvrcpItem;)V");
   method_handleGetPlayerItemsRsp = env->GetMethodID(
       clazz, "handleGetPlayerItemsRsp",
       "([B[Lcom/android/bluetooth/avrcpcontroller/AvrcpPlayer;)V");
 
   method_createFromNativeMediaItem =
       env->GetMethodID(clazz, "createFromNativeMediaItem",
-                       "(JILjava/lang/String;[I[Ljava/lang/String;)Landroid/"
-                       "support/v4/media/MediaBrowserCompat$MediaItem;");
+                       "([BJILjava/lang/String;[I[Ljava/lang/String;)Lcom/"
+                       "android/bluetooth/avrcpcontroller/AvrcpItem;");
   method_createFromNativeFolderItem = env->GetMethodID(
       clazz, "createFromNativeFolderItem",
-      "(JILjava/lang/String;I)Landroid/support/v4/media/MediaBrowserCompat$MediaItem;");
+      "([BJILjava/lang/String;I)Lcom/android/bluetooth/avrcpcontroller/"
+      "AvrcpItem;");
   method_createFromNativePlayerItem =
       env->GetMethodID(clazz, "createFromNativePlayerItem",
-                       "(ILjava/lang/String;[BII)Lcom/android/bluetooth/"
+                       "([BILjava/lang/String;[BII)Lcom/android/bluetooth/"
                        "avrcpcontroller/AvrcpPlayer;");
   method_handleChangeFolderRsp =
       env->GetMethodID(clazz, "handleChangeFolderRsp", "([BI)V");
@@ -840,9 +842,9 @@
 static void initNative(JNIEnv* env, jobject object) {
   std::unique_lock<std::shared_timed_mutex> lock(sCallbacks_mutex);
 
-  jclass tmpMediaItem =
-      env->FindClass("android/support/v4/media/MediaBrowserCompat$MediaItem");
-  class_MediaBrowser_MediaItem = (jclass)env->NewGlobalRef(tmpMediaItem);
+  jclass tmpAvrcpItem =
+      env->FindClass("com/android/bluetooth/avrcpcontroller/AvrcpItem");
+  class_AvrcpItem = (jclass)env->NewGlobalRef(tmpAvrcpItem);
 
   jclass tmpBtPlayer =
       env->FindClass("com/android/bluetooth/avrcpcontroller/AvrcpPlayer");
diff --git a/android/app/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerService.java b/android/app/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerService.java
index 5f601e9..059feeb 100755
--- a/android/app/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerService.java
+++ b/android/app/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerService.java
@@ -22,9 +22,7 @@
 import android.bluetooth.BluetoothProfile;
 import android.bluetooth.IBluetoothAvrcpController;
 import android.content.Intent;
-import android.os.Bundle;
 import android.support.v4.media.MediaBrowserCompat.MediaItem;
-import android.support.v4.media.MediaDescriptionCompat;
 import android.support.v4.media.session.PlaybackStateCompat;
 import android.util.Log;
 
@@ -48,7 +46,6 @@
     static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
     static final boolean VDBG = Log.isLoggable(TAG, Log.VERBOSE);
 
-    public static final String MEDIA_ITEM_UID_KEY = "media-item-uid-key";
     /*
      *  Play State Values from JNI
      */
@@ -140,10 +137,11 @@
     }
 
     private void refreshContents(BrowseTree.BrowseNode node) {
-        if (node.mDevice == null) {
+        BluetoothDevice device = node.getDevice();
+        if (device == null) {
             return;
         }
-        AvrcpControllerStateMachine stateMachine = getStateMachine(node.mDevice);
+        AvrcpControllerStateMachine stateMachine = getStateMachine(device);
         if (stateMachine != null) {
             stateMachine.requestContents(node);
         }
@@ -301,7 +299,7 @@
     // Called by JNI when a device has connected or disconnected.
     private synchronized void onConnectionStateChanged(boolean remoteControlConnected,
             boolean browsingConnected, byte[] address) {
-        BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
+        BluetoothDevice device = mAdapter.getRemoteDevice(address);
         if (DBG) {
             Log.d(TAG, "onConnectionStateChanged " + remoteControlConnected + " "
                     + browsingConnected + device);
@@ -336,7 +334,7 @@
         if (DBG) {
             Log.d(TAG, "handleRegisterNotificationAbsVol");
         }
-        BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
+        BluetoothDevice device = mAdapter.getRemoteDevice(address);
         AvrcpControllerStateMachine stateMachine = getStateMachine(device);
         if (stateMachine != null) {
             stateMachine.sendMessage(
@@ -349,7 +347,7 @@
         if (DBG) {
             Log.d(TAG, "handleSetAbsVolume ");
         }
-        BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
+        BluetoothDevice device = mAdapter.getRemoteDevice(address);
         AvrcpControllerStateMachine stateMachine = getStateMachine(device);
         if (stateMachine != null) {
             stateMachine.sendMessage(AvrcpControllerStateMachine.MESSAGE_PROCESS_SET_ABS_VOL_CMD,
@@ -364,11 +362,18 @@
             Log.d(TAG, "onTrackChanged");
         }
 
-        BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
+        BluetoothDevice device = mAdapter.getRemoteDevice(address);
         AvrcpControllerStateMachine stateMachine = getStateMachine(device);
         if (stateMachine != null) {
+            AvrcpItem.Builder aib = new AvrcpItem.Builder();
+            aib.fromAvrcpAttributeArray(attributes, attribVals);
+            aib.setDevice(device);
+            aib.setItemType(AvrcpItem.TYPE_MEDIA);
+            aib.setUuid(UUID.randomUUID().toString());
+            AvrcpItem item = aib.build();
+
             stateMachine.sendMessage(AvrcpControllerStateMachine.MESSAGE_PROCESS_TRACK_CHANGED,
-                    TrackInfo.getMetadata(attributes, attribVals));
+                    item);
         }
     }
 
@@ -378,7 +383,7 @@
         if (DBG) {
             Log.d(TAG, "onPlayPositionChanged pos " + currSongPosition);
         }
-        BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
+        BluetoothDevice device = mAdapter.getRemoteDevice(address);
         AvrcpControllerStateMachine stateMachine = getStateMachine(device);
         if (stateMachine != null) {
             stateMachine.sendMessage(
@@ -412,7 +417,7 @@
             default:
                 playbackState = PlaybackStateCompat.STATE_NONE;
         }
-        BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
+        BluetoothDevice device = mAdapter.getRemoteDevice(address);
         AvrcpControllerStateMachine stateMachine = getStateMachine(device);
         if (stateMachine != null) {
             stateMachine.sendMessage(
@@ -426,7 +431,7 @@
         if (DBG) {
             Log.d(TAG, "handlePlayerAppSetting rspLen = " + rspLen);
         }
-        BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
+        BluetoothDevice device = mAdapter.getRemoteDevice(address);
         AvrcpControllerStateMachine stateMachine = getStateMachine(device);
         if (stateMachine != null) {
             PlayerApplicationSettings supportedSettings =
@@ -442,7 +447,7 @@
         if (DBG) {
             Log.d(TAG, "onPlayerAppSettingChanged ");
         }
-        BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
+        BluetoothDevice device = mAdapter.getRemoteDevice(address);
         AvrcpControllerStateMachine stateMachine = getStateMachine(device);
         if (stateMachine != null) {
 
@@ -467,27 +472,21 @@
     }
 
     // Browsing related JNI callbacks.
-    void handleGetFolderItemsRsp(byte[] address, int status, MediaItem[] items) {
+    void handleGetFolderItemsRsp(byte[] address, int status, AvrcpItem[] items) {
         if (DBG) {
             Log.d(TAG, "handleGetFolderItemsRsp called with status " + status + " items "
                     + items.length + " items.");
         }
-        for (MediaItem item : items) {
-            if (VDBG) {
-                Log.d(TAG, "media item: " + item + " uid: "
-                        + item.getDescription().getMediaId());
-            }
-        }
-        ArrayList<MediaItem> itemsList = new ArrayList<>();
-        for (MediaItem item : items) {
+
+        List<AvrcpItem> itemsList = new ArrayList<>();
+        for (AvrcpItem item : items) {
+            if (VDBG) Log.d(TAG, item.toString());
             itemsList.add(item);
         }
 
-        BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
-
+        BluetoothDevice device = mAdapter.getRemoteDevice(address);
         AvrcpControllerStateMachine stateMachine = getStateMachine(device);
         if (stateMachine != null) {
-
             stateMachine.sendMessage(AvrcpControllerStateMachine.MESSAGE_PROCESS_GET_FOLDER_ITEMS,
                     itemsList);
         }
@@ -499,17 +498,13 @@
             Log.d(TAG, "handleGetFolderItemsRsp called with " + items.length + " items.");
         }
 
-        for (AvrcpPlayer item : items) {
-            if (VDBG) {
-                Log.d(TAG, "bt player item: " + item);
-            }
-        }
         List<AvrcpPlayer> itemsList = new ArrayList<>();
-        for (AvrcpPlayer p : items) {
-            itemsList.add(p);
+        for (AvrcpPlayer item : items) {
+            if (VDBG) Log.d(TAG, "bt player item: " + item);
+            itemsList.add(item);
         }
 
-        BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
+        BluetoothDevice device = mAdapter.getRemoteDevice(address);
         AvrcpControllerStateMachine stateMachine = getStateMachine(device);
         if (stateMachine != null) {
             stateMachine.sendMessage(AvrcpControllerStateMachine.MESSAGE_PROCESS_GET_PLAYER_ITEMS,
@@ -518,60 +513,57 @@
     }
 
     // JNI Helper functions to convert native objects to java.
-    MediaItem createFromNativeMediaItem(long uid, int type, String name, int[] attrIds,
-            String[] attrVals) {
+    AvrcpItem createFromNativeMediaItem(byte[] address, long uid, int type, String name,
+            int[] attrIds, String[] attrVals) {
         if (VDBG) {
-            Log.d(TAG, "createFromNativeMediaItem uid: " + uid + " type " + type + " name "
-                    + name + " attrids " + attrIds + " attrVals " + attrVals);
+            Log.d(TAG, "createFromNativeMediaItem uid: " + uid + " type: " + type + " name: " + name
+                    + " attrids: " + attrIds + " attrVals: " + attrVals);
         }
-        MediaDescriptionCompat.Builder mdb = new MediaDescriptionCompat.Builder();
 
-        Bundle mdExtra = new Bundle();
-        mdExtra.putLong(MEDIA_ITEM_UID_KEY, uid);
-        mdb.setExtras(mdExtra);
+        BluetoothDevice device = mAdapter.getRemoteDevice(address);
+        AvrcpItem.Builder aib = new AvrcpItem.Builder().fromAvrcpAttributeArray(attrIds, attrVals);
+        aib.setDevice(device);
+        aib.setItemType(AvrcpItem.TYPE_MEDIA);
+        aib.setType(type);
+        aib.setUid(uid);
+        aib.setUuid(UUID.randomUUID().toString());
+        aib.setPlayable(true);
+        AvrcpItem item = aib.build();
 
-
-        // 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);
+        return item;
     }
 
-    MediaItem createFromNativeFolderItem(long uid, int type, String name, int playable) {
+    AvrcpItem createFromNativeFolderItem(byte[] address, long uid, int type, String name,
+            int playable) {
         if (VDBG) {
             Log.d(TAG, "createFromNativeFolderItem uid: " + uid + " type " + type + " name "
                     + name + " playable " + playable);
         }
-        MediaDescriptionCompat.Builder mdb = new MediaDescriptionCompat.Builder();
 
-        Bundle mdExtra = new Bundle();
-        mdExtra.putLong(MEDIA_ITEM_UID_KEY, 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);
+        BluetoothDevice device = mAdapter.getRemoteDevice(address);
+        AvrcpItem.Builder aib = new AvrcpItem.Builder();
+        aib.setDevice(device);
+        aib.setItemType(AvrcpItem.TYPE_FOLDER);
+        aib.setType(type);
+        aib.setUid(uid);
+        aib.setUuid(UUID.randomUUID().toString());
+        aib.setDisplayableName(name);
+        aib.setPlayable(playable == 0x01);
+        aib.setBrowsable(true);
+        return aib.build();
     }
 
-    AvrcpPlayer createFromNativePlayerItem(int id, String name, byte[] transportFlags,
-            int playStatus, int playerType) {
+    AvrcpPlayer createFromNativePlayerItem(byte[] address, int id, String name,
+            byte[] transportFlags, int playStatus, int playerType) {
         if (VDBG) {
             Log.d(TAG,
                     "createFromNativePlayerItem name: " + name + " transportFlags "
                             + transportFlags + " play status " + playStatus + " player type "
                             + playerType);
         }
-        AvrcpPlayer player = new AvrcpPlayer(id, name, transportFlags, playStatus, playerType);
+        BluetoothDevice device = mAdapter.getRemoteDevice(address);
+        AvrcpPlayer player = new AvrcpPlayer(device, id, name, transportFlags, playStatus,
+                playerType);
         return player;
     }
 
@@ -579,7 +571,7 @@
         if (DBG) {
             Log.d(TAG, "handleChangeFolderRsp count: " + count);
         }
-        BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
+        BluetoothDevice device = mAdapter.getRemoteDevice(address);
         AvrcpControllerStateMachine stateMachine = getStateMachine(device);
         if (stateMachine != null) {
             stateMachine.sendMessage(AvrcpControllerStateMachine.MESSAGE_PROCESS_FOLDER_PATH,
@@ -591,7 +583,7 @@
         if (DBG) {
             Log.d(TAG, "handleSetBrowsedPlayerRsp depth: " + depth);
         }
-        BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
+        BluetoothDevice device = mAdapter.getRemoteDevice(address);
 
         AvrcpControllerStateMachine stateMachine = getStateMachine(device);
         if (stateMachine != null) {
@@ -604,7 +596,7 @@
         if (DBG) {
             Log.d(TAG, "handleSetAddressedPlayerRsp status: " + status);
         }
-        BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
+        BluetoothDevice device = mAdapter.getRemoteDevice(address);
 
         AvrcpControllerStateMachine stateMachine = getStateMachine(device);
         if (stateMachine != null) {
@@ -617,7 +609,7 @@
         if (DBG) {
             Log.d(TAG, "handleAddressedPlayerChanged id: " + id);
         }
-        BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
+        BluetoothDevice device = mAdapter.getRemoteDevice(address);
 
         AvrcpControllerStateMachine stateMachine = getStateMachine(device);
         if (stateMachine != null) {
@@ -630,7 +622,7 @@
         if (DBG) {
             Log.d(TAG, "handleNowPlayingContentChanged");
         }
-        BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
+        BluetoothDevice device = mAdapter.getRemoteDevice(address);
 
         AvrcpControllerStateMachine stateMachine = getStateMachine(device);
         if (stateMachine != null) {
diff --git a/android/app/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java b/android/app/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java
index a31ada7..a133d72 100755
--- a/android/app/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java
+++ b/android/app/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java
@@ -24,8 +24,6 @@
 import android.media.AudioManager;
 import android.os.Bundle;
 import android.os.Message;
-import android.support.v4.media.MediaBrowserCompat.MediaItem;
-import android.support.v4.media.MediaMetadataCompat;
 import android.support.v4.media.session.MediaSessionCompat;
 import android.support.v4.media.session.PlaybackStateCompat;
 import android.util.Log;
@@ -397,9 +395,10 @@
                     return true;
 
                 case MESSAGE_PROCESS_TRACK_CHANGED:
-                    mAddressedPlayer.updateCurrentTrack((MediaMetadataCompat) msg.obj);
+                    AvrcpItem track = (AvrcpItem) msg.obj;
+                    mAddressedPlayer.updateCurrentTrack(track);
                     if (isActive()) {
-                        BluetoothMediaBrowserService.trackChanged((MediaMetadataCompat) msg.obj);
+                        BluetoothMediaBrowserService.trackChanged(track);
                     }
                     return true;
 
@@ -577,14 +576,15 @@
             logD(STATE_TAG + " processMessage " + msg.what);
             switch (msg.what) {
                 case MESSAGE_PROCESS_GET_FOLDER_ITEMS:
-                    ArrayList<MediaItem> folderList = (ArrayList<MediaItem>) msg.obj;
+                    ArrayList<AvrcpItem> folderList = (ArrayList<AvrcpItem>) msg.obj;
                     int endIndicator = mBrowseNode.getExpectedChildren() - 1;
                     logD("GetFolderItems: End " + endIndicator
                             + " received " + folderList.size());
 
                     // Always update the node so that the user does not wait forever
                     // for the list to populate.
-                    mBrowseNode.addChildren(folderList);
+                    int newSize = mBrowseNode.addChildren(folderList);
+                    logD("Added " + newSize + " items to the browse tree");
                     notifyChanged(mBrowseNode);
 
                     if (mBrowseNode.getChildrenCount() >= endIndicator || folderList.size() == 0
@@ -695,6 +695,9 @@
             int start = target.getChildrenCount();
             int end = Math.min(target.getExpectedChildren(), target.getChildrenCount()
                     + ITEM_PAGE_SIZE) - 1;
+            logD("fetchContents(title=" + target.getID() + ", scope=" + target.getScope()
+                    + ", start=" + start + ", end=" + end + ", expected="
+                    + target.getExpectedChildren() + ")");
             switch (target.getScope()) {
                 case AvrcpControllerService.BROWSE_SCOPE_PLAYER_LIST:
                     mService.getPlayerListNative(mDeviceAddress,
@@ -873,7 +876,7 @@
 
         @Override
         public void onSkipToQueueItem(long id) {
-            logD("onSkipToQueueItem" + id);
+            logD("onSkipToQueueItem id=" + id);
             onPrepare();
             BrowseTree.BrowseNode node = mBrowseTree.getTrackFromNowPlayingList((int) id);
             if (node != null) {
diff --git a/android/app/src/com/android/bluetooth/avrcpcontroller/AvrcpItem.java b/android/app/src/com/android/bluetooth/avrcpcontroller/AvrcpItem.java
new file mode 100644
index 0000000..3864394
--- /dev/null
+++ b/android/app/src/com/android/bluetooth/avrcpcontroller/AvrcpItem.java
@@ -0,0 +1,485 @@
+/*
+ * Copyright (C) 2020 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.BluetoothDevice;
+import android.net.Uri;
+import android.os.Bundle;
+import android.support.v4.media.MediaBrowserCompat.MediaItem;
+import android.support.v4.media.MediaDescriptionCompat;
+import android.support.v4.media.MediaMetadataCompat;
+import android.util.Log;
+
+/**
+ * An object representing a single item returned from an AVRCP folder listing in the VFS scope.
+ *
+ * This object knows how to turn itself into each of the Android Media Framework objects so the
+ * metadata can easily be shared with the system.
+ */
+public class AvrcpItem {
+    private static final String TAG = "AvrcpItem";
+    private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
+
+    // AVRCP Specification defined item types
+    public static final int TYPE_PLAYER = 0x1;
+    public static final int TYPE_FOLDER = 0x2;
+    public static final int TYPE_MEDIA = 0x3;
+
+    // AVRCP Specification defined folder item sub types
+    public static final int FOLDER_MIXED = 0x00;
+    public static final int FOLDER_TITLES = 0x01;
+    public static final int FOLDER_ALBUMS = 0x02;
+    public static final int FOLDER_ARTISTS = 0x03;
+    public static final int FOLDER_GENRES = 0x04;
+    public static final int FOLDER_PLAYLISTS = 0x05;
+    public static final int FOLDER_YEARS = 0x06;
+
+    // AVRCP Specification defined media item sub types
+    public static final int MEDIA_AUDIO = 0x00;
+    public static final int MEDIA_VIDEO = 0x01;
+
+    // Keys for packaging extra data with MediaItems
+    public static final String AVRCP_ITEM_KEY_UID = "avrcp-item-key-uid";
+
+    // Type of item, one of [TYPE_PLAYER, TYPE_FOLDER, TYPE_MEDIA]
+    private int mItemType;
+
+    // Sub type of item, dependant on whether it's a folder or media item
+    private int mType;
+
+    // Bluetooth Device this piece of metadata came from
+    private BluetoothDevice mDevice;
+
+    // AVRCP Specification defined metadata for browsed media items
+    private long mUid;
+    private String mDisplayableName;
+
+    // AVRCP Specification defined set of available attributes
+    private String mTitle;
+    private String mArtistName;
+    private String mAlbumName;
+    private long mTrackNumber;
+    private long mTotalNumberOfTracks;
+    private String mGenre;
+    private long mPlayingTime;
+    private String mCoverArtHandle;
+
+    private boolean mPlayable = false;
+    private boolean mBrowsable = false;
+
+    // Our own book keeping value since database unaware players sometimes send repeat UIDs.
+    private String mUuid;
+
+    // Our owned internal Uri value that points to downloaded cover art image
+    private Uri mImageUri;
+
+    private AvrcpItem() {
+    }
+
+    public BluetoothDevice getDevice() {
+        return mDevice;
+    }
+
+    public long getUid() {
+        return mUid;
+    }
+
+    public String getUuid() {
+        return mUuid;
+    }
+
+    public String getDisplayableName() {
+        return mDisplayableName;
+    }
+
+    public String getTitle() {
+        return mTitle;
+    }
+
+    public String getArtistName() {
+        return mArtistName;
+    }
+
+    public String getAlbumName() {
+        return mAlbumName;
+    }
+
+    public long getTrackNumber() {
+        return mTrackNumber;
+    }
+
+    public long getTotalNumberOfTracks() {
+        return mTotalNumberOfTracks;
+    }
+
+    public boolean isPlayable() {
+        return mPlayable;
+    }
+
+    public boolean isBrowsable() {
+        return mBrowsable;
+    }
+
+    public String getCoverArtHandle() {
+        return mCoverArtHandle;
+    }
+
+    public synchronized Uri getCoverArtLocation() {
+        return mImageUri;
+    }
+
+    public synchronized void setCoverArtLocation(Uri uri) {
+        mImageUri = uri;
+    }
+
+    /**
+     * Convert this item an Android Media Framework MediaMetadata
+     */
+    public MediaMetadataCompat toMediaMetadata() {
+        MediaMetadataCompat.Builder metaDataBuilder = new MediaMetadataCompat.Builder();
+        Uri coverArtUri = getCoverArtLocation();
+        String uriString = coverArtUri != null ? coverArtUri.toString() : null;
+        metaDataBuilder.putString(MediaMetadataCompat.METADATA_KEY_MEDIA_ID, mUuid);
+        metaDataBuilder.putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_TITLE, mDisplayableName);
+        metaDataBuilder.putString(MediaMetadataCompat.METADATA_KEY_TITLE, mTitle);
+        metaDataBuilder.putString(MediaMetadataCompat.METADATA_KEY_ARTIST, mArtistName);
+        metaDataBuilder.putString(MediaMetadataCompat.METADATA_KEY_ALBUM, mAlbumName);
+        metaDataBuilder.putLong(MediaMetadataCompat.METADATA_KEY_TRACK_NUMBER, mTrackNumber);
+        metaDataBuilder.putLong(MediaMetadataCompat.METADATA_KEY_NUM_TRACKS, mTotalNumberOfTracks);
+        metaDataBuilder.putString(MediaMetadataCompat.METADATA_KEY_GENRE, mGenre);
+        metaDataBuilder.putLong(MediaMetadataCompat.METADATA_KEY_DURATION, mPlayingTime);
+        metaDataBuilder.putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON_URI, uriString);
+        metaDataBuilder.putString(MediaMetadataCompat.METADATA_KEY_ART_URI, uriString);
+        metaDataBuilder.putString(MediaMetadataCompat.METADATA_KEY_ALBUM_ART_URI, uriString);
+        if (mItemType == TYPE_FOLDER) {
+            metaDataBuilder.putLong(MediaMetadataCompat.METADATA_KEY_BT_FOLDER_TYPE, mType);
+        }
+        return metaDataBuilder.build();
+    }
+
+    /**
+     * Convert this item an Android Media Framework MediaItem
+     */
+    public MediaItem toMediaItem() {
+        MediaDescriptionCompat.Builder descriptionBuilder = new MediaDescriptionCompat.Builder();
+
+        descriptionBuilder.setMediaId(mUuid);
+
+        String name = null;
+        if (mDisplayableName != null) {
+            name = mDisplayableName;
+        } else if (mTitle != null) {
+            name = mTitle;
+        }
+        descriptionBuilder.setTitle(name);
+
+        descriptionBuilder.setIconUri(getCoverArtLocation());
+
+        Bundle extras = new Bundle();
+        extras.putLong(AVRCP_ITEM_KEY_UID, mUid);
+        descriptionBuilder.setExtras(extras);
+
+        int flags = 0x0;
+        if (mPlayable) flags |= MediaItem.FLAG_PLAYABLE;
+        if (mBrowsable) flags |= MediaItem.FLAG_BROWSABLE;
+
+        return new MediaItem(descriptionBuilder.build(), flags);
+    }
+
+    @Override
+    public String toString() {
+        return "AvrcpItem{mUuid=" + mUuid + ", mUid=" + mUid + ", mItemType=" + mItemType
+                + ", mType=" + mType + ", mDisplayableName=" + mDisplayableName
+                + ", mTitle=" + mTitle + ", mPlayable=" + mPlayable + ", mBrowsable="
+                + mBrowsable + ", mCoverArtHandle=" + getCoverArtHandle() + "}";
+    }
+
+    /**
+     * Builder for an AvrcpItem
+     */
+    public static class Builder {
+        private static final String TAG = "AvrcpItem.Builder";
+        private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
+
+        // Attribute ID Values from AVRCP Specification
+        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 static final int MEDIA_ATTRIBUTE_COVER_ART_HANDLE = 0x08;
+
+        private AvrcpItem mAvrcpItem = new AvrcpItem();
+
+        /**
+         * Initialize all relevant AvrcpItem internals from the AVRCP specification defined set of
+         * item attributes
+         *
+         * @param attrIds The array of AVRCP specification defined IDs in the order they match to
+         *                the value string attrMap
+         * @param attrMap The mapped values for each ID
+         * @return This object so you can continue building
+         */
+        public Builder fromAvrcpAttributeArray(int[] attrIds, String[] attrMap) {
+            int attributeCount = Math.max(attrIds.length, attrMap.length);
+            for (int i = 0; i < attributeCount; i++) {
+                if (DBG) Log.d(TAG, attrIds[i] + " = " + attrMap[i]);
+                switch (attrIds[i]) {
+                    case MEDIA_ATTRIBUTE_TITLE:
+                        mAvrcpItem.mTitle = attrMap[i];
+                        break;
+                    case MEDIA_ATTRIBUTE_ARTIST_NAME:
+                        mAvrcpItem.mArtistName = attrMap[i];
+                        break;
+                    case MEDIA_ATTRIBUTE_ALBUM_NAME:
+                        mAvrcpItem.mAlbumName = attrMap[i];
+                        break;
+                    case MEDIA_ATTRIBUTE_TRACK_NUMBER:
+                        try {
+                            mAvrcpItem.mTrackNumber = Long.valueOf(attrMap[i]);
+                        } catch (java.lang.NumberFormatException e) {
+                            // If Track Number doesn't parse, leave it unset
+                        }
+                        break;
+                    case MEDIA_ATTRIBUTE_TOTAL_TRACK_NUMBER:
+                        try {
+                            mAvrcpItem.mTotalNumberOfTracks = Long.valueOf(attrMap[i]);
+                        } catch (java.lang.NumberFormatException e) {
+                            // If Total Track Number doesn't parse, leave it unset
+                        }
+                        break;
+                    case MEDIA_ATTRIBUTE_GENRE:
+                        mAvrcpItem.mGenre = attrMap[i];
+                        break;
+                    case MEDIA_ATTRIBUTE_PLAYING_TIME:
+                        try {
+                            mAvrcpItem.mPlayingTime = Long.valueOf(attrMap[i]);
+                        } catch (java.lang.NumberFormatException e) {
+                            // If Playing Time doesn't parse, leave it unset
+                        }
+                        break;
+                    case MEDIA_ATTRIBUTE_COVER_ART_HANDLE:
+                        mAvrcpItem.mCoverArtHandle = attrMap[i];
+                        break;
+                }
+            }
+            return this;
+        }
+
+        /**
+         * Set the item type for the AvrcpItem you are building
+         *
+         * Type can be one of PLAYER, FOLDER, or MEDIA
+         *
+         * @param itemType The item type as an AvrcpItem.* type value
+         * @return This object, so you can continue building
+         */
+        public Builder setItemType(int itemType) {
+            mAvrcpItem.mItemType = itemType;
+            return this;
+        }
+
+        /**
+         * Set the type for the AvrcpItem you are building
+         *
+         * This is the type of the PLAYER, FOLDER, or MEDIA item.
+         *
+         * @param type The type as one of the AvrcpItem.MEDIA_* or FOLDER_* types
+         * @return This object, so you can continue building
+         */
+        public Builder setType(int type) {
+            mAvrcpItem.mType = type;
+            return this;
+        }
+
+        /**
+         * Set the device for the AvrcpItem you are building
+         *
+         * @param device The BluetoothDevice object that this item came from
+         * @return This object, so you can continue building
+         */
+        public Builder setDevice(BluetoothDevice device) {
+            mAvrcpItem.mDevice = device;
+            return this;
+        }
+
+        /**
+         * Note that the AvrcpItem you are building is playable
+         *
+         * @param playable True if playable, false otherwise
+         * @return This object, so you can continue building
+         */
+        public Builder setPlayable(boolean playable) {
+            mAvrcpItem.mPlayable = playable;
+            return this;
+        }
+
+        /**
+         * Note that the AvrcpItem you are building is browsable
+         *
+         * @param browsable True if browsable, false otherwise
+         * @return This object, so you can continue building
+         */
+        public Builder setBrowsable(boolean browsable) {
+            mAvrcpItem.mBrowsable = browsable;
+            return this;
+        }
+
+        /**
+         * Set the AVRCP defined UID assigned to the AvrcpItem you are building
+         *
+         * @param uid The UID given to this item by the remote device
+         * @return This object, so you can continue building
+         */
+        public Builder setUid(long uid) {
+            mAvrcpItem.mUid = uid;
+            return this;
+        }
+
+        /**
+         * Set the UUID you wish to associate with the AvrcpItem you are building
+         *
+         * @param uuid A string UUID value
+         * @return This object, so you can continue building
+         */
+        public Builder setUuid(String uuid) {
+            mAvrcpItem.mUuid = uuid;
+            return this;
+        }
+
+        /**
+         * Set the displayable name for the AvrcpItem you are building
+         *
+         * @param displayableName A string representing a friendly, displayable name
+         * @return This object, so you can continue building
+         */
+        public Builder setDisplayableName(String displayableName) {
+            mAvrcpItem.mDisplayableName = displayableName;
+            return this;
+        }
+
+        /**
+         * Set the title for the AvrcpItem you are building
+         *
+         * @param title The title as a string
+         * @return This object, so you can continue building
+         */
+        public Builder setTitle(String title) {
+            mAvrcpItem.mTitle = title;
+            return this;
+        }
+
+        /**
+         * Set the artist name for the AvrcpItem you are building
+         *
+         * @param artistName The artist name as a string
+         * @return This object, so you can continue building
+         */
+        public Builder setArtistName(String artistName) {
+            mAvrcpItem.mArtistName = artistName;
+            return this;
+        }
+
+        /**
+         * Set the album name for the AvrcpItem you are building
+         *
+         * @param albumName The album name as a string
+         * @return This object, so you can continue building
+         */
+        public Builder setAlbumName(String albumName) {
+            mAvrcpItem.mAlbumName = albumName;
+            return this;
+        }
+
+        /**
+         * Set the track number for the AvrcpItem you are building
+         *
+         * @param trackNumber The track number
+         * @return This object, so you can continue building
+         */
+        public Builder setTrackNumber(long trackNumber) {
+            mAvrcpItem.mTrackNumber = trackNumber;
+            return this;
+        }
+
+        /**
+         * Set the total number of tracks on the playlist or album that this AvrcpItem is on
+         *
+         * @param totalNumberOfTracks The total number of tracks along side this item
+         * @return This object, so you can continue building
+         */
+        public Builder setTotalNumberOfTracks(long totalNumberOfTracks) {
+            mAvrcpItem.mTotalNumberOfTracks = totalNumberOfTracks;
+            return this;
+        }
+
+        /**
+         * Set the genre name for the AvrcpItem you are building
+         *
+         * @param genre The genre as a string
+         * @return This object, so you can continue building
+         */
+        public Builder setGenre(String genre) {
+            mAvrcpItem.mGenre = genre;
+            return this;
+        }
+
+        /**
+         * Set the total playing time for the AvrcpItem you are building
+         *
+         * @param playingTime The playing time in seconds
+         * @return This object, so you can continue building
+         */
+        public Builder setPlayingTime(long playingTime) {
+            mAvrcpItem.mPlayingTime = playingTime;
+            return this;
+        }
+
+        /**
+         * Set the cover art handle for the AvrcpItem you are building
+         *
+         * @param coverArtHandle The cover art image handle provided by a remote device
+         * @return This object, so you can continue building
+         */
+        public Builder setCoverArtHandle(String coverArtHandle) {
+            mAvrcpItem.mCoverArtHandle = coverArtHandle;
+            return this;
+        }
+
+        /**
+         * Set the location of the downloaded cover art for the AvrcpItem you are building
+         *
+         * @param uri The URI where our storage has placed the image associated with this item
+         * @return This object, so you can continue building
+         */
+        public Builder setCoverArtLocation(Uri uri) {
+            mAvrcpItem.setCoverArtLocation(uri);
+            return this;
+        }
+
+        /**
+         * Build the AvrcpItem
+         *
+         * @return An AvrcpItem object
+         */
+        public AvrcpItem build() {
+            return mAvrcpItem;
+        }
+    }
+}
diff --git a/android/app/src/com/android/bluetooth/avrcpcontroller/AvrcpPlayer.java b/android/app/src/com/android/bluetooth/avrcpcontroller/AvrcpPlayer.java
index 7079fe5..a693421 100644
--- a/android/app/src/com/android/bluetooth/avrcpcontroller/AvrcpPlayer.java
+++ b/android/app/src/com/android/bluetooth/avrcpcontroller/AvrcpPlayer.java
@@ -16,8 +16,8 @@
 
 package com.android.bluetooth.avrcpcontroller;
 
+import android.bluetooth.BluetoothDevice;
 import android.os.SystemClock;
-import android.support.v4.media.MediaMetadataCompat;
 import android.support.v4.media.session.MediaSessionCompat;
 import android.support.v4.media.session.PlaybackStateCompat;
 import android.util.Log;
@@ -42,6 +42,7 @@
     public static final int FEATURE_PREVIOUS = 48;
     public static final int FEATURE_BROWSING = 59;
 
+    private BluetoothDevice mDevice;
     private int mPlayStatus = PlaybackStateCompat.STATE_NONE;
     private long mPlayTime = PlaybackStateCompat.PLAYBACK_POSITION_UNKNOWN;
     private long mPlayTimeUpdate = 0;
@@ -51,13 +52,14 @@
     private int mPlayerType;
     private byte[] mPlayerFeatures = new byte[16];
     private long mAvailableActions = PlaybackStateCompat.ACTION_PREPARE;
-    private MediaMetadataCompat mCurrentTrack;
+    private AvrcpItem mCurrentTrack;
     private PlaybackStateCompat mPlaybackStateCompat;
     private PlayerApplicationSettings mSupportedPlayerApplicationSettings =
             new PlayerApplicationSettings();
     private PlayerApplicationSettings mCurrentPlayerApplicationSettings;
 
     AvrcpPlayer() {
+        mDevice = null;
         mId = INVALID_ID;
         //Set Default Actions in case Player data isn't available.
         mAvailableActions = PlaybackStateCompat.ACTION_PAUSE | PlaybackStateCompat.ACTION_PLAY
@@ -69,7 +71,9 @@
         mPlaybackStateCompat = playbackStateBuilder.build();
     }
 
-    AvrcpPlayer(int id, String name, byte[] playerFeatures, int playStatus, int playerType) {
+    AvrcpPlayer(BluetoothDevice device, int id, String name, byte[] playerFeatures, int playStatus,
+            int playerType) {
+        mDevice = device;
         mId = id;
         mName = name;
         mPlayStatus = playStatus;
@@ -81,6 +85,10 @@
         updateAvailableActions();
     }
 
+    public BluetoothDevice getDevice() {
+        return mDevice;
+    }
+
     public int getId() {
         return mId;
     }
@@ -166,9 +174,9 @@
         return mPlaybackStateCompat;
     }
 
-    public synchronized void updateCurrentTrack(MediaMetadataCompat update) {
+    public synchronized void updateCurrentTrack(AvrcpItem update) {
         if (update != null) {
-            long trackNumber = update.getLong(MediaMetadataCompat.METADATA_KEY_TRACK_NUMBER);
+            long trackNumber = update.getTrackNumber();
             mPlaybackStateCompat = new PlaybackStateCompat.Builder(
                     mPlaybackStateCompat).setActiveQueueItemId(
                     trackNumber - 1).build();
@@ -176,7 +184,7 @@
         mCurrentTrack = update;
     }
 
-    public synchronized MediaMetadataCompat getCurrentTrack() {
+    public synchronized AvrcpItem getCurrentTrack() {
         return mCurrentTrack;
     }
 
diff --git a/android/app/src/com/android/bluetooth/avrcpcontroller/BluetoothMediaBrowserService.java b/android/app/src/com/android/bluetooth/avrcpcontroller/BluetoothMediaBrowserService.java
index 1da3883..a47932d 100644
--- a/android/app/src/com/android/bluetooth/avrcpcontroller/BluetoothMediaBrowserService.java
+++ b/android/app/src/com/android/bluetooth/avrcpcontroller/BluetoothMediaBrowserService.java
@@ -20,7 +20,6 @@
 import android.content.Intent;
 import android.os.Bundle;
 import android.support.v4.media.MediaBrowserCompat.MediaItem;
-import android.support.v4.media.MediaMetadataCompat;
 import android.support.v4.media.session.MediaControllerCompat;
 import android.support.v4.media.session.MediaSessionCompat;
 import android.support.v4.media.session.PlaybackStateCompat;
@@ -185,9 +184,15 @@
         }
     }
 
-    static synchronized void trackChanged(MediaMetadataCompat mediaMetadata) {
+    static synchronized void trackChanged(AvrcpItem track) {
+        if (DBG) Log.d(TAG, "trackChanged setMetadata=" + track);
         if (sBluetoothMediaBrowserService != null) {
-            sBluetoothMediaBrowserService.mSession.setMetadata(mediaMetadata);
+            if (track != null) {
+                sBluetoothMediaBrowserService.mSession.setMetadata(track.toMediaMetadata());
+            } else {
+                sBluetoothMediaBrowserService.mSession.setMetadata(null);
+            }
+
         } else {
             Log.w(TAG, "trackChanged Unavailable");
         }
diff --git a/android/app/src/com/android/bluetooth/avrcpcontroller/BrowseTree.java b/android/app/src/com/android/bluetooth/avrcpcontroller/BrowseTree.java
index d94f6da..0af07d2 100644
--- a/android/app/src/com/android/bluetooth/avrcpcontroller/BrowseTree.java
+++ b/android/app/src/com/android/bluetooth/avrcpcontroller/BrowseTree.java
@@ -17,9 +17,7 @@
 package com.android.bluetooth.avrcpcontroller;
 
 import android.bluetooth.BluetoothDevice;
-import android.os.Bundle;
 import android.support.v4.media.MediaBrowserCompat.MediaItem;
-import android.support.v4.media.MediaDescriptionCompat;
 import android.util.Log;
 
 import java.util.ArrayList;
@@ -27,17 +25,22 @@
 import java.util.List;
 import java.util.UUID;
 
-// Browsing hierarchy.
-// Root:
-//      Player1:
-//        Now_Playing:
-//          MediaItem1
-//          MediaItem2
-//        Folder1
-//        Folder2
-//        ....
-//      Player2
-//      ....
+/**
+ * An object that holds the browse tree of available media from a remote device.
+ *
+ * Browsing hierarchy follows the AVRCP specification's description of various scopes and
+ * looks like follows:
+ *    Root:
+ *      Player1:
+ *        Now_Playing:
+ *           MediaItem1
+ *           MediaItem2
+ *        Folder1
+ *        Folder2
+ *          ....
+ *        Player2
+ *          ....
+ */
 public class BrowseTree {
     private static final String TAG = "BrowseTree";
     private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
@@ -60,26 +63,23 @@
 
     BrowseTree(BluetoothDevice device) {
         if (device == null) {
-            mRootNode = new BrowseNode(new MediaItem(new MediaDescriptionCompat.Builder()
-                    .setMediaId(ROOT).setTitle(ROOT).build(), MediaItem.FLAG_BROWSABLE));
+            mRootNode = new BrowseNode(new AvrcpItem.Builder()
+                    .setUuid(ROOT).setTitle(ROOT).setBrowsable(true).build());
             mRootNode.setCached(true);
         } else {
-            mRootNode = new BrowseNode(new MediaItem(new MediaDescriptionCompat.Builder()
-                    .setMediaId(ROOT + device.getAddress().toString()).setTitle(
-                            device.getName()).build(), MediaItem.FLAG_BROWSABLE));
-            mRootNode.mDevice = device;
-
+            mRootNode = new BrowseNode(new AvrcpItem.Builder().setDevice(device)
+                    .setUuid(ROOT + device.getAddress().toString())
+                    .setTitle(device.getName()).setBrowsable(true).build());
         }
         mRootNode.mBrowseScope = AvrcpControllerService.BROWSE_SCOPE_PLAYER_LIST;
         mRootNode.setExpectedChildren(255);
 
-        mNavigateUpNode = new BrowseNode(new MediaItem(new MediaDescriptionCompat.Builder()
-                .setMediaId(UP).setTitle(UP).build(),
-                MediaItem.FLAG_BROWSABLE));
+        mNavigateUpNode = new BrowseNode(new AvrcpItem.Builder()
+                .setUuid(UP).setTitle(UP).setBrowsable(true).build());
 
-        mNowPlayingNode = new BrowseNode(new MediaItem(new MediaDescriptionCompat.Builder()
-                .setMediaId(NOW_PLAYING_PREFIX)
-                .setTitle(NOW_PLAYING_PREFIX).build(), MediaItem.FLAG_BROWSABLE));
+        mNowPlayingNode = new BrowseNode(new AvrcpItem.Builder()
+                .setUuid(NOW_PLAYING_PREFIX).setTitle(NOW_PLAYING_PREFIX)
+                .setBrowsable(true).build());
         mNowPlayingNode.mBrowseScope = AvrcpControllerService.BROWSE_SCOPE_NOW_PLAYING;
         mNowPlayingNode.setExpectedChildren(255);
         mBrowseMap.put(ROOT, mRootNode);
@@ -104,11 +104,8 @@
 
     // 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;
-
-        BluetoothDevice mDevice;
-        long mBluetoothId;
+        // AvrcpItem to store the media related details.
+        AvrcpItem mItem;
 
         // Type of this browse node.
         // Since Media APIs do not define the player separately we define that
@@ -126,45 +123,43 @@
         private final List<BrowseNode> mChildren = new ArrayList<BrowseNode>();
         private int mExpectedChildrenCount;
 
-        BrowseNode(MediaItem item) {
+        BrowseNode(AvrcpItem item) {
             mItem = item;
-            Bundle extras = mItem.getDescription().getExtras();
-            if (extras != null) {
-                mBluetoothId = extras.getLong(AvrcpControllerService.MEDIA_ITEM_UID_KEY);
-            }
         }
 
         BrowseNode(AvrcpPlayer player) {
             mIsPlayer = true;
 
             // Transform the player into a item.
-            MediaDescriptionCompat.Builder mdb = new MediaDescriptionCompat.Builder();
-            String playerKey = PLAYER_PREFIX + player.getId();
-            mBluetoothId = player.getId();
-
-            mdb.setMediaId(UUID.randomUUID().toString());
-            mdb.setTitle(player.getName());
-            int mediaItemFlags = player.supportsFeature(AvrcpPlayer.FEATURE_BROWSING)
-                    ? MediaItem.FLAG_BROWSABLE : 0;
-            mItem = new MediaItem(mdb.build(), mediaItemFlags);
+            AvrcpItem.Builder aid = new AvrcpItem.Builder();
+            aid.setDevice(player.getDevice());
+            aid.setUid(player.getId());
+            aid.setUuid(UUID.randomUUID().toString());
+            aid.setDisplayableName(player.getName());
+            aid.setTitle(player.getName());
+            aid.setBrowsable(player.supportsFeature(AvrcpPlayer.FEATURE_BROWSING));
+            mItem = aid.build();
         }
 
         BrowseNode(BluetoothDevice device) {
-            boolean mIsPlayer = true;
-            mDevice = device;
-            MediaDescriptionCompat.Builder mdb = new MediaDescriptionCompat.Builder();
+            mIsPlayer = true;
             String playerKey = PLAYER_PREFIX + device.getAddress().toString();
-            mdb.setMediaId(playerKey);
-            mdb.setTitle(device.getName());
-            int mediaItemFlags = MediaItem.FLAG_BROWSABLE;
-            mItem = new MediaItem(mdb.build(), mediaItemFlags);
+
+            AvrcpItem.Builder aid = new AvrcpItem.Builder();
+            aid.setDevice(device);
+            aid.setUuid(playerKey);
+            aid.setDisplayableName(device.getName());
+            aid.setTitle(device.getName());
+            aid.setBrowsable(true);
+            mItem = aid.build();
         }
 
         private BrowseNode(String name) {
-            MediaDescriptionCompat.Builder mdb = new MediaDescriptionCompat.Builder();
-            mdb.setMediaId(name);
-            mdb.setTitle(name);
-            mItem = new MediaItem(mdb.build(), MediaItem.FLAG_BROWSABLE);
+            AvrcpItem.Builder aid = new AvrcpItem.Builder();
+            aid.setUuid(name);
+            aid.setDisplayableName(name);
+            aid.setTitle(name);
+            mItem = aid.build();
         }
 
         synchronized void setExpectedChildren(int count) {
@@ -178,8 +173,8 @@
         synchronized <E> int addChildren(List<E> newChildren) {
             for (E child : newChildren) {
                 BrowseNode currentNode = null;
-                if (child instanceof MediaItem) {
-                    currentNode = new BrowseNode((MediaItem) child);
+                if (child instanceof AvrcpItem) {
+                    currentNode = new BrowseNode((AvrcpItem) child);
                 } else if (child instanceof AvrcpPlayer) {
                     currentNode = new BrowseNode((AvrcpPlayer) child);
                 }
@@ -194,9 +189,6 @@
                 if (this.mBrowseScope == AvrcpControllerService.BROWSE_SCOPE_NOW_PLAYING) {
                     node.mBrowseScope = this.mBrowseScope;
                 }
-                if (node.mDevice == null) {
-                    node.mDevice = this.mDevice;
-                }
                 mChildren.add(node);
                 mBrowseMap.put(node.getID(), node);
                 return true;
@@ -228,6 +220,10 @@
             return mParent;
         }
 
+        synchronized BluetoothDevice getDevice() {
+            return mItem.getDevice();
+        }
+
         synchronized List<MediaItem> getContents() {
             if (mChildren.size() > 0 || mCached) {
                 List<MediaItem> contents = new ArrayList<MediaItem>(mChildren.size());
@@ -264,7 +260,7 @@
 
         // Fetch the Unique UID for this item, this is unique across all elements in the tree.
         synchronized String getID() {
-            return mItem.getDescription().getMediaId();
+            return mItem.getUuid();
         }
 
         // Get the BT Player ID associated with this node.
@@ -284,11 +280,11 @@
         }
 
         synchronized long getBluetoothID() {
-            return mBluetoothId;
+            return mItem.getUid();
         }
 
         synchronized MediaItem getMediaItem() {
-            return mItem;
+            return mItem.toMediaItem();
         }
 
         synchronized boolean isPlayer() {
@@ -311,7 +307,7 @@
         @Override
         public synchronized String toString() {
             if (VDBG) {
-                String serialized = "[ Name: " + mItem.getDescription().getTitle()
+                String serialized = "[ Name: " + mItem.getTitle()
                         + " Scope:" + mBrowseScope + " expected Children: "
                         + mExpectedChildrenCount + "] ";
                 for (BrowseNode node : mChildren) {
diff --git a/android/app/src/com/android/bluetooth/avrcpcontroller/TrackInfo.java b/android/app/src/com/android/bluetooth/avrcpcontroller/TrackInfo.java
deleted file mode 100644
index 9b192ac..0000000
--- a/android/app/src/com/android/bluetooth/avrcpcontroller/TrackInfo.java
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.bluetooth.avrcpcontroller;
-
-import android.support.v4.media.MediaMetadataCompat;
-
-final class TrackInfo {
-    /*
-     *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;
-
-    static MediaMetadataCompat getMetadata(int[] attrIds, String[] attrMap) {
-        MediaMetadataCompat.Builder metaDataBuilder = new MediaMetadataCompat.Builder();
-        int attributeCount = Math.max(attrIds.length, attrMap.length);
-        for (int i = 0; i < attributeCount; i++) {
-            switch (attrIds[i]) {
-                case MEDIA_ATTRIBUTE_TITLE:
-                    metaDataBuilder.putString(MediaMetadataCompat.METADATA_KEY_TITLE, attrMap[i]);
-                    break;
-                case MEDIA_ATTRIBUTE_ARTIST_NAME:
-                    metaDataBuilder.putString(MediaMetadataCompat.METADATA_KEY_ARTIST, attrMap[i]);
-                    break;
-                case MEDIA_ATTRIBUTE_ALBUM_NAME:
-                    metaDataBuilder.putString(MediaMetadataCompat.METADATA_KEY_ALBUM, attrMap[i]);
-                    break;
-                case MEDIA_ATTRIBUTE_TRACK_NUMBER:
-                    try {
-                        metaDataBuilder.putLong(MediaMetadataCompat.METADATA_KEY_TRACK_NUMBER,
-                                Long.valueOf(attrMap[i]));
-                    } catch (java.lang.NumberFormatException e) {
-                        // If Track Number doesn't parse, leave it unset
-                    }
-                    break;
-                case MEDIA_ATTRIBUTE_TOTAL_TRACK_NUMBER:
-                    try {
-                        metaDataBuilder.putLong(MediaMetadataCompat.METADATA_KEY_NUM_TRACKS,
-                                Long.valueOf(attrMap[i]));
-                    } catch (java.lang.NumberFormatException e) {
-                        // If Total Track Number doesn't parse, leave it unset
-                    }
-                    break;
-                case MEDIA_ATTRIBUTE_GENRE:
-                    metaDataBuilder.putString(MediaMetadataCompat.METADATA_KEY_GENRE, attrMap[i]);
-                    break;
-                case MEDIA_ATTRIBUTE_PLAYING_TIME:
-                    try {
-                        metaDataBuilder.putLong(MediaMetadataCompat.METADATA_KEY_DURATION,
-                                Long.valueOf(attrMap[i]));
-                    } catch (java.lang.NumberFormatException e) {
-                        // If Playing Time doesn't parse, leave it unset
-                    }
-                    break;
-            }
-        }
-        return metaDataBuilder.build();
-    }
-}
diff --git a/android/app/tests/unit/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachineTest.java b/android/app/tests/unit/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachineTest.java
index 494994d..f869fe1 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachineTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachineTest.java
@@ -485,7 +485,7 @@
         //Provide back a player object
         byte[] playerFeatures =
                 new byte[]{0, 0, 0, 0, 0, (byte) 0xb7, 0x01, 0x0c, 0x0a, 0, 0, 0, 0, 0, 0, 0};
-        AvrcpPlayer playerOne = new AvrcpPlayer(1, playerName, playerFeatures, 1, 1);
+        AvrcpPlayer playerOne = new AvrcpPlayer(mTestDevice, 1, playerName, playerFeatures, 1, 1);
         List<AvrcpPlayer> testPlayers = new ArrayList<>();
         testPlayers.add(playerOne);
         mAvrcpStateMachine.sendMessage(AvrcpControllerStateMachine.MESSAGE_PROCESS_GET_PLAYER_ITEMS,
@@ -541,7 +541,7 @@
         //Provide back a player object
         byte[] playerFeatures =
                 new byte[]{0, 0, 0, 0, 0, (byte) 0xb7, 0x01, 0x0c, 0x0a, 0, 0, 0, 0, 0, 0, 0};
-        AvrcpPlayer playerOne = new AvrcpPlayer(1, playerName, playerFeatures, 1, 1);
+        AvrcpPlayer playerOne = new AvrcpPlayer(mTestDevice, 1, playerName, playerFeatures, 1, 1);
         List<AvrcpPlayer> testPlayers = new ArrayList<>();
         testPlayers.add(playerOne);
         mAvrcpStateMachine.sendMessage(AvrcpControllerStateMachine.MESSAGE_PROCESS_GET_PLAYER_ITEMS,