Loading android/app/src/com/android/bluetooth/Utils.java +9 −0 Original line number Diff line number Diff line Loading @@ -98,6 +98,15 @@ final public class Utils { return converter.getInt(offset); } public static String byteArrayToString(byte[] valueBuf) { StringBuilder sb = new StringBuilder(); for (int idx = 0; idx < valueBuf.length; idx++) { if (idx != 0) sb.append(" "); sb.append(String.format("%02x", valueBuf[idx])); } return sb.toString(); } public static byte[] intToByteArray(int value) { ByteBuffer converter = ByteBuffer.allocate(4); converter.order(ByteOrder.nativeOrder()); Loading android/app/src/com/android/bluetooth/avrcp/AddressedMediaPlayer.java +21 −31 Original line number Diff line number Diff line Loading @@ -25,6 +25,8 @@ import android.media.MediaMetadata; import android.os.Bundle; import android.util.Log; import com.android.bluetooth.Utils; import java.nio.ByteBuffer; import java.util.List; import java.util.Arrays; Loading Loading @@ -93,7 +95,7 @@ public class AddressedMediaPlayer { return; } if (DEBUG) printByteArray("getItemAttr-UID", itemAttr.mUid); 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); Loading Loading @@ -170,8 +172,7 @@ public class AddressedMediaPlayer { if (current == null) bundle.putString(key, metadata.getString(key)); } for (String key : longKeys) { String current = bundle.getString(key); if (current == null) bundle.putString(key, metadata.getLong(key) + ""); if (!bundle.containsKey(key)) bundle.putLong(key, metadata.getLong(key)); } return bundle; } Loading Loading @@ -255,7 +256,7 @@ public class AddressedMediaPlayer { } /* for any item associated with NowPlaying, uid is queueId */ track = ByteBuffer.allocate(AvrcpConstants.UID_SIZE).putLong(qid).array(); if (DEBUG) printByteArray("trackChangedRsp", track); if (DEBUG) Log.d(TAG, "trackChangedRsp: 0x" + Utils.byteArrayToString(track)); mMediaInterface.trackChangedRsp(trackChangedNT, track); } Loading Loading @@ -316,8 +317,9 @@ public class AddressedMediaPlayer { ArrayList<Integer> attrId = new ArrayList<Integer>(); for (int itemIndex = 0; itemIndex < result_items.size(); itemIndex++) { MediaSession.QueueItem item = result_items.get(itemIndex); // get the queue id long qid = result_items.get(itemIndex).getQueueId(); 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 Loading @@ -327,7 +329,7 @@ public class AddressedMediaPlayer { /* Set display name for current item */ folderDataNative.mDisplayNames[itemIndex] = result_items.get(itemIndex).getDescription().getTitle().toString(); getAttrValue(AvrcpConstants.ATTRID_TITLE, item, mediaController); int maxAttributesRequested = 0; boolean isAllAttribRequested = false; Loading @@ -351,18 +353,12 @@ public class AddressedMediaPlayer { int attribId = isAllAttribRequested ? (idx + 1) : folderItemsReqObj.mAttrIDs[idx]; if (attribId >= AvrcpConstants.ATTRID_TITLE && attribId <= AvrcpConstants.ATTRID_PLAY_TIME) { value = getAttrValue( attribId, result_items.get(itemIndex), mediaController); value = getAttrValue(attribId, item, mediaController); if (value != null) { attrArray.add(value); attrId.add(attribId); attrCnt++; } } else { Log.w(TAG, "invalid attribute id is requested: " + attribId); } } /* add num attr actually received from media player for a particular item */ folderDataNative.mAttributesNum[itemIndex] = attrCnt; Loading Loading @@ -403,6 +399,7 @@ public class AddressedMediaPlayer { PlaybackState state = mediaController.getPlaybackState(); Bundle extras = desc.getExtras(); if (state != null && (item.getQueueId() == state.getActiveQueueItemId())) { if (DEBUG) Log.d(TAG, "getAttrValue: item is active, filling extra data"); extras = fillBundle(mediaController.getMetadata(), extras); } if (DEBUG) Log.d(TAG, "getAttrValue: item " + item + " : " + desc); Loading Loading @@ -437,20 +434,22 @@ public class AddressedMediaPlayer { break; case AvrcpConstants.ATTRID_COVER_ART: Log.e(TAG, "Cover art attribute not supported"); break; Log.e(TAG, "getAttrValue: Cover art attribute not supported"); return null; default: Log.e(TAG, "Unknown attribute ID"); 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) { return "<Unknown Title>"; } attrValue = "<Unknown Title>"; } else { return null; } } if (DEBUG) Log.d(TAG, "getAttrValue: attrvalue = " + attrValue + ", attr id:" + attr); return attrValue; } Loading Loading @@ -511,13 +510,4 @@ public class AddressedMediaPlayer { return; } } private void printByteArray(String arrName, byte[] array) { StringBuilder byteArray = new StringBuilder(arrName + ": 0x"); for (int idx = 0; idx < array.length; idx++) { byteArray.append(String.format(" %02x", array[idx])); } Log.d(TAG, byteArray + ""); } } android/app/src/com/android/bluetooth/avrcp/Avrcp.java +8 −5 Original line number Diff line number Diff line Loading @@ -703,8 +703,8 @@ public final class Avrcp { break; case MSG_NATIVE_REQ_GET_FOLDER_ITEMS: { if (DEBUG) Log.v(TAG, "MSG_NATIVE_REQ_GET_FOLDER_ITEMS"); AvrcpCmd.FolderItemsCmd folderObj = (AvrcpCmd.FolderItemsCmd) msg.obj; if (DEBUG) Log.v(TAG, "MSG_NATIVE_REQ_GET_FOLDER_ITEMS " + folderObj); switch (folderObj.mScope) { case AvrcpConstants.BTRC_SCOPE_PLAYER_LIST: handleMediaPlayerListRsp(folderObj); Loading @@ -731,8 +731,9 @@ public final class Avrcp { case MSG_NATIVE_REQ_GET_ITEM_ATTR: // msg object contains the item attribute object if (DEBUG) Log.v(TAG, "MSG_NATIVE_REQ_GET_ITEM_ATTR"); handleGetItemAttr((AvrcpCmd.ItemAttrCmd) msg.obj); AvrcpCmd.ItemAttrCmd cmd = (AvrcpCmd.ItemAttrCmd) msg.obj; if (DEBUG) Log.v(TAG, "MSG_NATIVE_REQ_GET_ITEM_ATTR " + cmd); handleGetItemAttr(cmd); break; case MSG_NATIVE_REQ_SET_BR_PLAYER: Loading Loading @@ -760,11 +761,13 @@ public final class Avrcp { case MSG_NATIVE_REQ_PLAY_ITEM: { if (DEBUG) Log.v(TAG, "MSG_NATIVE_REQ_PLAY_ITEM"); Bundle data = msg.getData(); byte[] bdaddr = data.getByteArray("BdAddress"); byte[] uid = data.getByteArray("uid"); byte scope = data.getByte("scope"); if (DEBUG) Log.v(TAG, "MSG_NATIVE_REQ_PLAY_ITEM scope=" + scope + " id=" + Utils.byteArrayToString(uid)); handlePlayItemResponse(bdaddr, uid, scope); break; } Loading android/app/src/com/android/bluetooth/avrcp/AvrcpHelperClasses.java +27 −0 Original line number Diff line number Diff line Loading @@ -18,6 +18,8 @@ package com.android.bluetooth.avrcp; import android.media.session.MediaSession; import com.android.bluetooth.Utils; import java.util.List; import java.util.Arrays; import java.util.ArrayDeque; Loading Loading @@ -51,6 +53,19 @@ class AvrcpCmd { this.mNumAttr = numAttr; this.mAttrIDs = attrIds; } 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 { Loading @@ -70,6 +85,18 @@ class AvrcpCmd { mNumAttr = numAttr; mAttrIDs = attrIDs; } 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 { Loading android/app/src/com/android/bluetooth/avrcp/BrowsedMediaPlayer.java +111 −122 Original line number Diff line number Diff line Loading @@ -24,6 +24,7 @@ import android.media.browse.MediaBrowser; import android.media.browse.MediaBrowser.MediaItem; import android.media.session.MediaSession; import android.media.session.MediaSession.QueueItem; import android.os.Bundle; import android.util.Log; import java.math.BigInteger; Loading Loading @@ -336,50 +337,57 @@ class BrowsedMediaPlayer { mItemAttrReqObj = itemAttr; /* check if uid is valid by doing a lookup in hashmap */ if ((mediaID = byteToString(itemAttr.mUid)) == null) { mediaID = byteToString(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) { if (mMediaBrowser != null) { mMediaBrowser.subscribe(mediaID, itemAttrCb); } else { Log.e(TAG, "mMediaBrowser is null"); mMediaInterface.getItemAttrRsp(mBDAddr, AvrcpConstants.RSP_INTERNAL_ERR, null); } } else { 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; } mMediaBrowser.subscribe(mediaID, itemAttrCb); } public void getTotalNumOfItems(byte scope) { if (DEBUG) Log.d(TAG, "getTotalNumOfItems scope = " + scope); switch (scope) { case AvrcpConstants.BTRC_SCOPE_FILE_SYSTEM: if (mFolderItems != null) { /* find num items using size of already cached folder items */ mMediaInterface.getTotalNumOfItemsRsp(mBDAddr, AvrcpConstants.RSP_NO_ERROR, 0, mFolderItems.size()); } else { Log.e(TAG, "mFolderItems is null, sending internal error"); /* folderitems were not fetched during change path */ mMediaInterface.getTotalNumOfItemsRsp(mBDAddr, AvrcpConstants.RSP_INTERNAL_ERR, 0, 0); } break; default: if (scope != AvrcpConstants.BTRC_SCOPE_FILE_SYSTEM) { Log.e(TAG, "getTotalNumOfItems error" + scope); mMediaInterface.getTotalNumOfItemsRsp(mBDAddr, AvrcpConstants.RSP_INV_SCOPE, 0, 0); break; 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()) { 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; Loading @@ -387,17 +395,13 @@ class BrowsedMediaPlayer { /* Failed to fetch folder items from media player. Send error to remote device */ Log.e(TAG, "Failed to fetch folder items during getFolderItemsVFS"); mMediaInterface.folderItemsRsp(mBDAddr, AvrcpConstants.RSP_INTERNAL_ERR, null); } else { 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); } } else { Log.e(TAG, "unable to connect to media player, sending internal error"); /* unable to connect to media player. Send error response to remote device */ mMediaInterface.folderItemsRsp(mBDAddr, AvrcpConstants.RSP_INTERNAL_ERR, null); } AvrcpConstants.BTRC_SCOPE_FILE_SYSTEM, mFolderItemsReqObj.mStartItem, mFolderItemsReqObj.mEndItem); } /* Instructs media player to play particular media item */ Loading Loading @@ -453,11 +457,9 @@ class BrowsedMediaPlayer { } catch (IndexOutOfBoundsException ex) { Log.w(TAG, "Index out of bounds start item ="+ startItem + " end item = "+ Math.min(children.size(), endItem + 1)); mMediaInterface.folderItemsRsp(bdaddr, AvrcpConstants.RSP_INV_RANGE, null); return null; } catch (IllegalArgumentException ex) { Log.i(TAG, "Index out of bounds start item =" + startItem + " > size"); mMediaInterface.folderItemsRsp(bdaddr, AvrcpConstants.RSP_INV_RANGE, null); return null; } } Loading @@ -468,14 +470,21 @@ class BrowsedMediaPlayer { */ 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); if (DEBUG) Log.d(TAG, "getFolderItemsFilterAttr: startItem =" + startItem + ", endItem = " + endItem); List<MediaBrowser.MediaItem> result_items = new ArrayList<MediaBrowser.MediaItem>(); if (children != null) { 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 */ if ((result_items = checkIndexOutofBounds(bdaddr, children, startItem, endItem)) == null) { result_items = checkIndexOutofBounds(bdaddr, children, startItem, endItem); if (result_items == null) { Log.w(TAG, "result_items is null."); mMediaInterface.folderItemsRsp(bdaddr, AvrcpConstants.RSP_INV_RANGE, null); return; Loading @@ -488,30 +497,29 @@ class BrowsedMediaPlayer { for (int itemIndex = 0; itemIndex < result_items.size(); itemIndex++) { /* item type. Needs to be set by media player */ if ((result_items.get(itemIndex).getFlags() & MediaBrowser.MediaItem.FLAG_BROWSABLE) != 0) { MediaBrowser.MediaItem item = result_items.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 ((result_items.get(itemIndex).getFlags() & MediaBrowser.MediaItem.FLAG_PLAYABLE) != 0) { 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 = stringToByte(result_items.get(itemIndex).getDescription().getMediaId()); byte[] uid = stringToByte(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] = result_items.get(itemIndex).getDescription().getTitle().toString(); getAttrValue(AvrcpConstants.ATTRID_TITLE, item); int maxAttributesRequested = 0; boolean isAllAttribRequested = false; Loading @@ -535,17 +543,12 @@ class BrowsedMediaPlayer { int attribId = isAllAttribRequested ? (idx + 1) : mFolderItemsReqObj.mAttrIDs[idx]; if(attribId >= AvrcpConstants.ATTRID_TITLE && attribId <= AvrcpConstants.ATTRID_PLAY_TIME) { if ((value = getAttrValue(attribId, result_items, itemIndex)) != null) { value = getAttrValue(attribId, result_items.get(itemIndex)); if (value != null) { attrArray.add(value); attrId.add(attribId); attrCnt++; } } else { Log.d(TAG, "invalid attributed id is requested: " + attribId); } } /* add num attr actually received from media player for a particular item */ folderDataNative.mAttributesNum[itemIndex] = attrCnt; Loading @@ -553,7 +556,7 @@ class BrowsedMediaPlayer { } /* copy filtered attr ids and attr values to response parameters */ if (mFolderItemsReqObj.mNumAttr != AvrcpConstants.NUM_ATTR_NONE) { if (attrId.size() > 0) { folderDataNative.mAttrIds = new int[attrId.size()]; for (int attrIndex = 0; attrIndex < attrId.size(); attrIndex++) folderDataNative.mAttrIds[attrIndex] = attrId.get(attrIndex); Loading @@ -561,76 +564,64 @@ class BrowsedMediaPlayer { } /* 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, FolderItemsRsp rspObj = new FolderItemsRsp(AvrcpConstants.RSP_NO_ERROR, Avrcp.sUIDCounter, scope, folderDataNative.mNumItems, folderDataNative.mFolderTypes, folderDataNative.mPlayable, folderDataNative.mItemTypes, folderDataNative.mItemUid, folderDataNative.mDisplayNames, folderDataNative.mAttributesNum, folderDataNative.mAttrIds, folderDataNative.mAttrValues); mMediaInterface.folderItemsRsp(bdaddr, AvrcpConstants.RSP_NO_ERROR, rspObj); } else { Log.e(TAG, "Error: children are null in getFolderItemsFilterAttr"); mMediaInterface.folderItemsRsp(bdaddr, AvrcpConstants.RSP_INV_RANGE, null); return; } } public static String getAttrValue(int attr, List<MediaBrowser.MediaItem> resultItems, int itemIndex) { 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 = resultItems.get(itemIndex).getDescription().getTitle().toString(); attrValue = desc.getTitle().toString(); break; case AvrcpConstants.ATTRID_ARTIST: attrValue = resultItems.get(itemIndex).getDescription().getExtras() .getString(MediaMetadata.METADATA_KEY_ARTIST); attrValue = extras.getString(MediaMetadata.METADATA_KEY_ARTIST); break; case AvrcpConstants.ATTRID_ALBUM: attrValue = resultItems.get(itemIndex).getDescription().getExtras() .getString(MediaMetadata.METADATA_KEY_ALBUM); attrValue = extras.getString(MediaMetadata.METADATA_KEY_ALBUM); break; case AvrcpConstants.ATTRID_TRACK_NUM: attrValue = resultItems.get(itemIndex).getDescription().getExtras() .getString(MediaMetadata.METADATA_KEY_TRACK_NUMBER); attrValue = extras.getString(MediaMetadata.METADATA_KEY_TRACK_NUMBER); break; case AvrcpConstants.ATTRID_NUM_TRACKS: attrValue = resultItems.get(itemIndex).getDescription().getExtras() .getString(MediaMetadata.METADATA_KEY_NUM_TRACKS); attrValue = extras.getString(MediaMetadata.METADATA_KEY_NUM_TRACKS); break; case AvrcpConstants.ATTRID_GENRE: attrValue = resultItems.get(itemIndex).getDescription().getExtras() .getString(MediaMetadata.METADATA_KEY_GENRE); attrValue = extras.getString(MediaMetadata.METADATA_KEY_GENRE); case AvrcpConstants.ATTRID_PLAY_TIME: attrValue = resultItems.get(itemIndex).getDescription().getExtras() .getString(MediaMetadata.METADATA_KEY_DURATION); attrValue = extras.getString(MediaMetadata.METADATA_KEY_DURATION); case AvrcpConstants.ATTRID_COVER_ART: Log.e(TAG, "Cover art attribute not supported"); break; Log.e(TAG, "getAttrValue: Cover art attribute not supported"); return null; default: Log.e(TAG, "Unknown attribute ID"); } } catch (IndexOutOfBoundsException ex) { Log.w(TAG, "getAttrValue: requested item index out of bounds"); 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) { return "<Unknown Title>"; } attrValue = "<Unknown Title>"; } else { return null; } } if (DEBUG) Log.d(TAG, "getAttrValue: attrvalue = " + attrValue + "attr id:" + attr); return attrValue; } Loading @@ -641,8 +632,6 @@ class BrowsedMediaPlayer { int[] attrIds = null; /* array of attr ids */ String[] attrValues = null; /* array of attr values */ int attrCounter = 0; /* num attributes for each item */ List<MediaBrowser.MediaItem> resultItems = new ArrayList<MediaBrowser.MediaItem>(); resultItems.add(mediaItem); /* variables to temperorily add attrs */ ArrayList<String> attrArray = new ArrayList<String>(); ArrayList<Integer> attrId = new ArrayList<Integer>(); Loading Loading @@ -672,8 +661,8 @@ class BrowsedMediaPlayer { /* lookup and copy values of attributes for ids requested above */ for (int idx = 0; idx < attrTempId.size(); idx++) { /* check if media player provided requested attributes */ String value = null; if ((value = getAttrValue(attrTempId.get(idx), resultItems, 0)) != null) { String value = getAttrValue(attrTempId.get(idx), mediaItem); if (value != null) { attrArray.add(value); attrId.add(attrTempId.get(idx)); attrCounter++; Loading Loading
android/app/src/com/android/bluetooth/Utils.java +9 −0 Original line number Diff line number Diff line Loading @@ -98,6 +98,15 @@ final public class Utils { return converter.getInt(offset); } public static String byteArrayToString(byte[] valueBuf) { StringBuilder sb = new StringBuilder(); for (int idx = 0; idx < valueBuf.length; idx++) { if (idx != 0) sb.append(" "); sb.append(String.format("%02x", valueBuf[idx])); } return sb.toString(); } public static byte[] intToByteArray(int value) { ByteBuffer converter = ByteBuffer.allocate(4); converter.order(ByteOrder.nativeOrder()); Loading
android/app/src/com/android/bluetooth/avrcp/AddressedMediaPlayer.java +21 −31 Original line number Diff line number Diff line Loading @@ -25,6 +25,8 @@ import android.media.MediaMetadata; import android.os.Bundle; import android.util.Log; import com.android.bluetooth.Utils; import java.nio.ByteBuffer; import java.util.List; import java.util.Arrays; Loading Loading @@ -93,7 +95,7 @@ public class AddressedMediaPlayer { return; } if (DEBUG) printByteArray("getItemAttr-UID", itemAttr.mUid); 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); Loading Loading @@ -170,8 +172,7 @@ public class AddressedMediaPlayer { if (current == null) bundle.putString(key, metadata.getString(key)); } for (String key : longKeys) { String current = bundle.getString(key); if (current == null) bundle.putString(key, metadata.getLong(key) + ""); if (!bundle.containsKey(key)) bundle.putLong(key, metadata.getLong(key)); } return bundle; } Loading Loading @@ -255,7 +256,7 @@ public class AddressedMediaPlayer { } /* for any item associated with NowPlaying, uid is queueId */ track = ByteBuffer.allocate(AvrcpConstants.UID_SIZE).putLong(qid).array(); if (DEBUG) printByteArray("trackChangedRsp", track); if (DEBUG) Log.d(TAG, "trackChangedRsp: 0x" + Utils.byteArrayToString(track)); mMediaInterface.trackChangedRsp(trackChangedNT, track); } Loading Loading @@ -316,8 +317,9 @@ public class AddressedMediaPlayer { ArrayList<Integer> attrId = new ArrayList<Integer>(); for (int itemIndex = 0; itemIndex < result_items.size(); itemIndex++) { MediaSession.QueueItem item = result_items.get(itemIndex); // get the queue id long qid = result_items.get(itemIndex).getQueueId(); 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 Loading @@ -327,7 +329,7 @@ public class AddressedMediaPlayer { /* Set display name for current item */ folderDataNative.mDisplayNames[itemIndex] = result_items.get(itemIndex).getDescription().getTitle().toString(); getAttrValue(AvrcpConstants.ATTRID_TITLE, item, mediaController); int maxAttributesRequested = 0; boolean isAllAttribRequested = false; Loading @@ -351,18 +353,12 @@ public class AddressedMediaPlayer { int attribId = isAllAttribRequested ? (idx + 1) : folderItemsReqObj.mAttrIDs[idx]; if (attribId >= AvrcpConstants.ATTRID_TITLE && attribId <= AvrcpConstants.ATTRID_PLAY_TIME) { value = getAttrValue( attribId, result_items.get(itemIndex), mediaController); value = getAttrValue(attribId, item, mediaController); if (value != null) { attrArray.add(value); attrId.add(attribId); attrCnt++; } } else { Log.w(TAG, "invalid attribute id is requested: " + attribId); } } /* add num attr actually received from media player for a particular item */ folderDataNative.mAttributesNum[itemIndex] = attrCnt; Loading Loading @@ -403,6 +399,7 @@ public class AddressedMediaPlayer { PlaybackState state = mediaController.getPlaybackState(); Bundle extras = desc.getExtras(); if (state != null && (item.getQueueId() == state.getActiveQueueItemId())) { if (DEBUG) Log.d(TAG, "getAttrValue: item is active, filling extra data"); extras = fillBundle(mediaController.getMetadata(), extras); } if (DEBUG) Log.d(TAG, "getAttrValue: item " + item + " : " + desc); Loading Loading @@ -437,20 +434,22 @@ public class AddressedMediaPlayer { break; case AvrcpConstants.ATTRID_COVER_ART: Log.e(TAG, "Cover art attribute not supported"); break; Log.e(TAG, "getAttrValue: Cover art attribute not supported"); return null; default: Log.e(TAG, "Unknown attribute ID"); 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) { return "<Unknown Title>"; } attrValue = "<Unknown Title>"; } else { return null; } } if (DEBUG) Log.d(TAG, "getAttrValue: attrvalue = " + attrValue + ", attr id:" + attr); return attrValue; } Loading Loading @@ -511,13 +510,4 @@ public class AddressedMediaPlayer { return; } } private void printByteArray(String arrName, byte[] array) { StringBuilder byteArray = new StringBuilder(arrName + ": 0x"); for (int idx = 0; idx < array.length; idx++) { byteArray.append(String.format(" %02x", array[idx])); } Log.d(TAG, byteArray + ""); } }
android/app/src/com/android/bluetooth/avrcp/Avrcp.java +8 −5 Original line number Diff line number Diff line Loading @@ -703,8 +703,8 @@ public final class Avrcp { break; case MSG_NATIVE_REQ_GET_FOLDER_ITEMS: { if (DEBUG) Log.v(TAG, "MSG_NATIVE_REQ_GET_FOLDER_ITEMS"); AvrcpCmd.FolderItemsCmd folderObj = (AvrcpCmd.FolderItemsCmd) msg.obj; if (DEBUG) Log.v(TAG, "MSG_NATIVE_REQ_GET_FOLDER_ITEMS " + folderObj); switch (folderObj.mScope) { case AvrcpConstants.BTRC_SCOPE_PLAYER_LIST: handleMediaPlayerListRsp(folderObj); Loading @@ -731,8 +731,9 @@ public final class Avrcp { case MSG_NATIVE_REQ_GET_ITEM_ATTR: // msg object contains the item attribute object if (DEBUG) Log.v(TAG, "MSG_NATIVE_REQ_GET_ITEM_ATTR"); handleGetItemAttr((AvrcpCmd.ItemAttrCmd) msg.obj); AvrcpCmd.ItemAttrCmd cmd = (AvrcpCmd.ItemAttrCmd) msg.obj; if (DEBUG) Log.v(TAG, "MSG_NATIVE_REQ_GET_ITEM_ATTR " + cmd); handleGetItemAttr(cmd); break; case MSG_NATIVE_REQ_SET_BR_PLAYER: Loading Loading @@ -760,11 +761,13 @@ public final class Avrcp { case MSG_NATIVE_REQ_PLAY_ITEM: { if (DEBUG) Log.v(TAG, "MSG_NATIVE_REQ_PLAY_ITEM"); Bundle data = msg.getData(); byte[] bdaddr = data.getByteArray("BdAddress"); byte[] uid = data.getByteArray("uid"); byte scope = data.getByte("scope"); if (DEBUG) Log.v(TAG, "MSG_NATIVE_REQ_PLAY_ITEM scope=" + scope + " id=" + Utils.byteArrayToString(uid)); handlePlayItemResponse(bdaddr, uid, scope); break; } Loading
android/app/src/com/android/bluetooth/avrcp/AvrcpHelperClasses.java +27 −0 Original line number Diff line number Diff line Loading @@ -18,6 +18,8 @@ package com.android.bluetooth.avrcp; import android.media.session.MediaSession; import com.android.bluetooth.Utils; import java.util.List; import java.util.Arrays; import java.util.ArrayDeque; Loading Loading @@ -51,6 +53,19 @@ class AvrcpCmd { this.mNumAttr = numAttr; this.mAttrIDs = attrIds; } 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 { Loading @@ -70,6 +85,18 @@ class AvrcpCmd { mNumAttr = numAttr; mAttrIDs = attrIDs; } 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 { Loading
android/app/src/com/android/bluetooth/avrcp/BrowsedMediaPlayer.java +111 −122 Original line number Diff line number Diff line Loading @@ -24,6 +24,7 @@ import android.media.browse.MediaBrowser; import android.media.browse.MediaBrowser.MediaItem; import android.media.session.MediaSession; import android.media.session.MediaSession.QueueItem; import android.os.Bundle; import android.util.Log; import java.math.BigInteger; Loading Loading @@ -336,50 +337,57 @@ class BrowsedMediaPlayer { mItemAttrReqObj = itemAttr; /* check if uid is valid by doing a lookup in hashmap */ if ((mediaID = byteToString(itemAttr.mUid)) == null) { mediaID = byteToString(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) { if (mMediaBrowser != null) { mMediaBrowser.subscribe(mediaID, itemAttrCb); } else { Log.e(TAG, "mMediaBrowser is null"); mMediaInterface.getItemAttrRsp(mBDAddr, AvrcpConstants.RSP_INTERNAL_ERR, null); } } else { 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; } mMediaBrowser.subscribe(mediaID, itemAttrCb); } public void getTotalNumOfItems(byte scope) { if (DEBUG) Log.d(TAG, "getTotalNumOfItems scope = " + scope); switch (scope) { case AvrcpConstants.BTRC_SCOPE_FILE_SYSTEM: if (mFolderItems != null) { /* find num items using size of already cached folder items */ mMediaInterface.getTotalNumOfItemsRsp(mBDAddr, AvrcpConstants.RSP_NO_ERROR, 0, mFolderItems.size()); } else { Log.e(TAG, "mFolderItems is null, sending internal error"); /* folderitems were not fetched during change path */ mMediaInterface.getTotalNumOfItemsRsp(mBDAddr, AvrcpConstants.RSP_INTERNAL_ERR, 0, 0); } break; default: if (scope != AvrcpConstants.BTRC_SCOPE_FILE_SYSTEM) { Log.e(TAG, "getTotalNumOfItems error" + scope); mMediaInterface.getTotalNumOfItemsRsp(mBDAddr, AvrcpConstants.RSP_INV_SCOPE, 0, 0); break; 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()) { 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; Loading @@ -387,17 +395,13 @@ class BrowsedMediaPlayer { /* Failed to fetch folder items from media player. Send error to remote device */ Log.e(TAG, "Failed to fetch folder items during getFolderItemsVFS"); mMediaInterface.folderItemsRsp(mBDAddr, AvrcpConstants.RSP_INTERNAL_ERR, null); } else { 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); } } else { Log.e(TAG, "unable to connect to media player, sending internal error"); /* unable to connect to media player. Send error response to remote device */ mMediaInterface.folderItemsRsp(mBDAddr, AvrcpConstants.RSP_INTERNAL_ERR, null); } AvrcpConstants.BTRC_SCOPE_FILE_SYSTEM, mFolderItemsReqObj.mStartItem, mFolderItemsReqObj.mEndItem); } /* Instructs media player to play particular media item */ Loading Loading @@ -453,11 +457,9 @@ class BrowsedMediaPlayer { } catch (IndexOutOfBoundsException ex) { Log.w(TAG, "Index out of bounds start item ="+ startItem + " end item = "+ Math.min(children.size(), endItem + 1)); mMediaInterface.folderItemsRsp(bdaddr, AvrcpConstants.RSP_INV_RANGE, null); return null; } catch (IllegalArgumentException ex) { Log.i(TAG, "Index out of bounds start item =" + startItem + " > size"); mMediaInterface.folderItemsRsp(bdaddr, AvrcpConstants.RSP_INV_RANGE, null); return null; } } Loading @@ -468,14 +470,21 @@ class BrowsedMediaPlayer { */ 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); if (DEBUG) Log.d(TAG, "getFolderItemsFilterAttr: startItem =" + startItem + ", endItem = " + endItem); List<MediaBrowser.MediaItem> result_items = new ArrayList<MediaBrowser.MediaItem>(); if (children != null) { 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 */ if ((result_items = checkIndexOutofBounds(bdaddr, children, startItem, endItem)) == null) { result_items = checkIndexOutofBounds(bdaddr, children, startItem, endItem); if (result_items == null) { Log.w(TAG, "result_items is null."); mMediaInterface.folderItemsRsp(bdaddr, AvrcpConstants.RSP_INV_RANGE, null); return; Loading @@ -488,30 +497,29 @@ class BrowsedMediaPlayer { for (int itemIndex = 0; itemIndex < result_items.size(); itemIndex++) { /* item type. Needs to be set by media player */ if ((result_items.get(itemIndex).getFlags() & MediaBrowser.MediaItem.FLAG_BROWSABLE) != 0) { MediaBrowser.MediaItem item = result_items.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 ((result_items.get(itemIndex).getFlags() & MediaBrowser.MediaItem.FLAG_PLAYABLE) != 0) { 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 = stringToByte(result_items.get(itemIndex).getDescription().getMediaId()); byte[] uid = stringToByte(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] = result_items.get(itemIndex).getDescription().getTitle().toString(); getAttrValue(AvrcpConstants.ATTRID_TITLE, item); int maxAttributesRequested = 0; boolean isAllAttribRequested = false; Loading @@ -535,17 +543,12 @@ class BrowsedMediaPlayer { int attribId = isAllAttribRequested ? (idx + 1) : mFolderItemsReqObj.mAttrIDs[idx]; if(attribId >= AvrcpConstants.ATTRID_TITLE && attribId <= AvrcpConstants.ATTRID_PLAY_TIME) { if ((value = getAttrValue(attribId, result_items, itemIndex)) != null) { value = getAttrValue(attribId, result_items.get(itemIndex)); if (value != null) { attrArray.add(value); attrId.add(attribId); attrCnt++; } } else { Log.d(TAG, "invalid attributed id is requested: " + attribId); } } /* add num attr actually received from media player for a particular item */ folderDataNative.mAttributesNum[itemIndex] = attrCnt; Loading @@ -553,7 +556,7 @@ class BrowsedMediaPlayer { } /* copy filtered attr ids and attr values to response parameters */ if (mFolderItemsReqObj.mNumAttr != AvrcpConstants.NUM_ATTR_NONE) { if (attrId.size() > 0) { folderDataNative.mAttrIds = new int[attrId.size()]; for (int attrIndex = 0; attrIndex < attrId.size(); attrIndex++) folderDataNative.mAttrIds[attrIndex] = attrId.get(attrIndex); Loading @@ -561,76 +564,64 @@ class BrowsedMediaPlayer { } /* 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, FolderItemsRsp rspObj = new FolderItemsRsp(AvrcpConstants.RSP_NO_ERROR, Avrcp.sUIDCounter, scope, folderDataNative.mNumItems, folderDataNative.mFolderTypes, folderDataNative.mPlayable, folderDataNative.mItemTypes, folderDataNative.mItemUid, folderDataNative.mDisplayNames, folderDataNative.mAttributesNum, folderDataNative.mAttrIds, folderDataNative.mAttrValues); mMediaInterface.folderItemsRsp(bdaddr, AvrcpConstants.RSP_NO_ERROR, rspObj); } else { Log.e(TAG, "Error: children are null in getFolderItemsFilterAttr"); mMediaInterface.folderItemsRsp(bdaddr, AvrcpConstants.RSP_INV_RANGE, null); return; } } public static String getAttrValue(int attr, List<MediaBrowser.MediaItem> resultItems, int itemIndex) { 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 = resultItems.get(itemIndex).getDescription().getTitle().toString(); attrValue = desc.getTitle().toString(); break; case AvrcpConstants.ATTRID_ARTIST: attrValue = resultItems.get(itemIndex).getDescription().getExtras() .getString(MediaMetadata.METADATA_KEY_ARTIST); attrValue = extras.getString(MediaMetadata.METADATA_KEY_ARTIST); break; case AvrcpConstants.ATTRID_ALBUM: attrValue = resultItems.get(itemIndex).getDescription().getExtras() .getString(MediaMetadata.METADATA_KEY_ALBUM); attrValue = extras.getString(MediaMetadata.METADATA_KEY_ALBUM); break; case AvrcpConstants.ATTRID_TRACK_NUM: attrValue = resultItems.get(itemIndex).getDescription().getExtras() .getString(MediaMetadata.METADATA_KEY_TRACK_NUMBER); attrValue = extras.getString(MediaMetadata.METADATA_KEY_TRACK_NUMBER); break; case AvrcpConstants.ATTRID_NUM_TRACKS: attrValue = resultItems.get(itemIndex).getDescription().getExtras() .getString(MediaMetadata.METADATA_KEY_NUM_TRACKS); attrValue = extras.getString(MediaMetadata.METADATA_KEY_NUM_TRACKS); break; case AvrcpConstants.ATTRID_GENRE: attrValue = resultItems.get(itemIndex).getDescription().getExtras() .getString(MediaMetadata.METADATA_KEY_GENRE); attrValue = extras.getString(MediaMetadata.METADATA_KEY_GENRE); case AvrcpConstants.ATTRID_PLAY_TIME: attrValue = resultItems.get(itemIndex).getDescription().getExtras() .getString(MediaMetadata.METADATA_KEY_DURATION); attrValue = extras.getString(MediaMetadata.METADATA_KEY_DURATION); case AvrcpConstants.ATTRID_COVER_ART: Log.e(TAG, "Cover art attribute not supported"); break; Log.e(TAG, "getAttrValue: Cover art attribute not supported"); return null; default: Log.e(TAG, "Unknown attribute ID"); } } catch (IndexOutOfBoundsException ex) { Log.w(TAG, "getAttrValue: requested item index out of bounds"); 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) { return "<Unknown Title>"; } attrValue = "<Unknown Title>"; } else { return null; } } if (DEBUG) Log.d(TAG, "getAttrValue: attrvalue = " + attrValue + "attr id:" + attr); return attrValue; } Loading @@ -641,8 +632,6 @@ class BrowsedMediaPlayer { int[] attrIds = null; /* array of attr ids */ String[] attrValues = null; /* array of attr values */ int attrCounter = 0; /* num attributes for each item */ List<MediaBrowser.MediaItem> resultItems = new ArrayList<MediaBrowser.MediaItem>(); resultItems.add(mediaItem); /* variables to temperorily add attrs */ ArrayList<String> attrArray = new ArrayList<String>(); ArrayList<Integer> attrId = new ArrayList<Integer>(); Loading Loading @@ -672,8 +661,8 @@ class BrowsedMediaPlayer { /* lookup and copy values of attributes for ids requested above */ for (int idx = 0; idx < attrTempId.size(); idx++) { /* check if media player provided requested attributes */ String value = null; if ((value = getAttrValue(attrTempId.get(idx), resultItems, 0)) != null) { String value = getAttrValue(attrTempId.get(idx), mediaItem); if (value != null) { attrArray.add(value); attrId.add(attrTempId.get(idx)); attrCounter++; Loading