Loading android/app/jni/com_android_bluetooth_avrcp_controller.cpp +3 −3 Original line number Diff line number Diff line Loading @@ -371,7 +371,7 @@ static void btavrcp_play_status_changed_callback(bt_bdaddr_t *bd_addr, } static void btavrcp_get_folder_items_callback(bt_bdaddr_t *bd_addr, const btrc_folder_items_t *folder_items, uint8_t count) { btrc_status_t status, const btrc_folder_items_t *folder_items, uint8_t count) { /* Folder items are list of items that can be either BTRC_ITEM_PLAYER * BTRC_ITEM_MEDIA, BTRC_ITEM_FOLDER. Here we translate them to their java * counterparts by calling the java constructor for each of the items. Loading Loading @@ -516,7 +516,7 @@ static void btavrcp_get_folder_items_callback(bt_bdaddr_t *bd_addr, playerItemArray); } else { sCallbackEnv->CallVoidMethod(sCallbacksObj, method_handleGetFolderItemsRsp, folderItemArray); status, folderItemArray); } if (isPlayerListing) { sCallbackEnv->DeleteLocalRef(playerItemArray); Loading Loading @@ -600,7 +600,7 @@ static void classInitNative(JNIEnv* env, jclass clazz) { env->GetMethodID(clazz, "onPlayStatusChanged", "([BB)V"); method_handleGetFolderItemsRsp = env->GetMethodID(clazz, "handleGetFolderItemsRsp", "([Landroid/media/browse/MediaBrowser$MediaItem;)V"); env->GetMethodID(clazz, "handleGetFolderItemsRsp", "(I[Landroid/media/browse/MediaBrowser$MediaItem;)V"); method_handleGetPlayerItemsRsp = env->GetMethodID(clazz, "handleGetPlayerItemsRsp", "([Lcom/android/bluetooth/avrcpcontroller/AvrcpPlayer;)V"); Loading android/app/src/com/android/bluetooth/a2dpsink/mbs/A2dpMediaBrowserService.java +7 −3 Original line number Diff line number Diff line Loading @@ -477,14 +477,18 @@ public class A2dpMediaBrowserService extends MediaBrowserService { String id = intent.getStringExtra(AvrcpControllerService.EXTRA_FOLDER_ID); Log.d(TAG, "Parent: " + id + " Folder list: " + folderList); synchronized (this) { // If we have a result object then we should send the result back // to client since it is blocking otherwise we may have gotten more items // from remote device, hence let client know to fetch again. Result<List<MediaItem>> results = mParentIdToRequestMap.remove(id); if (results == null) { Log.w(TAG, "Request no longer exists, hence ignoring reply!"); return; } Log.w(TAG, "Request no longer exists, notifying that children changed."); notifyChildrenChanged(id); } else { results.sendResult(folderList); } } } private void msgDeviceBrowseDisconnect(BluetoothDevice device) { Log.d(TAG, "msgDeviceBrowseDisconnect device " + device); Loading android/app/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerService.java +24 −5 Original line number Diff line number Diff line Loading @@ -85,6 +85,13 @@ public class AvrcpControllerService extends ProfileService { private static final int JNI_FOLDER_TYPE_PLAYLISTS = 0x05; private static final int JNI_FOLDER_TYPE_YEARS = 0x06; /* * AVRCP Error types as defined in spec. Also they should be in sync with btrc_status_t. * NOTE: Not all may be defined. */ private static final int JNI_AVRC_STS_NO_ERROR = 0x04; private static final int JNI_AVRC_INV_RANGE = 0x0b; /** * Intent used to broadcast the change in browse connection state of the AVRCP Controller * profile. Loading Loading @@ -911,10 +918,22 @@ public class AvrcpControllerService extends ProfileService { } // Browsing related JNI callbacks. void handleGetFolderItemsRsp(MediaItem[] items) { void handleGetFolderItemsRsp(int status, MediaItem[] items) { if (DBG) { Log.d(TAG, "handleGetFolderItemsRsp called with " + items.length + " items."); Log.d(TAG, "handleGetFolderItemsRsp called with status " + status + " items " + items.length + " items."); } if (status == JNI_AVRC_INV_RANGE) { Log.w(TAG, "Sending out of range message."); // Send a special message since this could be used by state machine // to take as a signal that fetch is finished. Message msg = mAvrcpCtSm.obtainMessage(AvrcpControllerStateMachine. MESSAGE_PROCESS_GET_FOLDER_ITEMS_OUT_OF_RANGE); mAvrcpCtSm.sendMessage(msg); return; } for (MediaItem item : items) { if (DBG) { Log.d(TAG, "media item: " + item + " uid: " + item.getDescription().getMediaId()); Loading Loading @@ -1081,11 +1100,11 @@ public class AvrcpControllerService extends ProfileService { int label); /* API used to fetch the current now playing list */ native static void getNowPlayingListNative(byte[] address, byte start, byte items); native static void getNowPlayingListNative(byte[] address, byte start, byte end); /* API used to fetch the current folder's listing */ native static void getFolderListNative(byte[] address, byte start, byte items); native static void getFolderListNative(byte[] address, byte start, byte end); /* API used to fetch the listing of players */ native static void getPlayerListNative(byte[] address, byte start, byte items); native static void getPlayerListNative(byte[] address, byte start, byte end); /* API used to change the folder */ native static void changeFolderPathNative(byte[] address, byte direction, byte[] uid); native static void playItemNative( Loading android/app/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java +159 −91 Original line number Diff line number Diff line Loading @@ -69,8 +69,9 @@ class AvrcpControllerStateMachine extends StateMachine { static final int MESSAGE_PROCESS_PLAY_STATUS_CHANGED = 107; static final int MESSAGE_PROCESS_VOLUME_CHANGED_NOTIFICATION = 108; static final int MESSAGE_PROCESS_GET_FOLDER_ITEMS = 109; static final int MESSAGE_PROCESS_GET_PLAYER_ITEMS = 110; static final int MESSAGE_PROCESS_FOLDER_PATH = 111; static final int MESSAGE_PROCESS_GET_FOLDER_ITEMS_OUT_OF_RANGE = 110; static final int MESSAGE_PROCESS_GET_PLAYER_ITEMS = 111; static final int MESSAGE_PROCESS_FOLDER_PATH = 112; static final int MESSAGE_PROCESS_SET_BROWSED_PLAYER = 113; // commands from A2DP sink Loading @@ -88,6 +89,8 @@ class AvrcpControllerStateMachine extends StateMachine { static final int MESSAGE_INTERNAL_CMD_TIMEOUT = 403; static final int CMD_TIMEOUT_MILLIS = 5000; // 5s // Fetch only 5 items at a time. static final int GET_FOLDER_ITEMS_PAGINATION_SIZE = 5; /* * Base value for absolute volume from JNI Loading @@ -102,8 +105,8 @@ class AvrcpControllerStateMachine extends StateMachine { private static final String TAG = "AvrcpControllerSM"; private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG); private static final boolean VDBG = Log.isLoggable(TAG, Log.VERBOSE); private static final boolean DBG = true; private static final boolean VDBG = true; private final Context mContext; private final AudioManager mAudioManager; Loading @@ -112,9 +115,8 @@ class AvrcpControllerStateMachine extends StateMachine { private final State mConnected; private final SetBrowsedPlayer mSetBrowsedPlayer; private final ChangeFolderPath mChangeFolderPath; private final GetFolderList mGetFolderListing; private final GetFolderList mGetFolderList; private final GetPlayerListing mGetPlayerListing; private final GetNowPlayingList mGetNowPlayingList; private final MoveToRoot mMoveToRoot; private final Object mLock = new Object(); Loading Loading @@ -155,9 +157,8 @@ class AvrcpControllerStateMachine extends StateMachine { // Used to change folder path and fetch the new folder listing. mSetBrowsedPlayer = new SetBrowsedPlayer(); mChangeFolderPath = new ChangeFolderPath(); mGetFolderListing = new GetFolderList(); mGetFolderList = new GetFolderList(); mGetPlayerListing = new GetPlayerListing(); mGetNowPlayingList = new GetNowPlayingList(); mMoveToRoot = new MoveToRoot(); addState(mDisconnected); Loading @@ -169,9 +170,8 @@ class AvrcpControllerStateMachine extends StateMachine { // deferred so that once we transition to the mConnected we can process them hence. addState(mSetBrowsedPlayer, mConnected); addState(mChangeFolderPath, mConnected); addState(mGetFolderListing, mConnected); addState(mGetFolderList, mConnected); addState(mGetPlayerListing, mConnected); addState(mGetNowPlayingList, mConnected); addState(mMoveToRoot, mConnected); setInitialState(mDisconnected); Loading @@ -185,6 +185,7 @@ class AvrcpControllerStateMachine extends StateMachine { switch (msg.what) { case MESSAGE_PROCESS_CONNECTION_CHANGE: if (msg.arg1 == BluetoothProfile.STATE_CONNECTED) { mBrowseTree.init(); transitionTo(mConnected); BluetoothDevice rtDevice = (BluetoothDevice) msg.obj; synchronized(mLock) { Loading Loading @@ -212,7 +213,6 @@ class AvrcpControllerStateMachine extends StateMachine { } class Connected extends State { @Override public boolean processMessage(Message msg) { Log.d(TAG, " HandleMessage: " + dumpMessageString(msg.what)); Loading Loading @@ -252,23 +252,19 @@ class AvrcpControllerStateMachine extends StateMachine { break; case MESSAGE_GET_NOW_PLAYING_LIST: AvrcpControllerService.getNowPlayingListNative( mRemoteDevice.getBluetoothAddress(), (byte) msg.arg1, (byte) msg.arg2); mGetNowPlayingList.setFolder((String) msg.obj); transitionTo(mGetNowPlayingList); mGetFolderList.setFolder((String) msg.obj); mGetFolderList.setBounds((int) msg.arg1, (int) msg.arg2); mGetFolderList.setScope(AvrcpControllerService.BROWSE_SCOPE_NOW_PLAYING); transitionTo(mGetFolderList); break; case MESSAGE_GET_FOLDER_LIST: AvrcpControllerService.getFolderListNative( mRemoteDevice.getBluetoothAddress(), (byte) msg.arg1, (byte) msg.arg2); // Whenever we transition we set the information for folder we need to // return result. mGetFolderListing.setFolder((String) msg.obj); transitionTo(mGetFolderListing); sendMessageDelayed(MESSAGE_INTERNAL_CMD_TIMEOUT, CMD_TIMEOUT_MILLIS); mGetFolderList.setBounds(msg.arg1, msg.arg2); mGetFolderList.setFolder((String) msg.obj); mGetFolderList.setScope(AvrcpControllerService.BROWSE_SCOPE_VFS); transitionTo(mGetFolderList); break; case MESSAGE_GET_PLAYER_LIST: Loading Loading @@ -322,6 +318,7 @@ class AvrcpControllerStateMachine extends StateMachine { mIsConnected = false; mRemoteDevice = null; } mBrowseTree.clear(); transitionTo(mDisconnected); BluetoothDevice rtDevice = (BluetoothDevice) msg.obj; Intent intent = new Intent( Loading Loading @@ -482,7 +479,7 @@ class AvrcpControllerStateMachine extends StateMachine { Log.d(STATE_TAG, "New browse depth " + mBrowseDepth); if (msg.arg1 > 0) { sendMessage(MESSAGE_GET_FOLDER_LIST, 0, 0xff, mID); sendMessage(MESSAGE_GET_FOLDER_LIST, 0, msg.arg1 -1, mID); } else { // Return an empty response to the upper layer. broadcastFolderList(mID, mEmptyMediaItemList); Loading Loading @@ -515,18 +512,91 @@ class AvrcpControllerStateMachine extends StateMachine { private String STATE_TAG = "AVRCPSM.GetFolderList"; String mID = ""; int mStartInd; int mEndInd; int mCurrInd; int mScope; private ArrayList<MediaItem> mFolderList = new ArrayList<>(); @Override public void enter() { mCurrInd = 0; mFolderList.clear(); callNativeFunctionForScope( mStartInd, Math.min(mEndInd, mStartInd + GET_FOLDER_ITEMS_PAGINATION_SIZE - 1)); } public void setScope(int scope) { mScope = scope; } public void setFolder(String id) { Log.d(STATE_TAG, "Setting folder to " + id); mID = id; } public void setBounds(int startInd, int endInd) { if (DBG) { Log.d(STATE_TAG, "startInd " + startInd + " endInd " + endInd); } mStartInd = startInd; mEndInd = endInd; } @Override public boolean processMessage(Message msg) { Log.d(STATE_TAG, "processMessage " + msg); switch (msg.what) { case MESSAGE_PROCESS_GET_FOLDER_ITEMS: ArrayList<MediaItem> folderList = (ArrayList<MediaItem>) msg.obj; mFolderList.addAll(folderList); if (DBG) { Log.d(STATE_TAG, "Start " + mStartInd + " End " + mEndInd + " Curr " + mCurrInd + " received " + folderList.size()); } mCurrInd += folderList.size(); // Always update the node so that the user does not wait forever // for the list to populate. sendFolderBroadcastAndUpdateNode(); if (mCurrInd > mEndInd) { transitionTo(mConnected); } else { // Fetch the next set of items. callNativeFunctionForScope( (byte) mCurrInd, (byte) Math.min( mEndInd, mCurrInd + GET_FOLDER_ITEMS_PAGINATION_SIZE - 1)); // Reset the timeout message since we are doing a new fetch now. removeMessages(MESSAGE_INTERNAL_CMD_TIMEOUT); sendMessageDelayed(MESSAGE_INTERNAL_CMD_TIMEOUT, CMD_TIMEOUT_MILLIS); } break; case MESSAGE_INTERNAL_CMD_TIMEOUT: // We have timed out to execute the request, we should simply send // whatever listing we have gotten until now. sendFolderBroadcastAndUpdateNode(); transitionTo(mConnected); break; case MESSAGE_PROCESS_GET_FOLDER_ITEMS_OUT_OF_RANGE: // If we have gotten an error for OUT OF RANGE we have // already sent all the items to the client hence simply // transition to Connected state here. transitionTo(mConnected); break; default: Log.d(STATE_TAG, "deferring message " + msg + " to connected!"); deferMessage(msg); } return true; } private void sendFolderBroadcastAndUpdateNode() { BrowseTree.BrowseNode bn = mBrowseTree.findBrowseNodeByID(mID); if (bn.isPlayer()) { // Add the now playing folder. Loading @@ -539,26 +609,31 @@ class AvrcpControllerStateMachine extends StateMachine { AvrcpControllerService.MEDIA_ITEM_UID_KEY, BrowseTree.NOW_PLAYING_PREFIX + ":" + bn.getID()); mdb.setExtras(mdBundle); folderList.add(new MediaItem(mdb.build(), MediaItem.FLAG_BROWSABLE)); mFolderList.add(new MediaItem(mdb.build(), MediaItem.FLAG_BROWSABLE)); } mBrowseTree.refreshChildren(bn, mFolderList); broadcastFolderList(mID, mFolderList); mBrowseTree.refreshChildren(bn, folderList); broadcastFolderList(mID, folderList); // For now playing we need to set the current browsed folder here. // For normal folders it is set after ChangeFolderPath. if (mScope == AvrcpControllerService.BROWSE_SCOPE_NOW_PLAYING) { mBrowseTree.setCurrentBrowsedFolder(mID); } } transitionTo(mConnected); private void callNativeFunctionForScope(int start, int end) { switch (mScope) { case AvrcpControllerService.BROWSE_SCOPE_NOW_PLAYING: AvrcpControllerService.getNowPlayingListNative( mRemoteDevice.getBluetoothAddress(), (byte) start, (byte) end); break; case MESSAGE_INTERNAL_CMD_TIMEOUT: // We have timed out to execute the request. broadcastFolderList(mID, mEmptyMediaItemList); transitionTo(mConnected); case AvrcpControllerService.BROWSE_SCOPE_VFS: AvrcpControllerService.getFolderListNative( mRemoteDevice.getBluetoothAddress(), (byte) start, (byte) end); break; default: Log.d(STATE_TAG, "deferring message " + msg + " to connected!"); deferMessage(msg); Log.e(STATE_TAG, "Scope " + mScope + " cannot be handled here."); } return true; } } Loading Loading @@ -601,41 +676,6 @@ class AvrcpControllerStateMachine extends StateMachine { } } class GetNowPlayingList extends CmdState { private String STATE_TAG = "AVRCPSM.NowPlayingList"; private String mID = ""; public void setFolder(String id) { mID = id; } @Override public boolean processMessage(Message msg) { Log.d(STATE_TAG, "processMessage " + msg); switch (msg.what) { case MESSAGE_PROCESS_GET_FOLDER_ITEMS: ArrayList<MediaItem> folderList = (ArrayList<MediaItem>) msg.obj; broadcastFolderList(mID, folderList); BrowseTree.BrowseNode bn = mBrowseTree.findBrowseNodeByID(mID); mBrowseTree.refreshChildren(bn, folderList); mBrowseTree.setCurrentBrowsedFolder(mID); transitionTo(mConnected); break; case MESSAGE_INTERNAL_CMD_TIMEOUT: broadcastFolderList(mID, mEmptyMediaItemList); transitionTo(mConnected); break; default: Log.d(STATE_TAG, "deferring message " + msg + " to connected!"); deferMessage(msg); } return true; } } class MoveToRoot extends CmdState { private String STATE_TAG = "AVRCPSM.MoveToRoot"; private String mID = ""; Loading Loading @@ -795,7 +835,14 @@ class AvrcpControllerStateMachine extends StateMachine { } } // Browsing related functions. // Entry point to the state machine where the services should call to fetch children // for a specific node. It checks if the currently browsed node is the same as the one being // asked for, in that case it returns the currently cached children. This saves bandwidth and // also if we are already fetching elements for a current folder (since we need to batch // fetches) then we should not submit another request but simply return what we have fetched // until now. // // It handles fetches to all VFS, Now Playing and Media Player lists. void getChildren(String parentMediaId, int start, int items) { BrowseTree.BrowseNode bn = mBrowseTree.findBrowseNodeByID(parentMediaId); if (bn == null) { Loading @@ -804,17 +851,36 @@ class AvrcpControllerStateMachine extends StateMachine { return; } if (bn.equals(mBrowseTree.getCurrentBrowsedFolder()) && bn.isCached()) { if (DBG) { Log.d(TAG, "Same cached folder -- returning existing children."); } BrowseTree.BrowseNode n = mBrowseTree.findBrowseNodeByID(parentMediaId); ArrayList<MediaItem> childrenList = new ArrayList<MediaItem>(); for (BrowseTree.BrowseNode cn : n.getChildren()) { childrenList.add(cn.getMediaItem()); } broadcastFolderList(parentMediaId, childrenList); return; } Message msg = null; int btDirection = mBrowseTree.getDirection(parentMediaId); BrowseTree.BrowseNode currFol = mBrowseTree.getCurrentBrowsedFolder(); if (DBG) { Log.d(TAG, "Browse direction parent " + mBrowseTree.getCurrentBrowsedFolder() + " req " + parentMediaId + " direction " + btDirection); } if (BrowseTree.ROOT.equals(parentMediaId)) { // Root contains the list of players. msg = obtainMessage(AvrcpControllerStateMachine.MESSAGE_GET_PLAYER_LIST, start, items); } else if (bn.isPlayer()) { } else if (bn.isPlayer() && btDirection != BrowseTree.DIRECTION_SAME) { // Set browsed (and addressed player) as the new player. // This should fetch the list of folders. msg = obtainMessage(AvrcpControllerStateMachine.MESSAGE_SET_BROWSED_PLAYER, bn.getPlayerID(), 0, bn.getID()); } else if (bn.isNowPlaying()) { // Get all songs in the list. // Issue a request to fetch the items. msg = obtainMessage( AvrcpControllerStateMachine.MESSAGE_GET_NOW_PLAYING_LIST, start, items, parentMediaId); Loading @@ -822,30 +888,31 @@ class AvrcpControllerStateMachine extends StateMachine { // Only change folder if desired. If an app refreshes a folder // (because it resumed etc) and current folder does not change // then we can simply fetch list. BrowseTree.BrowseNode currFol = mBrowseTree.getCurrentBrowsedFolder(); // We exempt two conditions from change folder: // a) If the new folder is the same as current folder (refresh of UI) // b) If the new folder is ROOT and current folder is NOW_PLAYING. In this // condition we 'fake' child-parent hierarchy but it does not exist in // b) If the new folder is ROOT and current folder is NOW_PLAYING (or vice-versa) // In this condition we 'fake' child-parent hierarchy but it does not exist in // bluetooth world. boolean isNowPlayingToRoot = currFol.isNowPlaying() && bn.getID().equals(BrowseTree.ROOT); if (!bn.equals(currFol) && !isNowPlayingToRoot) { if (!isNowPlayingToRoot) { // Find the direction of traversal. int direction; int btDirection = mBrowseTree.getDirection(parentMediaId); int direction = -1; Log.d(TAG, "Browse direction " + currFol + " " + bn + " = " + btDirection); if (btDirection == BrowseTree.DIRECTION_DOWN) { direction = AvrcpControllerService.FOLDER_NAVIGATION_DIRECTION_DOWN; } else if (btDirection == BrowseTree.DIRECTION_UP) { direction = AvrcpControllerService.FOLDER_NAVIGATION_DIRECTION_UP; } else { if (btDirection == BrowseTree.DIRECTION_UNKNOWN) { Log.w(TAG, "parent " + bn + " is not a direct " + "successor or predeccessor of current folder " + currFol); broadcastFolderList(parentMediaId, mEmptyMediaItemList); return; } if (btDirection == BrowseTree.DIRECTION_DOWN) { direction = AvrcpControllerService.FOLDER_NAVIGATION_DIRECTION_DOWN; } else if (btDirection == BrowseTree.DIRECTION_UP) { direction = AvrcpControllerService.FOLDER_NAVIGATION_DIRECTION_UP; } Bundle b = new Bundle(); b.putString(AvrcpControllerService.EXTRA_FOLDER_ID, bn.getID()); b.putString(AvrcpControllerService.EXTRA_FOLDER_BT_ID, bn.getFolderUID()); Loading @@ -858,6 +925,7 @@ class AvrcpControllerStateMachine extends StateMachine { start, items, bn.getFolderUID()); } } if (msg != null) { sendMessage(msg); } Loading android/app/src/com/android/bluetooth/avrcpcontroller/BrowseTree.java +29 −0 File changed.Preview size limit exceeded, changes collapsed. Show changes Loading
android/app/jni/com_android_bluetooth_avrcp_controller.cpp +3 −3 Original line number Diff line number Diff line Loading @@ -371,7 +371,7 @@ static void btavrcp_play_status_changed_callback(bt_bdaddr_t *bd_addr, } static void btavrcp_get_folder_items_callback(bt_bdaddr_t *bd_addr, const btrc_folder_items_t *folder_items, uint8_t count) { btrc_status_t status, const btrc_folder_items_t *folder_items, uint8_t count) { /* Folder items are list of items that can be either BTRC_ITEM_PLAYER * BTRC_ITEM_MEDIA, BTRC_ITEM_FOLDER. Here we translate them to their java * counterparts by calling the java constructor for each of the items. Loading Loading @@ -516,7 +516,7 @@ static void btavrcp_get_folder_items_callback(bt_bdaddr_t *bd_addr, playerItemArray); } else { sCallbackEnv->CallVoidMethod(sCallbacksObj, method_handleGetFolderItemsRsp, folderItemArray); status, folderItemArray); } if (isPlayerListing) { sCallbackEnv->DeleteLocalRef(playerItemArray); Loading Loading @@ -600,7 +600,7 @@ static void classInitNative(JNIEnv* env, jclass clazz) { env->GetMethodID(clazz, "onPlayStatusChanged", "([BB)V"); method_handleGetFolderItemsRsp = env->GetMethodID(clazz, "handleGetFolderItemsRsp", "([Landroid/media/browse/MediaBrowser$MediaItem;)V"); env->GetMethodID(clazz, "handleGetFolderItemsRsp", "(I[Landroid/media/browse/MediaBrowser$MediaItem;)V"); method_handleGetPlayerItemsRsp = env->GetMethodID(clazz, "handleGetPlayerItemsRsp", "([Lcom/android/bluetooth/avrcpcontroller/AvrcpPlayer;)V"); Loading
android/app/src/com/android/bluetooth/a2dpsink/mbs/A2dpMediaBrowserService.java +7 −3 Original line number Diff line number Diff line Loading @@ -477,14 +477,18 @@ public class A2dpMediaBrowserService extends MediaBrowserService { String id = intent.getStringExtra(AvrcpControllerService.EXTRA_FOLDER_ID); Log.d(TAG, "Parent: " + id + " Folder list: " + folderList); synchronized (this) { // If we have a result object then we should send the result back // to client since it is blocking otherwise we may have gotten more items // from remote device, hence let client know to fetch again. Result<List<MediaItem>> results = mParentIdToRequestMap.remove(id); if (results == null) { Log.w(TAG, "Request no longer exists, hence ignoring reply!"); return; } Log.w(TAG, "Request no longer exists, notifying that children changed."); notifyChildrenChanged(id); } else { results.sendResult(folderList); } } } private void msgDeviceBrowseDisconnect(BluetoothDevice device) { Log.d(TAG, "msgDeviceBrowseDisconnect device " + device); Loading
android/app/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerService.java +24 −5 Original line number Diff line number Diff line Loading @@ -85,6 +85,13 @@ public class AvrcpControllerService extends ProfileService { private static final int JNI_FOLDER_TYPE_PLAYLISTS = 0x05; private static final int JNI_FOLDER_TYPE_YEARS = 0x06; /* * AVRCP Error types as defined in spec. Also they should be in sync with btrc_status_t. * NOTE: Not all may be defined. */ private static final int JNI_AVRC_STS_NO_ERROR = 0x04; private static final int JNI_AVRC_INV_RANGE = 0x0b; /** * Intent used to broadcast the change in browse connection state of the AVRCP Controller * profile. Loading Loading @@ -911,10 +918,22 @@ public class AvrcpControllerService extends ProfileService { } // Browsing related JNI callbacks. void handleGetFolderItemsRsp(MediaItem[] items) { void handleGetFolderItemsRsp(int status, MediaItem[] items) { if (DBG) { Log.d(TAG, "handleGetFolderItemsRsp called with " + items.length + " items."); Log.d(TAG, "handleGetFolderItemsRsp called with status " + status + " items " + items.length + " items."); } if (status == JNI_AVRC_INV_RANGE) { Log.w(TAG, "Sending out of range message."); // Send a special message since this could be used by state machine // to take as a signal that fetch is finished. Message msg = mAvrcpCtSm.obtainMessage(AvrcpControllerStateMachine. MESSAGE_PROCESS_GET_FOLDER_ITEMS_OUT_OF_RANGE); mAvrcpCtSm.sendMessage(msg); return; } for (MediaItem item : items) { if (DBG) { Log.d(TAG, "media item: " + item + " uid: " + item.getDescription().getMediaId()); Loading Loading @@ -1081,11 +1100,11 @@ public class AvrcpControllerService extends ProfileService { int label); /* API used to fetch the current now playing list */ native static void getNowPlayingListNative(byte[] address, byte start, byte items); native static void getNowPlayingListNative(byte[] address, byte start, byte end); /* API used to fetch the current folder's listing */ native static void getFolderListNative(byte[] address, byte start, byte items); native static void getFolderListNative(byte[] address, byte start, byte end); /* API used to fetch the listing of players */ native static void getPlayerListNative(byte[] address, byte start, byte items); native static void getPlayerListNative(byte[] address, byte start, byte end); /* API used to change the folder */ native static void changeFolderPathNative(byte[] address, byte direction, byte[] uid); native static void playItemNative( Loading
android/app/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java +159 −91 Original line number Diff line number Diff line Loading @@ -69,8 +69,9 @@ class AvrcpControllerStateMachine extends StateMachine { static final int MESSAGE_PROCESS_PLAY_STATUS_CHANGED = 107; static final int MESSAGE_PROCESS_VOLUME_CHANGED_NOTIFICATION = 108; static final int MESSAGE_PROCESS_GET_FOLDER_ITEMS = 109; static final int MESSAGE_PROCESS_GET_PLAYER_ITEMS = 110; static final int MESSAGE_PROCESS_FOLDER_PATH = 111; static final int MESSAGE_PROCESS_GET_FOLDER_ITEMS_OUT_OF_RANGE = 110; static final int MESSAGE_PROCESS_GET_PLAYER_ITEMS = 111; static final int MESSAGE_PROCESS_FOLDER_PATH = 112; static final int MESSAGE_PROCESS_SET_BROWSED_PLAYER = 113; // commands from A2DP sink Loading @@ -88,6 +89,8 @@ class AvrcpControllerStateMachine extends StateMachine { static final int MESSAGE_INTERNAL_CMD_TIMEOUT = 403; static final int CMD_TIMEOUT_MILLIS = 5000; // 5s // Fetch only 5 items at a time. static final int GET_FOLDER_ITEMS_PAGINATION_SIZE = 5; /* * Base value for absolute volume from JNI Loading @@ -102,8 +105,8 @@ class AvrcpControllerStateMachine extends StateMachine { private static final String TAG = "AvrcpControllerSM"; private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG); private static final boolean VDBG = Log.isLoggable(TAG, Log.VERBOSE); private static final boolean DBG = true; private static final boolean VDBG = true; private final Context mContext; private final AudioManager mAudioManager; Loading @@ -112,9 +115,8 @@ class AvrcpControllerStateMachine extends StateMachine { private final State mConnected; private final SetBrowsedPlayer mSetBrowsedPlayer; private final ChangeFolderPath mChangeFolderPath; private final GetFolderList mGetFolderListing; private final GetFolderList mGetFolderList; private final GetPlayerListing mGetPlayerListing; private final GetNowPlayingList mGetNowPlayingList; private final MoveToRoot mMoveToRoot; private final Object mLock = new Object(); Loading Loading @@ -155,9 +157,8 @@ class AvrcpControllerStateMachine extends StateMachine { // Used to change folder path and fetch the new folder listing. mSetBrowsedPlayer = new SetBrowsedPlayer(); mChangeFolderPath = new ChangeFolderPath(); mGetFolderListing = new GetFolderList(); mGetFolderList = new GetFolderList(); mGetPlayerListing = new GetPlayerListing(); mGetNowPlayingList = new GetNowPlayingList(); mMoveToRoot = new MoveToRoot(); addState(mDisconnected); Loading @@ -169,9 +170,8 @@ class AvrcpControllerStateMachine extends StateMachine { // deferred so that once we transition to the mConnected we can process them hence. addState(mSetBrowsedPlayer, mConnected); addState(mChangeFolderPath, mConnected); addState(mGetFolderListing, mConnected); addState(mGetFolderList, mConnected); addState(mGetPlayerListing, mConnected); addState(mGetNowPlayingList, mConnected); addState(mMoveToRoot, mConnected); setInitialState(mDisconnected); Loading @@ -185,6 +185,7 @@ class AvrcpControllerStateMachine extends StateMachine { switch (msg.what) { case MESSAGE_PROCESS_CONNECTION_CHANGE: if (msg.arg1 == BluetoothProfile.STATE_CONNECTED) { mBrowseTree.init(); transitionTo(mConnected); BluetoothDevice rtDevice = (BluetoothDevice) msg.obj; synchronized(mLock) { Loading Loading @@ -212,7 +213,6 @@ class AvrcpControllerStateMachine extends StateMachine { } class Connected extends State { @Override public boolean processMessage(Message msg) { Log.d(TAG, " HandleMessage: " + dumpMessageString(msg.what)); Loading Loading @@ -252,23 +252,19 @@ class AvrcpControllerStateMachine extends StateMachine { break; case MESSAGE_GET_NOW_PLAYING_LIST: AvrcpControllerService.getNowPlayingListNative( mRemoteDevice.getBluetoothAddress(), (byte) msg.arg1, (byte) msg.arg2); mGetNowPlayingList.setFolder((String) msg.obj); transitionTo(mGetNowPlayingList); mGetFolderList.setFolder((String) msg.obj); mGetFolderList.setBounds((int) msg.arg1, (int) msg.arg2); mGetFolderList.setScope(AvrcpControllerService.BROWSE_SCOPE_NOW_PLAYING); transitionTo(mGetFolderList); break; case MESSAGE_GET_FOLDER_LIST: AvrcpControllerService.getFolderListNative( mRemoteDevice.getBluetoothAddress(), (byte) msg.arg1, (byte) msg.arg2); // Whenever we transition we set the information for folder we need to // return result. mGetFolderListing.setFolder((String) msg.obj); transitionTo(mGetFolderListing); sendMessageDelayed(MESSAGE_INTERNAL_CMD_TIMEOUT, CMD_TIMEOUT_MILLIS); mGetFolderList.setBounds(msg.arg1, msg.arg2); mGetFolderList.setFolder((String) msg.obj); mGetFolderList.setScope(AvrcpControllerService.BROWSE_SCOPE_VFS); transitionTo(mGetFolderList); break; case MESSAGE_GET_PLAYER_LIST: Loading Loading @@ -322,6 +318,7 @@ class AvrcpControllerStateMachine extends StateMachine { mIsConnected = false; mRemoteDevice = null; } mBrowseTree.clear(); transitionTo(mDisconnected); BluetoothDevice rtDevice = (BluetoothDevice) msg.obj; Intent intent = new Intent( Loading Loading @@ -482,7 +479,7 @@ class AvrcpControllerStateMachine extends StateMachine { Log.d(STATE_TAG, "New browse depth " + mBrowseDepth); if (msg.arg1 > 0) { sendMessage(MESSAGE_GET_FOLDER_LIST, 0, 0xff, mID); sendMessage(MESSAGE_GET_FOLDER_LIST, 0, msg.arg1 -1, mID); } else { // Return an empty response to the upper layer. broadcastFolderList(mID, mEmptyMediaItemList); Loading Loading @@ -515,18 +512,91 @@ class AvrcpControllerStateMachine extends StateMachine { private String STATE_TAG = "AVRCPSM.GetFolderList"; String mID = ""; int mStartInd; int mEndInd; int mCurrInd; int mScope; private ArrayList<MediaItem> mFolderList = new ArrayList<>(); @Override public void enter() { mCurrInd = 0; mFolderList.clear(); callNativeFunctionForScope( mStartInd, Math.min(mEndInd, mStartInd + GET_FOLDER_ITEMS_PAGINATION_SIZE - 1)); } public void setScope(int scope) { mScope = scope; } public void setFolder(String id) { Log.d(STATE_TAG, "Setting folder to " + id); mID = id; } public void setBounds(int startInd, int endInd) { if (DBG) { Log.d(STATE_TAG, "startInd " + startInd + " endInd " + endInd); } mStartInd = startInd; mEndInd = endInd; } @Override public boolean processMessage(Message msg) { Log.d(STATE_TAG, "processMessage " + msg); switch (msg.what) { case MESSAGE_PROCESS_GET_FOLDER_ITEMS: ArrayList<MediaItem> folderList = (ArrayList<MediaItem>) msg.obj; mFolderList.addAll(folderList); if (DBG) { Log.d(STATE_TAG, "Start " + mStartInd + " End " + mEndInd + " Curr " + mCurrInd + " received " + folderList.size()); } mCurrInd += folderList.size(); // Always update the node so that the user does not wait forever // for the list to populate. sendFolderBroadcastAndUpdateNode(); if (mCurrInd > mEndInd) { transitionTo(mConnected); } else { // Fetch the next set of items. callNativeFunctionForScope( (byte) mCurrInd, (byte) Math.min( mEndInd, mCurrInd + GET_FOLDER_ITEMS_PAGINATION_SIZE - 1)); // Reset the timeout message since we are doing a new fetch now. removeMessages(MESSAGE_INTERNAL_CMD_TIMEOUT); sendMessageDelayed(MESSAGE_INTERNAL_CMD_TIMEOUT, CMD_TIMEOUT_MILLIS); } break; case MESSAGE_INTERNAL_CMD_TIMEOUT: // We have timed out to execute the request, we should simply send // whatever listing we have gotten until now. sendFolderBroadcastAndUpdateNode(); transitionTo(mConnected); break; case MESSAGE_PROCESS_GET_FOLDER_ITEMS_OUT_OF_RANGE: // If we have gotten an error for OUT OF RANGE we have // already sent all the items to the client hence simply // transition to Connected state here. transitionTo(mConnected); break; default: Log.d(STATE_TAG, "deferring message " + msg + " to connected!"); deferMessage(msg); } return true; } private void sendFolderBroadcastAndUpdateNode() { BrowseTree.BrowseNode bn = mBrowseTree.findBrowseNodeByID(mID); if (bn.isPlayer()) { // Add the now playing folder. Loading @@ -539,26 +609,31 @@ class AvrcpControllerStateMachine extends StateMachine { AvrcpControllerService.MEDIA_ITEM_UID_KEY, BrowseTree.NOW_PLAYING_PREFIX + ":" + bn.getID()); mdb.setExtras(mdBundle); folderList.add(new MediaItem(mdb.build(), MediaItem.FLAG_BROWSABLE)); mFolderList.add(new MediaItem(mdb.build(), MediaItem.FLAG_BROWSABLE)); } mBrowseTree.refreshChildren(bn, mFolderList); broadcastFolderList(mID, mFolderList); mBrowseTree.refreshChildren(bn, folderList); broadcastFolderList(mID, folderList); // For now playing we need to set the current browsed folder here. // For normal folders it is set after ChangeFolderPath. if (mScope == AvrcpControllerService.BROWSE_SCOPE_NOW_PLAYING) { mBrowseTree.setCurrentBrowsedFolder(mID); } } transitionTo(mConnected); private void callNativeFunctionForScope(int start, int end) { switch (mScope) { case AvrcpControllerService.BROWSE_SCOPE_NOW_PLAYING: AvrcpControllerService.getNowPlayingListNative( mRemoteDevice.getBluetoothAddress(), (byte) start, (byte) end); break; case MESSAGE_INTERNAL_CMD_TIMEOUT: // We have timed out to execute the request. broadcastFolderList(mID, mEmptyMediaItemList); transitionTo(mConnected); case AvrcpControllerService.BROWSE_SCOPE_VFS: AvrcpControllerService.getFolderListNative( mRemoteDevice.getBluetoothAddress(), (byte) start, (byte) end); break; default: Log.d(STATE_TAG, "deferring message " + msg + " to connected!"); deferMessage(msg); Log.e(STATE_TAG, "Scope " + mScope + " cannot be handled here."); } return true; } } Loading Loading @@ -601,41 +676,6 @@ class AvrcpControllerStateMachine extends StateMachine { } } class GetNowPlayingList extends CmdState { private String STATE_TAG = "AVRCPSM.NowPlayingList"; private String mID = ""; public void setFolder(String id) { mID = id; } @Override public boolean processMessage(Message msg) { Log.d(STATE_TAG, "processMessage " + msg); switch (msg.what) { case MESSAGE_PROCESS_GET_FOLDER_ITEMS: ArrayList<MediaItem> folderList = (ArrayList<MediaItem>) msg.obj; broadcastFolderList(mID, folderList); BrowseTree.BrowseNode bn = mBrowseTree.findBrowseNodeByID(mID); mBrowseTree.refreshChildren(bn, folderList); mBrowseTree.setCurrentBrowsedFolder(mID); transitionTo(mConnected); break; case MESSAGE_INTERNAL_CMD_TIMEOUT: broadcastFolderList(mID, mEmptyMediaItemList); transitionTo(mConnected); break; default: Log.d(STATE_TAG, "deferring message " + msg + " to connected!"); deferMessage(msg); } return true; } } class MoveToRoot extends CmdState { private String STATE_TAG = "AVRCPSM.MoveToRoot"; private String mID = ""; Loading Loading @@ -795,7 +835,14 @@ class AvrcpControllerStateMachine extends StateMachine { } } // Browsing related functions. // Entry point to the state machine where the services should call to fetch children // for a specific node. It checks if the currently browsed node is the same as the one being // asked for, in that case it returns the currently cached children. This saves bandwidth and // also if we are already fetching elements for a current folder (since we need to batch // fetches) then we should not submit another request but simply return what we have fetched // until now. // // It handles fetches to all VFS, Now Playing and Media Player lists. void getChildren(String parentMediaId, int start, int items) { BrowseTree.BrowseNode bn = mBrowseTree.findBrowseNodeByID(parentMediaId); if (bn == null) { Loading @@ -804,17 +851,36 @@ class AvrcpControllerStateMachine extends StateMachine { return; } if (bn.equals(mBrowseTree.getCurrentBrowsedFolder()) && bn.isCached()) { if (DBG) { Log.d(TAG, "Same cached folder -- returning existing children."); } BrowseTree.BrowseNode n = mBrowseTree.findBrowseNodeByID(parentMediaId); ArrayList<MediaItem> childrenList = new ArrayList<MediaItem>(); for (BrowseTree.BrowseNode cn : n.getChildren()) { childrenList.add(cn.getMediaItem()); } broadcastFolderList(parentMediaId, childrenList); return; } Message msg = null; int btDirection = mBrowseTree.getDirection(parentMediaId); BrowseTree.BrowseNode currFol = mBrowseTree.getCurrentBrowsedFolder(); if (DBG) { Log.d(TAG, "Browse direction parent " + mBrowseTree.getCurrentBrowsedFolder() + " req " + parentMediaId + " direction " + btDirection); } if (BrowseTree.ROOT.equals(parentMediaId)) { // Root contains the list of players. msg = obtainMessage(AvrcpControllerStateMachine.MESSAGE_GET_PLAYER_LIST, start, items); } else if (bn.isPlayer()) { } else if (bn.isPlayer() && btDirection != BrowseTree.DIRECTION_SAME) { // Set browsed (and addressed player) as the new player. // This should fetch the list of folders. msg = obtainMessage(AvrcpControllerStateMachine.MESSAGE_SET_BROWSED_PLAYER, bn.getPlayerID(), 0, bn.getID()); } else if (bn.isNowPlaying()) { // Get all songs in the list. // Issue a request to fetch the items. msg = obtainMessage( AvrcpControllerStateMachine.MESSAGE_GET_NOW_PLAYING_LIST, start, items, parentMediaId); Loading @@ -822,30 +888,31 @@ class AvrcpControllerStateMachine extends StateMachine { // Only change folder if desired. If an app refreshes a folder // (because it resumed etc) and current folder does not change // then we can simply fetch list. BrowseTree.BrowseNode currFol = mBrowseTree.getCurrentBrowsedFolder(); // We exempt two conditions from change folder: // a) If the new folder is the same as current folder (refresh of UI) // b) If the new folder is ROOT and current folder is NOW_PLAYING. In this // condition we 'fake' child-parent hierarchy but it does not exist in // b) If the new folder is ROOT and current folder is NOW_PLAYING (or vice-versa) // In this condition we 'fake' child-parent hierarchy but it does not exist in // bluetooth world. boolean isNowPlayingToRoot = currFol.isNowPlaying() && bn.getID().equals(BrowseTree.ROOT); if (!bn.equals(currFol) && !isNowPlayingToRoot) { if (!isNowPlayingToRoot) { // Find the direction of traversal. int direction; int btDirection = mBrowseTree.getDirection(parentMediaId); int direction = -1; Log.d(TAG, "Browse direction " + currFol + " " + bn + " = " + btDirection); if (btDirection == BrowseTree.DIRECTION_DOWN) { direction = AvrcpControllerService.FOLDER_NAVIGATION_DIRECTION_DOWN; } else if (btDirection == BrowseTree.DIRECTION_UP) { direction = AvrcpControllerService.FOLDER_NAVIGATION_DIRECTION_UP; } else { if (btDirection == BrowseTree.DIRECTION_UNKNOWN) { Log.w(TAG, "parent " + bn + " is not a direct " + "successor or predeccessor of current folder " + currFol); broadcastFolderList(parentMediaId, mEmptyMediaItemList); return; } if (btDirection == BrowseTree.DIRECTION_DOWN) { direction = AvrcpControllerService.FOLDER_NAVIGATION_DIRECTION_DOWN; } else if (btDirection == BrowseTree.DIRECTION_UP) { direction = AvrcpControllerService.FOLDER_NAVIGATION_DIRECTION_UP; } Bundle b = new Bundle(); b.putString(AvrcpControllerService.EXTRA_FOLDER_ID, bn.getID()); b.putString(AvrcpControllerService.EXTRA_FOLDER_BT_ID, bn.getFolderUID()); Loading @@ -858,6 +925,7 @@ class AvrcpControllerStateMachine extends StateMachine { start, items, bn.getFolderUID()); } } if (msg != null) { sendMessage(msg); } Loading
android/app/src/com/android/bluetooth/avrcpcontroller/BrowseTree.java +29 −0 File changed.Preview size limit exceeded, changes collapsed. Show changes