1 /* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.bluetooth.avrcpcontroller; 18 19 import android.bluetooth.BluetoothAdapter; 20 import android.bluetooth.BluetoothAvrcpPlayerSettings; 21 import android.bluetooth.BluetoothDevice; 22 import android.bluetooth.BluetoothProfile; 23 import android.bluetooth.IBluetoothAvrcpController; 24 import android.content.Intent; 25 import android.media.MediaDescription; 26 import android.media.browse.MediaBrowser.MediaItem; 27 import android.media.session.PlaybackState; 28 import android.os.Bundle; 29 import android.util.Log; 30 31 import com.android.bluetooth.Utils; 32 import com.android.bluetooth.btservice.ProfileService; 33 34 import java.util.ArrayList; 35 import java.util.Arrays; 36 import java.util.List; 37 import java.util.Map; 38 import java.util.Set; 39 import java.util.UUID; 40 import java.util.concurrent.ConcurrentHashMap; 41 42 /** 43 * Provides Bluetooth AVRCP Controller profile, as a service in the Bluetooth application. 44 */ 45 public class AvrcpControllerService extends ProfileService { 46 static final String TAG = "AvrcpControllerService"; 47 static final int MAXIMUM_CONNECTED_DEVICES = 5; 48 static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG); 49 static final boolean VDBG = Log.isLoggable(TAG, Log.VERBOSE); 50 51 public static final String MEDIA_ITEM_UID_KEY = "media-item-uid-key"; 52 /* 53 * Play State Values from JNI 54 */ 55 private static final byte JNI_PLAY_STATUS_STOPPED = 0x00; 56 private static final byte JNI_PLAY_STATUS_PLAYING = 0x01; 57 private static final byte JNI_PLAY_STATUS_PAUSED = 0x02; 58 private static final byte JNI_PLAY_STATUS_FWD_SEEK = 0x03; 59 private static final byte JNI_PLAY_STATUS_REV_SEEK = 0x04; 60 private static final byte JNI_PLAY_STATUS_ERROR = -1; 61 62 /* Folder/Media Item scopes. 63 * Keep in sync with AVRCP 1.6 sec. 6.10.1 64 */ 65 public static final byte BROWSE_SCOPE_PLAYER_LIST = 0x00; 66 public static final byte BROWSE_SCOPE_VFS = 0x01; 67 public static final byte BROWSE_SCOPE_SEARCH = 0x02; 68 public static final byte BROWSE_SCOPE_NOW_PLAYING = 0x03; 69 70 /* Folder navigation directions 71 * This is borrowed from AVRCP 1.6 spec and must be kept with same values 72 */ 73 public static final byte FOLDER_NAVIGATION_DIRECTION_UP = 0x00; 74 public static final byte FOLDER_NAVIGATION_DIRECTION_DOWN = 0x01; 75 76 /* 77 * KeyCoded for Pass Through Commands 78 */ 79 public static final int PASS_THRU_CMD_ID_PLAY = 0x44; 80 public static final int PASS_THRU_CMD_ID_PAUSE = 0x46; 81 public static final int PASS_THRU_CMD_ID_VOL_UP = 0x41; 82 public static final int PASS_THRU_CMD_ID_VOL_DOWN = 0x42; 83 public static final int PASS_THRU_CMD_ID_STOP = 0x45; 84 public static final int PASS_THRU_CMD_ID_FF = 0x49; 85 public static final int PASS_THRU_CMD_ID_REWIND = 0x48; 86 public static final int PASS_THRU_CMD_ID_FORWARD = 0x4B; 87 public static final int PASS_THRU_CMD_ID_BACKWARD = 0x4C; 88 89 /* Key State Variables */ 90 public static final int KEY_STATE_PRESSED = 0; 91 public static final int KEY_STATE_RELEASED = 1; 92 93 static BrowseTree sBrowseTree; 94 private static AvrcpControllerService sService; 95 private final BluetoothAdapter mAdapter; 96 97 protected Map<BluetoothDevice, AvrcpControllerStateMachine> mDeviceStateMap = 98 new ConcurrentHashMap<>(1); 99 100 static { classInitNative()101 classInitNative(); 102 } 103 AvrcpControllerService()104 public AvrcpControllerService() { 105 super(); 106 mAdapter = BluetoothAdapter.getDefaultAdapter(); 107 } 108 109 @Override start()110 protected boolean start() { 111 initNative(); 112 sBrowseTree = new BrowseTree(null); 113 sService = this; 114 115 // Start the media browser service. 116 Intent startIntent = new Intent(this, BluetoothMediaBrowserService.class); 117 startService(startIntent); 118 return true; 119 } 120 121 @Override stop()122 protected boolean stop() { 123 Intent stopIntent = new Intent(this, BluetoothMediaBrowserService.class); 124 stopService(stopIntent); 125 for (AvrcpControllerStateMachine stateMachine : mDeviceStateMap.values()) { 126 stateMachine.quitNow(); 127 } 128 129 sService = null; 130 sBrowseTree = null; 131 return true; 132 } 133 getAvrcpControllerService()134 public static AvrcpControllerService getAvrcpControllerService() { 135 return sService; 136 } 137 newStateMachine(BluetoothDevice device)138 protected AvrcpControllerStateMachine newStateMachine(BluetoothDevice device) { 139 return new AvrcpControllerStateMachine(device, this); 140 } 141 refreshContents(BrowseTree.BrowseNode node)142 private void refreshContents(BrowseTree.BrowseNode node) { 143 if (node.mDevice == null) { 144 return; 145 } 146 AvrcpControllerStateMachine stateMachine = getStateMachine(node.mDevice); 147 if (stateMachine != null) { 148 stateMachine.requestContents(node); 149 } 150 } 151 152 /*Java API*/ 153 154 /** 155 * Get a List of MediaItems that are children of the specified media Id 156 * 157 * @param parentMediaId The player or folder to get the contents of 158 * @return List of Children if available, an empty list if there are none, 159 * or null if a search must be performed. 160 */ getContents(String parentMediaId)161 public synchronized List<MediaItem> getContents(String parentMediaId) { 162 if (DBG) Log.d(TAG, "getContents(" + parentMediaId + ")"); 163 164 BrowseTree.BrowseNode requestedNode = sBrowseTree.findBrowseNodeByID(parentMediaId); 165 if (requestedNode == null) { 166 for (AvrcpControllerStateMachine stateMachine : mDeviceStateMap.values()) { 167 requestedNode = stateMachine.findNode(parentMediaId); 168 if (requestedNode != null) { 169 Log.d(TAG, "Found a node"); 170 break; 171 } 172 } 173 } 174 175 if (requestedNode == null) { 176 if (DBG) Log.d(TAG, "Didn't find a node"); 177 return null; 178 } else { 179 if (!requestedNode.isCached()) { 180 if (DBG) Log.d(TAG, "node is not cached"); 181 refreshContents(requestedNode); 182 } 183 if (DBG) Log.d(TAG, "Returning contents"); 184 return requestedNode.getContents(); 185 } 186 } 187 188 @Override initBinder()189 protected IProfileServiceBinder initBinder() { 190 return new AvrcpControllerServiceBinder(this); 191 } 192 193 //Binder object: Must be static class or memory leak may occur 194 private static class AvrcpControllerServiceBinder extends IBluetoothAvrcpController.Stub 195 implements IProfileServiceBinder { 196 private AvrcpControllerService mService; 197 getService()198 private AvrcpControllerService getService() { 199 if (!Utils.checkCaller()) { 200 Log.w(TAG, "AVRCP call not allowed for non-active user"); 201 return null; 202 } 203 204 if (mService != null) { 205 return mService; 206 } 207 return null; 208 } 209 AvrcpControllerServiceBinder(AvrcpControllerService service)210 AvrcpControllerServiceBinder(AvrcpControllerService service) { 211 mService = service; 212 } 213 214 @Override cleanup()215 public void cleanup() { 216 mService = null; 217 } 218 219 @Override getConnectedDevices()220 public List<BluetoothDevice> getConnectedDevices() { 221 AvrcpControllerService service = getService(); 222 if (service == null) { 223 return new ArrayList<BluetoothDevice>(0); 224 } 225 return service.getConnectedDevices(); 226 } 227 228 @Override getDevicesMatchingConnectionStates(int[] states)229 public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { 230 AvrcpControllerService service = getService(); 231 if (service == null) { 232 return new ArrayList<BluetoothDevice>(0); 233 } 234 return service.getDevicesMatchingConnectionStates(states); 235 } 236 237 @Override getConnectionState(BluetoothDevice device)238 public int getConnectionState(BluetoothDevice device) { 239 AvrcpControllerService service = getService(); 240 if (service == null) { 241 return BluetoothProfile.STATE_DISCONNECTED; 242 } 243 return service.getConnectionState(device); 244 } 245 246 @Override sendGroupNavigationCmd(BluetoothDevice device, int keyCode, int keyState)247 public void sendGroupNavigationCmd(BluetoothDevice device, int keyCode, int keyState) { 248 Log.w(TAG, "sendGroupNavigationCmd not implemented"); 249 return; 250 } 251 252 @Override setPlayerApplicationSetting(BluetoothAvrcpPlayerSettings settings)253 public boolean setPlayerApplicationSetting(BluetoothAvrcpPlayerSettings settings) { 254 Log.w(TAG, "setPlayerApplicationSetting not implemented"); 255 return false; 256 } 257 258 @Override getPlayerSettings(BluetoothDevice device)259 public BluetoothAvrcpPlayerSettings getPlayerSettings(BluetoothDevice device) { 260 Log.w(TAG, "getPlayerSettings not implemented"); 261 return null; 262 } 263 } 264 265 266 /* JNI API*/ 267 // Called by JNI when a passthrough key was received. handlePassthroughRsp(int id, int keyState, byte[] address)268 private void handlePassthroughRsp(int id, int keyState, byte[] address) { 269 if (DBG) { 270 Log.d(TAG, "passthrough response received as: key: " + id 271 + " state: " + keyState + "address:" + address); 272 } 273 } 274 handleGroupNavigationRsp(int id, int keyState)275 private void handleGroupNavigationRsp(int id, int keyState) { 276 if (DBG) { 277 Log.d(TAG, "group navigation response received as: key: " + id + " state: " 278 + keyState); 279 } 280 } 281 282 // Called by JNI when a device has connected or disconnected. onConnectionStateChanged(boolean remoteControlConnected, boolean browsingConnected, byte[] address)283 private synchronized void onConnectionStateChanged(boolean remoteControlConnected, 284 boolean browsingConnected, byte[] address) { 285 BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address); 286 if (DBG) { 287 Log.d(TAG, "onConnectionStateChanged " + remoteControlConnected + " " 288 + browsingConnected + device); 289 } 290 if (device == null) { 291 Log.e(TAG, "onConnectionStateChanged Device is null"); 292 return; 293 } 294 295 StackEvent event = 296 StackEvent.connectionStateChanged(remoteControlConnected, browsingConnected); 297 AvrcpControllerStateMachine stateMachine = getOrCreateStateMachine(device); 298 if (remoteControlConnected || browsingConnected) { 299 stateMachine.connect(event); 300 } else { 301 stateMachine.disconnect(); 302 } 303 } 304 305 // Called by JNI to notify Avrcp of features supported by the Remote device. getRcFeatures(byte[] address, int features)306 private void getRcFeatures(byte[] address, int features) { 307 /* Do Nothing. */ 308 } 309 310 // Called by JNI setPlayerAppSettingRsp(byte[] address, byte accepted)311 private void setPlayerAppSettingRsp(byte[] address, byte accepted) { 312 /* Do Nothing. */ 313 } 314 315 // Called by JNI when remote wants to receive absolute volume notifications. handleRegisterNotificationAbsVol(byte[] address, byte label)316 private synchronized void handleRegisterNotificationAbsVol(byte[] address, byte label) { 317 if (DBG) { 318 Log.d(TAG, "handleRegisterNotificationAbsVol"); 319 } 320 BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address); 321 AvrcpControllerStateMachine stateMachine = getStateMachine(device); 322 if (stateMachine != null) { 323 stateMachine.sendMessage( 324 AvrcpControllerStateMachine.MESSAGE_PROCESS_REGISTER_ABS_VOL_NOTIFICATION); 325 } 326 } 327 328 // Called by JNI when remote wants to set absolute volume. handleSetAbsVolume(byte[] address, byte absVol, byte label)329 private synchronized void handleSetAbsVolume(byte[] address, byte absVol, byte label) { 330 if (DBG) { 331 Log.d(TAG, "handleSetAbsVolume "); 332 } 333 BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address); 334 AvrcpControllerStateMachine stateMachine = getStateMachine(device); 335 if (stateMachine != null) { 336 stateMachine.sendMessage(AvrcpControllerStateMachine.MESSAGE_PROCESS_SET_ABS_VOL_CMD, 337 absVol); 338 } 339 } 340 341 // Called by JNI when a track changes and local AvrcpController is registered for updates. onTrackChanged(byte[] address, byte numAttributes, int[] attributes, String[] attribVals)342 private synchronized void onTrackChanged(byte[] address, byte numAttributes, int[] attributes, 343 String[] attribVals) { 344 if (DBG) { 345 Log.d(TAG, "onTrackChanged"); 346 } 347 348 BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address); 349 AvrcpControllerStateMachine stateMachine = getStateMachine(device); 350 if (stateMachine != null) { 351 stateMachine.sendMessage(AvrcpControllerStateMachine.MESSAGE_PROCESS_TRACK_CHANGED, 352 TrackInfo.getMetadata(attributes, attribVals)); 353 } 354 } 355 356 // Called by JNI periodically based upon timer to update play position onPlayPositionChanged(byte[] address, int songLen, int currSongPosition)357 private synchronized void onPlayPositionChanged(byte[] address, int songLen, 358 int currSongPosition) { 359 if (DBG) { 360 Log.d(TAG, "onPlayPositionChanged pos " + currSongPosition); 361 } 362 BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address); 363 AvrcpControllerStateMachine stateMachine = getStateMachine(device); 364 if (stateMachine != null) { 365 stateMachine.sendMessage( 366 AvrcpControllerStateMachine.MESSAGE_PROCESS_PLAY_POS_CHANGED, 367 songLen, currSongPosition); 368 } 369 } 370 371 // Called by JNI on changes of play status onPlayStatusChanged(byte[] address, byte playStatus)372 private synchronized void onPlayStatusChanged(byte[] address, byte playStatus) { 373 if (DBG) { 374 Log.d(TAG, "onPlayStatusChanged " + playStatus); 375 } 376 int playbackState = PlaybackState.STATE_NONE; 377 switch (playStatus) { 378 case JNI_PLAY_STATUS_STOPPED: 379 playbackState = PlaybackState.STATE_STOPPED; 380 break; 381 case JNI_PLAY_STATUS_PLAYING: 382 playbackState = PlaybackState.STATE_PLAYING; 383 break; 384 case JNI_PLAY_STATUS_PAUSED: 385 playbackState = PlaybackState.STATE_PAUSED; 386 break; 387 case JNI_PLAY_STATUS_FWD_SEEK: 388 playbackState = PlaybackState.STATE_FAST_FORWARDING; 389 break; 390 case JNI_PLAY_STATUS_REV_SEEK: 391 playbackState = PlaybackState.STATE_REWINDING; 392 break; 393 default: 394 playbackState = PlaybackState.STATE_NONE; 395 } 396 BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address); 397 AvrcpControllerStateMachine stateMachine = getStateMachine(device); 398 if (stateMachine != null) { 399 stateMachine.sendMessage( 400 AvrcpControllerStateMachine.MESSAGE_PROCESS_PLAY_STATUS_CHANGED, playbackState); 401 } 402 } 403 404 // Called by JNI to report remote Player's capabilities handlePlayerAppSetting(byte[] address, byte[] playerAttribRsp, int rspLen)405 private synchronized void handlePlayerAppSetting(byte[] address, byte[] playerAttribRsp, 406 int rspLen) { 407 if (DBG) { 408 Log.d(TAG, "handlePlayerAppSetting rspLen = " + rspLen); 409 } 410 BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address); 411 AvrcpControllerStateMachine stateMachine = getStateMachine(device); 412 if (stateMachine != null) { 413 PlayerApplicationSettings supportedSettings = 414 PlayerApplicationSettings.makeSupportedSettings(playerAttribRsp); 415 } 416 /* Do nothing */ 417 418 } 419 onPlayerAppSettingChanged(byte[] address, byte[] playerAttribRsp, int rspLen)420 private synchronized void onPlayerAppSettingChanged(byte[] address, byte[] playerAttribRsp, 421 int rspLen) { 422 if (DBG) { 423 Log.d(TAG, "onPlayerAppSettingChanged "); 424 } 425 BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address); 426 AvrcpControllerStateMachine stateMachine = getStateMachine(device); 427 if (stateMachine != null) { 428 429 PlayerApplicationSettings desiredSettings = 430 PlayerApplicationSettings.makeSettings(playerAttribRsp); 431 } 432 /* Do nothing */ 433 } 434 435 // Browsing related JNI callbacks. handleGetFolderItemsRsp(byte[] address, int status, MediaItem[] items)436 void handleGetFolderItemsRsp(byte[] address, int status, MediaItem[] items) { 437 if (DBG) { 438 Log.d(TAG, "handleGetFolderItemsRsp called with status " + status + " items " 439 + items.length + " items."); 440 } 441 for (MediaItem item : items) { 442 if (VDBG) { 443 Log.d(TAG, "media item: " + item + " uid: " 444 + item.getDescription().getMediaId()); 445 } 446 } 447 ArrayList<MediaItem> itemsList = new ArrayList<>(); 448 for (MediaItem item : items) { 449 itemsList.add(item); 450 } 451 452 BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address); 453 454 AvrcpControllerStateMachine stateMachine = getStateMachine(device); 455 if (stateMachine != null) { 456 457 stateMachine.sendMessage(AvrcpControllerStateMachine.MESSAGE_PROCESS_GET_FOLDER_ITEMS, 458 itemsList); 459 } 460 } 461 462 handleGetPlayerItemsRsp(byte[] address, AvrcpPlayer[] items)463 void handleGetPlayerItemsRsp(byte[] address, AvrcpPlayer[] items) { 464 if (DBG) { 465 Log.d(TAG, "handleGetFolderItemsRsp called with " + items.length + " items."); 466 } 467 468 for (AvrcpPlayer item : items) { 469 if (VDBG) { 470 Log.d(TAG, "bt player item: " + item); 471 } 472 } 473 List<AvrcpPlayer> itemsList = new ArrayList<>(); 474 for (AvrcpPlayer p : items) { 475 itemsList.add(p); 476 } 477 478 BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address); 479 AvrcpControllerStateMachine stateMachine = getStateMachine(device); 480 if (stateMachine != null) { 481 stateMachine.sendMessage(AvrcpControllerStateMachine.MESSAGE_PROCESS_GET_PLAYER_ITEMS, 482 itemsList); 483 } 484 } 485 486 // JNI Helper functions to convert native objects to java. createFromNativeMediaItem(long uid, int type, String name, int[] attrIds, String[] attrVals)487 MediaItem createFromNativeMediaItem(long uid, int type, String name, int[] attrIds, 488 String[] attrVals) { 489 if (VDBG) { 490 Log.d(TAG, "createFromNativeMediaItem uid: " + uid + " type " + type + " name " 491 + name + " attrids " + attrIds + " attrVals " + attrVals); 492 } 493 MediaDescription.Builder mdb = new MediaDescription.Builder(); 494 495 Bundle mdExtra = new Bundle(); 496 mdExtra.putLong(MEDIA_ITEM_UID_KEY, uid); 497 mdb.setExtras(mdExtra); 498 499 500 // Generate a random UUID. We do this since database unaware TGs can send multiple 501 // items with same MEDIA_ITEM_UID_KEY. 502 mdb.setMediaId(UUID.randomUUID().toString()); 503 // Concise readable name. 504 mdb.setTitle(name); 505 506 // We skip the attributes since we can query them using UID for the item above 507 // Also MediaDescription does not give an easy way to provide this unless we pass 508 // it as an MediaMetadata which is put inside the extras. 509 return new MediaItem(mdb.build(), MediaItem.FLAG_PLAYABLE); 510 } 511 createFromNativeFolderItem(long uid, int type, String name, int playable)512 MediaItem createFromNativeFolderItem(long uid, int type, String name, int playable) { 513 if (VDBG) { 514 Log.d(TAG, "createFromNativeFolderItem uid: " + uid + " type " + type + " name " 515 + name + " playable " + playable); 516 } 517 MediaDescription.Builder mdb = new MediaDescription.Builder(); 518 519 Bundle mdExtra = new Bundle(); 520 mdExtra.putLong(MEDIA_ITEM_UID_KEY, uid); 521 mdb.setExtras(mdExtra); 522 523 // Generate a random UUID. We do this since database unaware TGs can send multiple 524 // items with same MEDIA_ITEM_UID_KEY. 525 mdb.setMediaId(UUID.randomUUID().toString()); 526 // Concise readable name. 527 mdb.setTitle(name); 528 529 return new MediaItem(mdb.build(), MediaItem.FLAG_BROWSABLE); 530 } 531 createFromNativePlayerItem(int id, String name, byte[] transportFlags, int playStatus, int playerType)532 AvrcpPlayer createFromNativePlayerItem(int id, String name, byte[] transportFlags, 533 int playStatus, int playerType) { 534 if (VDBG) { 535 Log.d(TAG, 536 "createFromNativePlayerItem name: " + name + " transportFlags " 537 + transportFlags + " play status " + playStatus + " player type " 538 + playerType); 539 } 540 AvrcpPlayer player = new AvrcpPlayer(id, name, transportFlags, playStatus, playerType); 541 return player; 542 } 543 handleChangeFolderRsp(byte[] address, int count)544 private void handleChangeFolderRsp(byte[] address, int count) { 545 if (DBG) { 546 Log.d(TAG, "handleChangeFolderRsp count: " + count); 547 } 548 BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address); 549 AvrcpControllerStateMachine stateMachine = getStateMachine(device); 550 if (stateMachine != null) { 551 stateMachine.sendMessage(AvrcpControllerStateMachine.MESSAGE_PROCESS_FOLDER_PATH, 552 count); 553 } 554 } 555 handleSetBrowsedPlayerRsp(byte[] address, int items, int depth)556 private void handleSetBrowsedPlayerRsp(byte[] address, int items, int depth) { 557 if (DBG) { 558 Log.d(TAG, "handleSetBrowsedPlayerRsp depth: " + depth); 559 } 560 BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address); 561 562 AvrcpControllerStateMachine stateMachine = getStateMachine(device); 563 if (stateMachine != null) { 564 stateMachine.sendMessage(AvrcpControllerStateMachine.MESSAGE_PROCESS_SET_BROWSED_PLAYER, 565 items, depth); 566 } 567 } 568 handleSetAddressedPlayerRsp(byte[] address, int status)569 private void handleSetAddressedPlayerRsp(byte[] address, int status) { 570 if (DBG) { 571 Log.d(TAG, "handleSetAddressedPlayerRsp status: " + status); 572 } 573 BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address); 574 575 AvrcpControllerStateMachine stateMachine = getStateMachine(device); 576 if (stateMachine != null) { 577 stateMachine.sendMessage( 578 AvrcpControllerStateMachine.MESSAGE_PROCESS_SET_ADDRESSED_PLAYER); 579 } 580 } 581 handleAddressedPlayerChanged(byte[] address, int id)582 private void handleAddressedPlayerChanged(byte[] address, int id) { 583 if (DBG) { 584 Log.d(TAG, "handleAddressedPlayerChanged id: " + id); 585 } 586 BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address); 587 588 AvrcpControllerStateMachine stateMachine = getStateMachine(device); 589 if (stateMachine != null) { 590 stateMachine.sendMessage( 591 AvrcpControllerStateMachine.MESSAGE_PROCESS_ADDRESSED_PLAYER_CHANGED, id); 592 } 593 } 594 handleNowPlayingContentChanged(byte[] address)595 private void handleNowPlayingContentChanged(byte[] address) { 596 if (DBG) { 597 Log.d(TAG, "handleNowPlayingContentChanged"); 598 } 599 BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address); 600 601 AvrcpControllerStateMachine stateMachine = getStateMachine(device); 602 if (stateMachine != null) { 603 stateMachine.nowPlayingContentChanged(); 604 } 605 } 606 607 /* Generic Profile Code */ 608 609 /** 610 * Disconnect the given Bluetooth device. 611 * 612 * @return true if disconnect is successful, false otherwise. 613 */ disconnect(BluetoothDevice device)614 public synchronized boolean disconnect(BluetoothDevice device) { 615 if (DBG) { 616 StringBuilder sb = new StringBuilder(); 617 dump(sb); 618 Log.d(TAG, "MAP disconnect device: " + device 619 + ", InstanceMap start state: " + sb.toString()); 620 } 621 AvrcpControllerStateMachine stateMachine = mDeviceStateMap.get(device); 622 // a map state machine instance doesn't exist. maybe it is already gone? 623 if (stateMachine == null) { 624 return false; 625 } 626 int connectionState = stateMachine.getState(); 627 if (connectionState != BluetoothProfile.STATE_CONNECTED 628 && connectionState != BluetoothProfile.STATE_CONNECTING) { 629 return false; 630 } 631 stateMachine.disconnect(); 632 if (DBG) { 633 StringBuilder sb = new StringBuilder(); 634 dump(sb); 635 Log.d(TAG, "MAP disconnect device: " + device 636 + ", InstanceMap start state: " + sb.toString()); 637 } 638 return true; 639 } 640 641 /** 642 * Remove state machine from device map once it is no longer needed. 643 */ removeStateMachine(AvrcpControllerStateMachine stateMachine)644 public void removeStateMachine(AvrcpControllerStateMachine stateMachine) { 645 mDeviceStateMap.remove(stateMachine.getDevice()); 646 } 647 getConnectedDevices()648 public List<BluetoothDevice> getConnectedDevices() { 649 return getDevicesMatchingConnectionStates(new int[]{BluetoothAdapter.STATE_CONNECTED}); 650 } 651 getStateMachine(BluetoothDevice device)652 protected AvrcpControllerStateMachine getStateMachine(BluetoothDevice device) { 653 return mDeviceStateMap.get(device); 654 } 655 getOrCreateStateMachine(BluetoothDevice device)656 protected AvrcpControllerStateMachine getOrCreateStateMachine(BluetoothDevice device) { 657 AvrcpControllerStateMachine stateMachine = mDeviceStateMap.get(device); 658 if (stateMachine == null) { 659 stateMachine = newStateMachine(device); 660 mDeviceStateMap.put(device, stateMachine); 661 stateMachine.start(); 662 } 663 return stateMachine; 664 } 665 getDevicesMatchingConnectionStates(int[] states)666 List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { 667 if (DBG) Log.d(TAG, "getDevicesMatchingConnectionStates" + Arrays.toString(states)); 668 List<BluetoothDevice> deviceList = new ArrayList<>(); 669 Set<BluetoothDevice> bondedDevices = mAdapter.getBondedDevices(); 670 int connectionState; 671 for (BluetoothDevice device : bondedDevices) { 672 connectionState = getConnectionState(device); 673 if (DBG) Log.d(TAG, "Device: " + device + "State: " + connectionState); 674 for (int i = 0; i < states.length; i++) { 675 if (connectionState == states[i]) { 676 deviceList.add(device); 677 } 678 } 679 } 680 if (DBG) Log.d(TAG, deviceList.toString()); 681 Log.d(TAG, "GetDevicesDone"); 682 return deviceList; 683 } 684 getConnectionState(BluetoothDevice device)685 synchronized int getConnectionState(BluetoothDevice device) { 686 AvrcpControllerStateMachine stateMachine = mDeviceStateMap.get(device); 687 return (stateMachine == null) ? BluetoothProfile.STATE_DISCONNECTED 688 : stateMachine.getState(); 689 } 690 691 @Override dump(StringBuilder sb)692 public void dump(StringBuilder sb) { 693 super.dump(sb); 694 ProfileService.println(sb, "Devices Tracked = " + mDeviceStateMap.size()); 695 696 for (AvrcpControllerStateMachine stateMachine : mDeviceStateMap.values()) { 697 ProfileService.println(sb, 698 "==== StateMachine for " + stateMachine.getDevice() + " ===="); 699 stateMachine.dump(sb); 700 } 701 sb.append("\n sBrowseTree: " + sBrowseTree.toString()); 702 } 703 704 /*JNI*/ classInitNative()705 private static native void classInitNative(); 706 initNative()707 private native void initNative(); 708 cleanupNative()709 private native void cleanupNative(); 710 711 /** 712 * Send button press commands to addressed device 713 * 714 * @param keyCode key code as defined in AVRCP specification 715 * @param keyState 0 = key pressed, 1 = key released 716 * @return command was sent 717 */ sendPassThroughCommandNative(byte[] address, int keyCode, int keyState)718 public native boolean sendPassThroughCommandNative(byte[] address, int keyCode, int keyState); 719 720 /** 721 * Send group navigation commands 722 * 723 * @param keyCode next/previous 724 * @param keyState state 725 * @return command was sent 726 */ sendGroupNavigationCommandNative(byte[] address, int keyCode, int keyState)727 public native boolean sendGroupNavigationCommandNative(byte[] address, int keyCode, 728 int keyState); 729 730 /** 731 * Change player specific settings such as shuffle 732 * 733 * @param numAttrib number of settings being sent 734 * @param attribIds list of settings to be changed 735 * @param attribVal list of settings values 736 */ setPlayerApplicationSettingValuesNative(byte[] address, byte numAttrib, byte[] attribIds, byte[] attribVal)737 public native void setPlayerApplicationSettingValuesNative(byte[] address, byte numAttrib, 738 byte[] attribIds, byte[] attribVal); 739 740 /** 741 * Send response to set absolute volume 742 * 743 * @param absVol new volume 744 * @param label label 745 */ sendAbsVolRspNative(byte[] address, int absVol, int label)746 public native void sendAbsVolRspNative(byte[] address, int absVol, int label); 747 748 /** 749 * Register for any volume level changes 750 * 751 * @param rspType type of response 752 * @param absVol current volume 753 * @param label label 754 */ sendRegisterAbsVolRspNative(byte[] address, byte rspType, int absVol, int label)755 public native void sendRegisterAbsVolRspNative(byte[] address, byte rspType, int absVol, 756 int label); 757 758 /** 759 * Fetch the playback state 760 */ getPlaybackStateNative(byte[] address)761 public native void getPlaybackStateNative(byte[] address); 762 763 /** 764 * Fetch the current now playing list 765 * 766 * @param start first index to retrieve 767 * @param end last index to retrieve 768 */ getNowPlayingListNative(byte[] address, int start, int end)769 public native void getNowPlayingListNative(byte[] address, int start, int end); 770 771 /** 772 * Fetch the current folder's listing 773 * 774 * @param start first index to retrieve 775 * @param end last index to retrieve 776 */ getFolderListNative(byte[] address, int start, int end)777 public native void getFolderListNative(byte[] address, int start, int end); 778 779 /** 780 * Fetch the listing of players 781 * 782 * @param start first index to retrieve 783 * @param end last index to retrieve 784 */ getPlayerListNative(byte[] address, int start, int end)785 public native void getPlayerListNative(byte[] address, int start, int end); 786 787 /** 788 * Change the current browsed folder 789 * 790 * @param direction up/down 791 * @param uid folder unique id 792 */ changeFolderPathNative(byte[] address, byte direction, long uid)793 public native void changeFolderPathNative(byte[] address, byte direction, long uid); 794 795 /** 796 * Play item with provided uid 797 * 798 * @param scope scope of item to played 799 * @param uid song unique id 800 * @param uidCounter counter 801 */ playItemNative(byte[] address, byte scope, long uid, int uidCounter)802 public native void playItemNative(byte[] address, byte scope, long uid, int uidCounter); 803 804 /** 805 * Set a specific player for browsing 806 * 807 * @param playerId player number 808 */ setBrowsedPlayerNative(byte[] address, int playerId)809 public native void setBrowsedPlayerNative(byte[] address, int playerId); 810 811 /** 812 * Set a specific player for handling playback commands 813 * 814 * @param playerId player number 815 */ setAddressedPlayerNative(byte[] address, int playerId)816 public native void setAddressedPlayerNative(byte[] address, int playerId); 817 } 818