/* * Copyright (C) 2014 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.bluetooth.avrcp; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothAvrcpController; import android.bluetooth.BluetoothAvrcpPlayerSettings; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothProfile; import android.bluetooth.IBluetoothAvrcpController; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.media.MediaMetadata; import android.media.session.PlaybackState; import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; import android.os.Message; import android.util.Log; import android.media.AudioManager; import com.android.bluetooth.a2dpsink.A2dpSinkService; import com.android.bluetooth.btservice.ProfileService; import com.android.bluetooth.Utils; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.HashMap; import java.nio.charset.Charset; import java.nio.ByteBuffer; /** * Provides Bluetooth AVRCP Controller profile, as a service in the Bluetooth application. * @hide */ public class AvrcpControllerService extends ProfileService { private static final boolean DBG = AvrcpControllerConstants.DBG; private static final boolean VDBG = AvrcpControllerConstants.VDBG; private static final String TAG = "AvrcpControllerService"; /* * Messages handled by mHandler */ RemoteDevice mAvrcpRemoteDevice; RemoteMediaPlayers mRemoteMediaPlayers; NowPlaying mRemoteNowPlayingList; private AvrcpMessageHandler mHandler; private static AvrcpControllerService sAvrcpControllerService; private static AudioManager mAudioManager; private final ArrayList mConnectedDevices = new ArrayList(); static { classInitNative(); } public AvrcpControllerService() { initNative(); } protected String getName() { return TAG; } protected IProfileServiceBinder initBinder() { return new BluetoothAvrcpControllerBinder(this); } protected boolean start() { HandlerThread thread = new HandlerThread("BluetoothAvrcpHandler"); thread.start(); Looper looper = thread.getLooper(); mHandler = new AvrcpMessageHandler(looper); setAvrcpControllerService(this); mAudioManager = (AudioManager)sAvrcpControllerService. getSystemService(Context.AUDIO_SERVICE); return true; } protected void resetRemoteData() { try { unregisterReceiver(mBroadcastReceiver); } catch (IllegalArgumentException e) { Log.e(TAG,"Receiver not registered"); } if(mAvrcpRemoteDevice != null) { mAvrcpRemoteDevice.cleanup(); mAvrcpRemoteDevice = null; } if(mRemoteMediaPlayers != null) { mRemoteMediaPlayers.cleanup(); mRemoteMediaPlayers = null; } if(mRemoteNowPlayingList != null) { mRemoteNowPlayingList.cleanup(); mRemoteNowPlayingList = null; } } protected boolean stop() { if (mHandler != null) { mHandler.removeCallbacksAndMessages(null); Looper looper = mHandler.getLooper(); if (looper != null) { looper.quit(); } } resetRemoteData(); return true; } protected boolean cleanup() { if (mHandler != null) { mHandler.removeCallbacksAndMessages(null); Looper looper = mHandler.getLooper(); if (looper != null) looper.quit(); } resetRemoteData(); clearAvrcpControllerService(); cleanupNative(); return true; } //API Methods public static synchronized AvrcpControllerService getAvrcpControllerService(){ if (sAvrcpControllerService != null && sAvrcpControllerService.isAvailable()) { if (DBG) Log.d(TAG, "getAvrcpControllerService(): returning " + sAvrcpControllerService); return sAvrcpControllerService; } if (DBG) { if (sAvrcpControllerService == null) { Log.d(TAG, "getAvrcpControllerService(): service is NULL"); } else if (!(sAvrcpControllerService.isAvailable())) { Log.d(TAG,"getAvrcpControllerService(): service is not available"); } } return null; } private static synchronized void setAvrcpControllerService(AvrcpControllerService instance) { if (instance != null && instance.isAvailable()) { if (DBG) Log.d(TAG, "setAvrcpControllerService(): set to: " + sAvrcpControllerService); sAvrcpControllerService = instance; } else { if (DBG) { if (sAvrcpControllerService == null) { Log.d(TAG, "setAvrcpControllerService(): service not available"); } else if (!sAvrcpControllerService.isAvailable()) { Log.d(TAG,"setAvrcpControllerService(): service is cleaning up"); } } } } private static synchronized void clearAvrcpControllerService() { sAvrcpControllerService = null; } public List getConnectedDevices() { enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); return mConnectedDevices; } List getDevicesMatchingConnectionStates(int[] states) { enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); for (int i = 0; i < states.length; i++) { if (states[i] == BluetoothProfile.STATE_CONNECTED) { return mConnectedDevices; } } return new ArrayList(); } int getConnectionState(BluetoothDevice device) { enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); return (mConnectedDevices.contains(device) ? BluetoothProfile.STATE_CONNECTED : BluetoothProfile.STATE_DISCONNECTED); } public void sendGroupNavigationCmd(BluetoothDevice device, int keyCode, int keyState) { Log.v(TAG, "sendGroupNavigationCmd keyCode: " + keyCode + " keyState: " + keyState); if (device == null) { throw new NullPointerException("device == null"); } if (!(mConnectedDevices.contains(device))) { for (BluetoothDevice cdevice : mConnectedDevices) { Log.e(TAG, "Device: " + cdevice); } Log.e(TAG," Device does not match " + device); return; } enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); Message msg = mHandler.obtainMessage(AvrcpControllerConstants. MESSAGE_SEND_GROUP_NAVIGATION_CMD,keyCode, keyState, device); mHandler.sendMessage(msg); } public void sendPassThroughCmd(BluetoothDevice device, int keyCode, int keyState) { Log.v(TAG, "sendPassThroughCmd keyCode: " + keyCode + " keyState: " + keyState); if (device == null) { throw new NullPointerException("device == null"); } if (!(mConnectedDevices.contains(device))) { Log.d(TAG," Device does not match"); return; } if ((mAvrcpRemoteDevice == null)|| (mAvrcpRemoteDevice.mRemoteFeatures == AvrcpControllerConstants.BTRC_FEAT_NONE)|| (mRemoteMediaPlayers == null) || (mRemoteMediaPlayers.getAddressedPlayer() == null)){ Log.d(TAG," Device connected but PlayState not present "); enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); Message msg = mHandler.obtainMessage(AvrcpControllerConstants.MESSAGE_SEND_PASS_THROUGH_CMD, keyCode, keyState, device); mHandler.sendMessage(msg); return; } boolean sendCommand = false; switch(keyCode) { case BluetoothAvrcpController.PASS_THRU_CMD_ID_PLAY: sendCommand = (mRemoteMediaPlayers.getPlayStatus() == AvrcpControllerConstants.PLAY_STATUS_STOPPED)|| (mRemoteMediaPlayers.getPlayStatus() == AvrcpControllerConstants.PLAY_STATUS_PAUSED) || (mRemoteMediaPlayers.getPlayStatus() == AvrcpControllerConstants.PLAY_STATUS_PLAYING); break; case BluetoothAvrcpController.PASS_THRU_CMD_ID_PAUSE: /* * allowing pause command in pause state to handle A2DP Sink Concurrency * If call is ongoing and Start is initiated from remote, we will send pause again * If acquireFocus fails, we will send Pause again * To Stop sending multiple Pause, check in application. */ sendCommand = (mRemoteMediaPlayers.getPlayStatus() == AvrcpControllerConstants.PLAY_STATUS_PLAYING)|| (mRemoteMediaPlayers.getPlayStatus() == AvrcpControllerConstants.PLAY_STATUS_FWD_SEEK)|| (mRemoteMediaPlayers.getPlayStatus() == AvrcpControllerConstants.PLAY_STATUS_STOPPED)|| (mRemoteMediaPlayers.getPlayStatus() == AvrcpControllerConstants.PLAY_STATUS_PAUSED)|| (mRemoteMediaPlayers.getPlayStatus() == AvrcpControllerConstants.PLAY_STATUS_REV_SEEK); break; case BluetoothAvrcpController.PASS_THRU_CMD_ID_STOP: sendCommand = (mRemoteMediaPlayers.getPlayStatus() == AvrcpControllerConstants.PLAY_STATUS_PLAYING)|| (mRemoteMediaPlayers.getPlayStatus() == AvrcpControllerConstants.PLAY_STATUS_FWD_SEEK)|| (mRemoteMediaPlayers.getPlayStatus() == AvrcpControllerConstants.PLAY_STATUS_REV_SEEK)|| (mRemoteMediaPlayers.getPlayStatus() == AvrcpControllerConstants.PLAY_STATUS_STOPPED)|| (mRemoteMediaPlayers.getPlayStatus() == AvrcpControllerConstants.PLAY_STATUS_PAUSED); break; case BluetoothAvrcpController.PASS_THRU_CMD_ID_BACKWARD: case BluetoothAvrcpController.PASS_THRU_CMD_ID_FORWARD: case BluetoothAvrcpController.PASS_THRU_CMD_ID_FF: case BluetoothAvrcpController.PASS_THRU_CMD_ID_REWIND: sendCommand = true; // we can send this command in all states break; } if (sendCommand) { enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); Message msg = mHandler.obtainMessage(AvrcpControllerConstants.MESSAGE_SEND_PASS_THROUGH_CMD, keyCode, keyState, device); mHandler.sendMessage(msg); } else { Log.e(TAG," Not in right state, don't send Pass Thru cmd "); } } public void startAvrcpUpdates() { mHandler.obtainMessage( AvrcpControllerConstants.MESSAGE_START_METADATA_BROADCASTS).sendToTarget(); } public void stopAvrcpUpdates() { mHandler.obtainMessage( AvrcpControllerConstants.MESSAGE_STOP_METADATA_BROADCASTS).sendToTarget(); } public MediaMetadata getMetaData(BluetoothDevice device) { Log.d(TAG, "getMetaData = "); enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); if((mRemoteNowPlayingList != null) && (mRemoteNowPlayingList.getCurrentTrack() != null)) { return getCurrentMetaData(AvrcpControllerConstants.AVRCP_SCOPE_NOW_PLAYING, 0); } else return null; } public PlaybackState getPlaybackState(BluetoothDevice device) { if (DBG) Log.d(TAG, "getPlayBackState device = "+ device); enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); return getCurrentPlayBackState(); } public BluetoothAvrcpPlayerSettings getPlayerSettings(BluetoothDevice device) { if (DBG) Log.d(TAG, "getPlayerApplicationSetting "); enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); return getCurrentPlayerAppSetting(); } public boolean setPlayerApplicationSetting(BluetoothAvrcpPlayerSettings plAppSetting) { if ((mAvrcpRemoteDevice == null)||(mRemoteMediaPlayers == null)) { return false; } enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); /* * We have to extract values from BluetoothAvrcpPlayerSettings */ int mSettings = plAppSetting.getSettings(); int numAttributes = 0; /* calculate number of attributes in request */ while(mSettings > 0) { numAttributes += ((mSettings & 0x01)!= 0)?1: 0; mSettings = mSettings >> 1; } byte[] attribArray = new byte [2*numAttributes]; mSettings = plAppSetting.getSettings(); /* * Now we will flatten it */ int i = 0; if((mSettings & BluetoothAvrcpPlayerSettings.SETTING_EQUALIZER) != 0) { attribArray[i++] = AvrcpControllerConstants.ATTRIB_EQUALIZER_STATUS; attribArray[i++] = (byte)AvrcpUtils.mapAvrcpPlayerSettingstoBTAttribVal( BluetoothAvrcpPlayerSettings.SETTING_EQUALIZER, plAppSetting. getSettingValue(BluetoothAvrcpPlayerSettings.SETTING_EQUALIZER)); } if((mSettings & BluetoothAvrcpPlayerSettings.SETTING_REPEAT) != 0) { attribArray[i++] = AvrcpControllerConstants.ATTRIB_REPEAT_STATUS; attribArray[i++] = (byte)AvrcpUtils.mapAvrcpPlayerSettingstoBTAttribVal( BluetoothAvrcpPlayerSettings.SETTING_REPEAT, plAppSetting. getSettingValue(BluetoothAvrcpPlayerSettings.SETTING_REPEAT)); } if((mSettings & BluetoothAvrcpPlayerSettings.SETTING_SHUFFLE) != 0) { attribArray[i++] = AvrcpControllerConstants.ATTRIB_SHUFFLE_STATUS; attribArray[i++] = (byte)AvrcpUtils.mapAvrcpPlayerSettingstoBTAttribVal( BluetoothAvrcpPlayerSettings.SETTING_SHUFFLE, plAppSetting. getSettingValue(BluetoothAvrcpPlayerSettings.SETTING_SHUFFLE)); } if((mSettings & BluetoothAvrcpPlayerSettings.SETTING_SCAN) != 0) { attribArray[i++] = AvrcpControllerConstants.ATTRIB_SCAN_STATUS; attribArray[i++] = (byte)AvrcpUtils.mapAvrcpPlayerSettingstoBTAttribVal( BluetoothAvrcpPlayerSettings.SETTING_SCAN, plAppSetting. getSettingValue(BluetoothAvrcpPlayerSettings.SETTING_SCAN)); } boolean isSettingSupported = mRemoteMediaPlayers.getAddressedPlayer(). isPlayerAppSettingSupported((byte)numAttributes, attribArray); if(isSettingSupported) { ByteBuffer bb = ByteBuffer.wrap(attribArray, 0, (2*numAttributes)); Message msg = mHandler.obtainMessage(AvrcpControllerConstants. MESSAGE_SEND_SET_CURRENT_PLAYER_APPLICATION_SETTINGS, numAttributes, 0, bb); mHandler.sendMessage(msg); } return isSettingSupported; } //Binder object: Must be static class or memory leak may occur private static class BluetoothAvrcpControllerBinder extends IBluetoothAvrcpController.Stub implements IProfileServiceBinder { private AvrcpControllerService mService; private AvrcpControllerService getService() { if (!Utils.checkCaller()) { Log.w(TAG,"AVRCP call not allowed for non-active user"); return null; } if (mService != null && mService.isAvailable()) { return mService; } return null; } BluetoothAvrcpControllerBinder(AvrcpControllerService svc) { mService = svc; } public boolean cleanup() { mService = null; return true; } public List getConnectedDevices() { AvrcpControllerService service = getService(); if (service == null) return new ArrayList(0); return service.getConnectedDevices(); } public List getDevicesMatchingConnectionStates(int[] states) { AvrcpControllerService service = getService(); if (service == null) return new ArrayList(0); return service.getDevicesMatchingConnectionStates(states); } public int getConnectionState(BluetoothDevice device) { AvrcpControllerService service = getService(); if (service == null) return BluetoothProfile.STATE_DISCONNECTED; return service.getConnectionState(device); } public void sendPassThroughCmd(BluetoothDevice device, int keyCode, int keyState) { Log.v(TAG,"Binder Call: sendPassThroughCmd"); AvrcpControllerService service = getService(); if (service == null) return; service.sendPassThroughCmd(device, keyCode, keyState); } @Override public void sendGroupNavigationCmd(BluetoothDevice device, int keyCode, int keyState) { Log.v(TAG,"Binder Call: sendGroupNavigationCmd"); AvrcpControllerService service = getService(); if (service == null) return; service.sendGroupNavigationCmd(device, keyCode, keyState); } @Override public BluetoothAvrcpPlayerSettings getPlayerSettings(BluetoothDevice device) { Log.v(TAG,"Binder Call: getPlayerApplicationSetting "); AvrcpControllerService service = getService(); if (service == null) return null; return service.getPlayerSettings(device); } @Override public MediaMetadata getMetadata(BluetoothDevice device) { Log.v(TAG,"Binder Call: getMetaData "); AvrcpControllerService service = getService(); if (service == null) return null; return service.getMetaData(device); } @Override public PlaybackState getPlaybackState(BluetoothDevice device) { Log.v(TAG,"Binder Call: getPlaybackState"); AvrcpControllerService service = getService(); if (service == null) return null; return service.getPlaybackState(device); } @Override public boolean setPlayerApplicationSetting(BluetoothAvrcpPlayerSettings plAppSetting) { Log.v(TAG,"Binder Call: setPlayerApplicationSetting " ); AvrcpControllerService service = getService(); if (service == null) return false; return service.setPlayerApplicationSetting(plAppSetting); } }; private String utf8ToString(byte[] input) { Charset UTF8_CHARSET = Charset.forName("UTF-8"); return new String(input,UTF8_CHARSET); } private int asciiToInt(int len, byte[] array) { return Integer.parseInt(utf8ToString(array)); } private BluetoothAvrcpPlayerSettings getCurrentPlayerAppSetting() { if((mRemoteMediaPlayers == null) || (mRemoteMediaPlayers.getAddressedPlayer() == null)) return null; return mRemoteMediaPlayers.getAddressedPlayer().getSupportedPlayerAppSetting(); } private PlaybackState getCurrentPlayBackState() { if ((mRemoteMediaPlayers == null) || (mRemoteMediaPlayers.getAddressedPlayer() == null)) { return new PlaybackState.Builder().setState(PlaybackState.STATE_ERROR, PlaybackState.PLAYBACK_POSITION_UNKNOWN, 0).build(); } return AvrcpUtils.mapBtPlayStatustoPlayBackState( mRemoteMediaPlayers.getAddressedPlayer().mPlayStatus, mRemoteMediaPlayers.getAddressedPlayer().mPlayTime); } private MediaMetadata getCurrentMetaData(int scope, int trackId) { /* if scope is now playing */ if(scope == AvrcpControllerConstants.AVRCP_SCOPE_NOW_PLAYING) { if((mRemoteNowPlayingList == null) || (mRemoteNowPlayingList. getTrackFromId(trackId) == null)) return null; TrackInfo mNowPlayingTrack = mRemoteNowPlayingList.getTrackFromId(trackId); return AvrcpUtils.getMediaMetaData(mNowPlayingTrack); } /* if scope is now playing */ else if(scope == AvrcpControllerConstants.AVRCP_SCOPE_VFS) { /* TODO for browsing */ } return null; } private void broadcastMetaDataChanged(MediaMetadata mMetaData) { Intent intent = new Intent(BluetoothAvrcpController.ACTION_TRACK_EVENT); intent.putExtra(BluetoothAvrcpController.EXTRA_METADATA, mMetaData); if(DBG) Log.d(TAG," broadcastMetaDataChanged = " + AvrcpUtils.displayMetaData(mMetaData)); sendBroadcast(intent, ProfileService.BLUETOOTH_PERM); } private void broadcastPlayBackStateChanged(PlaybackState state) { Intent intent = new Intent(BluetoothAvrcpController.ACTION_TRACK_EVENT); intent.putExtra(BluetoothAvrcpController.EXTRA_PLAYBACK, state); if(DBG) Log.d(TAG," broadcastPlayBackStateChanged = " + state.toString()); sendBroadcast(intent, ProfileService.BLUETOOTH_PERM); } private void broadcastPlayerAppSettingChanged(BluetoothAvrcpPlayerSettings mPlAppSetting) { Intent intent = new Intent(BluetoothAvrcpController.ACTION_PLAYER_SETTING); intent.putExtra(BluetoothAvrcpController.EXTRA_PLAYER_SETTING, mPlAppSetting); if(DBG) Log.d(TAG," broadcastPlayerAppSettingChanged = " + AvrcpUtils.displayBluetoothAvrcpSettings(mPlAppSetting)); sendBroadcast(intent, ProfileService.BLUETOOTH_PERM); } /** Handles Avrcp messages. */ private final class AvrcpMessageHandler extends Handler { private boolean mBroadcastMetadata = false; private AvrcpMessageHandler(Looper looper) { super(looper); } @Override public void handleMessage(Message msg) { Log.d(TAG," HandleMessage: "+ AvrcpControllerConstants.dumpMessageString(msg.what) + " Remote Connected " + !mConnectedDevices.isEmpty()); A2dpSinkService a2dpSinkService = A2dpSinkService.getA2dpSinkService(); switch (msg.what) { case AvrcpControllerConstants.MESSAGE_STOP_METADATA_BROADCASTS: // Any messages hence forth about play pos/play status/song info will be ignored. if(mRemoteMediaPlayers != null) { // Mock the current state to *look like* it is paused. The remote play state is // still cached in mRemoteMediaPlayers and will be restored when the // startAvrcpUpdates is called again. broadcastPlayBackStateChanged(AvrcpUtils.mapBtPlayStatustoPlayBackState ((byte) AvrcpControllerConstants.PLAY_STATUS_PAUSED, mRemoteMediaPlayers.getAddressedPlayer().mPlayTime)); } mBroadcastMetadata = false; break; case AvrcpControllerConstants.MESSAGE_START_METADATA_BROADCASTS: // Any messages hence forth about play pos/play status/song info will be sent. if(mRemoteMediaPlayers != null) { broadcastPlayBackStateChanged(getCurrentPlayBackState()); broadcastMetaDataChanged( getCurrentMetaData(AvrcpControllerConstants.AVRCP_SCOPE_NOW_PLAYING, 0)); } mBroadcastMetadata = true; break; case AvrcpControllerConstants.MESSAGE_SEND_PASS_THROUGH_CMD: BluetoothDevice device = (BluetoothDevice)msg.obj; sendPassThroughCommandNative(getByteAddress(device), msg.arg1, msg.arg2); if((a2dpSinkService != null)&&(!mConnectedDevices.isEmpty())) { Log.d(TAG," inform AVRCP Commands to A2DP Sink "); a2dpSinkService.informAvrcpPassThroughCmd(device, msg.arg1, msg.arg2); } break; case AvrcpControllerConstants.MESSAGE_SEND_GROUP_NAVIGATION_CMD: BluetoothDevice peerDevice = (BluetoothDevice)msg.obj; sendGroupNavigationCommandNative(getByteAddress(peerDevice), msg.arg1, msg.arg2); break; case AvrcpControllerConstants.MESSAGE_SEND_SET_CURRENT_PLAYER_APPLICATION_SETTINGS: byte numAttributes = (byte)msg.arg1; ByteBuffer bbRsp = (ByteBuffer)msg.obj; byte[] attributeIds = new byte [numAttributes]; byte[] attributeVals = new byte [numAttributes]; for(int i = 0; (bbRsp.hasRemaining())&&(i < numAttributes); i++) { attributeIds[i] = bbRsp.get(); attributeVals[i] = bbRsp.get(); } setPlayerApplicationSettingValuesNative(getByteAddress(mAvrcpRemoteDevice.mBTDevice), numAttributes, attributeIds, attributeVals); break; case AvrcpControllerConstants.MESSAGE_PROCESS_CONNECTION_CHANGE: int newState = msg.arg1; int oldState = msg.arg2; BluetoothDevice rtDevice = (BluetoothDevice)msg.obj; if ((newState == BluetoothProfile.STATE_CONNECTED) && (oldState == BluetoothProfile.STATE_DISCONNECTED)) { /* We create RemoteDevice and MediaPlayerList here * Now playing list after RC features */ if(mAvrcpRemoteDevice == null){ mAvrcpRemoteDevice = new RemoteDevice(rtDevice); /* Remote will have a player irrespective of AVRCP Version * Create a Default player, we will add entries in case Browsing * is supported by remote */ if(mRemoteMediaPlayers == null) { mRemoteMediaPlayers = new RemoteMediaPlayers(mAvrcpRemoteDevice); PlayerInfo mPlayer = new PlayerInfo(); mPlayer.mPlayerId = 0; mRemoteMediaPlayers.addPlayer(mPlayer); mRemoteMediaPlayers.setAddressedPlayer(mPlayer); } } } else if ((newState == BluetoothProfile.STATE_DISCONNECTED) && (oldState == BluetoothProfile.STATE_CONNECTED)) /* connection down */ { resetRemoteData(); mHandler.removeCallbacksAndMessages(null); } /* * Send intent now */ Intent intent = new Intent(BluetoothAvrcpController.ACTION_CONNECTION_STATE_CHANGED); intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, oldState); intent.putExtra(BluetoothProfile.EXTRA_STATE, newState); intent.putExtra(BluetoothDevice.EXTRA_DEVICE, rtDevice); // intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); sendBroadcast(intent, ProfileService.BLUETOOTH_PERM); break; case AvrcpControllerConstants.MESSAGE_PROCESS_RC_FEATURES: if(mAvrcpRemoteDevice == null) break; mAvrcpRemoteDevice.mRemoteFeatures = msg.arg1; /* in case of AVRCP version < 1.3, no need to add track info */ if(mAvrcpRemoteDevice.isMetaDataSupported()) { if(mRemoteNowPlayingList == null) mRemoteNowPlayingList = new NowPlaying(mAvrcpRemoteDevice); TrackInfo mTrack = new TrackInfo(); /* First element of NowPlayingList will be current Track * for 1.3 this will be the only song * for >= 1.4, others songs will have non-zero UID */ mTrack.mItemUid = 0; mRemoteNowPlayingList.addTrack(mTrack); mRemoteNowPlayingList.setCurrTrack(mTrack); } break; case AvrcpControllerConstants.MESSAGE_PROCESS_SET_ABS_VOL_CMD: mAvrcpRemoteDevice.mAbsVolNotificationState = AvrcpControllerConstants.DEFER_VOLUME_CHANGE_RSP; setAbsVolume(msg.arg1, msg.arg2); break; case AvrcpControllerConstants.MESSAGE_PROCESS_REGISTER_ABS_VOL_NOTIFICATION: /* start BroadcastReceiver now */ IntentFilter filter = new IntentFilter(AudioManager.VOLUME_CHANGED_ACTION); mAvrcpRemoteDevice.mNotificationLabel = msg.arg1; mAvrcpRemoteDevice.mAbsVolNotificationState = AvrcpControllerConstants.SEND_VOLUME_CHANGE_RSP; registerReceiver(mBroadcastReceiver, filter); int maxVolume = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC); int currIndex = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC); int percentageVol = ((currIndex* AvrcpControllerConstants.ABS_VOL_BASE)/maxVolume); Log.d(TAG," Sending Interim Response = "+ percentageVol + " label " + msg.arg1); sendRegisterAbsVolRspNative(getByteAddress(mAvrcpRemoteDevice.mBTDevice), (byte)AvrcpControllerConstants.NOTIFICATION_RSP_TYPE_INTERIM, percentageVol, mAvrcpRemoteDevice.mNotificationLabel); break; case AvrcpControllerConstants.MESSAGE_PROCESS_TRACK_CHANGED: if(mRemoteNowPlayingList != null) { mRemoteNowPlayingList.updateCurrentTrack((TrackInfo)msg.obj); if (!mBroadcastMetadata) { Log.d(TAG, "Metadata is not broadcasted, ignoring."); return; } broadcastMetaDataChanged(AvrcpUtils.getMediaMetaData (mRemoteNowPlayingList.getCurrentTrack())); } break; case AvrcpControllerConstants.MESSAGE_PROCESS_PLAY_POS_CHANGED: if(mRemoteMediaPlayers != null) { mRemoteMediaPlayers.getAddressedPlayer().mPlayTime = msg.arg2; if (!mBroadcastMetadata) { Log.d(TAG, "Metadata is not broadcasted, ignoring."); return; } broadcastPlayBackStateChanged(AvrcpUtils.mapBtPlayStatustoPlayBackState (mRemoteMediaPlayers.getAddressedPlayer().mPlayStatus, mRemoteMediaPlayers.getAddressedPlayer().mPlayTime)); } if(mRemoteNowPlayingList != null) { mRemoteNowPlayingList.getCurrentTrack().mTrackLen = msg.arg1; } break; case AvrcpControllerConstants.MESSAGE_PROCESS_PLAY_STATUS_CHANGED: if(mRemoteMediaPlayers != null) { int status = msg.arg1; mRemoteMediaPlayers.getAddressedPlayer().mPlayStatus = (byte) status; if (status == AvrcpControllerConstants.PLAY_STATUS_PLAYING) { a2dpSinkService.informTGStatePlaying(mConnectedDevices.get(0), true); } else if (status == AvrcpControllerConstants.PLAY_STATUS_PAUSED || status == AvrcpControllerConstants.PLAY_STATUS_STOPPED) { a2dpSinkService.informTGStatePlaying(mConnectedDevices.get(0), false); } if (mBroadcastMetadata) { broadcastPlayBackStateChanged(AvrcpUtils.mapBtPlayStatustoPlayBackState (mRemoteMediaPlayers.getAddressedPlayer().mPlayStatus, mRemoteMediaPlayers.getAddressedPlayer().mPlayTime)); } else { Log.d(TAG, "Metadata is not broadcasted, ignoring."); return; } } break; case AvrcpControllerConstants.MESSAGE_PROCESS_SUPPORTED_PLAYER_APP_SETTING: if(mRemoteMediaPlayers != null) mRemoteMediaPlayers.getAddressedPlayer(). setSupportedPlayerAppSetting((ByteBuffer)msg.obj); break; case AvrcpControllerConstants.MESSAGE_PROCESS_PLAYER_APP_SETTING_CHANGED: if(mRemoteMediaPlayers != null) { mRemoteMediaPlayers.getAddressedPlayer(). updatePlayerAppSetting((ByteBuffer)msg.obj); broadcastPlayerAppSettingChanged(getCurrentPlayerAppSetting()); } break; } } } private void setAbsVolume(int absVol, int label) { int maxVolume = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC); int currIndex = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC); if (mAvrcpRemoteDevice.mFirstAbsVolCmdRecvd) { int newIndex = (maxVolume*absVol)/AvrcpControllerConstants.ABS_VOL_BASE; Log.d(TAG," setAbsVolume ="+absVol + " maxVol = " + maxVolume + " cur = " + currIndex + " new = "+newIndex); /* * In some cases change in percentage is not sufficient enough to warrant * change in index values which are in range of 0-15. For such cases * no action is required */ if (newIndex != currIndex) { mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, newIndex, AudioManager.FLAG_SHOW_UI); } } else { mAvrcpRemoteDevice.mFirstAbsVolCmdRecvd = true; absVol = (currIndex*AvrcpControllerConstants.ABS_VOL_BASE)/maxVolume; Log.d(TAG," SetAbsVol recvd for first time, respond with " + absVol); } sendAbsVolRspNative(getByteAddress(mAvrcpRemoteDevice.mBTDevice), absVol, label); } private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (action.equals(AudioManager.VOLUME_CHANGED_ACTION)) { int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1); if (streamType == AudioManager.STREAM_MUSIC) { int streamValue = intent .getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, -1); int streamPrevValue = intent.getIntExtra( AudioManager.EXTRA_PREV_VOLUME_STREAM_VALUE, -1); if (streamValue != -1 && streamValue != streamPrevValue) { if ((mAvrcpRemoteDevice == null) ||((mAvrcpRemoteDevice.mRemoteFeatures & AvrcpControllerConstants.BTRC_FEAT_ABSOLUTE_VOLUME) == 0) ||(mConnectedDevices.isEmpty())) return; if(mAvrcpRemoteDevice.mAbsVolNotificationState == AvrcpControllerConstants.SEND_VOLUME_CHANGE_RSP) { int maxVol = mAudioManager. getStreamMaxVolume(AudioManager.STREAM_MUSIC); int currIndex = mAudioManager. getStreamVolume(AudioManager.STREAM_MUSIC); int percentageVol = ((currIndex* AvrcpControllerConstants.ABS_VOL_BASE)/maxVol); Log.d(TAG," Abs Vol Notify Rsp Changed vol = "+ percentageVol); sendRegisterAbsVolRspNative(getByteAddress(mAvrcpRemoteDevice.mBTDevice), (byte)AvrcpControllerConstants.NOTIFICATION_RSP_TYPE_CHANGED, percentageVol, mAvrcpRemoteDevice.mNotificationLabel); } else if (mAvrcpRemoteDevice.mAbsVolNotificationState == AvrcpControllerConstants.DEFER_VOLUME_CHANGE_RSP) { Log.d(TAG," Don't Complete Notification Rsp. "); mAvrcpRemoteDevice.mAbsVolNotificationState = AvrcpControllerConstants.SEND_VOLUME_CHANGE_RSP; } } } } } }; private void handlePassthroughRsp(int id, int keyState) { Log.d(TAG, "passthrough response received as: key: " + id + " state: " + keyState); } private void onConnectionStateChanged(boolean connected, byte[] address) { BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice (Utils.getAddressStringFromByte(address)); Log.d(TAG, "onConnectionStateChanged " + connected + " " + device+ " size "+ mConnectedDevices.size()); if (device == null) return; int oldState = (mConnectedDevices.contains(device) ? BluetoothProfile.STATE_CONNECTED : BluetoothProfile.STATE_DISCONNECTED); int newState = (connected ? BluetoothProfile.STATE_CONNECTED : BluetoothProfile.STATE_DISCONNECTED); if (connected && oldState == BluetoothProfile.STATE_DISCONNECTED) { /* AVRCPControllerService supports single connection */ if(mConnectedDevices.size() > 0) { Log.d(TAG,"A Connection already exists, returning"); return; } mConnectedDevices.add(device); Message msg = mHandler.obtainMessage( AvrcpControllerConstants.MESSAGE_PROCESS_CONNECTION_CHANGE, newState, oldState, device); mHandler.sendMessage(msg); } else if (!connected && oldState == BluetoothProfile.STATE_CONNECTED) { mConnectedDevices.remove(device); Message msg = mHandler.obtainMessage( AvrcpControllerConstants.MESSAGE_PROCESS_CONNECTION_CHANGE, newState, oldState, device); mHandler.sendMessage(msg); } } private void getRcFeatures(byte[] address, int features) { BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice (Utils.getAddressStringFromByte(address)); Message msg = mHandler.obtainMessage( AvrcpControllerConstants.MESSAGE_PROCESS_RC_FEATURES, features, 0, device); mHandler.sendMessage(msg); } private void setPlayerAppSettingRsp(byte[] address, byte accepted) { /* TODO do we need to do anything here */ } private void handleRegisterNotificationAbsVol(byte[] address, byte label) { Log.d(TAG,"handleRegisterNotificationAbsVol "); BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice (Utils.getAddressStringFromByte(address)); if (!mConnectedDevices.contains(device)) return; Message msg = mHandler.obtainMessage(AvrcpControllerConstants. MESSAGE_PROCESS_REGISTER_ABS_VOL_NOTIFICATION, label, 0); mHandler.sendMessage(msg); } private void handleSetAbsVolume(byte[] address, byte absVol, byte label) { Log.d(TAG,"handleSetAbsVolume "); BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice (Utils.getAddressStringFromByte(address)); if (!mConnectedDevices.contains(device)) return; Message msg = mHandler.obtainMessage( AvrcpControllerConstants.MESSAGE_PROCESS_SET_ABS_VOL_CMD, absVol, label); mHandler.sendMessage(msg); } private void onTrackChanged(byte[] address, byte numAttributes, int[] attributes, String[] attribVals) { Log.d(TAG,"onTrackChanged "); BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice (Utils.getAddressStringFromByte(address)); if (!mConnectedDevices.contains(device)) return; TrackInfo mTrack = new TrackInfo(0, numAttributes, attributes, attribVals); Message msg = mHandler.obtainMessage(AvrcpControllerConstants. MESSAGE_PROCESS_TRACK_CHANGED, numAttributes, 0, mTrack); mHandler.sendMessage(msg); } private void onPlayPositionChanged(byte[] address, int songLen, int currSongPosition) { Log.d(TAG,"onPlayPositionChanged "); BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice (Utils.getAddressStringFromByte(address)); if (!mConnectedDevices.contains(device)) return; Message msg = mHandler.obtainMessage(AvrcpControllerConstants. MESSAGE_PROCESS_PLAY_POS_CHANGED, songLen, currSongPosition); mHandler.sendMessage(msg); } private void onPlayStatusChanged(byte[] address, byte playStatus) { if(DBG) Log.d(TAG,"onPlayStatusChanged " + playStatus); BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice (Utils.getAddressStringFromByte(address)); if (!mConnectedDevices.contains(device)) return; Message msg = mHandler.obtainMessage(AvrcpControllerConstants. MESSAGE_PROCESS_PLAY_STATUS_CHANGED, playStatus, 0); mHandler.sendMessage(msg); } private void handlePlayerAppSetting(byte[] address, byte[] playerAttribRsp, int rspLen) { Log.d(TAG,"handlePlayerAppSetting rspLen = " + rspLen); BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice (Utils.getAddressStringFromByte(address)); if (!mConnectedDevices.contains(device)) return; ByteBuffer bb = ByteBuffer.wrap(playerAttribRsp, 0, rspLen); Message msg = mHandler.obtainMessage(AvrcpControllerConstants. MESSAGE_PROCESS_SUPPORTED_PLAYER_APP_SETTING, 0, 0, bb); mHandler.sendMessage(msg); } private void onPlayerAppSettingChanged(byte[] address, byte[] playerAttribRsp, int rspLen) { Log.d(TAG,"onPlayerAppSettingChanged "); BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice (Utils.getAddressStringFromByte(address)); if (!mConnectedDevices.contains(device)) return; ByteBuffer bb = ByteBuffer.wrap(playerAttribRsp, 0, rspLen); Message msg = mHandler.obtainMessage(AvrcpControllerConstants. MESSAGE_PROCESS_PLAYER_APP_SETTING_CHANGED, 0, 0, bb); mHandler.sendMessage(msg); } private void handleGroupNavigationRsp(int id, int keyState) { Log.d(TAG, "group navigation response received as: key: " + id + " state: " + keyState); } private byte[] getByteAddress(BluetoothDevice device) { return Utils.getBytesFromAddress(device.getAddress()); } @Override public void dump(StringBuilder sb) { super.dump(sb); } private native static void classInitNative(); private native void initNative(); private native void cleanupNative(); private native boolean sendPassThroughCommandNative(byte[] address, int keyCode, int keyState); private native boolean sendGroupNavigationCommandNative(byte[] address, int keyCode, int keyState); private native void setPlayerApplicationSettingValuesNative(byte[] address, byte numAttrib, byte[] atttibIds, byte[]attribVal); /* This api is used to send response to SET_ABS_VOL_CMD */ private native void sendAbsVolRspNative(byte[] address, int absVol, int label); /* This api is used to inform remote for any volume level changes */ private native void sendRegisterAbsVolRspNative(byte[] address, byte rspType, int absVol, int label); }