Merge "Move libutils static -> shared."
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 3b505fd..b43606a 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -310,7 +310,7 @@
         </service>
         <service
             android:process="@string/process"
-            android:name=".a2dpsink.mbs.A2dpMediaBrowserService"
+            android:name=".avrcpcontroller.BluetoothMediaBrowserService"
             android:exported="true"
             android:enabled="@bool/profile_supported_a2dp_sink"
             android:label="@string/a2dp_sink_mbs_label">
diff --git a/src/com/android/bluetooth/a2dpsink/A2dpSinkService.java b/src/com/android/bluetooth/a2dpsink/A2dpSinkService.java
index 17c8885..17095c0 100644
--- a/src/com/android/bluetooth/a2dpsink/A2dpSinkService.java
+++ b/src/com/android/bluetooth/a2dpsink/A2dpSinkService.java
@@ -25,8 +25,8 @@
 import android.util.Log;
 
 import com.android.bluetooth.Utils;
-import com.android.bluetooth.a2dpsink.mbs.A2dpMediaBrowserService;
 import com.android.bluetooth.avrcpcontroller.AvrcpControllerService;
+import com.android.bluetooth.avrcpcontroller.BluetoothMediaBrowserService;
 import com.android.bluetooth.btservice.ProfileService;
 
 import java.util.ArrayList;
@@ -54,7 +54,7 @@
             Log.d(TAG, "start()");
         }
         // Start the media browser service.
-        Intent startIntent = new Intent(this, A2dpMediaBrowserService.class);
+        Intent startIntent = new Intent(this, BluetoothMediaBrowserService.class);
         startService(startIntent);
         mStateMachine = A2dpSinkStateMachine.make(this, this);
         setA2dpSinkService(this);
@@ -70,7 +70,7 @@
         if (mStateMachine != null) {
             mStateMachine.doQuit();
         }
-        Intent stopIntent = new Intent(this, A2dpMediaBrowserService.class);
+        Intent stopIntent = new Intent(this, BluetoothMediaBrowserService.class);
         stopService(stopIntent);
         return true;
     }
diff --git a/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerService.java b/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerService.java
index 76adadb..d234c5b 100644
--- a/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerService.java
+++ b/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerService.java
@@ -172,8 +172,8 @@
     /* 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;
+    public static final byte FOLDER_NAVIGATION_DIRECTION_UP = 0x00;
+    public static final byte FOLDER_NAVIGATION_DIRECTION_DOWN = 0x01;
 
     /* Folder/Media Item scopes.
      * Keep in sync with AVRCP 1.6 sec. 6.10.1
@@ -398,77 +398,12 @@
     }
 
     /**
-     * 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
+     * Retreive the contents of the directory from the associated bluetooth device.
      */
-    public synchronized boolean getChildren(BluetoothDevice device, String parentMediaId, int start,
-            int items) {
-        if (DBG) {
-            Log.d(TAG, "getChildren device = " + device + " parent " + parentMediaId);
-        }
-
-        if (device == null) {
-            Log.e(TAG, "getChildren device is null");
-            return false;
-        }
-
-        if (!device.equals(mConnectedDevice)) {
-            Log.e(TAG, "getChildren device " + device + " does not match " + mConnectedDevice);
-            return false;
-        }
-
-        if (!mBrowseConnected) {
-            Log.e(TAG, "getChildren browse not yet connected");
-            return false;
-        }
-
-        if (!mAvrcpCtSm.isConnected()) {
-            return false;
-        }
-        mAvrcpCtSm.getChildren(parentMediaId, start, items);
-        return true;
+    public synchronized List<MediaItem> getContents(BluetoothDevice device, String parentMediaId) {
+        return mAvrcpCtSm.getContents(parentMediaId);
     }
-
-    public synchronized boolean 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 false;
-        }
-
-        if (!device.equals(mConnectedDevice)) {
-            Log.e(TAG,
-                    "getNowPlayingList device " + device + " does not match " + mConnectedDevice);
-            return false;
-        }
-
-        if (!mBrowseConnected) {
-            Log.e(TAG, "getNowPlayingList browse not yet connected");
-            return false;
-        }
-
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-
-        Message msg =
-                mAvrcpCtSm.obtainMessage(AvrcpControllerStateMachine.MESSAGE_GET_NOW_PLAYING_LIST,
-                        start, items, id);
-        mAvrcpCtSm.sendMessage(msg);
-        return true;
-    }
-
+/*
     public synchronized boolean getFolderList(BluetoothDevice device, String id, int start,
             int items) {
         if (DBG) {
@@ -499,100 +434,7 @@
         mAvrcpCtSm.sendMessage(msg);
         return true;
     }
-
-    public synchronized boolean 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 false;
-        }
-
-        if (!device.equals(mConnectedDevice)) {
-            Log.e(TAG, "getPlayerList device " + device + " does not match " + mConnectedDevice);
-            return false;
-        }
-
-        if (!mBrowseConnected) {
-            Log.e(TAG, "getPlayerList browse not yet connected");
-            return false;
-        }
-
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-
-        Message msg =
-                mAvrcpCtSm.obtainMessage(AvrcpControllerStateMachine.MESSAGE_GET_PLAYER_LIST, start,
-                        items);
-        mAvrcpCtSm.sendMessage(msg);
-        return true;
-    }
-
-    public synchronized boolean 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 false;
-        }
-
-        if (!device.equals(mConnectedDevice)) {
-            Log.e(TAG, "changeFolderPath device " + device + " does not match " + mConnectedDevice);
-            return false;
-        }
-
-        if (!mBrowseConnected) {
-            Log.e(TAG, "changeFolderPath browse not yet connected");
-            return false;
-        }
-
-        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);
-        return true;
-    }
-
-    public synchronized boolean 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 false;
-        }
-
-        if (!device.equals(mConnectedDevice)) {
-            Log.e(TAG, "changeFolderPath device " + device + " does not match " + mConnectedDevice);
-            return false;
-        }
-
-        if (!mBrowseConnected) {
-            Log.e(TAG, "setBrowsedPlayer browse not yet connected");
-            return false;
-        }
-
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-
-        Message msg =
-                mAvrcpCtSm.obtainMessage(AvrcpControllerStateMachine.MESSAGE_SET_BROWSED_PLAYER, id,
-                        0, fid);
-        mAvrcpCtSm.sendMessage(msg);
-        return true;
-    }
-
+*/
     public synchronized void fetchAttrAndPlayItem(BluetoothDevice device, String uid) {
         if (DBG) {
             Log.d(TAG, "fetchAttrAndPlayItem device = " + device + " uid " + uid);
diff --git a/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java b/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java
index 5624d22..b2b8f0a 100644
--- a/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java
+++ b/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java
@@ -17,7 +17,6 @@
 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;
@@ -25,11 +24,9 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.media.AudioManager;
-import android.media.MediaDescription;
 import android.media.MediaMetadata;
 import android.media.browse.MediaBrowser.MediaItem;
 import android.media.session.PlaybackState;
-import android.os.Bundle;
 import android.os.Message;
 import android.util.Log;
 import android.util.SparseArray;
@@ -54,12 +51,8 @@
     // 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;
@@ -86,8 +79,6 @@
     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 MESSAGE_INTERNAL_ABS_VOL_TIMEOUT = 404;
 
@@ -119,15 +110,10 @@
 
     private final State mDisconnected;
     private final State mConnected;
-    private final SetBrowsedPlayer mSetBrowsedPlayer;
     private final SetAddresedPlayerAndPlayItem mSetAddrPlayer;
-    private final ChangeFolderPath mChangeFolderPath;
     private final GetFolderList mGetFolderList;
-    private final GetPlayerListing mGetPlayerListing;
-    private final MoveToRoot mMoveToRoot;
 
     private final Object mLock = new Object();
-    private static final ArrayList<MediaItem> EMPTY_MEDIA_ITEM_LIST = new ArrayList<>();
     private static final MediaMetadata EMPTY_MEDIA_METADATA = new MediaMetadata.Builder().build();
 
     // APIs exist to access these so they must be thread safe
@@ -141,9 +127,6 @@
     private int mAddressedPlayerID = -1;
     private SparseArray<AvrcpPlayer> mAvailablePlayerList = new SparseArray<AvrcpPlayer>();
 
-    // Depth from root of current browsing. This can be used to move to root directly.
-    private int mBrowseDepth = 0;
-
     // Browse tree.
     private BrowseTree mBrowseTree = new BrowseTree();
 
@@ -159,12 +142,8 @@
         mConnected = new Connected();
 
         // Used to change folder path and fetch the new folder listing.
-        mSetBrowsedPlayer = new SetBrowsedPlayer();
         mSetAddrPlayer = new SetAddresedPlayerAndPlayItem();
-        mChangeFolderPath = new ChangeFolderPath();
         mGetFolderList = new GetFolderList();
-        mGetPlayerListing = new GetPlayerListing();
-        mMoveToRoot = new MoveToRoot();
 
         addState(mDisconnected);
         addState(mConnected);
@@ -173,12 +152,8 @@
         // 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(mSetAddrPlayer, mConnected);
-        addState(mChangeFolderPath, mConnected);
         addState(mGetFolderList, mConnected);
-        addState(mGetPlayerListing, mConnected);
-        addState(mMoveToRoot, mConnected);
 
         setInitialState(mDisconnected);
     }
@@ -191,7 +166,7 @@
             switch (msg.what) {
                 case MESSAGE_PROCESS_CONNECTION_CHANGE:
                     if (msg.arg1 == BluetoothProfile.STATE_CONNECTED) {
-                        mBrowseTree.init();
+                        mBrowseTree = new BrowseTree();
                         transitionTo(mConnected);
                         BluetoothDevice rtDevice = (BluetoothDevice) msg.obj;
                         synchronized (mLock) {
@@ -243,68 +218,36 @@
                                 mRemoteDevice.getBluetoothAddress(), msg.arg1, msg.arg2);
                         break;
 
-                    case MESSAGE_GET_NOW_PLAYING_LIST:
-                        mGetFolderList.setFolder((String) msg.obj);
-                        mGetFolderList.setBounds((int) msg.arg1, (int) msg.arg2);
-                        mGetFolderList.setScope(AvrcpControllerService.BROWSE_SCOPE_NOW_PLAYING);
-                        transitionTo(mGetFolderList);
-                        break;
-
                     case MESSAGE_GET_FOLDER_LIST:
                         // Whenever we transition we set the information for folder we need to
                         // return result.
-                        mGetFolderList.setBounds(msg.arg1, msg.arg2);
+                        if (DBG) Log.d(TAG, "Message_GET_FOLDER_LIST" + (String) msg.obj);
                         mGetFolderList.setFolder((String) msg.obj);
-                        mGetFolderList.setScope(AvrcpControllerService.BROWSE_SCOPE_VFS);
                         transitionTo(mGetFolderList);
                         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 playItemUid = (String) msg.obj;
                         BrowseTree.BrowseNode currBrPlayer = mBrowseTree.getCurrentBrowsedPlayer();
                         BrowseTree.BrowseNode currAddrPlayer =
                                 mBrowseTree.getCurrentAddressedPlayer();
+                        BrowseTree.BrowseNode itemToPlay =
+                                mBrowseTree.findBrowseNodeByID(playItemUid);
                         if (DBG) {
                             Log.d(TAG, "currBrPlayer " + currBrPlayer + " currAddrPlayer "
                                     + currAddrPlayer);
                         }
-
-                        if (currBrPlayer == null || currBrPlayer.equals(currAddrPlayer)) {
+                        if (currBrPlayer == null
+                                || currBrPlayer.equals(currAddrPlayer)
+                                || scope == AvrcpControllerService.BROWSE_SCOPE_NOW_PLAYING) {
                             // String is encoded as a Hex String (mostly for display purposes)
                             // hence convert this back to real byte string.
                             // NOTE: It may be possible that sending play while the same item is
                             // playing leads to reset of track.
                             AvrcpControllerService.playItemNative(
                                     mRemoteDevice.getBluetoothAddress(), (byte) scope,
-                                    AvrcpControllerService.hexStringToByteUID(playItemUid),
-                                    (int) 0);
+                                    AvrcpControllerService.hexStringToByteUID(playItemUid), 0);
                         } else {
                             // Send out the request for setting addressed player.
                             AvrcpControllerService.setAddressedPlayerNative(
@@ -317,22 +260,6 @@
                         break;
                     }
 
-                    case MESSAGE_SET_BROWSED_PLAYER: {
-                        AvrcpControllerService.setBrowsedPlayerNative(
-                                mRemoteDevice.getBluetoothAddress(), (int) msg.arg1);
-                        mSetBrowsedPlayer.setFolder((String) msg.obj);
-                        transitionTo(mSetBrowsedPlayer);
-                        break;
-                    }
-
-                    case MESSAGE_PROCESS_SET_ADDRESSED_PLAYER:
-                        AvrcpControllerService.getPlayerListNative(
-                                mRemoteDevice.getBluetoothAddress(), 0, 255);
-                        transitionTo(mGetPlayerListing);
-                        sendMessageDelayed(MESSAGE_INTERNAL_CMD_TIMEOUT, CMD_TIMEOUT_MILLIS);
-                        break;
-
-
                     case MESSAGE_PROCESS_CONNECTION_CHANGE:
                         if (msg.arg1 == BluetoothProfile.STATE_DISCONNECTED) {
                             synchronized (mLock) {
@@ -370,9 +297,6 @@
                         } 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);
                         }
@@ -438,9 +362,13 @@
 
                     case MESSAGE_PROCESS_TRACK_CHANGED:
                         // Music start playing automatically and update Metadata
-                        mAddressedPlayer.updateCurrentTrack((TrackInfo) msg.obj);
-                        broadcastMetaDataChanged(
-                                mAddressedPlayer.getCurrentTrack().getMediaMetaData());
+                        boolean updateTrack =
+                                mAddressedPlayer.updateCurrentTrack((TrackInfo) msg.obj);
+                        if (updateTrack) {
+                            mBrowseTree.mNowPlayingNode.setCached(false);
+                            broadcastMetaDataChanged(
+                                    mAddressedPlayer.getCurrentTrack().getMediaMetaData());
+                        }
                         break;
 
                     case MESSAGE_PROCESS_PLAY_POS_CHANGED:
@@ -469,11 +397,14 @@
                         if (updatedPlayer != null) {
                             mAddressedPlayer = updatedPlayer;
                             if (DBG) Log.d(TAG, "AddressedPlayer = " + mAddressedPlayer.getName());
+                        } else {
+                            mBrowseTree.mRootNode.setCached(false);
                         }
                         sendMessage(MESSAGE_PROCESS_SET_ADDRESSED_PLAYER);
                         break;
 
                     default:
+                        Log.d(TAG, "Unhandled message" + msg.what);
                         return false;
                 }
             }
@@ -481,133 +412,31 @@
         }
     }
 
-    // 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 static final 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) {
-            if (DBG) Log.d(STATE_TAG, "processMessage " + msg.what);
-            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.
-                    if (DBG) {
-                        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);
-                    }
-                    if (DBG) Log.d(STATE_TAG, "New browse depth " + mBrowseDepth);
-
-                    if (msg.arg1 > 0) {
-                        sendMessage(MESSAGE_GET_FOLDER_LIST, 0, msg.arg1 - 1, mID);
-                    } else {
-                        // Return an empty response to the upper layer.
-                        broadcastFolderList(mID, EMPTY_MEDIA_ITEM_LIST);
-                    }
-                    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, EMPTY_MEDIA_ITEM_LIST);
-                    transitionTo(mConnected);
-                    break;
-
-                case MESSAGE_SEND_PASS_THROUGH_CMD:
-                case MESSAGE_SEND_GROUP_NAVIGATION_CMD:
-                case MESSAGE_PROCESS_SET_ABS_VOL_CMD:
-                case MESSAGE_PROCESS_REGISTER_ABS_VOL_NOTIFICATION:
-                case MESSAGE_PROCESS_TRACK_CHANGED:
-                case MESSAGE_PROCESS_PLAY_POS_CHANGED:
-                case MESSAGE_PROCESS_PLAY_STATUS_CHANGED:
-                case MESSAGE_PROCESS_VOLUME_CHANGED_NOTIFICATION:
-                case MESSAGE_STOP_METADATA_BROADCASTS:
-                case MESSAGE_START_METADATA_BROADCASTS:
-                case MESSAGE_PROCESS_CONNECTION_CHANGE:
-                case MESSAGE_PROCESS_BROWSE_CONNECTION_CHANGE:
-                    // All of these messages should be handled by parent state immediately.
-                    return false;
-
-                default:
-                    if (DBG) {
-                        Log.d(STATE_TAG, "deferring message " + msg.what + " 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 static final String STATE_TAG = "AVRCPSM.GetFolderList";
 
-        String mID = "";
-        int mStartInd;
-        int mEndInd;
-        int mCurrInd;
-        int mScope;
-        private ArrayList<MediaItem> mFolderList = new ArrayList<>();
+        boolean mAbort;
+        BrowseTree.BrowseNode mBrowseNode;
+        BrowseTree.BrowseNode mNextStep;
 
         @Override
         public void enter() {
             // Setup the timeouts.
             super.enter();
-            mCurrInd = 0;
-            mFolderList.clear();
-            callNativeFunctionForScope(mStartInd,
-                    Math.min(mEndInd, mStartInd + GET_FOLDER_ITEMS_PAGINATION_SIZE - 1));
-        }
-
-        public void setScope(int scope) {
-            mScope = scope;
+            mAbort = false;
+            if (mBrowseNode == null) {
+                transitionTo(mConnected);
+            } else {
+                navigateToFolderOrRetrieve(mBrowseNode);
+            }
         }
 
         public void setFolder(String id) {
             if (DBG) Log.d(STATE_TAG, "Setting folder to " + id);
-            mID = id;
-        }
-
-        public void setBounds(int startInd, int endInd) {
-            if (DBG) {
-                Log.d(STATE_TAG, "startInd " + startInd + " endInd " + endInd);
-            }
-            mStartInd = startInd;
-            mEndInd = Math.min(endInd, MAX_FOLDER_ITEMS);
+            mBrowseNode = mBrowseTree.findBrowseNodeByID(id);
         }
 
         @Override
@@ -616,37 +445,75 @@
             switch (msg.what) {
                 case MESSAGE_PROCESS_GET_FOLDER_ITEMS:
                     ArrayList<MediaItem> folderList = (ArrayList<MediaItem>) msg.obj;
-                    mFolderList.addAll(folderList);
+                    int endIndicator = mBrowseNode.getExpectedChildren() - 1;
                     if (DBG) {
                         Log.d(STATE_TAG,
-                                "Start " + mStartInd + " End " + mEndInd + " Curr " + mCurrInd
+                                " End " + endIndicator
                                         + " received " + folderList.size());
                     }
-                    mCurrInd += folderList.size();
 
                     // Always update the node so that the user does not wait forever
                     // for the list to populate.
-                    sendFolderBroadcastAndUpdateNode();
+                    mBrowseNode.addChildren(folderList);
+                    broadcastFolderList(mBrowseNode.getID());
 
-                    if (mCurrInd > mEndInd || folderList.size() == 0) {
+                    if (mBrowseNode.getChildrenCount() >= endIndicator || folderList.size() == 0
+                            || mAbort) {
                         // If we have fetched all the elements or if the remotes sends us 0 elements
                         // (which can lead us into a loop since mCurrInd does not proceed) we simply
                         // abort.
+                        mBrowseNode.setCached(true);
                         transitionTo(mConnected);
                     } else {
                         // Fetch the next set of items.
-                        callNativeFunctionForScope(mCurrInd, Math.min(mEndInd,
-                                mCurrInd + GET_FOLDER_ITEMS_PAGINATION_SIZE - 1));
+                        fetchContents(mBrowseNode);
                         // Reset the timeout message since we are doing a new fetch now.
                         removeMessages(MESSAGE_INTERNAL_CMD_TIMEOUT);
                         sendMessageDelayed(MESSAGE_INTERNAL_CMD_TIMEOUT, CMD_TIMEOUT_MILLIS);
                     }
                     break;
+                case MESSAGE_PROCESS_SET_BROWSED_PLAYER:
+                    mBrowseTree.setCurrentBrowsedPlayer(mNextStep.getID(), msg.arg1, msg.arg2);
+                    removeMessages(MESSAGE_INTERNAL_CMD_TIMEOUT);
+                    sendMessageDelayed(MESSAGE_INTERNAL_CMD_TIMEOUT, CMD_TIMEOUT_MILLIS);
+                    navigateToFolderOrRetrieve(mBrowseNode);
+                    break;
+
+                case MESSAGE_PROCESS_FOLDER_PATH:
+                    mBrowseTree.setCurrentBrowsedFolder(mNextStep.getID());
+                    mBrowseTree.getCurrentBrowsedFolder().setExpectedChildren(msg.arg1);
+
+                    if (mAbort) {
+                        transitionTo(mConnected);
+                    } else {
+                        removeMessages(MESSAGE_INTERNAL_CMD_TIMEOUT);
+                        sendMessageDelayed(MESSAGE_INTERNAL_CMD_TIMEOUT, CMD_TIMEOUT_MILLIS);
+                        navigateToFolderOrRetrieve(mBrowseNode);
+                    }
+                    break;
+
+                case MESSAGE_PROCESS_GET_PLAYER_ITEMS:
+                    BrowseTree.BrowseNode rootNode = mBrowseTree.mRootNode;
+                    if (!rootNode.isCached()) {
+                        List<AvrcpPlayer> playerList = (List<AvrcpPlayer>) msg.obj;
+                        mAvailablePlayerList.clear();
+                        for (AvrcpPlayer player : playerList) {
+                            mAvailablePlayerList.put(player.getId(), player);
+                        }
+                        rootNode.addChildren(playerList);
+                        mBrowseTree.setCurrentBrowsedFolder(BrowseTree.ROOT);
+                        rootNode.setExpectedChildren(playerList.size());
+                        rootNode.setCached(true);
+                        broadcastFolderList(BrowseTree.ROOT);
+                    }
+                    transitionTo(mConnected);
+                    break;
+
 
                 case MESSAGE_INTERNAL_CMD_TIMEOUT:
                     // We have timed out to execute the request, we should simply send
                     // whatever listing we have gotten until now.
-                    sendFolderBroadcastAndUpdateNode();
+                    broadcastFolderList(mBrowseNode.getID());
                     transitionTo(mConnected);
                     break;
 
@@ -654,16 +521,24 @@
                     // If we have gotten an error for OUT OF RANGE we have
                     // already sent all the items to the client hence simply
                     // transition to Connected state here.
+                    mBrowseNode.setCached(true);
+                    broadcastFolderList(mBrowseNode.getID());
                     transitionTo(mConnected);
                     break;
 
-                case MESSAGE_CHANGE_FOLDER_PATH:
+                case MESSAGE_GET_FOLDER_LIST:
+                    if (!mBrowseNode.equals((String) msg.obj)) {
+                        mAbort = true;
+                        deferMessage(msg);
+                        Log.d(STATE_TAG, "Go Get Another Directory");
+                    } else {
+                        Log.d(STATE_TAG, "Get The Same Directory, ignore");
+                    }
+                    break;
+
                 case MESSAGE_FETCH_ATTR_AND_PLAY_ITEM:
-                case MESSAGE_GET_PLAYER_LIST:
-                case MESSAGE_GET_NOW_PLAYING_LIST:
-                case MESSAGE_SET_BROWSED_PLAYER:
                     // A new request has come in, no need to fetch more.
-                    mEndInd = 0;
+                    mAbort = true;
                     deferMessage(msg);
                     break;
 
@@ -689,246 +564,85 @@
             return true;
         }
 
-        private void sendFolderBroadcastAndUpdateNode() {
-            BrowseTree.BrowseNode bn = mBrowseTree.findBrowseNodeByID(mID);
-            if (bn == null) {
-                Log.e(TAG, "Can not find BrowseNode by ID: " + mID);
-                return;
-            }
-            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);
-                mFolderList.add(new MediaItem(mdb.build(), MediaItem.FLAG_BROWSABLE));
-            }
-            mBrowseTree.refreshChildren(bn, mFolderList);
-            broadcastFolderList(mID, mFolderList);
-
-            // For now playing we need to set the current browsed folder here.
-            // For normal folders it is set after ChangeFolderPath.
-            if (mScope == AvrcpControllerService.BROWSE_SCOPE_NOW_PLAYING) {
-                mBrowseTree.setCurrentBrowsedFolder(mID);
-            }
-        }
-
-        private void callNativeFunctionForScope(int start, int end) {
-            switch (mScope) {
+        private void fetchContents(BrowseTree.BrowseNode target) {
+            switch (target.getScope()) {
+                case AvrcpControllerService.BROWSE_SCOPE_PLAYER_LIST:
+                    AvrcpControllerService.getPlayerListNative(mRemoteDevice.getBluetoothAddress(),
+                            0, 255);
+                    break;
                 case AvrcpControllerService.BROWSE_SCOPE_NOW_PLAYING:
                     AvrcpControllerService.getNowPlayingListNative(
-                            mRemoteDevice.getBluetoothAddress(), start, end);
+                            mRemoteDevice.getBluetoothAddress(), target.getChildrenCount(),
+                            Math.min(target.getExpectedChildren(), target.getChildrenCount()
+                            + GET_FOLDER_ITEMS_PAGINATION_SIZE - 1));
                     break;
                 case AvrcpControllerService.BROWSE_SCOPE_VFS:
                     AvrcpControllerService.getFolderListNative(mRemoteDevice.getBluetoothAddress(),
-                            start, end);
+                            target.getChildrenCount(), Math.min(target.getExpectedChildren(),
+                                target.getChildrenCount() + GET_FOLDER_ITEMS_PAGINATION_SIZE - 1));
                     break;
                 default:
-                    Log.e(STATE_TAG, "Scope " + mScope + " cannot be handled here.");
+                    Log.e(STATE_TAG, "Scope " + target.getScope() + " cannot be handled here.");
             }
         }
-    }
 
-    // Handle the get player listing action
-    // a) Fetch the listing of players
-    // b) Once completed return the object listing
-    class GetPlayerListing extends CmdState {
-        private static final String STATE_TAG = "AVRCPSM.GetPlayerList";
-
-        @Override
-        public boolean processMessage(Message msg) {
-            if (DBG) Log.d(STATE_TAG, "processMessage " + msg.what);
-            switch (msg.what) {
-                case MESSAGE_PROCESS_GET_PLAYER_ITEMS:
-                    List<AvrcpPlayer> playerList = (List<AvrcpPlayer>) msg.obj;
-                    mAvailablePlayerList.clear();
-                    for (AvrcpPlayer player : playerList) {
-                        mAvailablePlayerList.put(player.getId(), player);
-                    }
-                    mBrowseTree.refreshChildren(BrowseTree.ROOT, playerList);
-                    ArrayList<MediaItem> mediaItemList = new ArrayList<>();
-                    for (BrowseTree.BrowseNode c : mBrowseTree.findBrowseNodeByID(BrowseTree.ROOT)
-                            .getChildren()) {
-                        mediaItemList.add(c.getMediaItem());
-                    }
-                    if (DBG) Log.d(TAG, "AddressedPlayer List Updated");
-                    AvrcpPlayer updatedPlayer = mAvailablePlayerList.get(mAddressedPlayerID);
-                    if (updatedPlayer != null) {
-                        mAddressedPlayer = updatedPlayer;
-                        if (DBG) Log.d(TAG, "AddressedPlayer = " + mAddressedPlayer.getName());
-                    }
-                    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, EMPTY_MEDIA_ITEM_LIST);
-                    transitionTo(mConnected);
-                    break;
-
-                case MESSAGE_SEND_PASS_THROUGH_CMD:
-                case MESSAGE_SEND_GROUP_NAVIGATION_CMD:
-                case MESSAGE_PROCESS_SET_ABS_VOL_CMD:
-                case MESSAGE_PROCESS_REGISTER_ABS_VOL_NOTIFICATION:
-                case MESSAGE_PROCESS_TRACK_CHANGED:
-                case MESSAGE_PROCESS_PLAY_POS_CHANGED:
-                case MESSAGE_PROCESS_PLAY_STATUS_CHANGED:
-                case MESSAGE_PROCESS_VOLUME_CHANGED_NOTIFICATION:
-                case MESSAGE_STOP_METADATA_BROADCASTS:
-                case MESSAGE_START_METADATA_BROADCASTS:
-                case MESSAGE_PROCESS_CONNECTION_CHANGE:
-                case MESSAGE_PROCESS_BROWSE_CONNECTION_CHANGE:
-                    // All of these messages should be handled by parent state immediately.
-                    return false;
-
-                default:
-                    if (DBG) Log.d(STATE_TAG, "deferring message " + msg.what + " to connected!");
-                    deferMessage(msg);
-            }
-            return true;
-        }
-    }
-
-    class MoveToRoot extends CmdState {
-        private static final String STATE_TAG = "AVRCPSM.MoveToRoot";
-        private String mID = "";
-
-        public void setFolder(String id) {
-            if (DBG) 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) {
+        /* One of several things can happen when trying to get a folder list
+         *
+         *
+         * 0: The folder handle is no longer valid
+         * 1: The folder contents can be retrieved directly (NowPlaying, Root, Current)
+         * 2: The folder is a browsable player
+         * 3: The folder is a non browsable player
+         * 4: The folder is not a child of the current folder
+         * 5: The folder is a child of the current folder
+         *
+         */
+        private void navigateToFolderOrRetrieve(BrowseTree.BrowseNode target) {
+            mNextStep = mBrowseTree.getNextStepToFolder(target);
             if (DBG) {
-                Log.d(STATE_TAG, "processMessage " + msg.what + " browse depth " + mBrowseDepth);
+                Log.d(TAG, "NAVIGATING From " + mBrowseTree.getCurrentBrowsedFolder().toString());
+                Log.d(TAG, "NAVIGATING Toward " + target.toString());
             }
-            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;
-                    if (DBG) 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;
-
-                case MESSAGE_INTERNAL_CMD_TIMEOUT:
-                    broadcastFolderList(BrowseTree.ROOT, EMPTY_MEDIA_ITEM_LIST);
+            if (mNextStep == null) {
+                sendMessage(MESSAGE_INTERNAL_CMD_TIMEOUT);
+            } else if (target.equals(mBrowseTree.mNowPlayingNode)
+                       || target.equals(mBrowseTree.mRootNode)
+                       || mNextStep.equals(mBrowseTree.getCurrentBrowsedFolder())) {
+                fetchContents(mNextStep);
+            } else if (mNextStep.isPlayer()) {
+                if (DBG) Log.d(TAG, "NAVIGATING Player " + mNextStep.toString());
+                if (mNextStep.isBrowsable()) {
+                    AvrcpControllerService.setBrowsedPlayerNative(
+                            mRemoteDevice.getBluetoothAddress(), mNextStep.getPlayerID());
+                } else {
+                    if (DBG) Log.d(TAG, "Player doesn't support browsing");
+                    mNextStep.setCached(true);
+                    broadcastFolderList(mNextStep.getID());
                     transitionTo(mConnected);
-                    break;
+                }
+            } else if (mNextStep.equals(mBrowseTree.mNavigateUpNode)) {
+                if (DBG) Log.d(TAG, "NAVIGATING UP " + mNextStep.toString());
+                mNextStep = mBrowseTree.getCurrentBrowsedFolder().getParent();
+                mBrowseTree.getCurrentBrowsedFolder().setCached(false);
 
-                case MESSAGE_SEND_PASS_THROUGH_CMD:
-                case MESSAGE_SEND_GROUP_NAVIGATION_CMD:
-                case MESSAGE_PROCESS_SET_ABS_VOL_CMD:
-                case MESSAGE_PROCESS_REGISTER_ABS_VOL_NOTIFICATION:
-                case MESSAGE_PROCESS_TRACK_CHANGED:
-                case MESSAGE_PROCESS_PLAY_POS_CHANGED:
-                case MESSAGE_PROCESS_PLAY_STATUS_CHANGED:
-                case MESSAGE_PROCESS_VOLUME_CHANGED_NOTIFICATION:
-                case MESSAGE_STOP_METADATA_BROADCASTS:
-                case MESSAGE_START_METADATA_BROADCASTS:
-                case MESSAGE_PROCESS_CONNECTION_CHANGE:
-                case MESSAGE_PROCESS_BROWSE_CONNECTION_CHANGE:
-                    // All of these messages should be handled by parent state immediately.
-                    return false;
+                AvrcpControllerService.changeFolderPathNative(
+                        mRemoteDevice.getBluetoothAddress(),
+                        AvrcpControllerService.FOLDER_NAVIGATION_DIRECTION_UP,
+                        AvrcpControllerService.hexStringToByteUID(null));
 
-                default:
-                    if (DBG) Log.d(STATE_TAG, "deferring message " + msg.what + " to connected!");
-                    deferMessage(msg);
+            } else {
+                if (DBG) Log.d(TAG, "NAVIGATING DOWN " + mNextStep.toString());
+                AvrcpControllerService.changeFolderPathNative(
+                        mRemoteDevice.getBluetoothAddress(),
+                        AvrcpControllerService.FOLDER_NAVIGATION_DIRECTION_DOWN,
+                        AvrcpControllerService.hexStringToByteUID(mNextStep.getFolderUID()));
             }
-            return true;
-        }
-    }
-
-    class SetBrowsedPlayer extends CmdState {
-        private static final String STATE_TAG = "AVRCPSM.SetBrowsedPlayer";
-        String mID = "";
-
-        public void setFolder(String id) {
-            mID = id;
         }
 
         @Override
-        public boolean processMessage(Message msg) {
-            if (DBG) Log.d(STATE_TAG, "processMessage " + msg.what);
-            switch (msg.what) {
-                case MESSAGE_PROCESS_SET_BROWSED_PLAYER:
-                    // Set the new depth.
-                    if (DBG) 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, EMPTY_MEDIA_ITEM_LIST);
-                        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);
-                    // Also set the browsed player here.
-                    mBrowseTree.setCurrentBrowsedPlayer(mID);
-                    break;
-
-                case MESSAGE_INTERNAL_CMD_TIMEOUT:
-                    broadcastFolderList(mID, EMPTY_MEDIA_ITEM_LIST);
-                    transitionTo(mConnected);
-                    break;
-
-                case MESSAGE_SEND_PASS_THROUGH_CMD:
-                case MESSAGE_SEND_GROUP_NAVIGATION_CMD:
-                case MESSAGE_PROCESS_SET_ABS_VOL_CMD:
-                case MESSAGE_PROCESS_REGISTER_ABS_VOL_NOTIFICATION:
-                case MESSAGE_PROCESS_TRACK_CHANGED:
-                case MESSAGE_PROCESS_PLAY_POS_CHANGED:
-                case MESSAGE_PROCESS_PLAY_STATUS_CHANGED:
-                case MESSAGE_PROCESS_VOLUME_CHANGED_NOTIFICATION:
-                case MESSAGE_STOP_METADATA_BROADCASTS:
-                case MESSAGE_START_METADATA_BROADCASTS:
-                case MESSAGE_PROCESS_CONNECTION_CHANGE:
-                case MESSAGE_PROCESS_BROWSE_CONNECTION_CHANGE:
-                    // All of these messages should be handled by parent state immediately.
-                    return false;
-
-                default:
-                    if (DBG) Log.d(STATE_TAG, "deferring message " + msg.what + " to connected!");
-                    deferMessage(msg);
-            }
-            return true;
+        public void exit() {
+            mBrowseNode = null;
+            super.exit();
         }
     }
 
@@ -1062,110 +776,26 @@
         }
     }
 
-    // Entry point to the state machine where the services should call to fetch children
-    // for a specific node. It checks if the currently browsed node is the same as the one being
-    // asked for, in that case it returns the currently cached children. This saves bandwidth and
-    // also if we are already fetching elements for a current folder (since we need to batch
-    // fetches) then we should not submit another request but simply return what we have fetched
-    // until now.
-    //
-    // It handles fetches to all VFS, Now Playing and Media Player lists.
-    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, EMPTY_MEDIA_ITEM_LIST);
-            return;
-        }
+    List<MediaItem> getContents(String uid) {
+        BrowseTree.BrowseNode currentNode = mBrowseTree.findBrowseNodeByID(uid);
 
-        if (DBG) {
-            Log.d(TAG, "To Browse folder " + bn + " is cached " + bn.isCached() + " current folder "
-                    + mBrowseTree.getCurrentBrowsedFolder());
-        }
-        if (bn.equals(mBrowseTree.getCurrentBrowsedFolder()) && bn.isCached()) {
-            if (DBG) {
-                Log.d(TAG, "Same cached folder -- returning existing children.");
+        if (DBG) Log.d(TAG, "getContents(" + uid + ") currentNode = " + currentNode);
+        if (currentNode != null) {
+            if (!currentNode.isCached()) {
+                sendMessage(AvrcpControllerStateMachine.MESSAGE_GET_FOLDER_LIST, uid);
             }
-            BrowseTree.BrowseNode n = mBrowseTree.findBrowseNodeByID(parentMediaId);
-            ArrayList<MediaItem> childrenList = new ArrayList<MediaItem>();
-            for (BrowseTree.BrowseNode cn : n.getChildren()) {
-                childrenList.add(cn.getMediaItem());
-            }
-            broadcastFolderList(parentMediaId, childrenList);
-            return;
+            return currentNode.getContents();
         }
-
-        Message msg = null;
-        int btDirection = mBrowseTree.getDirection(parentMediaId);
-        BrowseTree.BrowseNode currFol = mBrowseTree.getCurrentBrowsedFolder();
-        if (DBG) {
-            Log.d(TAG, "Browse direction parent " + mBrowseTree.getCurrentBrowsedFolder() + " req "
-                    + parentMediaId + " direction " + btDirection);
-        }
-        if (BrowseTree.ROOT.equals(parentMediaId)) {
-            // Root contains the list of players.
-            msg = obtainMessage(AvrcpControllerStateMachine.MESSAGE_GET_PLAYER_LIST, start, items);
-        } else if (bn.isPlayer() && btDirection != BrowseTree.DIRECTION_SAME) {
-            // 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()) {
-            // Issue a request to fetch the items.
-            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.
-
-            // 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 (or vice-versa)
-            // 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 (!isNowPlayingToRoot) {
-                // Find the direction of traversal.
-                int direction = -1;
-                if (DBG) Log.d(TAG, "Browse direction " + currFol + " " + bn + " = " + btDirection);
-                if (btDirection == BrowseTree.DIRECTION_UNKNOWN) {
-                    Log.w(TAG, "parent " + bn + " is not a direct "
-                            + "successor or predeccessor of current folder " + currFol);
-                    broadcastFolderList(parentMediaId, EMPTY_MEDIA_ITEM_LIST);
-                    return;
-                }
-
-                if (btDirection == BrowseTree.DIRECTION_DOWN) {
-                    direction = AvrcpControllerService.FOLDER_NAVIGATION_DIRECTION_DOWN;
-                } else if (btDirection == BrowseTree.DIRECTION_UP) {
-                    direction = AvrcpControllerService.FOLDER_NAVIGATION_DIRECTION_UP;
-                }
-
-                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);
-        }
+        return null;
     }
 
     public void fetchAttrAndPlayItem(String uid) {
-        BrowseTree.BrowseNode currItem = mBrowseTree.findFolderByIDLocked(uid);
+        BrowseTree.BrowseNode currItem = mBrowseTree.findBrowseNodeByID(uid);
         BrowseTree.BrowseNode currFolder = mBrowseTree.getCurrentBrowsedFolder();
         if (DBG) Log.d(TAG, "fetchAttrAndPlayItem mediaId=" + uid + " node=" + currItem);
         if (currItem != null) {
-            int scope = currFolder.isNowPlaying() ? AvrcpControllerService.BROWSE_SCOPE_NOW_PLAYING
+            int scope = currItem.getParent().isNowPlaying()
+                    ? AvrcpControllerService.BROWSE_SCOPE_NOW_PLAYING
                     : AvrcpControllerService.BROWSE_SCOPE_VFS;
             Message msg =
                     obtainMessage(AvrcpControllerStateMachine.MESSAGE_FETCH_ATTR_AND_PLAY_ITEM,
@@ -1181,14 +811,18 @@
             Log.d(TAG, " broadcastMetaDataChanged = " + metadata.getDescription());
         }
         mContext.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
+
     }
 
-    private void broadcastFolderList(String id, ArrayList<MediaItem> items) {
+    private void broadcastFolderList(String id) {
         Intent intent = new Intent(AvrcpControllerService.ACTION_FOLDER_LIST);
-        if (VDBG) Log.d(TAG, "broadcastFolderList id " + id + " items " + items);
+        if (VDBG) Log.d(TAG, "broadcastFolderList id " + id);
         intent.putExtra(AvrcpControllerService.EXTRA_FOLDER_ID, id);
-        intent.putParcelableArrayListExtra(AvrcpControllerService.EXTRA_FOLDER_LIST, items);
-        mContext.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
+        BluetoothMediaBrowserService bluetoothMediaBrowserService =
+                BluetoothMediaBrowserService.getBluetoothMediaBrowserService();
+        if (bluetoothMediaBrowserService != null) {
+            bluetoothMediaBrowserService.processInternalEvent(intent);
+        }
     }
 
     private void broadcastPlayBackStateChanged(PlaybackState state) {
@@ -1285,33 +919,4 @@
         }
         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
index 1f54488..6dbe6b7 100644
--- a/src/com/android/bluetooth/avrcpcontroller/AvrcpPlayer.java
+++ b/src/com/android/bluetooth/avrcpcontroller/AvrcpPlayer.java
@@ -26,7 +26,8 @@
  */
 class AvrcpPlayer {
     private static final String TAG = "AvrcpPlayer";
-    private static final boolean DBG = true;
+    private static final boolean DBG = false;
+    private static final boolean VDBG = false;
 
     public static final int INVALID_ID = -1;
 
@@ -120,8 +121,15 @@
             .setActions(mAvailableActions).build();
     }
 
-    public synchronized void updateCurrentTrack(TrackInfo update) {
+    public synchronized boolean updateCurrentTrack(TrackInfo update) {
+        if (update != null && mCurrentTrack != null
+                && update.toString().equals(mCurrentTrack.toString())) {
+            if (DBG) Log.d(TAG, "Update same as original");
+            return false;
+        }
+        if (VDBG) Log.d(TAG, "Track Changed Was:" + mCurrentTrack + "now " + update);
         mCurrentTrack = update;
+        return true;
     }
 
     public synchronized TrackInfo getCurrentTrack() {
diff --git a/src/com/android/bluetooth/a2dpsink/mbs/A2dpMediaBrowserService.java b/src/com/android/bluetooth/avrcpcontroller/BluetoothMediaBrowserService.java
similarity index 88%
rename from src/com/android/bluetooth/a2dpsink/mbs/A2dpMediaBrowserService.java
rename to src/com/android/bluetooth/avrcpcontroller/BluetoothMediaBrowserService.java
index 6d5f7d2..7860803 100644
--- a/src/com/android/bluetooth/a2dpsink/mbs/A2dpMediaBrowserService.java
+++ b/src/com/android/bluetooth/avrcpcontroller/BluetoothMediaBrowserService.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.bluetooth.a2dpsink.mbs;
+package com.android.bluetooth.avrcpcontroller;
 
 import android.bluetooth.BluetoothAvrcpController;
 import android.bluetooth.BluetoothDevice;
@@ -33,18 +33,14 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
-import android.os.Parcelable;
 import android.service.media.MediaBrowserService;
 import android.util.Log;
 import android.util.Pair;
 
 import com.android.bluetooth.R;
 import com.android.bluetooth.a2dpsink.A2dpSinkService;
-import com.android.bluetooth.avrcpcontroller.AvrcpControllerService;
-import com.android.bluetooth.avrcpcontroller.BrowseTree;
 
 import java.lang.ref.WeakReference;
-import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
@@ -64,8 +60,8 @@
  * 2. The session is de-activated when the device disconnects. It will be connected again when (1)
  * happens.
  */
-public class A2dpMediaBrowserService extends MediaBrowserService {
-    private static final String TAG = "A2dpMediaBrowserService";
+public class BluetoothMediaBrowserService extends MediaBrowserService {
+    private static final String TAG = "BluetoothMediaBrowserService";
     private static final boolean DBG = false;
     private static final boolean VDBG = false;
 
@@ -91,12 +87,13 @@
 
     // Custom actions for PTS testing.
     private static final String CUSTOM_ACTION_VOL_UP =
-            "com.android.bluetooth.a2dpsink.mbs.CUSTOM_ACTION_VOL_UP";
+            "com.android.bluetooth.avrcpcontroller.CUSTOM_ACTION_VOL_UP";
     private static final String CUSTOM_ACTION_VOL_DN =
-            "com.android.bluetooth.a2dpsink.mbs.CUSTOM_ACTION_VOL_DN";
+            "com.android.bluetooth.avrcpcontroller.CUSTOM_ACTION_VOL_DN";
     private static final String CUSTOM_ACTION_GET_PLAY_STATUS_NATIVE =
-            "com.android.bluetooth.a2dpsink.mbs.CUSTOM_ACTION_GET_PLAY_STATUS_NATIVE";
+            "com.android.bluetooth.avrcpcontroller.CUSTOM_ACTION_GET_PLAY_STATUS_NATIVE";
 
+    private static BluetoothMediaBrowserService sBluetoothMediaBrowserService;
     private MediaSession mSession;
     private MediaMetadata mA2dpMetadata;
 
@@ -112,16 +109,16 @@
     private List<MediaItem> mNowPlayingList = null;
 
     private static final class AvrcpCommandQueueHandler extends Handler {
-        WeakReference<A2dpMediaBrowserService> mInst;
+        WeakReference<BluetoothMediaBrowserService> mInst;
 
-        AvrcpCommandQueueHandler(Looper looper, A2dpMediaBrowserService sink) {
+        AvrcpCommandQueueHandler(Looper looper, BluetoothMediaBrowserService sink) {
             super(looper);
-            mInst = new WeakReference<A2dpMediaBrowserService>(sink);
+            mInst = new WeakReference<BluetoothMediaBrowserService>(sink);
         }
 
         @Override
         public void handleMessage(Message msg) {
-            A2dpMediaBrowserService inst = mInst.get();
+            BluetoothMediaBrowserService inst = mInst.get();
             if (inst == null) {
                 Log.e(TAG, "Parent class has died; aborting.");
                 return;
@@ -185,18 +182,45 @@
         synchronized (this) {
             mParentIdToRequestMap.clear();
         }
+        setBluetoothMediaBrowserService(this);
+
     }
 
     @Override
     public void onDestroy() {
         if (DBG) Log.d(TAG, "onDestroy");
+        setBluetoothMediaBrowserService(null);
         mSession.release();
         unregisterReceiver(mBtReceiver);
         super.onDestroy();
     }
 
+
+    /**
+     *  getBluetoothMediaBrowserService()
+     *  Routine to get direct access to MediaBrowserService from within the same process.
+     */
+    public static synchronized BluetoothMediaBrowserService getBluetoothMediaBrowserService() {
+        if (sBluetoothMediaBrowserService == null) {
+            Log.w(TAG, "getBluetoothMediaBrowserService(): service is NULL");
+            return null;
+        }
+        if (DBG) {
+            Log.d(TAG, "getBluetoothMediaBrowserService(): returning "
+                    + sBluetoothMediaBrowserService);
+        }
+        return sBluetoothMediaBrowserService;
+    }
+
+    private static synchronized void setBluetoothMediaBrowserService(
+            BluetoothMediaBrowserService instance) {
+        if (DBG) Log.d(TAG, "setBluetoothMediaBrowserService(): set to: " + instance);
+        sBluetoothMediaBrowserService = instance;
+    }
+
     @Override
     public BrowserRoot onGetRoot(String clientPackageName, int clientUid, Bundle rootHints) {
+        if (DBG) Log.d(TAG, "onGetRoot");
         return new BrowserRoot(BrowseTree.ROOT, null);
     }
 
@@ -210,17 +234,15 @@
         }
 
         if (DBG) Log.d(TAG, "onLoadChildren parentMediaId=" + parentMediaId);
-        if (!mAvrcpCtrlSrvc.getChildren(mA2dpDevice, parentMediaId, 0, 0xff)) {
-            result.sendResult(Collections.emptyList());
-            return;
+        List<MediaItem> contents = mAvrcpCtrlSrvc.getContents(mA2dpDevice, parentMediaId);
+        if (contents == null) {
+            mParentIdToRequestMap.put(parentMediaId, result);
+            result.detach();
+        } else {
+            result.sendResult(contents);
         }
 
-        // 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();
+        return;
     }
 
     @Override
@@ -294,7 +316,7 @@
 
         @Override
         public void onPlayFromMediaId(String mediaId, Bundle extras) {
-            synchronized (A2dpMediaBrowserService.this) {
+            synchronized (BluetoothMediaBrowserService.this) {
                 // Play the item if possible.
                 mAvrcpCtrlSrvc.fetchAttrAndPlayItem(mA2dpDevice, mediaId);
 
@@ -501,7 +523,7 @@
             mCurrentlyHeldKey = cmd;
         } else {
             mAvrcpCtrlSrvc.sendPassThroughCmd(mA2dpDevice, cmd,
-                AvrcpControllerService.KEY_STATE_RELEASED);
+                    AvrcpControllerService.KEY_STATE_RELEASED);
         }
     }
 
@@ -532,16 +554,8 @@
 
     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);
-        if (VDBG) Log.d(TAG, "Parent: " + id + " Folder list: " + folderList);
+        if (VDBG) Log.d(TAG, "Parent: " + id);
         synchronized (this) {
             // If we have a result object then we should send the result back
             // to client since it is blocking otherwise we may have gotten more items
@@ -551,11 +565,23 @@
                 Log.w(TAG, "Request no longer exists, notifying that children changed.");
                 notifyChildrenChanged(id);
             } else {
+                List<MediaItem> folderList = mAvrcpCtrlSrvc.getContents(mA2dpDevice, id);
                 results.sendResult(folderList);
             }
         }
     }
 
+    /**
+     * processInternalEvent(Intent intent)
+     * Routine to provide MediaBrowserService with content updates from within the same process.
+     */
+    public void processInternalEvent(Intent intent) {
+        String action = intent.getAction();
+        if (AvrcpControllerService.ACTION_FOLDER_LIST.equals(action)) {
+            mAvrcpCommandQueue.obtainMessage(MSG_FOLDER_LIST, intent).sendToTarget();
+        }
+    }
+
     private void msgDeviceBrowseDisconnect(BluetoothDevice device) {
         if (DBG) Log.d(TAG, "msgDeviceBrowseDisconnect device " + device);
         // Disconnect only if mA2dpDevice is non null
@@ -566,4 +592,5 @@
         }
         mBrowseConnected = false;
     }
+
 }
diff --git a/src/com/android/bluetooth/avrcpcontroller/BrowseTree.java b/src/com/android/bluetooth/avrcpcontroller/BrowseTree.java
index fed0d5d..17a192a 100644
--- a/src/com/android/bluetooth/avrcpcontroller/BrowseTree.java
+++ b/src/com/android/bluetooth/avrcpcontroller/BrowseTree.java
@@ -20,7 +20,6 @@
 import android.media.browse.MediaBrowser;
 import android.media.browse.MediaBrowser.MediaItem;
 import android.os.Bundle;
-import android.service.media.MediaBrowserService.Result;
 import android.util.Log;
 
 import java.util.ArrayList;
@@ -43,12 +42,8 @@
     private static final boolean DBG = false;
     private static final boolean VDBG = false;
 
-    public static final int DIRECTION_DOWN = 0;
-    public static final int DIRECTION_UP = 1;
-    public static final int DIRECTION_SAME = 2;
-    public static final int DIRECTION_UNKNOWN = -1;
-
     public static final String ROOT = "__ROOT__";
+    public static final String UP = "__UP__";
     public static final String NOW_PLAYING_PREFIX = "NOW_PLAYING";
     public static final String PLAYER_PREFIX = "PLAYER";
 
@@ -57,19 +52,36 @@
     private BrowseNode mCurrentBrowseNode;
     private BrowseNode mCurrentBrowsedPlayer;
     private BrowseNode mCurrentAddressedPlayer;
+    private int mDepth = 0;
+    final BrowseNode mRootNode;
+    final BrowseNode mNavigateUpNode;
+    final BrowseNode mNowPlayingNode;
 
     BrowseTree() {
-    }
-
-    public void init() {
-        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);
+        mRootNode = new BrowseNode(new MediaItem(new MediaDescription.Builder().setExtras(mdBundle)
+              .setMediaId(ROOT).setTitle(ROOT).build(), MediaItem.FLAG_BROWSABLE));
+        mRootNode.mBrowseScope = AvrcpControllerService.BROWSE_SCOPE_PLAYER_LIST;
+
+        Bundle upnodeBundle = new Bundle();
+        upnodeBundle.putString(AvrcpControllerService.MEDIA_ITEM_UID_KEY, UP);
+        mNavigateUpNode = new BrowseNode(new MediaItem(new MediaDescription.Builder()
+              .setExtras(upnodeBundle).setMediaId(UP).setTitle(UP).build(),
+              MediaItem.FLAG_BROWSABLE));
+
+        Bundle nowPlayingBundle = new Bundle();
+        nowPlayingBundle.putString(AvrcpControllerService.MEDIA_ITEM_UID_KEY, NOW_PLAYING_PREFIX);
+        mNowPlayingNode = new BrowseNode(new MediaItem(new MediaDescription.Builder()
+              .setExtras(nowPlayingBundle).setMediaId(NOW_PLAYING_PREFIX)
+              .setTitle(NOW_PLAYING_PREFIX).build(), MediaItem.FLAG_BROWSABLE));
+        mNowPlayingNode.mBrowseScope = AvrcpControllerService.BROWSE_SCOPE_NOW_PLAYING;
+        mNowPlayingNode.setExpectedChildren(255);
+        mBrowseMap.put(ROOT, mRootNode);
+        mRootNode.mChildren.add(mNowPlayingNode);
+        mBrowseMap.put(NOW_PLAYING_PREFIX, mNowPlayingNode);
+
+        mCurrentBrowseNode = mRootNode;
     }
 
     public void clear() {
@@ -91,12 +103,12 @@
         // without doing another fetch.
         boolean mCached = 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;
+        int mBrowseScope = AvrcpControllerService.BROWSE_SCOPE_VFS;
 
         // List of children.
-        final List<BrowseNode> mChildren = new ArrayList<BrowseNode>();
+        private BrowseNode mParent;
+        private final List<BrowseNode> mChildren = new ArrayList<BrowseNode>();
+        private int mExpectedChildrenCount;
 
         BrowseNode(MediaItem item) {
             mItem = item;
@@ -118,25 +130,90 @@
             mItem = new MediaBrowser.MediaItem(mdb.build(), mediaItemFlags);
         }
 
+        private BrowseNode(String name) {
+            MediaDescription.Builder mdb = new MediaDescription.Builder();
+            Bundle mdExtra = new Bundle();
+            mdExtra.putString(AvrcpControllerService.MEDIA_ITEM_UID_KEY, name);
+            mdb.setExtras(mdExtra);
+            mdb.setMediaId(name);
+            mdb.setTitle(name);
+            mItem = new MediaBrowser.MediaItem(mdb.build(), MediaBrowser.MediaItem.FLAG_BROWSABLE);
+        }
+
+        synchronized void setExpectedChildren(int count) {
+            mExpectedChildrenCount = count;
+        }
+
+        synchronized int getExpectedChildren() {
+            return mExpectedChildrenCount;
+        }
+
+        synchronized <E> int addChildren(List<E> newChildren) {
+            for (E child : newChildren) {
+                BrowseNode currentNode = null;
+                if (child instanceof MediaItem) {
+                    currentNode = new BrowseNode((MediaItem) child);
+                } else if (child instanceof AvrcpPlayer) {
+                    currentNode = new BrowseNode((AvrcpPlayer) child);
+                }
+                if (currentNode != null) {
+                    currentNode.mParent = this;
+                    mChildren.add(currentNode);
+                    mBrowseMap.put(currentNode.getID(), currentNode);
+                }
+            }
+            return newChildren.size();
+        }
+
+        synchronized int getChildrenCount() {
+            return mChildren.size();
+        }
+
         synchronized List<BrowseNode> getChildren() {
             return mChildren;
         }
 
-        synchronized boolean isChild(BrowseNode node) {
-            for (BrowseNode bn : mChildren) {
-                if (bn.equals(node)) {
-                    return true;
+        synchronized BrowseNode getParent() {
+            return mParent;
+        }
+
+        synchronized List<MediaItem> getContents() {
+            if (mChildren != null) {
+                List<MediaItem> contents = new ArrayList<MediaItem>(mChildren.size());
+                for (BrowseNode child : mChildren) {
+                    contents.add(child.getMediaItem());
                 }
+                return contents;
             }
-            return false;
+            return null;
+        }
+
+        synchronized boolean isChild(BrowseNode node) {
+            return mChildren.contains(node);
         }
 
         synchronized boolean isCached() {
             return mCached;
         }
 
+        synchronized boolean isBrowsable() {
+            return mItem.isBrowsable();
+        }
+
         synchronized void setCached(boolean cached) {
+            if (DBG) Log.d(TAG, "Set Cache" + cached + "Node" + toString());
             mCached = cached;
+            if (!cached) {
+                for (BrowseNode child : mChildren) {
+                    mBrowseMap.remove(child.getID());
+                }
+                mChildren.clear();
+                if (this == mRootNode) {
+                    mNowPlayingNode.setCached(false);
+                    mRootNode.mChildren.add(mNowPlayingNode);
+                    mBrowseMap.put(NOW_PLAYING_PREFIX, mNowPlayingNode);
+                }
+            }
         }
 
         // Fetch the Unique UID for this item, this is unique across all elements in the tree.
@@ -149,6 +226,10 @@
             return Integer.parseInt(getID().replace(PLAYER_PREFIX, ""));
         }
 
+        synchronized int getScope() {
+            return mBrowseScope;
+        }
+
         // 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.
@@ -182,50 +263,16 @@
         @Override
         public String toString() {
             if (VDBG) {
-                return "ID: " + getID() + " desc: " + mItem;
+                return "[ Name: " + mItem.getDescription().getTitle() + " expected Children: "
+                        + mExpectedChildrenCount + "] ";
             } else {
                 return "ID: " + getID();
             }
         }
-    }
-
-    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;
+        // Returns true if target is a descendant of this.
+        synchronized boolean isDescendant(BrowseNode target) {
+            return getEldestChild(this, target) == null ? false : true;
         }
-        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 (VDBG) {
-            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());
-        }
-
-        parent.setCached(true);
     }
 
     synchronized BrowseNode findBrowseNodeByID(String parentID) {
@@ -235,50 +282,13 @@
             return null;
         }
         if (VDBG) {
-            Log.d(TAG, "Browse map: " + mBrowseMap);
+            Log.d(TAG, "Size" + mBrowseMap.size());
         }
         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 if (fromFolder.equals(toFolder)) {
-            return DIRECTION_SAME;
-        } else {
-            Log.w(TAG, "from folder " + mCurrentBrowseNode + "to folder " + toUID);
-            return DIRECTION_UNKNOWN;
-        }
-    }
-
     synchronized boolean setCurrentBrowsedFolder(String uid) {
-        BrowseNode bn = findFolderByIDLocked(uid);
+        BrowseNode bn = mBrowseMap.get(uid);
         if (bn == null) {
             Log.e(TAG, "Setting an unknown browsed folder, ignoring bn " + uid);
             return false;
@@ -286,10 +296,8 @@
 
         // Set the previous folder as not cached so that we fetch the contents again.
         if (!bn.equals(mCurrentBrowseNode)) {
-            Log.d(TAG, "Set cache false " + bn + " curr " + mCurrentBrowseNode);
-            mCurrentBrowseNode.setCached(false);
+            Log.d(TAG, "Set cache  " + bn + " curr " + mCurrentBrowseNode);
         }
-
         mCurrentBrowseNode = bn;
         return true;
     }
@@ -298,13 +306,21 @@
         return mCurrentBrowseNode;
     }
 
-    synchronized boolean setCurrentBrowsedPlayer(String uid) {
-        BrowseNode bn = findFolderByIDLocked(uid);
+    synchronized boolean setCurrentBrowsedPlayer(String uid, int items, int depth) {
+        BrowseNode bn = mBrowseMap.get(uid);
         if (bn == null) {
             Log.e(TAG, "Setting an unknown browsed player, ignoring bn " + uid);
             return false;
         }
         mCurrentBrowsedPlayer = bn;
+        mCurrentBrowseNode = mCurrentBrowsedPlayer;
+        for (Integer level = 0; level < depth; level++) {
+            BrowseNode dummyNode = new BrowseNode(level.toString());
+            dummyNode.mParent = mCurrentBrowseNode;
+            mCurrentBrowseNode = dummyNode;
+        }
+        mCurrentBrowseNode.setExpectedChildren(items);
+        mDepth = depth;
         return true;
     }
 
@@ -313,9 +329,12 @@
     }
 
     synchronized boolean setCurrentAddressedPlayer(String uid) {
-        BrowseNode bn = findFolderByIDLocked(uid);
+        BrowseNode bn = mBrowseMap.get(uid);
         if (bn == null) {
-            Log.e(TAG, "Setting an unknown addressed player, ignoring bn " + uid);
+            if (DBG) Log.d(TAG, "Setting an unknown addressed player, ignoring bn " + uid);
+            mRootNode.setCached(false);
+            mRootNode.mChildren.add(mNowPlayingNode);
+            mBrowseMap.put(NOW_PLAYING_PREFIX, mNowPlayingNode);
             return false;
         }
         mCurrentAddressedPlayer = bn;
@@ -328,6 +347,60 @@
 
     @Override
     public String toString() {
-        return mBrowseMap.toString();
+        return "Size: " + mBrowseMap.size();
+    }
+
+    // Calculates the path to target node.
+    // Returns: UP node to go up
+    // Returns: target node if there
+    // Returns: named node to go down
+    // Returns: null node if unknown
+    BrowseNode getNextStepToFolder(BrowseNode target) {
+        if (target == null) {
+            return null;
+        } else if (target.equals(mCurrentBrowseNode)
+                || target.equals(mNowPlayingNode)) {
+            return target;
+        } else if (target.isPlayer()) {
+            if (mDepth > 0) {
+                mDepth--;
+                return mNavigateUpNode;
+            } else {
+                return target;
+            }
+        } else if (mBrowseMap.get(target.getID()) == null) {
+            return null;
+        } else {
+            BrowseNode nextChild = getEldestChild(mCurrentBrowseNode, target);
+            if (nextChild == null) {
+                return mNavigateUpNode;
+            } else {
+                return nextChild;
+            }
+        }
+          /*
+          if (mCurrentBrowseNode.isDescendant(target)) {
+            return getEldestChild(mCurrentBrowseNode, target);
+        } else {
+            if (DBG) Log.d(TAG, "NAVIGATING UP");
+            return mNavigateUpNode;
+        }
+        */
+    }
+
+    BrowseNode getEldestChild(BrowseNode ancestor, BrowseNode target) {
+        // ancestor is an ancestor of target
+        BrowseNode descendant = target;
+        if (DBG) {
+            Log.d(TAG, "NAVIGATING ancestor" + ancestor.toString() + "Target" + target.toString());
+        }
+        while (!ancestor.equals(descendant.mParent)) {
+            descendant = descendant.mParent;
+            if (descendant == null) {
+                return null;
+            }
+        }
+        if (DBG) Log.d(TAG, "NAVIGATING Descendant" + descendant.toString());
+        return descendant;
     }
 }