blob: 313764e83830faacfa33dac1c2f1a85006acb7a7 [file] [log] [blame]
/*
* 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<BluetoothDevice> mConnectedDevices
= new ArrayList<BluetoothDevice>();
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<BluetoothDevice> getConnectedDevices() {
enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
return mConnectedDevices;
}
List<BluetoothDevice> 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<BluetoothDevice>();
}
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 <id, val>
*/
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<BluetoothDevice> getConnectedDevices() {
AvrcpControllerService service = getService();
if (service == null) return new ArrayList<BluetoothDevice>(0);
return service.getConnectedDevices();
}
public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
AvrcpControllerService service = getService();
if (service == null) return new ArrayList<BluetoothDevice>(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);
}