Merge "BT: Converging AVRCP files for multi-connection FR" into bt.lnx.5.0
diff --git a/packages_apps_bluetooth_ext/AddressedMediaPlayer_ext.java b/packages_apps_bluetooth_ext/AddressedMediaPlayer_ext.java
new file mode 100644
index 0000000..5e61b75
--- /dev/null
+++ b/packages_apps_bluetooth_ext/AddressedMediaPlayer_ext.java
@@ -0,0 +1,639 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.avrcp;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.media.MediaDescription;
+import android.media.MediaMetadata;
+import android.media.session.MediaSession;
+import android.media.session.MediaSession.QueueItem;
+import android.media.session.PlaybackState;
+import android.os.Bundle;
+import android.util.Log;
+
+import com.android.bluetooth.Utils;
+import com.android.bluetooth.btservice.ProfileService;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/*************************************************************************************************
+ * Provides functionality required for Addressed Media Player, like Now Playing List related
+ * browsing commands, control commands to the current addressed player(playItem, play, pause, etc)
+ * Acts as an Interface to communicate with media controller APIs for NowPlayingItems.
+ ************************************************************************************************/
+
+public class AddressedMediaPlayer_ext {
+    private static final String TAG = "AddressedMediaPlayer_ext";
+    private static final Boolean DEBUG = true;
+
+    private static final long SINGLE_QID = 1;
+    private static final String UNKNOWN_TITLE = "(unknown)";
+
+    static private final String GPM_BUNDLE_METADATA_KEY =
+            "com.google.android.music.mediasession.music_metadata";
+
+    private AvrcpMediaRspInterface mMediaInterface;
+    private Avrcp_ext mAvrcp = null;
+    @NonNull private List<MediaSession.QueueItem> mNowPlayingList;
+
+    private final List<MediaSession.QueueItem> mEmptyNowPlayingList;
+
+    private long mLastTrackIdSent;
+
+    public AddressedMediaPlayer(AvrcpMediaRspInterface mediaInterface) {
+        mEmptyNowPlayingList = new ArrayList<MediaSession.QueueItem>();
+        mNowPlayingList = mEmptyNowPlayingList;
+        mMediaInterface = mediaInterface;
+        mLastTrackIdSent = MediaSession.QueueItem.UNKNOWN_ID;
+    }
+
+    public AddressedMediaPlayer(AvrcpMediaRspInterface mediaInterface, Avrcp_ext mAvrcp_ext) {
+        this(mediaInterface);
+        mAvrcp = mAvrcp_ext;
+    }
+
+    void cleanup() {
+        if (DEBUG) {
+            Log.v(TAG, "cleanup");
+        }
+        mNowPlayingList = mEmptyNowPlayingList;
+        mMediaInterface = null;
+        mLastTrackIdSent = MediaSession.QueueItem.UNKNOWN_ID;
+    }
+
+    /* get now playing list from addressed player */
+    void getFolderItemsNowPlaying(byte[] bdaddr, AvrcpCmd.FolderItemsCmd reqObj,
+            @Nullable MediaController mediaController) {
+        if (mediaController == null) {
+            // No players (if a player exists, we would have selected it)
+            Log.e(TAG, "mediaController = null, sending no available players response");
+            mMediaInterface.folderItemsRsp(bdaddr, AvrcpConstants.RSP_NO_AVBL_PLAY, null);
+            return;
+        }
+        List<MediaSession.QueueItem> items = updateNowPlayingList(mediaController);
+        getFolderItemsFilterAttr(bdaddr, reqObj, items, AvrcpConstants.BTRC_SCOPE_NOW_PLAYING,
+                reqObj.mStartItem, reqObj.mEndItem, mediaController);
+    }
+
+    /* get item attributes for item in now playing list */
+    void getItemAttr(byte[] bdaddr, AvrcpCmd.ItemAttrCmd itemAttr,
+            @Nullable MediaController mediaController) {
+        int status = AvrcpConstants.RSP_NO_ERROR;
+        long mediaId = ByteBuffer.wrap(itemAttr.mUid).getLong();
+        List<MediaSession.QueueItem> items = updateNowPlayingList(mediaController);
+
+        // NOTE: this is out-of-spec (AVRCP 1.6.1 sec 6.10.4.3, p90) but we answer it anyway
+        // because some CTs ask for it.
+        if (Arrays.equals(itemAttr.mUid, AvrcpConstants.TRACK_IS_SELECTED)) {
+            mediaId = getActiveQueueItemId(mediaController);
+            if (DEBUG) {
+                Log.d(TAG, "getItemAttr: Remote requests for now playing contents, sending UID: "
+                        + mediaId);
+            }
+        }
+
+        if (DEBUG) {
+            Log.d(TAG, "getItemAttr-UID: 0x" + Utils.byteArrayToString(itemAttr.mUid));
+        }
+        for (MediaSession.QueueItem item : items) {
+            if (item.getQueueId() == mediaId) {
+                getItemAttrFilterAttr(bdaddr, itemAttr, item, mediaController);
+                return;
+            }
+        }
+
+        // Couldn't find it, so the id is invalid
+        mMediaInterface.getItemAttrRsp(bdaddr, AvrcpConstants.RSP_INV_ITEM, null);
+    }
+
+    /* Refresh and get the queue of now playing.
+     */
+    @NonNull
+    List<MediaSession.QueueItem> updateNowPlayingList(@Nullable MediaController mediaController) {
+        if (mediaController == null) {
+            return mEmptyNowPlayingList;
+        }
+        List<MediaSession.QueueItem> items = mediaController.getQueue();
+        if (items == null) {
+            Log.i(TAG, "null queue from " + mediaController.getPackageName()
+                    + ", constructing single-item list");
+
+            // Because we are database-unaware, we can just number the item here whatever we want
+            // because they have to re-poll it every time.
+            MediaMetadata metadata = mediaController.getMetadata();
+            if (metadata == null) {
+                Log.w(TAG, "Controller has no metadata!? Making an empty one");
+                metadata = (new MediaMetadata.Builder()).build();
+            }
+
+            MediaDescription.Builder bob = new MediaDescription.Builder();
+            MediaDescription desc = metadata.getDescription();
+
+            // set the simple ones that MediaMetadata builds for us
+            bob.setMediaId(desc.getMediaId());
+            bob.setTitle(desc.getTitle());
+            bob.setSubtitle(desc.getSubtitle());
+            bob.setDescription(desc.getDescription());
+            // fill the ones that we use later
+            bob.setExtras(fillBundle(metadata, desc.getExtras()));
+
+            // build queue item with the new metadata
+            MediaSession.QueueItem current = new QueueItem(bob.build(), SINGLE_QID);
+
+            items = new ArrayList<MediaSession.QueueItem>();
+            items.add(current);
+        }
+
+        if (!items.equals(mNowPlayingList)) {
+            sendNowPlayingListChanged();
+        }
+        mNowPlayingList = items;
+
+        return mNowPlayingList;
+    }
+
+    private void sendNowPlayingListChanged() {
+        if (mMediaInterface == null) {
+            return;
+        }
+        if (DEBUG) {
+            Log.d(TAG, "sendNowPlayingListChanged()");
+        }
+        mMediaInterface.nowPlayingChangedRsp(AvrcpConstants.NOTIFICATION_TYPE_CHANGED);
+    }
+
+    private Bundle fillBundle(MediaMetadata metadata, Bundle currentExtras) {
+        if (metadata == null) {
+            Log.i(TAG, "fillBundle: metadata is null");
+            return currentExtras;
+        }
+
+        Bundle bundle = currentExtras;
+        if (bundle == null) {
+            bundle = new Bundle();
+        }
+
+        String[] stringKeys = {
+                MediaMetadata.METADATA_KEY_TITLE,
+                MediaMetadata.METADATA_KEY_ARTIST,
+                MediaMetadata.METADATA_KEY_ALBUM,
+                MediaMetadata.METADATA_KEY_GENRE
+        };
+        for (String key : stringKeys) {
+            String current = bundle.getString(key);
+            if (current == null) {
+                bundle.putString(key, metadata.getString(key));
+            }
+        }
+
+        String[] longKeys = {
+                MediaMetadata.METADATA_KEY_TRACK_NUMBER,
+                MediaMetadata.METADATA_KEY_NUM_TRACKS,
+                MediaMetadata.METADATA_KEY_DURATION
+        };
+        for (String key : longKeys) {
+            if (!bundle.containsKey(key)) {
+                bundle.putLong(key, metadata.getLong(key));
+            }
+        }
+        return bundle;
+    }
+
+    /* Instructs media player to play particular media item */
+    void playItem(byte[] bdaddr, byte[] uid, @Nullable MediaController mediaController) {
+        long qid = ByteBuffer.wrap(uid).getLong();
+        List<MediaSession.QueueItem> items = updateNowPlayingList(mediaController);
+
+        if (mediaController == null) {
+            Log.e(TAG, "No mediaController when PlayItem " + qid + " requested");
+            mMediaInterface.playItemRsp(bdaddr, AvrcpConstants.RSP_INTERNAL_ERR);
+            return;
+        }
+
+        MediaController.TransportControls mediaControllerCntrl =
+                mediaController.getTransportControls();
+
+        if (items == null) {
+            Log.w(TAG, "nowPlayingItems is null");
+            mMediaInterface.playItemRsp(bdaddr, AvrcpConstants.RSP_INTERNAL_ERR);
+            return;
+        }
+
+        for (MediaSession.QueueItem item : items) {
+            if (qid == item.getQueueId()) {
+                if (DEBUG) {
+                    Log.d(TAG, "Skipping to ID " + qid);
+                }
+                mediaControllerCntrl.skipToQueueItem(qid);
+                mMediaInterface.playItemRsp(bdaddr, AvrcpConstants.RSP_NO_ERROR);
+                return;
+            }
+        }
+
+        Log.w(TAG, "Invalid now playing Queue ID " + qid);
+        mMediaInterface.playItemRsp(bdaddr, AvrcpConstants.RSP_INV_ITEM);
+    }
+
+    void getTotalNumOfItems(byte[] bdaddr, @Nullable MediaController mediaController) {
+        List<MediaSession.QueueItem> items = updateNowPlayingList(mediaController);
+        if (DEBUG) {
+            Log.d(TAG, "getTotalNumOfItems: " + items.size() + " items.");
+        }
+        mMediaInterface.getTotalNumOfItemsRsp(bdaddr, AvrcpConstants.RSP_NO_ERROR, 0, items.size());
+    }
+
+    void sendTrackChangeWithId(int type, @Nullable MediaController mediaController) {
+        Log.d(TAG, "sendTrackChangeWithId (" + type + "): controller " + mediaController);
+        long qid = getActiveQueueItemId(mediaController);
+        byte[] track = ByteBuffer.allocate(AvrcpConstants.UID_SIZE).putLong(qid).array();
+        Log.d(TAG, "qid: " + qid );
+        if ((type == AvrcpConstants.NOTIFICATION_TYPE_INTERIM) &&
+            (qid != MediaSession.QueueItem.UNKNOWN_ID) &&
+            (qid != mLastTrackIdSent)) {
+             byte[] lastTrack =
+                    ByteBuffer.allocate(AvrcpConstants.UID_SIZE).putLong(mLastTrackIdSent).array();
+             mMediaInterface.trackChangedRsp(type, lastTrack);
+             type = AvrcpConstants.NOTIFICATION_TYPE_CHANGED;
+        }
+        // The nowPlayingList changed: the new list has the full data for the current item
+        Log.d(TAG, "last_sent_qid: " + mLastTrackIdSent);
+        mMediaInterface.trackChangedRsp(type, track);
+        mLastTrackIdSent = qid;
+    }
+
+    void sendTrackChangeWithId(int type, @Nullable MediaController mediaController, byte[] bdaddr) {
+        Log.d(TAG, "sendTrackChangeWithId (" + type + "): controller " + mediaController);
+        if(mAvrcp == null) {
+            sendTrackChangeWithId(type, mediaController);
+            return;
+        }
+        long qid = getActiveQueueItemId(mediaController);
+        byte[] track = ByteBuffer.allocate(AvrcpConstants.UID_SIZE).putLong(qid).array();
+        Log.d(TAG, "qid: " + qid );
+        if ((type == AvrcpConstants.NOTIFICATION_TYPE_INTERIM) &&
+            (qid != MediaSession.QueueItem.UNKNOWN_ID) &&
+            (qid != mLastTrackIdSent)) {
+             byte[] lastTrack =
+                    ByteBuffer.allocate(AvrcpConstants.UID_SIZE).putLong(mLastTrackIdSent).array();
+             mAvrcp.trackChangedAddressedRsp(type, lastTrack, bdaddr);
+             type = AvrcpConstants.NOTIFICATION_TYPE_CHANGED;
+        }
+        // The nowPlayingList changed: the new list has the full data for the current item
+        Log.d(TAG, "last_sent_qid: " + mLastTrackIdSent);
+        mAvrcp.trackChangedAddressedRsp(type, track, bdaddr);
+        mLastTrackIdSent = qid;
+    }
+
+    /*
+     * helper method to check if startItem and endItem index is with range of
+     * MediaItem list. (Resultset containing all items in current path)
+     */
+    @Nullable
+    private List<MediaSession.QueueItem> getQueueSubset(@NonNull List<MediaSession.QueueItem> items,
+            long startItem, long endItem) {
+        if (endItem > items.size()) {
+            endItem = items.size() - 1;
+        }
+        if (startItem > Integer.MAX_VALUE) {
+            startItem = Integer.MAX_VALUE;
+        }
+        try {
+            List<MediaSession.QueueItem> selected =
+                    items.subList((int) startItem, (int) Math.min(items.size(), endItem + 1));
+            if (selected.isEmpty()) {
+                Log.i(TAG, "itemsSubList is empty.");
+                return null;
+            }
+            return selected;
+        } catch (IndexOutOfBoundsException ex) {
+            Log.i(TAG, "Range (" + startItem + ", " + endItem + ") invalid");
+        } catch (IllegalArgumentException ex) {
+            Log.i(TAG, "Range start " + startItem + " > size (" + items.size() + ")");
+        }
+        return null;
+    }
+
+    /*
+     * helper method to filter required attibutes before sending GetFolderItems
+     * response
+     */
+    private void getFolderItemsFilterAttr(byte[] bdaddr, AvrcpCmd.FolderItemsCmd folderItemsReqObj,
+            @NonNull List<MediaSession.QueueItem> items, byte scope, long startItem, long endItem,
+            @NonNull MediaController mediaController) {
+        if (DEBUG) {
+            Log.d(TAG,
+                    "getFolderItemsFilterAttr: startItem =" + startItem + ", endItem = " + endItem);
+        }
+
+        List<MediaSession.QueueItem> resultItems = getQueueSubset(items, startItem, endItem);
+        /* check for index out of bound errors */
+        if (resultItems == null) {
+            Log.w(TAG, "getFolderItemsFilterAttr: resultItems is empty");
+            mMediaInterface.folderItemsRsp(bdaddr, AvrcpConstants.RSP_INV_RANGE, null);
+            return;
+        }
+
+        FolderItemsData folderDataNative = new FolderItemsData(resultItems.size());
+
+        /* variables to accumulate attrs */
+        ArrayList<String> attrArray = new ArrayList<String>();
+        ArrayList<Integer> attrId = new ArrayList<Integer>();
+
+        for (int itemIndex = 0; itemIndex < resultItems.size(); itemIndex++) {
+            MediaSession.QueueItem item = resultItems.get(itemIndex);
+            // get the queue id
+            long qid = item.getQueueId();
+            byte[] uid = ByteBuffer.allocate(AvrcpConstants.UID_SIZE).putLong(qid).array();
+
+            // get the array of uid from 2d to array 1D array
+            for (int idx = 0; idx < AvrcpConstants.UID_SIZE; idx++) {
+                folderDataNative.mItemUid[itemIndex * AvrcpConstants.UID_SIZE + idx] = uid[idx];
+            }
+
+            /* Set display name for current item */
+            folderDataNative.mDisplayNames[itemIndex] =
+                    getAttrValue(AvrcpConstants.ATTRID_TITLE, item, mediaController);
+
+            int maxAttributesRequested = 0;
+            boolean isAllAttribRequested = false;
+            /* check if remote requested for attributes */
+            if (folderItemsReqObj.mNumAttr != AvrcpConstants.NUM_ATTR_NONE) {
+                int attrCnt = 0;
+
+                /* add requested attr ids to a temp array */
+                if (folderItemsReqObj.mNumAttr == AvrcpConstants.NUM_ATTR_ALL) {
+                    isAllAttribRequested = true;
+                    maxAttributesRequested = AvrcpConstants.MAX_NUM_ATTR;
+                } else {
+                    /* get only the requested attribute ids from the request */
+                    maxAttributesRequested = folderItemsReqObj.mNumAttr;
+                }
+
+                /* lookup and copy values of attributes for ids requested above */
+                for (int idx = 0; idx < maxAttributesRequested; idx++) {
+                    /* check if media player provided requested attributes */
+                    String value = null;
+
+                    int attribId =
+                            isAllAttribRequested ? (idx + 1) : folderItemsReqObj.mAttrIDs[idx];
+                    value = getAttrValue(attribId, item, mediaController);
+                    if (value != null) {
+                        attrArray.add(value);
+                        attrId.add(attribId);
+                        attrCnt++;
+                    }
+                }
+                /* add num attr actually received from media player for a particular item */
+                folderDataNative.mAttributesNum[itemIndex] = attrCnt;
+            }
+        }
+
+        /* copy filtered attr ids and attr values to response parameters */
+        if (folderItemsReqObj.mNumAttr != AvrcpConstants.NUM_ATTR_NONE) {
+            folderDataNative.mAttrIds = new int[attrId.size()];
+            for (int attrIndex = 0; attrIndex < attrId.size(); attrIndex++) {
+                folderDataNative.mAttrIds[attrIndex] = attrId.get(attrIndex);
+            }
+            folderDataNative.mAttrValues = attrArray.toArray(new String[attrArray.size()]);
+        }
+        for (int attrIndex = 0; attrIndex < folderDataNative.mAttributesNum.length; attrIndex++) {
+            if (DEBUG) {
+                Log.d(TAG, "folderDataNative.mAttributesNum"
+                        + folderDataNative.mAttributesNum[attrIndex] + " attrIndex " + attrIndex);
+            }
+        }
+
+        /* create rsp object and send response to remote device */
+        FolderItemsRsp rspObj =
+                new FolderItemsRsp(AvrcpConstants.RSP_NO_ERROR, Avrcp.sUIDCounter, scope,
+                        folderDataNative.mNumItems, folderDataNative.mFolderTypes,
+                        folderDataNative.mPlayable, folderDataNative.mItemTypes,
+                        folderDataNative.mItemUid, folderDataNative.mDisplayNames,
+                        folderDataNative.mAttributesNum, folderDataNative.mAttrIds,
+                        folderDataNative.mAttrValues);
+        mMediaInterface.folderItemsRsp(bdaddr, AvrcpConstants.RSP_NO_ERROR, rspObj);
+    }
+
+    private String getAttrValue(int attr, MediaSession.QueueItem item,
+            @Nullable MediaController mediaController) {
+        String attrValue = null;
+        if (item == null) {
+            if (DEBUG) {
+                Log.d(TAG, "getAttrValue received null item");
+            }
+            return null;
+        }
+        try {
+            MediaDescription desc = item.getDescription();
+            Bundle extras = desc.getExtras();
+            boolean isCurrentTrack = item.getQueueId() == getActiveQueueItemId(mediaController);
+            MediaMetadata data = null;
+            if (isCurrentTrack) {
+                if (DEBUG) {
+                    Log.d(TAG, "getAttrValue: item is active, using current data");
+                }
+                data = mediaController.getMetadata();
+                if (data == null) {
+                    Log.e(TAG, "getMetadata didn't give us any metadata for the current track");
+                }
+            }
+
+            if (data == null) {
+                // TODO: This code can be removed when b/63117921 is resolved
+                data = (MediaMetadata) extras.get(GPM_BUNDLE_METADATA_KEY);
+                extras = null; // We no longer need the data in here
+            }
+
+            extras = fillBundle(data, extras);
+
+            if (DEBUG) {
+                Log.d(TAG, "getAttrValue: item " + item + " : " + desc);
+            }
+            switch (attr) {
+                case AvrcpConstants.ATTRID_TITLE:
+                    /* Title is mandatory attribute */
+                    if (isCurrentTrack) {
+                        attrValue = extras.getString(MediaMetadata.METADATA_KEY_TITLE);
+                    } else {
+                        attrValue = desc.getTitle().toString();
+                    }
+                    break;
+
+                case AvrcpConstants.ATTRID_ARTIST:
+                    attrValue = extras.getString(MediaMetadata.METADATA_KEY_ARTIST);
+                    break;
+
+                case AvrcpConstants.ATTRID_ALBUM:
+                    attrValue = extras.getString(MediaMetadata.METADATA_KEY_ALBUM);
+                    break;
+
+                case AvrcpConstants.ATTRID_TRACK_NUM:
+                    attrValue =
+                            Long.toString(extras.getLong(MediaMetadata.METADATA_KEY_TRACK_NUMBER));
+                    break;
+
+                case AvrcpConstants.ATTRID_NUM_TRACKS:
+                    attrValue =
+                            Long.toString(extras.getLong(MediaMetadata.METADATA_KEY_NUM_TRACKS));
+                    break;
+
+                case AvrcpConstants.ATTRID_GENRE:
+                    attrValue = extras.getString(MediaMetadata.METADATA_KEY_GENRE);
+                    break;
+
+                case AvrcpConstants.ATTRID_PLAY_TIME:
+                    attrValue = Long.toString(extras.getLong(MediaMetadata.METADATA_KEY_DURATION));
+                    break;
+
+                case AvrcpConstants.ATTRID_COVER_ART:
+                    attrValue = Avrcp_ext.getImgHandleFromTitle(desc.getTitle().toString());
+                    break;
+
+                default:
+                    Log.e(TAG, "getAttrValue: Unknown attribute ID requested: " + attr);
+                    return null;
+            }
+        } catch (NullPointerException ex) {
+            Log.w(TAG, "getAttrValue: attr id not found in result");
+            /* checking if attribute is title, then it is mandatory and cannot send null */
+            if (attr == AvrcpConstants.ATTRID_TITLE) {
+                attrValue = "<Unknown Title>";
+            } else {
+                return null;
+            }
+        }
+        if (DEBUG) {
+            Log.d(TAG, "getAttrValue: attrvalue = " + attrValue + ", attr id: " + attr);
+        }
+        return attrValue;
+    }
+
+    private void getItemAttrFilterAttr(byte[] bdaddr, AvrcpCmd.ItemAttrCmd mItemAttrReqObj,
+            MediaSession.QueueItem mediaItem, @Nullable MediaController mediaController) {
+        /* Response parameters */
+        int[] attrIds = null; /* array of attr ids */
+        String[] attrValues = null; /* array of attr values */
+
+        /* variables to temperorily add attrs */
+        ArrayList<String> attrArray = new ArrayList<String>();
+        ArrayList<Integer> attrId = new ArrayList<Integer>();
+        ArrayList<Integer> attrTempId = new ArrayList<Integer>();
+
+        /* check if remote device has requested for attributes */
+        if (mItemAttrReqObj.mNumAttr != AvrcpConstants.NUM_ATTR_NONE) {
+            if (mItemAttrReqObj.mNumAttr == AvrcpConstants.NUM_ATTR_ALL) {
+                for (int idx = 1; idx < AvrcpConstants.MAX_NUM_ATTR; idx++) {
+                    attrTempId.add(idx); /* attr id 0x00 is unused */
+                }
+            } else {
+                /* get only the requested attribute ids from the request */
+                for (int idx = 0; idx < mItemAttrReqObj.mNumAttr; idx++) {
+                    if (DEBUG) {
+                        Log.d(TAG, "getItemAttrFilterAttr: attr id[" + idx + "] :"
+                                + mItemAttrReqObj.mAttrIDs[idx]);
+                    }
+                    attrTempId.add(mItemAttrReqObj.mAttrIDs[idx]);
+                }
+            }
+        }
+
+        if (DEBUG) {
+            Log.d(TAG, "getItemAttrFilterAttr: attr id list size:" + attrTempId.size());
+        }
+        /* lookup and copy values of attributes for ids requested above */
+        for (int idx = 0; idx < attrTempId.size(); idx++) {
+            /* check if media player provided requested attributes */
+            String value = getAttrValue(attrTempId.get(idx), mediaItem, mediaController);
+            if (value != null) {
+                attrArray.add(value);
+                attrId.add(attrTempId.get(idx));
+            }
+        }
+
+        /* copy filtered attr ids and attr values to response parameters */
+        if (mItemAttrReqObj.mNumAttr != AvrcpConstants.NUM_ATTR_NONE) {
+            attrIds = new int[attrId.size()];
+
+            for (int attrIndex = 0; attrIndex < attrId.size(); attrIndex++) {
+                attrIds[attrIndex] = attrId.get(attrIndex);
+            }
+
+            attrValues = attrArray.toArray(new String[attrId.size()]);
+
+            /* create rsp object and send response */
+            ItemAttrRsp rspObj = new ItemAttrRsp(AvrcpConstants.RSP_NO_ERROR, attrIds, attrValues);
+            mMediaInterface.getItemAttrRsp(bdaddr, AvrcpConstants.RSP_NO_ERROR, rspObj);
+            return;
+        }
+    }
+
+    private long getActiveQueueItemId(@Nullable MediaController controller) {
+        if (controller == null) {
+            return MediaSession.QueueItem.UNKNOWN_ID;
+        }
+        PlaybackState state = controller.getPlaybackState();
+        if (state == null || state.getState() == PlaybackState.STATE_NONE) {
+            return MediaSession.QueueItem.UNKNOWN_ID;
+        }
+        long qid = state.getActiveQueueItemId();
+        if (qid != MediaSession.QueueItem.UNKNOWN_ID) {
+            return qid;
+        }
+        // Check if we're presenting a "one item queue"
+        if (controller.getMetadata() != null) {
+            return SINGLE_QID;
+        }
+        return MediaSession.QueueItem.UNKNOWN_ID;
+    }
+
+    String displayMediaItem(MediaSession.QueueItem item) {
+        StringBuilder sb = new StringBuilder();
+        sb.append("#");
+        sb.append(item.getQueueId());
+        sb.append(": ");
+        sb.append(Utils.ellipsize(getAttrValue(AvrcpConstants.ATTRID_TITLE, item, null)));
+        sb.append(" - ");
+        sb.append(Utils.ellipsize(getAttrValue(AvrcpConstants.ATTRID_ALBUM, item, null)));
+        sb.append(" by ");
+        sb.append(Utils.ellipsize(getAttrValue(AvrcpConstants.ATTRID_ARTIST, item, null)));
+        sb.append(" (");
+        sb.append(getAttrValue(AvrcpConstants.ATTRID_PLAY_TIME, item, null));
+        sb.append(" ");
+        sb.append(getAttrValue(AvrcpConstants.ATTRID_TRACK_NUM, item, null));
+        sb.append("/");
+        sb.append(getAttrValue(AvrcpConstants.ATTRID_NUM_TRACKS, item, null));
+        sb.append(") ");
+        sb.append(getAttrValue(AvrcpConstants.ATTRID_GENRE, item, null));
+        return sb.toString();
+    }
+
+    public void dump(StringBuilder sb, @Nullable MediaController mediaController) {
+        ProfileService.println(sb, "AddressedPlayer info:");
+        ProfileService.println(sb, "mLastTrackIdSent: " + mLastTrackIdSent);
+        ProfileService.println(sb, "mNowPlayingList: " + mNowPlayingList.size() + " elements");
+        long currentQueueId = getActiveQueueItemId(mediaController);
+        for (MediaSession.QueueItem item : mNowPlayingList) {
+            long itemId = item.getQueueId();
+            ProfileService.println(sb,
+                    (itemId == currentQueueId ? "*" : " ") + displayMediaItem(item));
+        }
+    }
+}
diff --git a/packages_apps_bluetooth_ext/AvrcpConstants_ext.java b/packages_apps_bluetooth_ext/AvrcpConstants_ext.java
new file mode 100644
index 0000000..82ef436
--- /dev/null
+++ b/packages_apps_bluetooth_ext/AvrcpConstants_ext.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.avrcp;
+
+import android.util.Log;
+
+/*************************************************************************************************
+ * Grouped all HAL constants into a file to be consistent with the stack.
+ * Moved the constants used in Avrcp to this new file to be used across multiple files.
+ * Helps in easier modifications and future enhancements in the constants.
+ ************************************************************************************************/
+
+/*
+ * @hide
+ */
+final class AvrcpConstants_ext {
+
+    /* Do not modify without upating the HAL bt_rc.h file */
+    /** Response Error codes **/
+    static final byte RSP_BAD_CMD = 0x00; /* Invalid command */
+    static final byte RSP_BAD_PARAM = 0x01; /* Invalid parameter */
+    static final byte RSP_NOT_FOUND = 0x02; /* Specified parameter is
+                                                              * wrong or not found */
+    static final byte RSP_INTERNAL_ERR = 0x03; /* Internal Error */
+    static final byte RSP_NO_ERROR = 0x04; /* Operation Success */
+    static final byte RSP_UID_CHANGED = 0x05; /* UIDs changed */
+    static final byte RSP_RESERVED = 0x06; /* Reserved */
+    static final byte RSP_INV_DIRN = 0x07; /* Invalid direction */
+    static final byte RSP_INV_DIRECTORY = 0x08; /* Invalid directory */
+    static final byte RSP_INV_ITEM = 0x09; /* Invalid Item */
+    static final byte RSP_INV_SCOPE = 0x0a; /* Invalid scope */
+    static final byte RSP_INV_RANGE = 0x0b; /* Invalid range */
+    static final byte RSP_DIRECTORY = 0x0c; /* UID is a directory */
+    static final byte RSP_MEDIA_IN_USE = 0x0d; /* Media in use */
+    static final byte RSP_PLAY_LIST_FULL = 0x0e; /* Playing list full */
+    static final byte RSP_SRCH_NOT_SPRTD = 0x0f; /* Search not supported */
+    static final byte RSP_SRCH_IN_PROG = 0x10; /* Search in progress */
+    static final byte RSP_INV_PLAYER = 0x11; /* Invalid player */
+    static final byte RSP_PLAY_NOT_BROW = 0x12; /* Player not browsable */
+    static final byte RSP_PLAY_NOT_ADDR = 0x13; /* Player not addressed */
+    static final byte RSP_INV_RESULTS = 0x14; /* Invalid results */
+    static final byte RSP_NO_AVBL_PLAY = 0x15; /* No available players */
+    static final byte RSP_ADDR_PLAY_CHGD = 0x16; /* Addressed player changed */
+
+    /* valid scopes for get_folder_items */
+    static final byte BTRC_SCOPE_PLAYER_LIST = 0x00; /* Media Player List */
+    static final byte BTRC_SCOPE_FILE_SYSTEM = 0x01; /* Virtual File System */
+    static final byte BTRC_SCOPE_SEARCH = 0x02; /* Search */
+    static final byte BTRC_SCOPE_NOW_PLAYING = 0x03; /* Now Playing */
+
+    /* valid directions for change path */
+    static final byte DIR_UP = 0x00;
+    static final byte DIR_DOWN = 0x01;
+
+    /* item type to browse */
+    static final byte BTRC_ITEM_PLAYER = 0x01;
+    static final byte BTRC_ITEM_FOLDER = 0x02;
+    static final byte BTRC_ITEM_MEDIA = 0x03;
+
+    /* valid folder types */
+    static final byte FOLDER_TYPE_MIXED = 0x00;
+    static final byte FOLDER_TYPE_TITLES = 0x01;
+    static final byte FOLDER_TYPE_ALBUMS = 0x02;
+    static final byte FOLDER_TYPE_ARTISTS = 0x03;
+    static final byte FOLDER_TYPE_GENRES = 0x04;
+    static final byte FOLDER_TYPE_PLAYLISTS = 0x05;
+    static final byte FOLDER_TYPE_YEARS = 0x06;
+
+    /* valid playable flags */
+    static final byte ITEM_NOT_PLAYABLE = 0x00;
+    static final byte ITEM_PLAYABLE = 0x01;
+
+    /* valid Attribute ids for media elements */
+    static final int ATTRID_TITLE = 0x01;
+    static final int ATTRID_ARTIST = 0x02;
+    static final int ATTRID_ALBUM = 0x03;
+    static final int ATTRID_TRACK_NUM = 0x04;
+    static final int ATTRID_NUM_TRACKS = 0x05;
+    static final int ATTRID_GENRE = 0x06;
+    static final int ATTRID_PLAY_TIME = 0x07;
+    static final int ATTRID_COVER_ART = 0x08;
+
+    /* constants to send in Track change response */
+    static final byte[] NO_TRACK_SELECTED = {
+            (byte) 0xFF,
+            (byte) 0xFF,
+            (byte) 0xFF,
+            (byte) 0xFF,
+            (byte) 0xFF,
+            (byte) 0xFF,
+            (byte) 0xFF,
+            (byte) 0xFF
+    };
+    static final byte[] TRACK_IS_SELECTED = {
+            (byte) 0x00,
+            (byte) 0x00,
+            (byte) 0x00,
+            (byte) 0x00,
+            (byte) 0x00,
+            (byte) 0x00,
+            (byte) 0x00,
+            (byte) 0x00
+    };
+
+    /* UID size */
+    static final int UID_SIZE = 8;
+
+    static final short DEFAULT_UID_COUNTER = 0x0000;
+
+    /* Bitmask size for Media Players */
+    static final int AVRC_FEATURE_MASK_SIZE = 16;
+
+    /* Maximum attributes for media item */
+    static final int MAX_NUM_ATTR = 8;
+
+    /* notification types for remote device */
+    static final int NOTIFICATION_TYPE_INTERIM = 0;
+    static final int NOTIFICATION_TYPE_CHANGED = 1;
+
+    static final int TRACK_ID_SIZE = 8;
+
+    /* player feature bit mask constants */
+    static final short AVRC_PF_PLAY_BIT_NO = 40;
+    static final short AVRC_PF_STOP_BIT_NO = 41;
+    static final short AVRC_PF_PAUSE_BIT_NO = 42;
+    static final short AVRC_PF_REWIND_BIT_NO = 44;
+    static final short AVRC_PF_FAST_FWD_BIT_NO = 45;
+    static final short AVRC_PF_FORWARD_BIT_NO = 47;
+    static final short AVRC_PF_BACKWARD_BIT_NO = 48;
+    static final short AVRC_PF_ADV_CTRL_BIT_NO = 58;
+    static final short AVRC_PF_BROWSE_BIT_NO = 59;
+    static final short AVRC_PF_ADD2NOWPLAY_BIT_NO = 61;
+    static final short AVRC_PF_UID_UNIQUE_BIT_NO = 62;
+    static final short AVRC_PF_NOW_PLAY_BIT_NO = 65;
+    static final short AVRC_PF_GET_NUM_OF_ITEMS_BIT_NO = 67;
+    static final short AVRC_PF_COVER_ART_BIT_NO = 68;
+    static final byte PLAYER_TYPE_AUDIO = 1;
+    static final int PLAYER_SUBTYPE_NONE = 0;
+
+    // match up with btrc_play_status_t enum of bt_rc.h
+    static final int PLAYSTATUS_STOPPED = 0;
+    static final int PLAYSTATUS_PLAYING = 1;
+    static final int PLAYSTATUS_PAUSED = 2;
+    static final int PLAYSTATUS_FWD_SEEK = 3;
+    static final int PLAYSTATUS_REV_SEEK = 4;
+    static final int PLAYSTATUS_ERROR = 255;
+
+    static final byte NUM_ATTR_ALL = (byte) 0x00;
+    static final byte NUM_ATTR_NONE = (byte) 0xFF;
+
+    static final int KEY_STATE_PRESS = 1;
+    static final int KEY_STATE_RELEASE = 0;
+
+    static final int GET_ATTRIBUTE_IDS = 0;
+    static final int GET_VALUE_IDS = 1;
+    static final int GET_ATTRIBUTE_TEXT = 2;
+    static final int GET_VALUE_TEXT     = 3;
+    static final int GET_ATTRIBUTE_VALUES = 4;
+    static final int NOTIFY_ATTRIBUTE_VALUES = 5;
+    static final int SET_ATTRIBUTE_VALUES  = 6;
+    static final int GET_INVALID = 0xff;
+
+    public static final String TAG = "Avrcp";
+    public static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
+}
diff --git a/packages_apps_bluetooth_ext/AvrcpHelperClasses_ext.java b/packages_apps_bluetooth_ext/AvrcpHelperClasses_ext.java
new file mode 100644
index 0000000..ea6d7b8
--- /dev/null
+++ b/packages_apps_bluetooth_ext/AvrcpHelperClasses_ext.java
@@ -0,0 +1,451 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.avrcp;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import com.android.bluetooth.Utils;
+
+import java.util.ArrayDeque;
+import java.util.Arrays;
+import java.util.Collection;
+
+/*************************************************************************************************
+ * Helper classes used for callback/response of browsing commands:-
+ *     1) To bundle parameters for  native callbacks/response.
+ *     2) Stores information of Addressed and Browsed Media Players.
+ ************************************************************************************************/
+
+class AvrcpCmd {
+
+    AvrcpCmd() {}
+
+    /* Helper classes to pass parameters from callbacks to Avrcp handler */
+    class FolderItemsCmd {
+        byte mScope;
+        long mStartItem;
+        long mEndItem;
+        byte mNumAttr;
+        int[] mAttrIDs;
+        public byte[] mAddress;
+
+        FolderItemsCmd(byte[] address, byte scope, long startItem, long endItem, byte numAttr,
+                int[] attrIds) {
+            mAddress = address;
+            this.mScope = scope;
+            this.mStartItem = startItem;
+            this.mEndItem = endItem;
+            this.mNumAttr = numAttr;
+            this.mAttrIDs = attrIds;
+        }
+
+        @Override
+        public String toString() {
+            StringBuilder sb = new StringBuilder();
+            sb.append("[FolderItemCmd: scope " + mScope);
+            sb.append(" start " + mStartItem);
+            sb.append(" end " + mEndItem);
+            sb.append(" numAttr " + mNumAttr);
+            sb.append(" attrs: ");
+            for (int i = 0; i < mNumAttr; i++) {
+                sb.append(mAttrIDs[i] + " ");
+            }
+            return sb.toString();
+        }
+    }
+
+    class ItemAttrCmd {
+        byte mScope;
+        byte[] mUid;
+        int mUidCounter;
+        byte mNumAttr;
+        int[] mAttrIDs;
+        public byte[] mAddress;
+
+        ItemAttrCmd(byte[] address, byte scope, byte[] uid, int uidCounter, byte numAttr,
+                int[] attrIDs) {
+            mAddress = address;
+            mScope = scope;
+            mUid = uid;
+            mUidCounter = uidCounter;
+            mNumAttr = numAttr;
+            mAttrIDs = attrIDs;
+        }
+
+        @Override
+        public String toString() {
+            StringBuilder sb = new StringBuilder();
+            sb.append("[ItemAttrCmd: scope " + mScope);
+            sb.append(" uid " + Utils.byteArrayToString(mUid));
+            sb.append(" numAttr " + mNumAttr);
+            sb.append(" attrs: ");
+            for (int i = 0; i < mNumAttr; i++) {
+                sb.append(mAttrIDs[i] + " ");
+            }
+            return sb.toString();
+        }
+    }
+
+    class ElementAttrCmd {
+        byte mNumAttr;
+        int[] mAttrIDs;
+        public byte[] mAddress;
+
+        ElementAttrCmd(byte[] address, byte numAttr, int[] attrIDs) {
+            mAddress = address;
+            mNumAttr = numAttr;
+            mAttrIDs = attrIDs;
+        }
+    }
+}
+
+/* Helper classes to pass parameters to native response */
+class MediaPlayerListRsp {
+    byte mStatus;
+    short mUIDCounter;
+    byte mItemType;
+    int[] mPlayerIds;
+    byte[] mPlayerTypes;
+    int[] mPlayerSubTypes;
+    byte[] mPlayStatusValues;
+    short[] mFeatureBitMaskValues;
+    String[] mPlayerNameList;
+    int mNumItems;
+
+    MediaPlayerListRsp(byte status, short uidCounter, int numItems, byte itemType, int[] playerIds,
+            byte[] playerTypes, int[] playerSubTypes, byte[] playStatusValues,
+            short[] featureBitMaskValues, String[] playerNameList) {
+        this.mStatus = status;
+        this.mUIDCounter = uidCounter;
+        this.mNumItems = numItems;
+        this.mItemType = itemType;
+        this.mPlayerIds = playerIds;
+        this.mPlayerTypes = playerTypes;
+        this.mPlayerSubTypes = new int[numItems];
+        this.mPlayerSubTypes = playerSubTypes;
+        this.mPlayStatusValues = new byte[numItems];
+        this.mPlayStatusValues = playStatusValues;
+        int bitMaskSize = AvrcpConstants.AVRC_FEATURE_MASK_SIZE;
+        this.mFeatureBitMaskValues = new short[numItems * bitMaskSize];
+        for (int bitMaskIndex = 0; bitMaskIndex < (numItems * bitMaskSize); bitMaskIndex++) {
+            this.mFeatureBitMaskValues[bitMaskIndex] = featureBitMaskValues[bitMaskIndex];
+        }
+        this.mPlayerNameList = playerNameList;
+    }
+}
+
+class FolderItemsRsp {
+    byte mStatus;
+    short mUIDCounter;
+    byte mScope;
+    int mNumItems;
+    byte[] mFolderTypes;
+    byte[] mPlayable;
+    byte[] mItemTypes;
+    byte[] mItemUid;
+    String[] mDisplayNames; /* display name of the item. Eg: Folder name or song name */
+    int[] mAttributesNum;
+    int[] mAttrIds;
+    String[] mAttrValues;
+
+    FolderItemsRsp(byte status, short uidCounter, byte scope, int numItems, byte[] folderTypes,
+            byte[] playable, byte[] itemTypes, byte[] itemsUid, String[] displayNameArray,
+            int[] attributesNum, int[] attrIds, String[] attrValues) {
+        this.mStatus = status;
+        this.mUIDCounter = uidCounter;
+        this.mScope = scope;
+        this.mNumItems = numItems;
+        this.mFolderTypes = folderTypes;
+        this.mPlayable = playable;
+        this.mItemTypes = itemTypes;
+        this.mItemUid = itemsUid;
+        this.mDisplayNames = displayNameArray;
+        this.mAttributesNum = attributesNum;
+        this.mAttrIds = attrIds;
+        this.mAttrValues = attrValues;
+    }
+}
+
+class ItemAttrRsp {
+    byte mStatus;
+    byte mNumAttr;
+    int[] mAttributesIds;
+    String[] mAttributesArray;
+
+    ItemAttrRsp(byte status, int[] attributesIds, String[] attributesArray) {
+        mStatus = status;
+        mNumAttr = (byte) attributesIds.length;
+        mAttributesIds = attributesIds;
+        mAttributesArray = attributesArray;
+    }
+}
+
+/* stores information of Media Players in the system */
+class MediaPlayerInfo {
+
+    private byte mMajorType;
+    private int mSubType;
+    private byte mPlayStatus;
+    private short[] mFeatureBitMask;
+    @NonNull private String mPackageName;
+    @NonNull private String mDisplayableName;
+    @Nullable private MediaController mMediaController;
+
+    MediaPlayerInfo(@Nullable MediaController controller, byte majorType, int subType,
+            byte playStatus, short[] featureBitMask, @NonNull String packageName,
+            @Nullable String displayableName) {
+        this.setMajorType(majorType);
+        this.setSubType(subType);
+        this.mPlayStatus = playStatus;
+        // store a copy the FeatureBitMask array
+        this.mFeatureBitMask = Arrays.copyOf(featureBitMask, featureBitMask.length);
+        Arrays.sort(this.mFeatureBitMask);
+        this.setPackageName(packageName);
+        this.setDisplayableName(displayableName);
+        this.setMediaController(controller);
+    }
+
+    /* getters and setters */
+    byte getPlayStatus() {
+        return mPlayStatus;
+    }
+
+    void setPlayStatus(byte playStatus) {
+        this.mPlayStatus = playStatus;
+    }
+
+    MediaController getMediaController() {
+        return mMediaController;
+    }
+
+    void setMediaController(MediaController mediaController) {
+        if (mediaController != null) {
+            this.mPackageName = mediaController.getPackageName();
+        }
+        this.mMediaController = mediaController;
+    }
+
+    void setPackageName(@NonNull String name) {
+        // Controller determines package name when it is set.
+        if (mMediaController != null) {
+            return;
+        }
+        this.mPackageName = name;
+    }
+
+    String getPackageName() {
+        if (mMediaController != null) {
+            return mMediaController.getPackageName();
+        } else if (mPackageName != null) {
+            return mPackageName;
+        }
+        return null;
+    }
+
+    byte getMajorType() {
+        return mMajorType;
+    }
+
+    void setMajorType(byte majorType) {
+        this.mMajorType = majorType;
+    }
+
+    int getSubType() {
+        return mSubType;
+    }
+
+    void setSubType(int subType) {
+        this.mSubType = subType;
+    }
+
+    String getDisplayableName() {
+        return mDisplayableName;
+    }
+
+    void setDisplayableName(@Nullable String displayableName) {
+        if (displayableName == null) {
+            displayableName = "";
+        }
+        this.mDisplayableName = displayableName;
+    }
+
+    short[] getFeatureBitMask() {
+        return mFeatureBitMask;
+    }
+
+    void setFeatureBitMask(short[] featureBitMask) {
+        synchronized (this) {
+            this.mFeatureBitMask = Arrays.copyOf(featureBitMask, featureBitMask.length);
+            Arrays.sort(this.mFeatureBitMask);
+        }
+    }
+
+    boolean isBrowseSupported() {
+        synchronized (this) {
+            if (this.mFeatureBitMask == null) {
+                return false;
+            }
+            for (short bit : this.mFeatureBitMask) {
+                if (bit == AvrcpConstants.AVRC_PF_BROWSE_BIT_NO) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    /** Tests if the view of this player presented to the controller is different enough to
+     *  justify sending an Available Players Changed update */
+    public boolean equalView(MediaPlayerInfo other) {
+        return (this.mMajorType == other.getMajorType()) && (this.mSubType == other.getSubType())
+                && Arrays.equals(this.mFeatureBitMask, other.getFeatureBitMask())
+                && this.mDisplayableName.equals(other.getDisplayableName());
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append("MediaPlayerInfo ");
+        sb.append(getPackageName());
+        sb.append(" (as '" + getDisplayableName() + "')");
+        sb.append(" Type = " + getMajorType());
+        sb.append(", SubType = " + getSubType());
+        sb.append(", Status = " + mPlayStatus);
+        sb.append(" Feature Bits [");
+        short[] bits = getFeatureBitMask();
+        for (int i = 0; i < bits.length; i++) {
+            if (i != 0) {
+                sb.append(" ");
+            }
+            sb.append(bits[i]);
+        }
+        sb.append("] Controller: ");
+        sb.append(getMediaController());
+        return sb.toString();
+    }
+}
+
+/* stores information for browsable Media Players available in the system */
+class BrowsePlayerInfo {
+    public String packageName;
+    public String displayableName;
+    public String serviceClass;
+
+    BrowsePlayerInfo(String packageName, String displayableName, String serviceClass) {
+        this.packageName = packageName;
+        this.displayableName = displayableName;
+        this.serviceClass = serviceClass;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append("BrowsePlayerInfo ");
+        sb.append(packageName);
+        sb.append(" ( as '" + displayableName + "')");
+        sb.append(" service " + serviceClass);
+        return sb.toString();
+    }
+}
+
+class FolderItemsData {
+    /* initialize sizes for rsp parameters */ int mNumItems;
+    int[] mAttributesNum;
+    byte[] mFolderTypes;
+    byte[] mItemTypes;
+    byte[] mPlayable;
+    byte[] mItemUid;
+    String[] mDisplayNames;
+    int[] mAttrIds;
+    String[] mAttrValues;
+    int mAttrCounter;
+
+    FolderItemsData(int size) {
+        mNumItems = size;
+        mAttributesNum = new int[size];
+
+        mFolderTypes = new byte[size]; /* folderTypes */
+        mItemTypes = new byte[size]; /* folder or media item */
+        mPlayable = new byte[size];
+        Arrays.fill(mFolderTypes, AvrcpConstants.FOLDER_TYPE_MIXED);
+        Arrays.fill(mItemTypes, AvrcpConstants.BTRC_ITEM_MEDIA);
+        Arrays.fill(mPlayable, AvrcpConstants.ITEM_PLAYABLE);
+
+        mItemUid = new byte[size * AvrcpConstants.UID_SIZE];
+        mDisplayNames = new String[size];
+
+        mAttrIds = null; /* array of attr ids */
+        mAttrValues = null; /* array of attr values */
+    }
+}
+
+/** A queue that evicts the first element when you add an element to the end when it reaches a
+ * maximum size.
+ * This is useful for keeping a FIFO queue of items where the items drop off the front, i.e. a log
+ * with a maximum size.
+ */
+class EvictingQueue<E> extends ArrayDeque<E> {
+    private int mMaxSize;
+
+    EvictingQueue(int maxSize) {
+        super();
+        mMaxSize = maxSize;
+    }
+
+    EvictingQueue(int maxSize, int initialElements) {
+        super(initialElements);
+        mMaxSize = maxSize;
+    }
+
+    EvictingQueue(int maxSize, Collection<? extends E> c) {
+        super(c);
+        mMaxSize = maxSize;
+    }
+
+    @Override
+    public void addFirst(E e) {
+        if (super.size() == mMaxSize) {
+            return;
+        }
+        super.addFirst(e);
+    }
+
+    @Override
+    public void addLast(E e) {
+        if (super.size() == mMaxSize) {
+            super.remove();
+        }
+        super.addLast(e);
+    }
+
+    @Override
+    public boolean offerFirst(E e) {
+        if (super.size() == mMaxSize) {
+            return false;
+        }
+        return super.offerFirst(e);
+    }
+
+    @Override
+    public boolean offerLast(E e) {
+        if (super.size() == mMaxSize) {
+            super.remove();
+        }
+        return super.offerLast(e);
+    }
+}
diff --git a/packages_apps_bluetooth_ext/AvrcpMediaRspInterface_ext.java b/packages_apps_bluetooth_ext/AvrcpMediaRspInterface_ext.java
new file mode 100644
index 0000000..4be98d3
--- /dev/null
+++ b/packages_apps_bluetooth_ext/AvrcpMediaRspInterface_ext.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.avrcp;
+
+
+/*************************************************************************************************
+ * Interface for classes which handle callbacks from AvrcpMediaManager.
+ * These callbacks should map to native responses and used to communicate with the native layer.
+ ************************************************************************************************/
+
+public interface AvrcpMediaRspInterface_ext {
+    void setAddrPlayerRsp(byte[] address, int rspStatus);
+
+    void setBrowsedPlayerRsp(byte[] address, int rspStatus, byte depth, int numItems,
+            String[] textArray);
+
+    void mediaPlayerListRsp(byte[] address, int rspStatus, MediaPlayerListRsp rspObj);
+
+    void folderItemsRsp(byte[] address, int rspStatus, FolderItemsRsp rspObj);
+
+    void changePathRsp(byte[] address, int rspStatus, int numItems);
+
+    void getItemAttrRsp(byte[] address, int rspStatus, ItemAttrRsp rspObj);
+
+    void playItemRsp(byte[] address, int rspStatus);
+
+    void getTotalNumOfItemsRsp(byte[] address, int rspStatus, int uidCounter, int numItems);
+
+    void addrPlayerChangedRsp(int type, int playerId, int uidCounter);
+
+    void avalPlayerChangedRsp(byte[] address, int type);
+
+    void uidsChangedRsp(int type);
+
+    void nowPlayingChangedRsp(int type);
+
+    void trackChangedRsp(int type, byte[] uid);
+}
+
diff --git a/packages_apps_bluetooth_ext/BrowsedMediaPlayer_ext.java b/packages_apps_bluetooth_ext/BrowsedMediaPlayer_ext.java
new file mode 100644
index 0000000..d6d4b25
--- /dev/null
+++ b/packages_apps_bluetooth_ext/BrowsedMediaPlayer_ext.java
@@ -0,0 +1,975 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.avrcp;
+
+import android.annotation.NonNull;
+import android.content.ComponentName;
+import android.content.Context;
+import android.media.MediaDescription;
+import android.media.MediaMetadata;
+import android.media.browse.MediaBrowser;
+import android.media.browse.MediaBrowser.MediaItem;
+import android.media.session.MediaSession;
+import android.os.Bundle;
+import android.util.Log;
+
+import java.math.BigInteger;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Stack;
+
+/*************************************************************************************************
+ * Provides functionality required for Browsed Media Player like browsing Virtual File System, get
+ * Item Attributes, play item from the file system, etc.
+ * Acts as an Interface to communicate with Media Browsing APIs for browsing FileSystem.
+ ************************************************************************************************/
+
+class BrowsedMediaPlayer_ext {
+    private static final boolean DEBUG = true;
+    private static final String TAG = "BrowsedMediaPlayer_ext";
+
+    /* connection state with MediaBrowseService */
+    private static final int DISCONNECTED = 0;
+    private static final int CONNECTED = 1;
+    private static final int SUSPENDED = 2;
+
+    private static final int BROWSED_ITEM_ID_INDEX = 2;
+
+    private static final String[] ROOT_FOLDER = {"root"};
+
+    /*  package and service name of target Media Player which is set for browsing */
+    private String mPackageName;
+    private String mConnectingPackageName;
+    private String mClassName;
+    private Context mContext;
+    private AvrcpMediaRspInterface mMediaInterface;
+    private byte[] mBDAddr;
+
+    /* Object used to connect to MediaBrowseService of Media Player */
+    private MediaBrowser mMediaBrowser = null;
+    private MediaController mMediaController = null;
+
+    /* The mediaId to be used for subscribing for children using the MediaBrowser */
+    private String mMediaId = null;
+    private String mRootFolderUid = null;
+    private int mConnState = DISCONNECTED;
+
+    /* stores the path trail during changePath */
+    private Stack<String> mPathStack = null;
+
+    /* Number of items in current folder */
+    private int mCurrFolderNumItems = 0;
+
+    /* store mapping between uid(Avrcp) and mediaId(Media Player) for Media Item */
+    private HashMap<Integer, String> mMediaHmap = new HashMap<Integer, String>();
+
+    /* store mapping between uid(Avrcp) and mediaId(Media Player) for Folder Item */
+    private HashMap<Integer, String> mFolderHmap = new HashMap<Integer, String>();
+
+    /* command objects from avrcp handler */
+    private AvrcpCmd.FolderItemsCmd mFolderItemsReqObj;
+
+    /* store result of getfolderitems with scope="vfs" */
+    private List<MediaBrowser.MediaItem> mFolderItems = null;
+
+    /* Connection state callback handler */
+    class MediaConnectionCallback extends MediaBrowser.ConnectionCallback {
+        private String mCallbackPackageName;
+        private MediaBrowser mBrowser;
+
+        MediaConnectionCallback(String packageName) {
+            this.mCallbackPackageName = packageName;
+        }
+
+        public void setBrowser(MediaBrowser b) {
+            mBrowser = b;
+        }
+
+        @Override
+        public void onConnected() {
+            mConnState = CONNECTED;
+            if (DEBUG) {
+                Log.d(TAG, "mediaBrowser CONNECTED to " + mPackageName);
+            }
+            /* perform init tasks and set player as browsed player on successful connection */
+            onBrowseConnect(mCallbackPackageName, mBrowser);
+
+            // Remove what could be a circular dependency causing GC to never happen on this object
+            mBrowser = null;
+        }
+
+        @Override
+        public void onConnectionFailed() {
+            mConnState = DISCONNECTED;
+            // Remove what could be a circular dependency causing GC to never happen on this object
+            mBrowser = null;
+            Log.e(TAG, "mediaBrowser Connection failed with " + mPackageName
+                    + ", Sending fail response!");
+            mMediaInterface.setBrowsedPlayerRsp(mBDAddr, AvrcpConstants.RSP_INTERNAL_ERR,
+                    (byte) 0x00, 0, null);
+        }
+
+        @Override
+        public void onConnectionSuspended() {
+            mBrowser = null;
+            mConnState = SUSPENDED;
+            Log.e(TAG, "mediaBrowser SUSPENDED connection with " + mPackageName);
+        }
+    }
+
+    /* Subscription callback handler. Subscribe to a folder to get its contents */
+    private MediaBrowser.SubscriptionCallback mFolderItemsCb =
+            new MediaBrowser.SubscriptionCallback() {
+
+                @Override
+                public void onChildrenLoaded(String parentId,
+                        List<MediaBrowser.MediaItem> children) {
+                    if (DEBUG) {
+                        Log.d(TAG, "OnChildren Loaded folder items: childrens= " + children.size());
+                    }
+
+            /*
+             * cache current folder items and send as rsp when remote requests
+             * get_folder_items (scope = vfs)
+             */
+                    if (mFolderItems == null) {
+                        if (DEBUG) {
+                            Log.d(TAG, "sending setbrowsed player rsp");
+                        }
+                        mFolderItems = children;
+                        mMediaInterface.setBrowsedPlayerRsp(mBDAddr, AvrcpConstants.RSP_NO_ERROR,
+                                (byte) 0x00, children.size(), ROOT_FOLDER);
+                    } else {
+                        mFolderItems = children;
+                        mCurrFolderNumItems = mFolderItems.size();
+                        mMediaInterface.changePathRsp(mBDAddr, AvrcpConstants.RSP_NO_ERROR,
+                                mCurrFolderNumItems);
+                    }
+                    refreshFolderItems(mFolderItems);
+                    mMediaBrowser.unsubscribe(parentId);
+                }
+
+                /* UID is invalid */
+                @Override
+                public void onError(String id) {
+                    Log.e(TAG, "set browsed player rsp. Could not get root folder items");
+                    mMediaInterface.setBrowsedPlayerRsp(mBDAddr, AvrcpConstants.RSP_INTERNAL_ERR,
+                            (byte) 0x00, 0, null);
+                }
+            };
+
+    /* callback from media player in response to getitemAttr request */
+    private class ItemAttribSubscriber extends MediaBrowser.SubscriptionCallback {
+        private String mMediaId;
+        private AvrcpCmd.ItemAttrCmd mAttrReq;
+
+        ItemAttribSubscriber(@NonNull AvrcpCmd.ItemAttrCmd attrReq, @NonNull String mediaId) {
+            mAttrReq = attrReq;
+            mMediaId = mediaId;
+        }
+
+        @Override
+        public void onChildrenLoaded(String parentId, List<MediaBrowser.MediaItem> children) {
+            String logprefix = "ItemAttribSubscriber(" + mMediaId + "): ";
+            if (DEBUG) {
+                Log.d(TAG, logprefix + "OnChildren Loaded");
+            }
+            int status = AvrcpConstants.RSP_INV_ITEM;
+
+            if (children == null) {
+                Log.w(TAG, logprefix + "children list is null parentId: " + parentId);
+            } else {
+                /* find the item in the folder */
+                for (MediaBrowser.MediaItem item : children) {
+                    if (item.getMediaId().equals(mMediaId)) {
+                        if (DEBUG) {
+                            Log.d(TAG, logprefix + "found item");
+                        }
+                        getItemAttrFilterAttr(item);
+                        status = AvrcpConstants.RSP_NO_ERROR;
+                        break;
+                    }
+                }
+            }
+            /* Send only error from here, in case of success, getItemAttrFilterAttr sends */
+            if (status != AvrcpConstants.RSP_NO_ERROR) {
+                Log.e(TAG, logprefix + "not able to find item from " + parentId);
+                mMediaInterface.getItemAttrRsp(mBDAddr, status, null);
+            }
+            mMediaBrowser.unsubscribe(parentId);
+        }
+
+        @Override
+        public void onError(String id) {
+            Log.e(TAG, "Could not get attributes from media player id: " + id);
+            mMediaInterface.getItemAttrRsp(mBDAddr, AvrcpConstants.RSP_INTERNAL_ERR, null);
+        }
+
+        /* helper method to filter required attibuteand send GetItemAttr response */
+        private void getItemAttrFilterAttr(@NonNull MediaBrowser.MediaItem mediaItem) {
+            /* Response parameters */
+            int[] attrIds = null; /* array of attr ids */
+            String[] attrValues = null; /* array of attr values */
+
+            /* variables to temperorily add attrs */
+            ArrayList<Integer> attrIdArray = new ArrayList<Integer>();
+            ArrayList<String> attrValueArray = new ArrayList<String>();
+            ArrayList<Integer> attrReqIds = new ArrayList<Integer>();
+
+            if (mAttrReq.mNumAttr == AvrcpConstants.NUM_ATTR_NONE) {
+                // Note(jamuraa): the stack should never send this, remove?
+                Log.i(TAG, "getItemAttrFilterAttr: No attributes requested");
+                mMediaInterface.getItemAttrRsp(mBDAddr, AvrcpConstants.RSP_BAD_PARAM, null);
+                return;
+            }
+
+            /* check if remote device has requested all attributes */
+            if (mAttrReq.mNumAttr == AvrcpConstants.NUM_ATTR_ALL
+                    || mAttrReq.mNumAttr == AvrcpConstants.MAX_NUM_ATTR) {
+                for (int idx = 1; idx <= AvrcpConstants.MAX_NUM_ATTR; idx++) {
+                    attrReqIds.add(idx); /* attr id 0x00 is unused */
+                }
+            } else {
+                /* get only the requested attribute ids from the request */
+                for (int idx = 0; idx < mAttrReq.mNumAttr; idx++) {
+                    attrReqIds.add(mAttrReq.mAttrIDs[idx]);
+                }
+            }
+
+            /* lookup and copy values of attributes for ids requested above */
+            for (int attrId : attrReqIds) {
+                /* check if media player provided requested attributes */
+                String value = getAttrValue(attrId, mediaItem);
+                if (value != null) {
+                    attrIdArray.add(attrId);
+                    attrValueArray.add(value);
+                }
+            }
+
+            /* copy filtered attr ids and attr values to response parameters */
+            attrIds = new int[attrIdArray.size()];
+            for (int i = 0; i < attrIdArray.size(); i++) {
+                attrIds[i] = attrIdArray.get(i);
+            }
+
+            attrValues = attrValueArray.toArray(new String[attrIdArray.size()]);
+
+            /* create rsp object and send response */
+            ItemAttrRsp rspObj = new ItemAttrRsp(AvrcpConstants.RSP_NO_ERROR, attrIds, attrValues);
+            mMediaInterface.getItemAttrRsp(mBDAddr, AvrcpConstants.RSP_NO_ERROR, rspObj);
+        }
+    }
+
+    /* Constructor */
+    BrowsedMediaPlayer(byte[] address, Context context,
+            AvrcpMediaRspInterface mAvrcpMediaRspInterface) {
+        mContext = context;
+        mMediaInterface = mAvrcpMediaRspInterface;
+        mBDAddr = address;
+    }
+
+    /* initialize mediacontroller in order to communicate with media player. */
+    private void onBrowseConnect(String connectedPackage, MediaBrowser browser) {
+        if (!connectedPackage.equals(mConnectingPackageName)) {
+            Log.w(TAG, "onBrowseConnect: recieved callback for package" + mConnectingPackageName +
+                    "we aren't connecting to " + connectedPackage);
+            mMediaInterface.setBrowsedPlayerRsp(
+                    mBDAddr, AvrcpConstants.RSP_INTERNAL_ERR, (byte) 0x00, 0, null);
+            return;
+        }
+        mConnectingPackageName = null;
+
+        if (browser == null) {
+            Log.e(TAG, "onBrowseConnect: received a null browser for " + connectedPackage);
+            mMediaInterface.setBrowsedPlayerRsp(mBDAddr, AvrcpConstants.RSP_INTERNAL_ERR,
+                    (byte) 0x00, 0, null);
+            return;
+        }
+
+        MediaSession.Token token = null;
+        try {
+            if (!browser.isConnected()) {
+                Log.e(TAG, "setBrowsedPlayer: " + mPackageName + "not connected");
+            } else if ((token = browser.getSessionToken()) == null) {
+                Log.e(TAG, "setBrowsedPlayer: " + mPackageName + "no Session token");
+            } else {
+                /* update to the new MediaBrowser */
+                if (mMediaBrowser != null) {
+                    mMediaBrowser.disconnect();
+                }
+                mMediaBrowser = browser;
+                mPackageName = connectedPackage;
+
+                /* get rootfolder uid from media player */
+                if (mMediaId == null) {
+                    mMediaId = mMediaBrowser.getRoot();
+                    Log.d(TAG, "media browser root = " + mMediaId);
+
+                    if (mMediaId == null || mMediaId.length() == 0) {
+                        Log.e(TAG, "onBrowseConnect: root value is empty or null");
+                        mMediaInterface.setBrowsedPlayerRsp(
+                                mBDAddr, AvrcpConstants.RSP_INTERNAL_ERR, (byte) 0x00, 0, null);
+                        return;
+                    }
+
+                    /*
+                     * assuming that root folder uid will not change on uids changed
+                     */
+                    mRootFolderUid = mMediaId;
+                    /* store root folder uid to stack */
+                    mPathStack.push(mMediaId);
+                    /* get root folder items */
+                    mMediaBrowser.subscribe(mRootFolderUid, mFolderItemsCb);
+                }
+
+                mMediaController = MediaControllerFactory.make(mContext, token);
+                return;
+            }
+        } catch (NullPointerException ex) {
+            Log.e(TAG, "setBrowsedPlayer : Null pointer during init");
+            ex.printStackTrace();
+        }
+
+        mMediaInterface.setBrowsedPlayerRsp(mBDAddr, AvrcpConstants.RSP_INTERNAL_ERR, (byte) 0x00,
+                0, null);
+    }
+
+    public void setBrowsed(String packageName, String cls) {
+        Log.w(TAG, "!! In setBrowse function !!" + mFolderItems);
+        if ((mPackageName != null && packageName != null
+                && !mPackageName.equals(packageName)) || (mFolderItems == null)) {
+            Log.d(TAG, "setBrowse for packageName = " + packageName);
+            mConnectingPackageName = packageName;
+            mPackageName = packageName;
+            mClassName = cls;
+
+           /* cleanup variables from previous browsed calls */
+           mFolderItems = null;
+           mMediaId = null;
+           mRootFolderUid = null;
+           /*
+            * create stack to store the navigation trail (current folder ID). This
+            * will be required while navigating up the folder
+            */
+           mPathStack = new Stack<String>();
+           /* Bind to MediaBrowseService of MediaPlayer */
+           MediaConnectionCallback callback = new MediaConnectionCallback(packageName);
+           MediaBrowser tempBrowser = new MediaBrowser(
+                   mContext, new ComponentName(packageName, mClassName), callback, null);
+           callback.setBrowser(tempBrowser);
+           tempBrowser.connect();
+        } else if (mFolderItems != null) {
+            mPackageName = packageName;
+            mClassName = cls;
+            int rsp_status = AvrcpConstants.RSP_NO_ERROR;
+            int folder_depth = (mPathStack.size() > 0) ? (mPathStack.size() - 1) : 0;
+            if (!mPathStack.empty()) {
+                Log.d(TAG, "~~current Path = " + mPathStack.peek());
+                if (mPathStack.size() > 1) {
+                    String top = mPathStack.peek();
+                    mPathStack.pop();
+                    String path = mPathStack.peek();
+                    mPathStack.push(top);
+                    String [] ExternalPath = path.split("/");
+                    String [] folderPath = new String[ExternalPath.length - 1];
+                    for (int i = 0; i < (ExternalPath.length - 1); i++) {
+                        folderPath[i] = ExternalPath[i + 1];
+                        Log.d(TAG,"folderPath[" + i + "] = " + folderPath[i]);
+                    }
+                    mMediaInterface.setBrowsedPlayerRsp(mBDAddr, rsp_status,
+                            (byte)folder_depth, mFolderItems.size(), folderPath);
+                } else if (mPathStack.size() == 1) {
+                    Log.d(TAG, "On root send SetBrowse response with root properties");
+                    mMediaInterface.setBrowsedPlayerRsp(mBDAddr, rsp_status, (byte)folder_depth,
+                            mFolderItems.size(), ROOT_FOLDER);
+                }
+            } else {
+                Log.e(TAG, "Path Stack empty sending internal error !!!");
+                rsp_status = AvrcpConstants.RSP_INTERNAL_ERR;
+                mMediaInterface.setBrowsedPlayerRsp(mBDAddr, rsp_status, (byte)0x00, 0, null);
+            }
+            Log.d(TAG, "send setbrowse rsp status=" + rsp_status + " folder_depth=" + folder_depth);
+        }
+    }
+
+    /* called when connection to media player is closed */
+    public void cleanup() {
+        if (DEBUG) {
+            Log.d(TAG, "cleanup");
+        }
+
+        if (mConnState != DISCONNECTED) {
+            if (mMediaBrowser != null) mMediaBrowser.disconnect();
+        }
+
+        mMediaHmap = null;
+        mFolderHmap = null;
+        mMediaController = null;
+        mMediaBrowser = null;
+        mPathStack = null;
+    }
+
+    public boolean isPlayerConnected() {
+        if (mMediaBrowser == null) {
+            if (DEBUG) {
+                Log.d(TAG, "isPlayerConnected: mMediaBrowser = null!");
+            }
+            return false;
+        }
+
+        return mMediaBrowser.isConnected();
+    }
+
+    /* returns number of items in new path as reponse */
+    public void changePath(byte[] folderUid, byte direction) {
+        if (DEBUG) {
+            Log.d(TAG, "changePath.direction = " + direction);
+        }
+        String newPath = "";
+
+        if (!isPlayerConnected()) {
+            Log.w(TAG, "changePath: disconnected from player service, sending internal error");
+            mMediaInterface.changePathRsp(mBDAddr, AvrcpConstants.RSP_INTERNAL_ERR, 0);
+            return;
+        }
+
+        if (mMediaBrowser == null) {
+            Log.e(TAG, "Media browser is null, sending internal error");
+            mMediaInterface.changePathRsp(mBDAddr, AvrcpConstants.RSP_INTERNAL_ERR, 0);
+            return;
+        }
+
+        /* check direction and change the path */
+        if (direction == AvrcpConstants.DIR_DOWN) { /* move down */
+            if ((newPath = byteToStringFolder(folderUid)) == null) {
+                Log.e(TAG, "Could not get media item from folder Uid, sending err response");
+                mMediaInterface.changePathRsp(mBDAddr, AvrcpConstants.RSP_INV_ITEM, 0);
+            } else if (!isBrowsableFolderDn(newPath)) {
+                /* new path is not browsable */
+                Log.e(TAG, "ItemUid received from changePath cmd is not browsable");
+                mMediaInterface.changePathRsp(mBDAddr, AvrcpConstants.RSP_INV_DIRECTORY, 0);
+            } else if (mPathStack.peek().equals(newPath)) {
+                /* new_folder is same as current folder */
+                Log.e(TAG, "new_folder is same as current folder, Invalid direction!");
+                mMediaInterface.changePathRsp(mBDAddr, AvrcpConstants.RSP_INV_DIRN, 0);
+            } else {
+                mMediaBrowser.subscribe(newPath, mFolderItemsCb);
+                /* assume that call is success and update stack with new folder path */
+                mPathStack.push(newPath);
+            }
+        } else if (direction == AvrcpConstants.DIR_UP) { /* move up */
+            if (!isBrowsableFolderUp()) {
+                /* Already on the root, cannot allow up: PTS: test case TC_TG_MCN_CB_BI_02_C
+                 * This is required, otherwise some CT will keep on sending change path up
+                 * until they receive error */
+                Log.w(TAG, "Cannot go up from now, already in the root, Invalid direction!");
+                mMediaInterface.changePathRsp(mBDAddr, AvrcpConstants.RSP_INV_DIRN, 0);
+            } else {
+                /* move folder up */
+                mPathStack.pop();
+                newPath = mPathStack.peek();
+                mMediaBrowser.subscribe(newPath, mFolderItemsCb);
+            }
+        } else { /* invalid direction */
+            Log.w(TAG, "changePath : Invalid direction " + direction);
+            mMediaInterface.changePathRsp(mBDAddr, AvrcpConstants.RSP_INV_DIRN, 0);
+        }
+    }
+
+    public void getItemAttr(AvrcpCmd.ItemAttrCmd itemAttr) {
+        String mediaID;
+        if (DEBUG) {
+            Log.d(TAG, "getItemAttr");
+        }
+
+        /* check if uid is valid by doing a lookup in hashmap */
+        mediaID = byteToStringMedia(itemAttr.mUid);
+        if (mediaID == null) {
+            Log.e(TAG, "uid is invalid");
+            mMediaInterface.getItemAttrRsp(mBDAddr, AvrcpConstants.RSP_INV_ITEM, null);
+            return;
+        }
+
+        /* check scope */
+        if (itemAttr.mScope != AvrcpConstants.BTRC_SCOPE_FILE_SYSTEM) {
+            Log.e(TAG, "invalid scope");
+            mMediaInterface.getItemAttrRsp(mBDAddr, AvrcpConstants.RSP_INV_SCOPE, null);
+            return;
+        }
+
+        if (mMediaBrowser == null) {
+            Log.e(TAG, "mMediaBrowser is null");
+            mMediaInterface.getItemAttrRsp(mBDAddr, AvrcpConstants.RSP_INTERNAL_ERR, null);
+            return;
+        }
+
+        /* Subscribe to the parent to list items and retrieve the right one */
+        mMediaBrowser.subscribe(mPathStack.peek(), new ItemAttribSubscriber(itemAttr, mediaID));
+    }
+
+    public void getTotalNumOfItems(byte scope) {
+        if (DEBUG) {
+            Log.d(TAG, "getTotalNumOfItems scope = " + scope);
+        }
+        if (scope != AvrcpConstants.BTRC_SCOPE_FILE_SYSTEM) {
+            Log.e(TAG, "getTotalNumOfItems error" + scope);
+            mMediaInterface.getTotalNumOfItemsRsp(mBDAddr, AvrcpConstants.RSP_INV_SCOPE, 0, 0);
+            return;
+        }
+
+        if (mFolderItems == null) {
+            Log.e(TAG, "mFolderItems is null, sending internal error");
+            /* folderitems were not fetched during change path */
+            mMediaInterface.getTotalNumOfItemsRsp(mBDAddr, AvrcpConstants.RSP_INTERNAL_ERR, 0, 0);
+            return;
+        }
+
+        /* find num items using size of already cached folder items */
+        mMediaInterface.getTotalNumOfItemsRsp(mBDAddr, AvrcpConstants.RSP_NO_ERROR, 0,
+                mFolderItems.size());
+    }
+
+    public void getFolderItemsVFS(AvrcpCmd.FolderItemsCmd reqObj) {
+        if (!isPlayerConnected()) {
+            Log.e(TAG, "unable to connect to media player, sending internal error");
+            /* unable to connect to media player. Send error response to remote device */
+            mMediaInterface.folderItemsRsp(mBDAddr, AvrcpConstants.RSP_INTERNAL_ERR, null);
+            return;
+        }
+
+        if (DEBUG) {
+            Log.d(TAG, "getFolderItemsVFS");
+        }
+        mFolderItemsReqObj = reqObj;
+
+        if (mFolderItems == null) {
+            /* Failed to fetch folder items from media player. Send error to remote device */
+            Log.e(TAG, "Failed to fetch folder items during getFolderItemsVFS");
+            mMediaInterface.folderItemsRsp(mBDAddr, AvrcpConstants.RSP_INTERNAL_ERR, null);
+            return;
+        }
+
+        /* Filter attributes based on the request and send response to remote device */
+        getFolderItemsFilterAttr(mBDAddr, reqObj, mFolderItems,
+                AvrcpConstants.BTRC_SCOPE_FILE_SYSTEM, mFolderItemsReqObj.mStartItem,
+                mFolderItemsReqObj.mEndItem);
+    }
+
+    /* Instructs media player to play particular media item */
+    public void playItem(byte[] uid, byte scope) {
+        String folderUid;
+
+        if (isPlayerConnected()) {
+            /* check if uid is valid */
+            if ((folderUid = byteToStringMedia(uid)) == null) {
+                Log.e(TAG, "uid is invalid!");
+                mMediaInterface.playItemRsp(mBDAddr, AvrcpConstants.RSP_INV_ITEM);
+                return;
+            }
+
+            if (mMediaController != null) {
+                MediaController.TransportControls mediaControllerCntrl =
+                        mMediaController.getTransportControls();
+                if (DEBUG) {
+                    Log.d(TAG, "Sending playID: " + folderUid);
+                }
+
+                if (scope == AvrcpConstants.BTRC_SCOPE_FILE_SYSTEM) {
+                    mediaControllerCntrl.playFromMediaId(folderUid, null);
+                    mMediaInterface.playItemRsp(mBDAddr, AvrcpConstants.RSP_NO_ERROR);
+                } else {
+                    Log.e(TAG, "playItem received for invalid scope!");
+                    mMediaInterface.playItemRsp(mBDAddr, AvrcpConstants.RSP_INV_SCOPE);
+                }
+            } else {
+                Log.e(TAG, "mediaController is null");
+                mMediaInterface.playItemRsp(mBDAddr, AvrcpConstants.RSP_INTERNAL_ERR);
+            }
+        } else {
+            Log.e(TAG, "playItem: Not connected to media player");
+            mMediaInterface.playItemRsp(mBDAddr, AvrcpConstants.RSP_INTERNAL_ERR);
+        }
+    }
+
+    /*
+     * helper method to check if startItem and endItem index is with range of
+     * MediaItem list. (Resultset containing all items in current path)
+     */
+    private List<MediaBrowser.MediaItem> checkIndexOutofBounds(byte[] bdaddr,
+            List<MediaBrowser.MediaItem> children, long startItem, long endItem) {
+        if (endItem >= children.size()) {
+            endItem = children.size() - 1;
+        }
+        if (startItem >= Integer.MAX_VALUE) {
+            startItem = Integer.MAX_VALUE;
+        }
+        try {
+            List<MediaBrowser.MediaItem> childrenSubList =
+                    children.subList((int) startItem, (int) endItem + 1);
+            if (childrenSubList.isEmpty()) {
+                Log.i(TAG, "childrenSubList is empty.");
+                throw new IndexOutOfBoundsException();
+            }
+            return childrenSubList;
+        } catch (IndexOutOfBoundsException ex) {
+            Log.w(TAG, "Index out of bounds start item =" + startItem + " end item = " + Math.min(
+                    children.size(), endItem + 1));
+            return null;
+        } catch (IllegalArgumentException ex) {
+            Log.i(TAG, "Index out of bounds start item =" + startItem + " > size");
+            return null;
+        }
+    }
+
+
+    /*
+     * helper method to filter required attibutes before sending GetFolderItems response
+     */
+    public void getFolderItemsFilterAttr(byte[] bdaddr, AvrcpCmd.FolderItemsCmd mFolderItemsReqObj,
+            List<MediaBrowser.MediaItem> children, byte scope, long startItem, long endItem) {
+        if (DEBUG) {
+            Log.d(TAG,
+                    "getFolderItemsFilterAttr: startItem =" + startItem + ", endItem = " + endItem);
+        }
+
+        List<MediaBrowser.MediaItem> resultItems = new ArrayList<MediaBrowser.MediaItem>();
+
+        if (children == null) {
+            Log.e(TAG, "Error: children are null in getFolderItemsFilterAttr");
+            mMediaInterface.folderItemsRsp(bdaddr, AvrcpConstants.RSP_INV_RANGE, null);
+            return;
+        }
+
+        /* check for index out of bound errors */
+        resultItems = checkIndexOutofBounds(bdaddr, children, startItem, endItem);
+        if (resultItems == null) {
+            Log.w(TAG, "resultItems is null.");
+            mMediaInterface.folderItemsRsp(bdaddr, AvrcpConstants.RSP_INV_RANGE, null);
+            return;
+        }
+        FolderItemsData folderDataNative = new FolderItemsData(resultItems.size());
+
+        /* variables to temperorily add attrs */
+        ArrayList<String> attrArray = new ArrayList<String>();
+        ArrayList<Integer> attrId = new ArrayList<Integer>();
+
+        for (int itemIndex = 0; itemIndex < resultItems.size(); itemIndex++) {
+            /* item type. Needs to be set by media player */
+            MediaBrowser.MediaItem item = resultItems.get(itemIndex);
+            int flags = item.getFlags();
+            if ((flags & MediaBrowser.MediaItem.FLAG_BROWSABLE) != 0) {
+                folderDataNative.mItemTypes[itemIndex] = AvrcpConstants.BTRC_ITEM_FOLDER;
+            } else {
+                folderDataNative.mItemTypes[itemIndex] = AvrcpConstants.BTRC_ITEM_MEDIA;
+            }
+
+            /* set playable */
+            if ((flags & MediaBrowser.MediaItem.FLAG_PLAYABLE) != 0) {
+                folderDataNative.mPlayable[itemIndex] = AvrcpConstants.ITEM_PLAYABLE;
+            } else {
+                folderDataNative.mPlayable[itemIndex] = AvrcpConstants.ITEM_NOT_PLAYABLE;
+            }
+            /* set uid for current item */
+            byte[] uid;
+            if (folderDataNative.mItemTypes[itemIndex] == AvrcpConstants.BTRC_ITEM_MEDIA)
+                uid = stringToByteMedia(item.getDescription().getMediaId());
+            else
+                uid = stringToByteFolder(item.getDescription().getMediaId());
+
+            for (int idx = 0; idx < AvrcpConstants.UID_SIZE; idx++) {
+                folderDataNative.mItemUid[itemIndex * AvrcpConstants.UID_SIZE + idx] = uid[idx];
+            }
+
+            /* Set display name for current item */
+            folderDataNative.mDisplayNames[itemIndex] =
+                    getAttrValue(AvrcpConstants.ATTRID_TITLE, item);
+
+            int maxAttributesRequested = 0;
+            boolean isAllAttribRequested = false;
+            /* check if remote requested for attributes */
+            if (mFolderItemsReqObj.mNumAttr != AvrcpConstants.NUM_ATTR_NONE) {
+                int attrCnt = 0;
+
+                /* add requested attr ids to a temp array */
+                if (mFolderItemsReqObj.mNumAttr == AvrcpConstants.NUM_ATTR_ALL) {
+                    isAllAttribRequested = true;
+                    maxAttributesRequested = AvrcpConstants.MAX_NUM_ATTR;
+                } else {
+                    /* get only the requested attribute ids from the request */
+                    maxAttributesRequested = mFolderItemsReqObj.mNumAttr;
+                }
+
+                /* lookup and copy values of attributes for ids requested above */
+                for (int idx = 0; idx < maxAttributesRequested; idx++) {
+                    /* check if media player provided requested attributes */
+                    String value = null;
+
+                    int attribId =
+                            isAllAttribRequested ? (idx + 1) : mFolderItemsReqObj.mAttrIDs[idx];
+                    value = getAttrValue(attribId, resultItems.get(itemIndex));
+                    if (value != null) {
+                        attrArray.add(value);
+                        attrId.add(attribId);
+                        attrCnt++;
+                    }
+                }
+                /* add num attr actually received from media player for a particular item */
+                folderDataNative.mAttributesNum[itemIndex] = attrCnt;
+            }
+        }
+
+        /* copy filtered attr ids and attr values to response parameters */
+        if (attrId.size() > 0) {
+            folderDataNative.mAttrIds = new int[attrId.size()];
+            for (int attrIndex = 0; attrIndex < attrId.size(); attrIndex++) {
+                folderDataNative.mAttrIds[attrIndex] = attrId.get(attrIndex);
+            }
+            folderDataNative.mAttrValues = attrArray.toArray(new String[attrArray.size()]);
+        }
+
+        /* create rsp object and send response to remote device */
+        FolderItemsRsp rspObj =
+                new FolderItemsRsp(AvrcpConstants.RSP_NO_ERROR, Avrcp.sUIDCounter, scope,
+                        folderDataNative.mNumItems, folderDataNative.mFolderTypes,
+                        folderDataNative.mPlayable, folderDataNative.mItemTypes,
+                        folderDataNative.mItemUid, folderDataNative.mDisplayNames,
+                        folderDataNative.mAttributesNum, folderDataNative.mAttrIds,
+                        folderDataNative.mAttrValues);
+        mMediaInterface.folderItemsRsp(bdaddr, AvrcpConstants.RSP_NO_ERROR, rspObj);
+    }
+
+    public static String getAttrValue(int attr, MediaBrowser.MediaItem item) {
+        String attrValue = null;
+        try {
+            MediaDescription desc = item.getDescription();
+            Bundle extras = desc.getExtras();
+            switch (attr) {
+                /* Title is mandatory attribute */
+                case AvrcpConstants.ATTRID_TITLE:
+                    attrValue = desc.getTitle().toString();
+                    break;
+
+                case AvrcpConstants.ATTRID_ARTIST:
+                    attrValue = extras.getString(MediaMetadata.METADATA_KEY_ARTIST);
+                    break;
+
+                case AvrcpConstants.ATTRID_ALBUM:
+                    attrValue = extras.getString(MediaMetadata.METADATA_KEY_ALBUM);
+                    break;
+
+                case AvrcpConstants.ATTRID_TRACK_NUM:
+                    attrValue = extras.getString(MediaMetadata.METADATA_KEY_TRACK_NUMBER);
+                    break;
+
+                case AvrcpConstants.ATTRID_NUM_TRACKS:
+                    attrValue = extras.getString(MediaMetadata.METADATA_KEY_NUM_TRACKS);
+                    break;
+
+                case AvrcpConstants.ATTRID_GENRE:
+                    attrValue = extras.getString(MediaMetadata.METADATA_KEY_GENRE);
+                    break;
+
+                case AvrcpConstants.ATTRID_PLAY_TIME:
+                    attrValue = extras.getString(MediaMetadata.METADATA_KEY_DURATION);
+                    break;
+
+                case AvrcpConstants.ATTRID_COVER_ART:
+                    attrValue = Avrcp_ext.getImgHandleFromTitle(desc.getTitle().toString());
+                    break;
+
+                default:
+                    Log.e(TAG, "getAttrValue: Unknown attribute ID requested: " + attr);
+                    return null;
+            }
+        } catch (NullPointerException ex) {
+            Log.w(TAG, "getAttrValue: attr id not found in result");
+            /* checking if attribute is title, then it is mandatory and cannot send null */
+            if (attr == AvrcpConstants.ATTRID_TITLE) {
+                attrValue = "<Unknown Title>";
+            } else {
+                return null;
+            }
+        }
+        if (DEBUG) {
+            Log.d(TAG, "getAttrValue: attrvalue = " + attrValue + " attr id: " + attr);
+        }
+        return attrValue;
+    }
+
+
+    public String getPackageName() {
+        return mPackageName;
+    }
+
+    /* Helper methods */
+
+    /* check if item is browsable Down*/
+    private boolean isBrowsableFolderDn(String uid) {
+        for (MediaBrowser.MediaItem item : mFolderItems) {
+            if (item.getMediaId().equals(uid) && (
+                    (item.getFlags() & MediaBrowser.MediaItem.FLAG_BROWSABLE)
+                            == MediaBrowser.MediaItem.FLAG_BROWSABLE)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /* check if browsable Up*/
+    private boolean isBrowsableFolderUp() {
+        if (mPathStack.peek().equals(mRootFolderUid)) {
+            /* Already on the root, cannot go up */
+            return false;
+        }
+        return true;
+    }
+
+    private String parseQueueId(String mediaId) {
+        if (isNumeric(mediaId)) {
+            Log.d(TAG, "Get queue id: " + mediaId);
+            return mediaId.trim();
+        } else {
+            String[] mediaIdItems = mediaId.split(",");
+            if (mediaIdItems != null && mediaIdItems.length > BROWSED_ITEM_ID_INDEX) {
+                Log.d(TAG, "Get queue id: " + mediaIdItems[BROWSED_ITEM_ID_INDEX]);
+                return mediaIdItems[BROWSED_ITEM_ID_INDEX].trim();
+            }
+        }
+
+        Log.d(TAG, "Unkown queue id");
+        return null;
+    }
+
+    /* convert uid to mediaId for Media item*/
+    private String byteToStringMedia(byte[] byteArray) {
+        int uid = new BigInteger(byteArray).intValue();
+        String mediaId = mMediaHmap.get(uid);
+        return mediaId;
+    }
+
+    /* convert uid to mediaId for Folder item*/
+    private String byteToStringFolder(byte[] byteArray) {
+        int uid = new BigInteger(byteArray).intValue();
+        String mediaId = mFolderHmap.get(uid);
+        return mediaId;
+    }
+
+    /* convert mediaId to uid for Media item*/
+    private byte[] stringToByteMedia(String mediaId) {
+        /* check if this mediaId already exists in hashmap */
+        if (!mMediaHmap.containsValue(mediaId)) { /* add to hashmap */
+            int uid;
+            String queueId = parseQueueId(mediaId);
+            if (queueId == null) {
+                uid = mMediaHmap.size() + 1;
+            } else {
+                uid = Integer.valueOf(queueId).intValue();
+            }
+
+            mMediaHmap.put(uid, mediaId);
+            return intToByteArray(uid);
+        } else { /* search key for give mediaId */
+            for (int uid : mMediaHmap.keySet()) {
+                if (mMediaHmap.get(uid).equals(mediaId)) {
+                    return intToByteArray(uid);
+                }
+            }
+        }
+        return null;
+    }
+
+    /* convert mediaId to uid for Folder item*/
+    private byte[] stringToByteFolder(String mediaId) {
+        /* check if this mediaId already exists in hashmap */
+        if (!mFolderHmap.containsValue(mediaId)) { /* add to hashmap */
+            // Offset by one as uid 0 is reserved
+            int uid = mFolderHmap.size() + 1;
+            mFolderHmap.put(uid, mediaId);
+            return intToByteArray(uid);
+        } else { /* search key for give mediaId */
+            for (int uid : mFolderHmap.keySet()) {
+                if (mFolderHmap.get(uid).equals(mediaId)) {
+                    return intToByteArray(uid);
+                }
+            }
+        }
+        return null;
+    }
+
+    private void refreshFolderItems(List<MediaBrowser.MediaItem> folderItems) {
+        for (int itemIndex = 0; itemIndex < folderItems.size(); itemIndex++) {
+            MediaBrowser.MediaItem item = folderItems.get(itemIndex);
+            int flags = item.getFlags();
+            if ((flags & MediaBrowser.MediaItem.FLAG_BROWSABLE) != 0) {
+                Log.d(TAG, "Folder item, no need refresh hashmap from mediaId to uid");
+            } else {
+                Log.d(TAG, "Media item, refresh haspmap from mediaId to uid");
+                stringToByteMedia(item.getDescription().getMediaId());
+            }
+        }
+    }
+
+    /* converts queue item received from getQueue call, to MediaItem used by FilterAttr method */
+    private List<MediaBrowser.MediaItem> queueItem2MediaItem(
+            List<MediaSession.QueueItem> tempItems) {
+
+        List<MediaBrowser.MediaItem> tempMedia = new ArrayList<MediaBrowser.MediaItem>();
+        for (int itemCount = 0; itemCount < tempItems.size(); itemCount++) {
+            MediaDescription.Builder build = new MediaDescription.Builder();
+            build.setMediaId(Long.toString(tempItems.get(itemCount).getQueueId()));
+            build.setTitle(tempItems.get(itemCount).getDescription().getTitle());
+            build.setExtras(tempItems.get(itemCount).getDescription().getExtras());
+            MediaDescription des = build.build();
+            MediaItem item = new MediaItem((des), MediaItem.FLAG_PLAYABLE);
+            tempMedia.add(item);
+        }
+        return tempMedia;
+    }
+
+    /* convert integer to byte array of size 8 bytes */
+    public byte[] intToByteArray(int value) {
+        int index = 0;
+        byte[] encodedValue = new byte[AvrcpConstants.UID_SIZE];
+
+        encodedValue[index++] = (byte) 0x00;
+        encodedValue[index++] = (byte) 0x00;
+        encodedValue[index++] = (byte) 0x00;
+        encodedValue[index++] = (byte) 0x00;
+        encodedValue[index++] = (byte) (value >> 24);
+        encodedValue[index++] = (byte) (value >> 16);
+        encodedValue[index++] = (byte) (value >> 8);
+        encodedValue[index++] = (byte) value;
+
+        return encodedValue;
+    }
+
+    public boolean isNumeric(String mediaId) {
+        String trimStr = mediaId.trim();
+        int length = trimStr.length();
+
+        for(int i = 0; i < length; i++) {
+            char c = trimStr.charAt(i);
+            if (!((c >= '0' && c <= '9'))) {
+                Log.v(TAG, "Non-Numeric media Id");
+                return false;
+            }
+        }
+        Log.v(TAG, "Numeric media Id");
+        return true;
+    }
+}
diff --git a/packages_apps_bluetooth_ext/com_android_bluetooth_avrcp.cpp b/packages_apps_bluetooth_ext/com_android_bluetooth_avrcp.cpp
new file mode 100644
index 0000000..fc72517
--- /dev/null
+++ b/packages_apps_bluetooth_ext/com_android_bluetooth_avrcp.cpp
@@ -0,0 +1,1554 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "BluetoothAvrcpServiceJni"
+
+#define LOG_NDEBUG 0
+
+#include "android_runtime/AndroidRuntime.h"
+#include "com_android_bluetooth.h"
+#include "hardware/bt_rc.h"
+#include "utils/Log.h"
+
+#include <inttypes.h>
+#include <string.h>
+#include <mutex>
+#include <shared_mutex>
+
+namespace android {
+static jmethodID method_getRcFeatures;
+static jmethodID method_getPlayStatus;
+static jmethodID method_getElementAttr;
+static jmethodID method_registerNotification;
+static jmethodID method_volumeChangeCallback;
+static jmethodID method_handlePassthroughCmd;
+static jmethodID method_getFolderItemsCallback;
+static jmethodID method_setAddressedPlayerCallback;
+
+static jmethodID method_setBrowsedPlayerCallback;
+static jmethodID method_changePathCallback;
+static jmethodID method_searchCallback;
+static jmethodID method_playItemCallback;
+static jmethodID method_getItemAttrCallback;
+static jmethodID method_addToPlayListCallback;
+static jmethodID method_getTotalNumOfItemsCallback;
+
+static const btrc_interface_t* sBluetoothAvrcpInterface = NULL;
+static jobject mCallbacksObj = NULL;
+static std::shared_timed_mutex callbacks_mutex;
+
+/* Function declarations */
+static bool copy_item_attributes(JNIEnv* env, jobject object,
+                                 btrc_folder_items_t* pitem,
+                                 jint* p_attributesIds,
+                                 jobjectArray attributesArray, int item_idx,
+                                 int attribCopiedIndex);
+
+static bool copy_jstring(uint8_t* str, int maxBytes, jstring jstr, JNIEnv* env);
+
+static void cleanup_items(btrc_folder_items_t* p_items, int numItems);
+
+static void btavrcp_remote_features_callback(const RawAddress& bd_addr,
+                                             btrc_remote_features_t features) {
+  CallbackEnv sCallbackEnv(__func__);
+  std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
+  if (!sCallbackEnv.valid()) return;
+
+  if (!mCallbacksObj) {
+    ALOGE("%s: mCallbacksObj is null", __func__);
+    return;
+  }
+
+  ScopedLocalRef<jbyteArray> addr(
+      sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
+  if (!addr.get()) {
+    ALOGE("Unable to allocate byte array for bd_addr");
+    return;
+  }
+
+  sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
+                                   (jbyte*)bd_addr.address);
+  sCallbackEnv->CallVoidMethod(mCallbacksObj, method_getRcFeatures, addr.get(),
+                               (jint)features);
+}
+
+/** Callback for play status request */
+static void btavrcp_get_play_status_callback(const RawAddress& bd_addr) {
+  CallbackEnv sCallbackEnv(__func__);
+  std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
+  if (!sCallbackEnv.valid()) return;
+
+  if (!mCallbacksObj) {
+    ALOGE("%s: mCallbacksObj is null", __func__);
+    return;
+  }
+
+  ScopedLocalRef<jbyteArray> addr(
+      sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
+  if (!addr.get()) {
+    ALOGE("Fail to new jbyteArray bd addr for get_play_status command");
+    return;
+  }
+
+  sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
+                                   (jbyte*)bd_addr.address);
+  sCallbackEnv->CallVoidMethod(mCallbacksObj, method_getPlayStatus, addr.get());
+}
+
+static void btavrcp_get_element_attr_callback(uint8_t num_attr,
+                                              btrc_media_attr_t* p_attrs,
+                                              const RawAddress& bd_addr) {
+  CallbackEnv sCallbackEnv(__func__);
+  std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
+  if (!sCallbackEnv.valid()) return;
+
+  if (!mCallbacksObj) {
+    ALOGE("%s: mCallbacksObj is null", __func__);
+    return;
+  }
+
+  ScopedLocalRef<jbyteArray> addr(
+      sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
+  if (!addr.get()) {
+    ALOGE("Fail to new jbyteArray bd addr for get_element_attr command");
+    return;
+  }
+
+  ScopedLocalRef<jintArray> attrs(
+      sCallbackEnv.get(), (jintArray)sCallbackEnv->NewIntArray(num_attr));
+  if (!attrs.get()) {
+    ALOGE("Fail to new jintArray for attrs");
+    return;
+  }
+
+  sCallbackEnv->SetIntArrayRegion(attrs.get(), 0, num_attr, (jint*)p_attrs);
+
+  sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
+                                   (jbyte*)bd_addr.address);
+  sCallbackEnv->CallVoidMethod(mCallbacksObj, method_getElementAttr, addr.get(),
+                               (jbyte)num_attr, attrs.get());
+}
+
+static void btavrcp_register_notification_callback(btrc_event_id_t event_id,
+                                                   uint32_t param,
+                                                   const RawAddress& bd_addr) {
+  CallbackEnv sCallbackEnv(__func__);
+  std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
+  if (!sCallbackEnv.valid()) return;
+
+  if (!mCallbacksObj) {
+    ALOGE("%s: mCallbacksObj is null", __func__);
+    return;
+  }
+
+  ScopedLocalRef<jbyteArray> addr(
+      sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
+  if (!addr.get()) {
+    ALOGE("Fail to new jbyteArray bd addr for register_notification command");
+    return;
+  }
+
+  sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
+                                   (jbyte*)bd_addr.address);
+  sCallbackEnv->CallVoidMethod(mCallbacksObj, method_registerNotification,
+                               addr.get(), (jint)event_id, (jint)param);
+}
+
+static void btavrcp_volume_change_callback(uint8_t volume, uint8_t ctype,
+                                           const RawAddress& bd_addr) {
+  CallbackEnv sCallbackEnv(__func__);
+  std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
+  if (!sCallbackEnv.valid()) return;
+
+  if (!mCallbacksObj) {
+    ALOGE("%s: mCallbacksObj is null", __func__);
+    return;
+  }
+
+  ScopedLocalRef<jbyteArray> addr(
+      sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
+  if (!addr.get()) {
+    ALOGE("Fail to new jbyteArray bd addr for volume_change command");
+    return;
+  }
+
+  sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
+                                   (jbyte*)bd_addr.address);
+
+  sCallbackEnv->CallVoidMethod(mCallbacksObj, method_volumeChangeCallback,
+                               addr.get(), (jint)volume, (jint)ctype);
+}
+
+static void btavrcp_passthrough_command_callback(int id, int pressed,
+                                                 const RawAddress& bd_addr) {
+  CallbackEnv sCallbackEnv(__func__);
+  std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
+  if (!sCallbackEnv.valid()) return;
+
+  if (!mCallbacksObj) {
+    ALOGE("%s: mCallbacksObj is null", __func__);
+    return;
+  }
+
+  ScopedLocalRef<jbyteArray> addr(
+      sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
+  if (!addr.get()) {
+    ALOGE("Fail to new jbyteArray bd addr for passthrough_command command");
+    return;
+  }
+  sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
+                                   (jbyte*)bd_addr.address);
+
+  sCallbackEnv->CallVoidMethod(mCallbacksObj, method_handlePassthroughCmd,
+                               addr.get(), (jint)id, (jint)pressed);
+}
+
+static void btavrcp_set_addressed_player_callback(uint16_t player_id,
+                                                  const RawAddress& bd_addr) {
+  CallbackEnv sCallbackEnv(__func__);
+  std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
+  if (!sCallbackEnv.valid()) return;
+
+  if (!mCallbacksObj) {
+    ALOGE("%s: mCallbacksObj is null", __func__);
+    return;
+  }
+
+  ScopedLocalRef<jbyteArray> addr(
+      sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
+  if (!addr.get()) {
+    ALOGE("Fail to new jbyteArray bd addr for set_addressed_player command");
+    return;
+  }
+
+  sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
+                                   (jbyte*)bd_addr.address);
+  sCallbackEnv->CallVoidMethod(mCallbacksObj, method_setAddressedPlayerCallback,
+                               addr.get(), (jint)player_id);
+}
+
+static void btavrcp_set_browsed_player_callback(uint16_t player_id,
+                                                const RawAddress& bd_addr) {
+  CallbackEnv sCallbackEnv(__func__);
+  std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
+  if (!sCallbackEnv.valid()) return;
+  if (!mCallbacksObj) {
+    ALOGE("%s: mCallbacksObj is null", __func__);
+    return;
+  }
+
+  ScopedLocalRef<jbyteArray> addr(
+      sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
+  if (!addr.get()) {
+    ALOGE("Fail to new jbyteArray bd addr for set_browsed_player command");
+    return;
+  }
+  sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
+                                   (jbyte*)bd_addr.address);
+
+  sCallbackEnv->CallVoidMethod(mCallbacksObj, method_setBrowsedPlayerCallback,
+                               addr.get(), (jint)player_id);
+}
+
+static void btavrcp_get_folder_items_callback(
+    uint8_t scope, uint32_t start_item, uint32_t end_item, uint8_t num_attr,
+    uint32_t* p_attr_ids, const RawAddress& bd_addr) {
+  CallbackEnv sCallbackEnv(__func__);
+  std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
+  if (!sCallbackEnv.valid()) return;
+
+  if (!mCallbacksObj) {
+    ALOGE("%s: mCallbacksObj is null", __func__);
+    return;
+  }
+
+  ScopedLocalRef<jbyteArray> addr(
+      sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
+  if (!addr.get()) {
+    ALOGE("Fail to new jbyteArray bd addr for get_folder_items command");
+    return;
+  }
+
+  uint32_t* puiAttr = (uint32_t*)p_attr_ids;
+  ScopedLocalRef<jintArray> attr_ids(sCallbackEnv.get(), NULL);
+  sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
+                                   (jbyte*)bd_addr.address);
+
+  /* check number of attributes requested by remote device */
+  if ((num_attr != BTRC_NUM_ATTR_ALL) && (num_attr != BTRC_NUM_ATTR_NONE)) {
+    /* allocate memory for attr_ids only if some attributes passed from below
+     * layer */
+    attr_ids.reset((jintArray)sCallbackEnv->NewIntArray(num_attr));
+    if (!attr_ids.get()) {
+      ALOGE("Fail to allocate new jintArray for attrs");
+      return;
+    }
+    sCallbackEnv->SetIntArrayRegion(attr_ids.get(), 0, num_attr,
+                                    (jint*)puiAttr);
+  }
+
+  sCallbackEnv->CallVoidMethod(
+      mCallbacksObj, method_getFolderItemsCallback, addr.get(), (jbyte)scope,
+      (jlong)start_item, (jlong)end_item, (jbyte)num_attr, attr_ids.get());
+}
+
+static void btavrcp_change_path_callback(uint8_t direction, uint8_t* folder_uid,
+                                         const RawAddress& bd_addr) {
+  CallbackEnv sCallbackEnv(__func__);
+  std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
+  if (!sCallbackEnv.valid()) return;
+
+  if (!mCallbacksObj) {
+    ALOGE("%s: mCallbacksObj is null", __func__);
+    return;
+  }
+
+  ScopedLocalRef<jbyteArray> attrs(sCallbackEnv.get(),
+                                   sCallbackEnv->NewByteArray(BTRC_UID_SIZE));
+  if (!attrs.get()) {
+    ALOGE("Fail to new jintArray for attrs");
+    return;
+  }
+
+  ScopedLocalRef<jbyteArray> addr(
+      sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
+  if (!addr.get()) {
+    ALOGE("Fail to new jbyteArray bd addr for change_path command");
+    return;
+  }
+
+  sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
+                                   (jbyte*)bd_addr.address);
+  sCallbackEnv->SetByteArrayRegion(
+      attrs.get(), 0, sizeof(uint8_t) * BTRC_UID_SIZE, (jbyte*)folder_uid);
+  sCallbackEnv->CallVoidMethod(mCallbacksObj, method_changePathCallback,
+                               addr.get(), (jbyte)direction, attrs.get());
+}
+
+static void btavrcp_get_item_attr_callback(uint8_t scope, uint8_t* uid,
+                                           uint16_t uid_counter,
+                                           uint8_t num_attr,
+                                           btrc_media_attr_t* p_attrs,
+                                           const RawAddress& bd_addr) {
+  CallbackEnv sCallbackEnv(__func__);
+  std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
+  if (!sCallbackEnv.valid()) return;
+
+  if (!mCallbacksObj) {
+    ALOGE("%s: mCallbacksObj is null", __func__);
+    return;
+  }
+
+  ScopedLocalRef<jbyteArray> attr_uid(
+      sCallbackEnv.get(), sCallbackEnv->NewByteArray(BTRC_UID_SIZE));
+  if (!attr_uid.get()) {
+    ALOGE("Fail to new jintArray for attr_uid");
+    return;
+  }
+
+  ScopedLocalRef<jbyteArray> addr(
+      sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
+  if (!addr.get()) {
+    ALOGE("Fail to new jbyteArray bd addr for get_item_attr command");
+    return;
+  }
+
+  ScopedLocalRef<jintArray> attrs(
+      sCallbackEnv.get(), (jintArray)sCallbackEnv->NewIntArray(num_attr));
+  if (!attrs.get()) {
+    ALOGE("Fail to new jintArray for attrs");
+    return;
+  }
+
+  sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
+                                   (jbyte*)bd_addr.address);
+  sCallbackEnv->SetIntArrayRegion(attrs.get(), 0, num_attr, (jint*)p_attrs);
+  sCallbackEnv->SetByteArrayRegion(
+      attr_uid.get(), 0, sizeof(uint8_t) * BTRC_UID_SIZE, (jbyte*)uid);
+
+  sCallbackEnv->CallVoidMethod(mCallbacksObj, method_getItemAttrCallback,
+                               addr.get(), (jbyte)scope, attr_uid.get(),
+                               (jint)uid_counter, (jbyte)num_attr, attrs.get());
+}
+
+static void btavrcp_play_item_callback(uint8_t scope, uint16_t uid_counter,
+                                       uint8_t* uid,
+                                       const RawAddress& bd_addr) {
+  CallbackEnv sCallbackEnv(__func__);
+  std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
+  if (!sCallbackEnv.valid()) return;
+  if (!mCallbacksObj) {
+    ALOGE("%s: mCallbacksObj is null", __func__);
+    return;
+  }
+
+  ScopedLocalRef<jbyteArray> attrs(sCallbackEnv.get(),
+                                   sCallbackEnv->NewByteArray(BTRC_UID_SIZE));
+  if (!attrs.get()) {
+    ALOGE("%s: Fail to new jByteArray attrs for play_item command", __func__);
+    return;
+  }
+
+  ScopedLocalRef<jbyteArray> addr(
+      sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
+  if (!addr.get()) {
+    ALOGE("Fail to new jbyteArray bd addr for play_item command");
+    return;
+  }
+
+  sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
+                                   (jbyte*)bd_addr.address);
+  sCallbackEnv->SetByteArrayRegion(
+      attrs.get(), 0, sizeof(uint8_t) * BTRC_UID_SIZE, (jbyte*)uid);
+  sCallbackEnv->CallVoidMethod(mCallbacksObj, method_playItemCallback,
+                               addr.get(), (jbyte)scope, (jint)uid_counter,
+                               attrs.get());
+}
+
+static void btavrcp_get_total_num_items_callback(uint8_t scope,
+                                                 const RawAddress& bd_addr) {
+  CallbackEnv sCallbackEnv(__func__);
+  std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
+  if (!sCallbackEnv.valid()) return;
+  if (!mCallbacksObj) {
+    ALOGE("%s: mCallbacksObj is null", __func__);
+    return;
+  }
+
+  ScopedLocalRef<jbyteArray> addr(
+      sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
+  if (!addr.get()) {
+    ALOGE("Fail to new jbyteArray bd addr for get_total_num_items command");
+    return;
+  }
+
+  sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
+                                   (jbyte*)bd_addr.address);
+  sCallbackEnv->CallVoidMethod(mCallbacksObj, method_getTotalNumOfItemsCallback,
+                               addr.get(), (jbyte)scope);
+}
+
+static void btavrcp_search_callback(uint16_t charset_id, uint16_t str_len,
+                                    uint8_t* p_str, const RawAddress& bd_addr) {
+  CallbackEnv sCallbackEnv(__func__);
+  std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
+  if (!sCallbackEnv.valid()) return;
+  if (!mCallbacksObj) {
+    ALOGE("%s: mCallbacksObj is null", __func__);
+    return;
+  }
+
+  ScopedLocalRef<jbyteArray> attrs(sCallbackEnv.get(),
+                                   sCallbackEnv->NewByteArray(str_len));
+  if (!attrs.get()) {
+    ALOGE("Fail to new jintArray for attrs");
+    return;
+  }
+
+  ScopedLocalRef<jbyteArray> addr(
+      sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
+  if (!addr.get()) {
+    ALOGE("Fail to new jbyteArray bd addr for search command");
+    return;
+  }
+
+  sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
+                                   (jbyte*)bd_addr.address);
+  sCallbackEnv->SetByteArrayRegion(attrs.get(), 0, str_len * sizeof(uint8_t),
+                                   (jbyte*)p_str);
+  sCallbackEnv->CallVoidMethod(mCallbacksObj, method_searchCallback, addr.get(),
+                               (jint)charset_id, attrs.get());
+}
+
+static void btavrcp_add_to_play_list_callback(uint8_t scope, uint8_t* uid,
+                                              uint16_t uid_counter,
+                                              const RawAddress& bd_addr) {
+  CallbackEnv sCallbackEnv(__func__);
+  std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
+  if (!sCallbackEnv.valid()) return;
+  if (!mCallbacksObj) {
+    ALOGE("%s: mCallbacksObj is null", __func__);
+    return;
+  }
+
+  ScopedLocalRef<jbyteArray> addr(
+      sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
+  if (!addr.get()) {
+    ALOGE("Fail to new jbyteArray bd addr for add_to_play_list command");
+    return;
+  }
+
+  ScopedLocalRef<jbyteArray> attrs(sCallbackEnv.get(),
+                                   sCallbackEnv->NewByteArray(BTRC_UID_SIZE));
+  if (!attrs.get()) {
+    ALOGE("Fail to new jByteArray for attrs");
+    return;
+  }
+
+  sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
+                                   (jbyte*)bd_addr.address);
+  sCallbackEnv->SetByteArrayRegion(
+      attrs.get(), 0, sizeof(uint8_t) * BTRC_UID_SIZE, (jbyte*)uid);
+  sCallbackEnv->CallVoidMethod(mCallbacksObj, method_addToPlayListCallback,
+                               addr.get(), (jbyte)scope, attrs.get(),
+                               (jint)uid_counter);
+}
+
+static btrc_callbacks_t sBluetoothAvrcpCallbacks = {
+    sizeof(sBluetoothAvrcpCallbacks),
+    btavrcp_remote_features_callback,
+    btavrcp_get_play_status_callback,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    btavrcp_get_element_attr_callback,
+    btavrcp_register_notification_callback,
+    btavrcp_volume_change_callback,
+    btavrcp_passthrough_command_callback,
+    btavrcp_set_addressed_player_callback,
+    btavrcp_set_browsed_player_callback,
+    btavrcp_get_folder_items_callback,
+    btavrcp_change_path_callback,
+    btavrcp_get_item_attr_callback,
+    btavrcp_play_item_callback,
+    btavrcp_get_total_num_items_callback,
+    btavrcp_search_callback,
+    btavrcp_add_to_play_list_callback,
+};
+
+static void classInitNative(JNIEnv* env, jclass clazz) {
+  method_getRcFeatures =
+      env->GetMethodID(clazz, "getRcFeaturesRequestFromNative", "([BI)V");
+  method_getPlayStatus =
+      env->GetMethodID(clazz, "getPlayStatusRequestFromNative", "([B)V");
+
+  method_getElementAttr =
+      env->GetMethodID(clazz, "getElementAttrRequestFromNative", "([BB[I)V");
+
+  method_registerNotification = env->GetMethodID(
+      clazz, "registerNotificationRequestFromNative", "([BII)V");
+
+  method_volumeChangeCallback =
+      env->GetMethodID(clazz, "volumeChangeRequestFromNative", "([BII)V");
+
+  method_handlePassthroughCmd = env->GetMethodID(
+      clazz, "handlePassthroughCmdRequestFromNative", "([BII)V");
+
+  method_setAddressedPlayerCallback =
+      env->GetMethodID(clazz, "setAddressedPlayerRequestFromNative", "([BI)V");
+
+  method_setBrowsedPlayerCallback =
+      env->GetMethodID(clazz, "setBrowsedPlayerRequestFromNative", "([BI)V");
+
+  method_getFolderItemsCallback =
+      env->GetMethodID(clazz, "getFolderItemsRequestFromNative", "([BBJJB[I)V");
+
+  method_changePathCallback =
+      env->GetMethodID(clazz, "changePathRequestFromNative", "([BB[B)V");
+
+  method_getItemAttrCallback =
+      env->GetMethodID(clazz, "getItemAttrRequestFromNative", "([BB[BIB[I)V");
+
+  method_playItemCallback =
+      env->GetMethodID(clazz, "playItemRequestFromNative", "([BBI[B)V");
+
+  method_getTotalNumOfItemsCallback =
+      env->GetMethodID(clazz, "getTotalNumOfItemsRequestFromNative", "([BB)V");
+
+  method_searchCallback =
+      env->GetMethodID(clazz, "searchRequestFromNative", "([BI[B)V");
+
+  method_addToPlayListCallback =
+      env->GetMethodID(clazz, "addToPlayListRequestFromNative", "([BB[BI)V");
+
+  ALOGI("%s: succeeds", __func__);
+}
+
+static void initNative(JNIEnv* env, jobject object) {
+  std::unique_lock<std::shared_timed_mutex> lock(callbacks_mutex);
+  const bt_interface_t* btInf = getBluetoothInterface();
+  if (btInf == NULL) {
+    ALOGE("Bluetooth module is not loaded");
+    return;
+  }
+
+  if (sBluetoothAvrcpInterface != NULL) {
+    ALOGW("Cleaning up Avrcp Interface before initializing...");
+    sBluetoothAvrcpInterface->cleanup();
+    sBluetoothAvrcpInterface = NULL;
+  }
+
+  if (mCallbacksObj != NULL) {
+    ALOGW("Cleaning up Avrcp callback object");
+    env->DeleteGlobalRef(mCallbacksObj);
+    mCallbacksObj = NULL;
+  }
+
+  sBluetoothAvrcpInterface =
+      (btrc_interface_t*)btInf->get_profile_interface(BT_PROFILE_AV_RC_ID);
+  if (sBluetoothAvrcpInterface == NULL) {
+    ALOGE("Failed to get Bluetooth Avrcp Interface");
+    return;
+  }
+
+  bt_status_t status =
+      sBluetoothAvrcpInterface->init(&sBluetoothAvrcpCallbacks);
+  if (status != BT_STATUS_SUCCESS) {
+    ALOGE("Failed to initialize Bluetooth Avrcp, status: %d", status);
+    sBluetoothAvrcpInterface = NULL;
+    return;
+  }
+
+  mCallbacksObj = env->NewGlobalRef(object);
+}
+
+static void cleanupNative(JNIEnv* env, jobject object) {
+  std::unique_lock<std::shared_timed_mutex> lock(callbacks_mutex);
+  const bt_interface_t* btInf = getBluetoothInterface();
+  if (btInf == NULL) {
+    ALOGE("Bluetooth module is not loaded");
+    return;
+  }
+
+  if (sBluetoothAvrcpInterface != NULL) {
+    sBluetoothAvrcpInterface->cleanup();
+    sBluetoothAvrcpInterface = NULL;
+  }
+
+  if (mCallbacksObj != NULL) {
+    env->DeleteGlobalRef(mCallbacksObj);
+    mCallbacksObj = NULL;
+  }
+}
+
+static jboolean getPlayStatusRspNative(JNIEnv* env, jobject object,
+                                       jbyteArray address, jint playStatus,
+                                       jint songLen, jint songPos) {
+  if (!sBluetoothAvrcpInterface) {
+    ALOGE("%s: sBluetoothAvrcpInterface is null", __func__);
+    return JNI_FALSE;
+  }
+
+  jbyte* addr = env->GetByteArrayElements(address, NULL);
+  if (!addr) {
+    jniThrowIOException(env, EINVAL);
+    return JNI_FALSE;
+  }
+  RawAddress rawAddress;
+  rawAddress.FromOctets((uint8_t*)addr);
+
+  bt_status_t status = sBluetoothAvrcpInterface->get_play_status_rsp(
+      rawAddress, (btrc_play_status_t)playStatus, songLen, songPos);
+  if (status != BT_STATUS_SUCCESS) {
+    ALOGE("Failed get_play_status_rsp, status: %d", status);
+  }
+  env->ReleaseByteArrayElements(address, addr, 0);
+  return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
+}
+
+static jboolean getElementAttrRspNative(JNIEnv* env, jobject object,
+                                        jbyteArray address, jbyte numAttr,
+                                        jintArray attrIds,
+                                        jobjectArray textArray) {
+  if (!sBluetoothAvrcpInterface) {
+    ALOGE("%s: sBluetoothAvrcpInterface is null", __func__);
+    return JNI_FALSE;
+  }
+
+  if (numAttr > BTRC_MAX_ELEM_ATTR_SIZE) {
+    ALOGE("get_element_attr_rsp: number of attributes exceed maximum");
+    return JNI_FALSE;
+  }
+
+  jbyte* addr = env->GetByteArrayElements(address, NULL);
+  if (!addr) {
+    jniThrowIOException(env, EINVAL);
+    return JNI_FALSE;
+  }
+
+  btrc_element_attr_val_t* pAttrs = new btrc_element_attr_val_t[numAttr];
+  if (!pAttrs) {
+    ALOGE("get_element_attr_rsp: not have enough memeory");
+    env->ReleaseByteArrayElements(address, addr, 0);
+    return JNI_FALSE;
+  }
+
+  jint* attr = env->GetIntArrayElements(attrIds, NULL);
+  if (!attr) {
+    delete[] pAttrs;
+    jniThrowIOException(env, EINVAL);
+    env->ReleaseByteArrayElements(address, addr, 0);
+    return JNI_FALSE;
+  }
+
+  int attr_cnt;
+  for (attr_cnt = 0; attr_cnt < numAttr; ++attr_cnt) {
+    pAttrs[attr_cnt].attr_id = attr[attr_cnt];
+    ScopedLocalRef<jstring> text(
+        env, (jstring)env->GetObjectArrayElement(textArray, attr_cnt));
+
+    if (!copy_jstring(pAttrs[attr_cnt].text, BTRC_MAX_ATTR_STR_LEN, text.get(),
+                      env)) {
+      break;
+    }
+  }
+
+  if (attr_cnt < numAttr) {
+    delete[] pAttrs;
+    env->ReleaseIntArrayElements(attrIds, attr, 0);
+    ALOGE("%s: Failed to copy attributes", __func__);
+    return JNI_FALSE;
+  }
+
+  RawAddress rawAddress;
+  rawAddress.FromOctets((uint8_t*)addr);
+  bt_status_t status = sBluetoothAvrcpInterface->get_element_attr_rsp(
+      rawAddress, numAttr, pAttrs);
+  if (status != BT_STATUS_SUCCESS) {
+    ALOGE("Failed get_element_attr_rsp, status: %d", status);
+  }
+
+  delete[] pAttrs;
+  env->ReleaseIntArrayElements(attrIds, attr, 0);
+  env->ReleaseByteArrayElements(address, addr, 0);
+  return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
+}
+
+static jboolean getItemAttrRspNative(JNIEnv* env, jobject object,
+                                     jbyteArray address, jint rspStatus,
+                                     jbyte numAttr, jintArray attrIds,
+                                     jobjectArray textArray) {
+  if (!sBluetoothAvrcpInterface) {
+    ALOGE("%s: sBluetoothAvrcpInterface is null", __func__);
+    return JNI_FALSE;
+  }
+
+  jbyte* addr = env->GetByteArrayElements(address, NULL);
+  if (!addr) {
+    jniThrowIOException(env, EINVAL);
+    return JNI_FALSE;
+  }
+
+  if (numAttr > BTRC_MAX_ELEM_ATTR_SIZE) {
+    ALOGE("get_element_attr_rsp: number of attributes exceed maximum");
+    return JNI_FALSE;
+  }
+
+  btrc_element_attr_val_t* pAttrs = new btrc_element_attr_val_t[numAttr];
+  if (!pAttrs) {
+    ALOGE("%s: not have enough memory", __func__);
+    env->ReleaseByteArrayElements(address, addr, 0);
+    return JNI_FALSE;
+  }
+
+  jint* attr = NULL;
+  if (attrIds != NULL) {
+    attr = env->GetIntArrayElements(attrIds, NULL);
+    if (!attr) {
+      delete[] pAttrs;
+      jniThrowIOException(env, EINVAL);
+      env->ReleaseByteArrayElements(address, addr, 0);
+      return JNI_FALSE;
+    }
+
+    for (int attr_cnt = 0; attr_cnt < numAttr; ++attr_cnt) {
+      pAttrs[attr_cnt].attr_id = attr[attr_cnt];
+      ScopedLocalRef<jstring> text(
+          env, (jstring)env->GetObjectArrayElement(textArray, attr_cnt));
+
+      if (!copy_jstring(pAttrs[attr_cnt].text, BTRC_MAX_ATTR_STR_LEN, text.get(),
+                        env)) {
+        rspStatus = BTRC_STS_INTERNAL_ERR;
+        ALOGE("%s: Failed to copy attributes", __func__);
+        break;
+      }
+    }
+  }
+  RawAddress rawAddress;
+  rawAddress.FromOctets((uint8_t*)addr);
+
+  bt_status_t status = sBluetoothAvrcpInterface->get_item_attr_rsp(
+      rawAddress, (btrc_status_t)rspStatus, numAttr, pAttrs);
+  if (status != BT_STATUS_SUCCESS)
+    ALOGE("Failed get_item_attr_rsp, status: %d", status);
+
+  if (pAttrs) delete[] pAttrs;
+  if (attr) env->ReleaseIntArrayElements(attrIds, attr, 0);
+  env->ReleaseByteArrayElements(address, addr, 0);
+
+  return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
+}
+
+static jboolean registerNotificationRspPlayStatusNative(JNIEnv* env,
+                                                        jobject object,
+                                                        jint type,
+                                                        jint playStatus) {
+  if (!sBluetoothAvrcpInterface) {
+    ALOGE("%s: sBluetoothAvrcpInterface is null", __func__);
+    return JNI_FALSE;
+  }
+
+  btrc_register_notification_t param;
+  param.play_status = (btrc_play_status_t)playStatus;
+
+  bt_status_t status = sBluetoothAvrcpInterface->register_notification_rsp(
+      BTRC_EVT_PLAY_STATUS_CHANGED, (btrc_notification_type_t)type, &param);
+  if (status != BT_STATUS_SUCCESS) {
+    ALOGE("Failed register_notification_rsp play status, status: %d", status);
+  }
+
+  return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
+}
+
+static jboolean registerNotificationRspTrackChangeNative(JNIEnv* env,
+                                                         jobject object,
+                                                         jint type,
+                                                         jbyteArray track) {
+  if (!sBluetoothAvrcpInterface) {
+    ALOGE("%s: sBluetoothAvrcpInterface is null", __func__);
+    return JNI_FALSE;
+  }
+
+  jbyte* trk = env->GetByteArrayElements(track, NULL);
+  if (!trk) {
+    jniThrowIOException(env, EINVAL);
+    return JNI_FALSE;
+  }
+
+  btrc_register_notification_t param;
+  uint64_t uid = 0;
+  for (int uid_idx = 0; uid_idx < BTRC_UID_SIZE; ++uid_idx) {
+    param.track[uid_idx] = trk[uid_idx];
+    uid = uid + (trk[uid_idx] << (BTRC_UID_SIZE - 1 - uid_idx));
+  }
+
+  ALOGV("%s: Sending track change notification: %d -> %" PRIu64, __func__, type,
+        uid);
+
+  bt_status_t status = sBluetoothAvrcpInterface->register_notification_rsp(
+      BTRC_EVT_TRACK_CHANGE, (btrc_notification_type_t)type, &param);
+  if (status != BT_STATUS_SUCCESS) {
+    ALOGE("Failed register_notification_rsp track change, status: %d", status);
+  }
+
+  env->ReleaseByteArrayElements(track, trk, 0);
+  return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
+}
+
+static jboolean registerNotificationRspPlayPosNative(JNIEnv* env,
+                                                     jobject object, jint type,
+                                                     jint playPos) {
+  if (!sBluetoothAvrcpInterface) {
+    ALOGE("%s: sBluetoothAvrcpInterface is null", __func__);
+    return JNI_FALSE;
+  }
+
+  btrc_register_notification_t param;
+  param.song_pos = (uint32_t)playPos;
+
+  bt_status_t status = sBluetoothAvrcpInterface->register_notification_rsp(
+      BTRC_EVT_PLAY_POS_CHANGED, (btrc_notification_type_t)type, &param);
+  if (status != BT_STATUS_SUCCESS) {
+    ALOGE("Failed register_notification_rsp play position, status: %d", status);
+  }
+
+  return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
+}
+
+static jboolean registerNotificationRspNowPlayingChangedNative(JNIEnv* env,
+                                                               jobject object,
+                                                               jint type) {
+  if (!sBluetoothAvrcpInterface) {
+    ALOGE("%s: sBluetoothAvrcpInterface is null", __func__);
+    return JNI_FALSE;
+  }
+
+  btrc_register_notification_t param;
+  bt_status_t status = sBluetoothAvrcpInterface->register_notification_rsp(
+      BTRC_EVT_NOW_PLAYING_CONTENT_CHANGED, (btrc_notification_type_t)type,
+      &param);
+  if (status != BT_STATUS_SUCCESS) {
+    ALOGE("Failed register_notification_rsp, nowPlaying Content status: %d",
+          status);
+  }
+  return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
+}
+
+static jboolean registerNotificationRspUIDsChangedNative(JNIEnv* env,
+                                                         jobject object,
+                                                         jint type,
+                                                         jint uidCounter) {
+  if (!sBluetoothAvrcpInterface) {
+    ALOGE("%s: sBluetoothAvrcpInterface is null", __func__);
+    return JNI_FALSE;
+  }
+
+  btrc_register_notification_t param;
+  param.uids_changed.uid_counter = (uint16_t)uidCounter;
+
+  bt_status_t status = sBluetoothAvrcpInterface->register_notification_rsp(
+      BTRC_EVT_UIDS_CHANGED, (btrc_notification_type_t)type, &param);
+  if (status != BT_STATUS_SUCCESS) {
+    ALOGE("Failed register_notification_rsp, uids changed status: %d", status);
+  }
+
+  return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
+}
+
+static jboolean registerNotificationRspAddrPlayerChangedNative(
+    JNIEnv* env, jobject object, jint type, jint playerId, jint uidCounter) {
+  if (!sBluetoothAvrcpInterface) {
+    ALOGE("%s: sBluetoothAvrcpInterface is null", __func__);
+    return JNI_FALSE;
+  }
+
+  btrc_register_notification_t param;
+  param.addr_player_changed.player_id = (uint16_t)playerId;
+  param.addr_player_changed.uid_counter = (uint16_t)uidCounter;
+
+  bt_status_t status = sBluetoothAvrcpInterface->register_notification_rsp(
+      BTRC_EVT_ADDR_PLAYER_CHANGE, (btrc_notification_type_t)type, &param);
+  if (status != BT_STATUS_SUCCESS) {
+    ALOGE("Failed register_notification_rsp address player changed status: %d",
+          status);
+  }
+
+  return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
+}
+
+static jboolean registerNotificationRspAvalPlayerChangedNative(JNIEnv* env,
+                                                               jobject object,
+                                                               jint type) {
+  if (!sBluetoothAvrcpInterface) {
+    ALOGE("%s: sBluetoothAvrcpInterface is null", __func__);
+    return JNI_FALSE;
+  }
+
+  btrc_register_notification_t param;
+  bt_status_t status = sBluetoothAvrcpInterface->register_notification_rsp(
+      BTRC_EVT_AVAL_PLAYER_CHANGE, (btrc_notification_type_t)type, &param);
+  if (status != BT_STATUS_SUCCESS) {
+    ALOGE(
+        "Failed register_notification_rsp available player changed status, "
+        "status: %d",
+        status);
+  }
+
+  return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
+}
+
+static jboolean setVolumeNative(JNIEnv* env, jobject object, jint volume) {
+  if (!sBluetoothAvrcpInterface) {
+    ALOGE("%s: sBluetoothAvrcpInterface is null", __func__);
+    return JNI_FALSE;
+  }
+
+  bt_status_t status = sBluetoothAvrcpInterface->set_volume((uint8_t)volume);
+  if (status != BT_STATUS_SUCCESS) {
+    ALOGE("Failed set_volume, status: %d", status);
+  }
+
+  return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
+}
+
+/* native response for scope as Media player */
+static jboolean mediaPlayerListRspNative(
+    JNIEnv* env, jobject object, jbyteArray address, jint rspStatus,
+    jint uidCounter, jbyte itemType, jint numItems, jintArray playerIds,
+    jbyteArray playerTypes, jintArray playerSubtypes,
+    jbyteArray playStatusValues, jshortArray featureBitmask,
+    jobjectArray textArray) {
+  if (!sBluetoothAvrcpInterface) {
+    ALOGE("%s: sBluetoothAvrcpInterface is null", __func__);
+    return JNI_FALSE;
+  }
+
+  jbyte* addr = env->GetByteArrayElements(address, NULL);
+  if (!addr) {
+    jniThrowIOException(env, EINVAL);
+    return JNI_FALSE;
+  }
+
+  jbyte *p_playerTypes = NULL, *p_PlayStatusValues = NULL;
+  jshort* p_FeatBitMaskValues = NULL;
+  jint *p_playerIds = NULL, *p_playerSubTypes = NULL;
+  btrc_folder_items_t* p_items = NULL;
+  if (rspStatus == BTRC_STS_NO_ERROR) {
+    /* allocate memory */
+    p_playerIds = env->GetIntArrayElements(playerIds, NULL);
+    p_playerTypes = env->GetByteArrayElements(playerTypes, NULL);
+    p_playerSubTypes = env->GetIntArrayElements(playerSubtypes, NULL);
+    p_PlayStatusValues = env->GetByteArrayElements(playStatusValues, NULL);
+    p_FeatBitMaskValues = env->GetShortArrayElements(featureBitmask, NULL);
+    p_items = new btrc_folder_items_t[numItems];
+    /* deallocate memory and return if allocation failed */
+    if (!p_playerIds || !p_playerTypes || !p_playerSubTypes ||
+        !p_PlayStatusValues || !p_FeatBitMaskValues || !p_items) {
+      if (p_playerIds) env->ReleaseIntArrayElements(playerIds, p_playerIds, 0);
+      if (p_playerTypes)
+        env->ReleaseByteArrayElements(playerTypes, p_playerTypes, 0);
+      if (p_playerSubTypes)
+        env->ReleaseIntArrayElements(playerSubtypes, p_playerSubTypes, 0);
+      if (p_PlayStatusValues)
+        env->ReleaseByteArrayElements(playStatusValues, p_PlayStatusValues, 0);
+      if (p_FeatBitMaskValues)
+        env->ReleaseShortArrayElements(featureBitmask, p_FeatBitMaskValues, 0);
+      if (p_items) delete[] p_items;
+
+      jniThrowIOException(env, EINVAL);
+      ALOGE("%s: not have enough memory", __func__);
+      return JNI_FALSE;
+    }
+
+    p_items->item_type = (uint8_t)itemType;
+
+    /* copy list of media players along with other parameters */
+    int itemIdx;
+    for (itemIdx = 0; itemIdx < numItems; ++itemIdx) {
+      p_items[itemIdx].player.player_id = p_playerIds[itemIdx];
+      p_items[itemIdx].player.major_type = p_playerTypes[itemIdx];
+      p_items[itemIdx].player.sub_type = p_playerSubTypes[itemIdx];
+      p_items[itemIdx].player.play_status = p_PlayStatusValues[itemIdx];
+      p_items[itemIdx].player.charset_id = BTRC_CHARSET_ID_UTF8;
+
+      ScopedLocalRef<jstring> text(
+          env, (jstring)env->GetObjectArrayElement(textArray, itemIdx));
+      /* copy player name */
+      if (!copy_jstring(p_items[itemIdx].player.name, BTRC_MAX_ATTR_STR_LEN,
+                        text.get(), env))
+        break;
+
+      /* Feature bit mask is 128-bit value each */
+      for (int InnCnt = 0; InnCnt < 16; InnCnt++) {
+        p_items[itemIdx].player.features[InnCnt] =
+            (uint8_t)p_FeatBitMaskValues[(itemIdx * 16) + InnCnt];
+      }
+    }
+
+    /* failed to copy list of media players */
+    if (itemIdx < numItems) {
+      rspStatus = BTRC_STS_INTERNAL_ERR;
+      ALOGE("%s: Failed to copy Media player attributes", __func__);
+    }
+  }
+
+  RawAddress* btAddr = (RawAddress*)addr;
+  bt_status_t status = sBluetoothAvrcpInterface->get_folder_items_list_rsp(
+      *btAddr, (btrc_status_t)rspStatus, uidCounter, numItems, p_items);
+  if (status != BT_STATUS_SUCCESS) {
+    ALOGE("Failed get_folder_items_list_rsp, status: %d", status);
+  }
+
+  /* release allocated memory */
+  if (p_items) delete[] p_items;
+  if (p_playerTypes)
+    env->ReleaseByteArrayElements(playerTypes, p_playerTypes, 0);
+  if (p_playerSubTypes)
+    env->ReleaseIntArrayElements(playerSubtypes, p_playerSubTypes, 0);
+  if (p_PlayStatusValues)
+    env->ReleaseByteArrayElements(playStatusValues, p_PlayStatusValues, 0);
+  if (p_FeatBitMaskValues) {
+    env->ReleaseShortArrayElements(featureBitmask, p_FeatBitMaskValues, 0);
+  }
+  env->ReleaseByteArrayElements(address, addr, 0);
+
+  return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
+}
+
+static jboolean getFolderItemsRspNative(
+    JNIEnv* env, jobject object, jbyteArray address, jint rspStatus,
+    jshort uidCounter, jbyte scope, jint numItems, jbyteArray folderType,
+    jbyteArray playable, jbyteArray itemType, jbyteArray itemUidArray,
+    jobjectArray displayNameArray, jintArray numAttrs, jintArray attributesIds,
+    jobjectArray attributesArray) {
+  if (!sBluetoothAvrcpInterface) {
+    ALOGE("%s: sBluetoothAvrcpInterface is null", __func__);
+    return JNI_FALSE;
+  }
+
+  jbyte* addr = env->GetByteArrayElements(address, NULL);
+  if (!addr) {
+    jniThrowIOException(env, EINVAL);
+    return JNI_FALSE;
+  }
+
+  jbyte *p_playable = NULL, *p_item_uid = NULL;
+  jbyte* p_item_types = NULL; /* Folder or Media Item */
+  jint* p_attributesIds = NULL;
+  jbyte* p_folder_types =
+      NULL; /* Folder properties like Album/Genre/Artists etc */
+  jint* p_num_attrs = NULL;
+  btrc_folder_items_t* p_items = NULL;
+  /* none of the parameters should be null when no error */
+  if (rspStatus == BTRC_STS_NO_ERROR) {
+    /* allocate memory to each rsp item */
+    if (folderType != NULL)
+      p_folder_types = env->GetByteArrayElements(folderType, NULL);
+    if (playable != NULL)
+      p_playable = env->GetByteArrayElements(playable, NULL);
+    if (itemType != NULL)
+      p_item_types = env->GetByteArrayElements(itemType, NULL);
+    if (NULL != numAttrs)
+      p_num_attrs = env->GetIntArrayElements(numAttrs, NULL);
+    if (NULL != attributesIds)
+      p_attributesIds = env->GetIntArrayElements(attributesIds, NULL);
+    if (itemUidArray != NULL)
+      p_item_uid = (jbyte*)env->GetByteArrayElements(itemUidArray, NULL);
+
+    p_items = new btrc_folder_items_t[numItems];
+
+    /* if memory alloc failed, release memory */
+    if (p_items && p_folder_types && p_playable && p_item_types && p_item_uid &&
+        /* attributes can be null if remote requests 0 attributes */
+        ((numAttrs != NULL && p_num_attrs) || (!numAttrs && !p_num_attrs)) &&
+        ((attributesIds != NULL && p_attributesIds) ||
+         (!attributesIds && !p_attributesIds))) {
+      memset(p_items, 0, sizeof(btrc_folder_items_t) * numItems);
+      if (scope == BTRC_SCOPE_FILE_SYSTEM || scope == BTRC_SCOPE_SEARCH ||
+          scope == BTRC_SCOPE_NOW_PLAYING) {
+        int attribCopiedIndex = 0;
+        for (int item_idx = 0; item_idx < numItems; item_idx++) {
+          if (BTRC_ITEM_FOLDER == p_item_types[item_idx]) {
+            btrc_folder_items_t* pitem = &p_items[item_idx];
+
+            memcpy(pitem->folder.uid, p_item_uid + item_idx * BTRC_UID_SIZE,
+                   BTRC_UID_SIZE);
+            pitem->item_type = (uint8_t)BTRC_ITEM_FOLDER;
+            pitem->folder.charset_id = BTRC_CHARSET_ID_UTF8;
+            pitem->folder.type = p_folder_types[item_idx];
+            pitem->folder.playable = p_playable[item_idx];
+
+            ScopedLocalRef<jstring> text(
+                env, (jstring)env->GetObjectArrayElement(displayNameArray,
+                                                         item_idx));
+            if (!copy_jstring(pitem->folder.name, BTRC_MAX_ATTR_STR_LEN,
+                              text.get(), env)) {
+              rspStatus = BTRC_STS_INTERNAL_ERR;
+              ALOGE("%s: failed to copy display name of folder item", __func__);
+              break;
+            }
+          } else if (BTRC_ITEM_MEDIA == p_item_types[item_idx]) {
+            btrc_folder_items_t* pitem = &p_items[item_idx];
+            memcpy(pitem->media.uid, p_item_uid + item_idx * BTRC_UID_SIZE,
+                   BTRC_UID_SIZE);
+
+            pitem->item_type = (uint8_t)BTRC_ITEM_MEDIA;
+            pitem->media.charset_id = BTRC_CHARSET_ID_UTF8;
+            pitem->media.type = BTRC_MEDIA_TYPE_AUDIO;
+            pitem->media.num_attrs =
+                (p_num_attrs != NULL) ? p_num_attrs[item_idx] : 0;
+
+            ScopedLocalRef<jstring> text(
+                env, (jstring)env->GetObjectArrayElement(displayNameArray,
+                                                         item_idx));
+            if (!copy_jstring(pitem->media.name, BTRC_MAX_ATTR_STR_LEN,
+                              text.get(), env)) {
+              rspStatus = BTRC_STS_INTERNAL_ERR;
+              ALOGE("%s: failed to copy display name of media item", __func__);
+              break;
+            }
+
+            /* copy item attributes */
+            if (!copy_item_attributes(env, object, pitem, p_attributesIds,
+                                      attributesArray, item_idx,
+                                      attribCopiedIndex)) {
+              ALOGE("%s: error in copying attributes of item = %s", __func__,
+                    pitem->media.name);
+              rspStatus = BTRC_STS_INTERNAL_ERR;
+              break;
+            }
+            attribCopiedIndex += pitem->media.num_attrs;
+          }
+        }
+      }
+    } else {
+      rspStatus = BTRC_STS_INTERNAL_ERR;
+      ALOGE("%s: unable to allocate memory", __func__);
+    }
+  }
+
+  RawAddress* btAddr = (RawAddress*)addr;
+  bt_status_t status = sBluetoothAvrcpInterface->get_folder_items_list_rsp(
+      *btAddr, (btrc_status_t)rspStatus, uidCounter, numItems, p_items);
+  if (status != BT_STATUS_SUCCESS)
+    ALOGE("Failed get_folder_items_list_rsp, status: %d", status);
+
+  /* Release allocated memory for all attributes in each media item */
+  if (p_items) cleanup_items(p_items, numItems);
+
+  /* Release allocated memory  */
+  if (p_folder_types)
+    env->ReleaseByteArrayElements(folderType, p_folder_types, 0);
+  if (p_playable) env->ReleaseByteArrayElements(playable, p_playable, 0);
+  if (p_item_types) env->ReleaseByteArrayElements(itemType, p_item_types, 0);
+  if (p_num_attrs) env->ReleaseIntArrayElements(numAttrs, p_num_attrs, 0);
+  if (p_attributesIds)
+    env->ReleaseIntArrayElements(attributesIds, p_attributesIds, 0);
+  if (p_item_uid) env->ReleaseByteArrayElements(itemUidArray, p_item_uid, 0);
+  if (p_items) delete[] p_items;
+  env->ReleaseByteArrayElements(address, addr, 0);
+
+  return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
+}
+
+static jboolean setAddressedPlayerRspNative(JNIEnv* env, jobject object,
+                                            jbyteArray address,
+                                            jint rspStatus) {
+  if (!sBluetoothAvrcpInterface) {
+    ALOGE("%s: sBluetoothAvrcpInterface is null", __func__);
+    return JNI_FALSE;
+  }
+
+  jbyte* addr = env->GetByteArrayElements(address, NULL);
+  if (!addr) {
+    jniThrowIOException(env, EINVAL);
+    return JNI_FALSE;
+  }
+
+  RawAddress* btAddr = (RawAddress*)addr;
+  bt_status_t status = sBluetoothAvrcpInterface->set_addressed_player_rsp(
+      *btAddr, (btrc_status_t)rspStatus);
+  if (status != BT_STATUS_SUCCESS) {
+    ALOGE("Failed set_addressed_player_rsp, status: %d", status);
+  }
+  env->ReleaseByteArrayElements(address, addr, 0);
+
+  return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
+}
+
+static jboolean setBrowsedPlayerRspNative(JNIEnv* env, jobject object,
+                                          jbyteArray address, jint rspStatus,
+                                          jbyte depth, jint numItems,
+                                          jobjectArray textArray) {
+  if (!sBluetoothAvrcpInterface) {
+    ALOGE("%s: sBluetoothAvrcpInterface is null", __func__);
+    return JNI_FALSE;
+  }
+
+  jbyte* addr = env->GetByteArrayElements(address, NULL);
+  if (!addr) {
+    jniThrowIOException(env, EINVAL);
+    return JNI_FALSE;
+  }
+
+  btrc_br_folder_name_t* p_folders = NULL;
+  if (rspStatus == BTRC_STS_NO_ERROR) {
+    if (depth > 0) {
+      p_folders = new btrc_br_folder_name_t[depth];
+
+      if (!p_folders ) {
+        jniThrowIOException(env, EINVAL);
+        ALOGE("%s: not have enough memeory", __func__);
+        return JNI_FALSE;
+      }
+      for (int folder_idx = 0; folder_idx < depth; folder_idx++) {
+        /* copy folder names */
+        ScopedLocalRef<jstring> text(
+            env, (jstring)env->GetObjectArrayElement(textArray, folder_idx));
+
+        if (!copy_jstring(p_folders[folder_idx].p_str, BTRC_MAX_ATTR_STR_LEN,
+                          text.get(), env)) {
+          rspStatus = BTRC_STS_INTERNAL_ERR;
+          delete[] p_folders;
+          env->ReleaseByteArrayElements(address, addr, 0);
+          ALOGE("%s: Failed to copy folder name", __func__);
+          return JNI_FALSE;
+        }
+
+        p_folders[folder_idx].str_len =
+            strlen((char*)p_folders[folder_idx].p_str);
+      }
+    }
+  }
+
+  uint8_t folder_depth =
+      depth; /* folder_depth is 0 if current folder is root */
+  uint16_t charset_id = BTRC_CHARSET_ID_UTF8;
+  RawAddress* btAddr = (RawAddress*)addr;
+  bt_status_t status = sBluetoothAvrcpInterface->set_browsed_player_rsp(
+      *btAddr, (btrc_status_t)rspStatus, numItems, charset_id, folder_depth,
+      p_folders);
+  if (status != BT_STATUS_SUCCESS) {
+    ALOGE("%s: Failed set_browsed_player_rsp, status: %d", __func__, status);
+  }
+
+  if (depth > 0) {
+    delete[] p_folders;
+  }
+
+  env->ReleaseByteArrayElements(address, addr, 0);
+  return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
+}
+
+static jboolean changePathRspNative(JNIEnv* env, jobject object,
+                                    jbyteArray address, jint rspStatus,
+                                    jint numItems) {
+  if (!sBluetoothAvrcpInterface) {
+    ALOGE("%s: sBluetoothAvrcpInterface is null", __func__);
+    return JNI_FALSE;
+  }
+
+  jbyte* addr = env->GetByteArrayElements(address, NULL);
+  if (!addr) {
+    jniThrowIOException(env, EINVAL);
+    return JNI_FALSE;
+  }
+
+  uint32_t nItems = (uint32_t)numItems;
+  RawAddress* btAddr = (RawAddress*)addr;
+  bt_status_t status = sBluetoothAvrcpInterface->change_path_rsp(
+      *btAddr, (btrc_status_t)rspStatus, (uint32_t)nItems);
+  if (status != BT_STATUS_SUCCESS) {
+    ALOGE("Failed change_path_rsp, status: %d", status);
+  }
+  env->ReleaseByteArrayElements(address, addr, 0);
+
+  return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
+}
+
+static jboolean searchRspNative(JNIEnv* env, jobject object, jbyteArray address,
+                                jint rspStatus, jint uidCounter,
+                                jint numItems) {
+  if (!sBluetoothAvrcpInterface) {
+    ALOGE("%s: sBluetoothAvrcpInterface is null", __func__);
+    return JNI_FALSE;
+  }
+
+  jbyte* addr = env->GetByteArrayElements(address, NULL);
+  if (!addr) {
+    jniThrowIOException(env, EINVAL);
+    return JNI_FALSE;
+  }
+
+  uint32_t nItems = (uint32_t)numItems;
+  RawAddress* btAddr = (RawAddress*)addr;
+  bt_status_t status = sBluetoothAvrcpInterface->search_rsp(
+      *btAddr, (btrc_status_t)rspStatus, (uint32_t)uidCounter,
+      (uint32_t)nItems);
+  if (status != BT_STATUS_SUCCESS) {
+    ALOGE("Failed search_rsp, status: %d", status);
+  }
+
+  env->ReleaseByteArrayElements(address, addr, 0);
+
+  return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
+}
+
+static jboolean playItemRspNative(JNIEnv* env, jobject object,
+                                  jbyteArray address, jint rspStatus) {
+  if (!sBluetoothAvrcpInterface) {
+    ALOGE("%s: sBluetoothAvrcpInterface is null", __func__);
+    return JNI_FALSE;
+  }
+
+  jbyte* addr = env->GetByteArrayElements(address, NULL);
+  if (!addr) {
+    jniThrowIOException(env, EINVAL);
+    return JNI_FALSE;
+  }
+
+  RawAddress* btAddr = (RawAddress*)addr;
+  bt_status_t status = sBluetoothAvrcpInterface->play_item_rsp(
+      *btAddr, (btrc_status_t)rspStatus);
+  if (status != BT_STATUS_SUCCESS) {
+    ALOGE("Failed play_item_rsp, status: %d", status);
+  }
+  env->ReleaseByteArrayElements(address, addr, 0);
+
+  return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
+}
+
+static jboolean getTotalNumOfItemsRspNative(JNIEnv* env, jobject object,
+                                            jbyteArray address, jint rspStatus,
+                                            jint uidCounter, jint numItems) {
+  if (!sBluetoothAvrcpInterface) {
+    ALOGE("%s: sBluetoothAvrcpInterface is null", __func__);
+    return JNI_FALSE;
+  }
+
+  jbyte* addr = env->GetByteArrayElements(address, NULL);
+  if (!addr) {
+    jniThrowIOException(env, EINVAL);
+    return JNI_FALSE;
+  }
+
+  uint32_t nItems = (uint32_t)numItems;
+  RawAddress* btAddr = (RawAddress*)addr;
+  bt_status_t status = sBluetoothAvrcpInterface->get_total_num_of_items_rsp(
+      *btAddr, (btrc_status_t)rspStatus, (uint32_t)uidCounter,
+      (uint32_t)nItems);
+  if (status != BT_STATUS_SUCCESS) {
+    ALOGE("Failed get_total_num_of_items_rsp, status: %d", status);
+  }
+  env->ReleaseByteArrayElements(address, addr, 0);
+
+  return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
+}
+
+static jboolean addToNowPlayingRspNative(JNIEnv* env, jobject object,
+                                         jbyteArray address, jint rspStatus) {
+  if (!sBluetoothAvrcpInterface) {
+    ALOGE("%s: sBluetoothAvrcpInterface is null", __func__);
+    return JNI_FALSE;
+  }
+
+  jbyte* addr = env->GetByteArrayElements(address, NULL);
+  if (!addr) {
+    jniThrowIOException(env, EINVAL);
+    return JNI_FALSE;
+  }
+
+  RawAddress* btAddr = (RawAddress*)addr;
+  bt_status_t status = sBluetoothAvrcpInterface->add_to_now_playing_rsp(
+      *btAddr, (btrc_status_t)rspStatus);
+  if (status != BT_STATUS_SUCCESS) {
+    ALOGE("Failed add_to_now_playing_rsp, status: %d", status);
+  }
+  env->ReleaseByteArrayElements(address, addr, 0);
+
+  return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
+}
+
+static JNINativeMethod sMethods[] = {
+    {"classInitNative", "()V", (void*)classInitNative},
+    {"initNative", "()V", (void*)initNative},
+    {"cleanupNative", "()V", (void*)cleanupNative},
+    {"getPlayStatusRspNative", "([BIII)Z", (void*)getPlayStatusRspNative},
+    {"getElementAttrRspNative", "([BB[I[Ljava/lang/String;)Z",
+     (void*)getElementAttrRspNative},
+    {"registerNotificationRspPlayStatusNative", "(II)Z",
+     (void*)registerNotificationRspPlayStatusNative},
+    {"registerNotificationRspTrackChangeNative", "(I[B)Z",
+     (void*)registerNotificationRspTrackChangeNative},
+    {"registerNotificationRspPlayPosNative", "(II)Z",
+     (void*)registerNotificationRspPlayPosNative},
+    {"setVolumeNative", "(I)Z", (void*)setVolumeNative},
+
+    {"setAddressedPlayerRspNative", "([BI)Z",
+     (void*)setAddressedPlayerRspNative},
+
+    {"setBrowsedPlayerRspNative", "([BIBI[Ljava/lang/String;)Z",
+     (void*)setBrowsedPlayerRspNative},
+
+    {"mediaPlayerListRspNative", "([BIIBI[I[B[I[B[S[Ljava/lang/String;)Z",
+     (void*)mediaPlayerListRspNative},
+
+    {"getFolderItemsRspNative",
+     "([BISBI[B[B[B[B[Ljava/lang/String;[I[I[Ljava/lang/String;)Z",
+     (void*)getFolderItemsRspNative},
+
+    {"changePathRspNative", "([BII)Z", (void*)changePathRspNative},
+
+    {"getItemAttrRspNative", "([BIB[I[Ljava/lang/String;)Z",
+     (void*)getItemAttrRspNative},
+
+    {"playItemRspNative", "([BI)Z", (void*)playItemRspNative},
+
+    {"getTotalNumOfItemsRspNative", "([BIII)Z",
+     (void*)getTotalNumOfItemsRspNative},
+
+    {"searchRspNative", "([BIII)Z", (void*)searchRspNative},
+
+    {"addToNowPlayingRspNative", "([BI)Z", (void*)addToNowPlayingRspNative},
+
+    {"registerNotificationRspAddrPlayerChangedNative", "(III)Z",
+     (void*)registerNotificationRspAddrPlayerChangedNative},
+
+    {"registerNotificationRspAvalPlayerChangedNative", "(I)Z",
+     (void*)registerNotificationRspAvalPlayerChangedNative},
+
+    {"registerNotificationRspUIDsChangedNative", "(II)Z",
+     (void*)registerNotificationRspUIDsChangedNative},
+
+    {"registerNotificationRspNowPlayingChangedNative", "(I)Z",
+     (void*)registerNotificationRspNowPlayingChangedNative}};
+
+int register_com_android_bluetooth_avrcp(JNIEnv* env) {
+  return jniRegisterNativeMethods(env, "com/android/bluetooth/avrcp/Avrcp",
+                                  sMethods, NELEM(sMethods));
+}
+
+/* Helper function to copy attributes of item.
+ * Assumes that all items in response have same number of attributes
+ *
+ * returns true on succes, false otherwise.
+*/
+static bool copy_item_attributes(JNIEnv* env, jobject object,
+                                 btrc_folder_items_t* pitem,
+                                 jint* p_attributesIds,
+                                 jobjectArray attributesArray, int item_idx,
+                                 int attribCopiedIndex) {
+  bool success = true;
+
+  /* copy attributes of the item */
+  if (0 < pitem->media.num_attrs) {
+    int num_attrs = pitem->media.num_attrs;
+    ALOGI("%s num_attr = %d", __func__, num_attrs);
+    pitem->media.p_attrs = new btrc_element_attr_val_t[num_attrs];
+    if (!pitem->media.p_attrs) {
+      return false;
+    }
+
+    for (int tempAtrCount = 0; tempAtrCount < pitem->media.num_attrs;
+         ++tempAtrCount) {
+      pitem->media.p_attrs[tempAtrCount].attr_id =
+          p_attributesIds[attribCopiedIndex + tempAtrCount];
+
+      ScopedLocalRef<jstring> text(
+          env, (jstring)env->GetObjectArrayElement(
+                   attributesArray, attribCopiedIndex + tempAtrCount));
+
+      if (!copy_jstring(pitem->media.p_attrs[tempAtrCount].text,
+                        BTRC_MAX_ATTR_STR_LEN, text.get(), env)) {
+        success = false;
+        ALOGE("%s: failed to copy attributes", __func__);
+        break;
+      }
+    }
+  }
+  return success;
+}
+
+/* Helper function to copy String data from java to native
+ *
+ * returns true on succes, false otherwise
+ */
+static bool copy_jstring(uint8_t* str, int maxBytes, jstring jstr,
+                         JNIEnv* env) {
+  if (str == NULL || jstr == NULL || env == NULL) return false;
+
+  memset(str, 0, maxBytes);
+  const char* p_str = env->GetStringUTFChars(jstr, NULL);
+  size_t len = strnlen(p_str, maxBytes - 1);
+  memcpy(str, p_str, len);
+
+  env->ReleaseStringUTFChars(jstr, p_str);
+  return true;
+}
+
+/* Helper function to cleanup items */
+static void cleanup_items(btrc_folder_items_t* p_items, int numItems) {
+  for (int item_idx = 0; item_idx < numItems; item_idx++) {
+    /* release memory for attributes in case item is media item */
+    if ((BTRC_ITEM_MEDIA == p_items[item_idx].item_type) &&
+        p_items[item_idx].media.p_attrs != NULL)
+      delete[] p_items[item_idx].media.p_attrs;
+  }
+}
+}