/* * 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.a2dpsink; import android.bluetooth.BluetoothAudioConfig; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothProfile; import android.bluetooth.IBluetoothA2dpSink; import android.content.Intent; import android.provider.Settings; import android.util.Log; import com.android.bluetooth.avrcpcontroller.AvrcpControllerService; import com.android.bluetooth.a2dpsink.mbs.A2dpMediaBrowserService; import com.android.bluetooth.btservice.ProfileService; import com.android.bluetooth.Utils; import java.util.ArrayList; import java.util.List; /** * Provides Bluetooth A2DP Sink profile, as a service in the Bluetooth application. * @hide */ public class A2dpSinkService extends ProfileService { private static final boolean DBG = true; private static final String TAG = "A2dpSinkService"; private A2dpSinkStateMachine mStateMachine; private static A2dpSinkService sA2dpSinkService; protected String getName() { return TAG; } protected IProfileServiceBinder initBinder() { return new BluetoothA2dpSinkBinder(this); } protected boolean start() { if (DBG) { Log.d(TAG, "start()"); } // Start the media browser service. Intent startIntent = new Intent(this, A2dpMediaBrowserService.class); startService(startIntent); mStateMachine = A2dpSinkStateMachine.make(this, this); setA2dpSinkService(this); return true; } protected boolean stop() { if (DBG) { Log.d(TAG, "stop()"); } if(mStateMachine != null) { mStateMachine.doQuit(); } Intent stopIntent = new Intent(this, A2dpMediaBrowserService.class); stopService(stopIntent); return true; } protected boolean cleanup() { if (mStateMachine!= null) { mStateMachine.cleanup(); } clearA2dpSinkService(); return true; } //API Methods public static synchronized A2dpSinkService getA2dpSinkService(){ if (sA2dpSinkService != null && sA2dpSinkService.isAvailable()) { if (DBG) Log.d(TAG, "getA2dpSinkService(): returning " + sA2dpSinkService); return sA2dpSinkService; } if (DBG) { if (sA2dpSinkService == null) { Log.d(TAG, "getA2dpSinkService(): service is NULL"); } else if (!(sA2dpSinkService.isAvailable())) { Log.d(TAG,"getA2dpSinkService(): service is not available"); } } return null; } private static synchronized void setA2dpSinkService(A2dpSinkService instance) { if (instance != null && instance.isAvailable()) { if (DBG) Log.d(TAG, "setA2dpSinkService(): set to: " + sA2dpSinkService); sA2dpSinkService = instance; } else { if (DBG) { if (sA2dpSinkService == null) { Log.d(TAG, "setA2dpSinkService(): service not available"); } else if (!sA2dpSinkService.isAvailable()) { Log.d(TAG,"setA2dpSinkService(): service is cleaning up"); } } } } private static synchronized void clearA2dpSinkService() { sA2dpSinkService = null; } public boolean connect(BluetoothDevice device) { enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission"); int connectionState = mStateMachine.getConnectionState(device); if (connectionState == BluetoothProfile.STATE_CONNECTED || connectionState == BluetoothProfile.STATE_CONNECTING) { return false; } mStateMachine.sendMessage(A2dpSinkStateMachine.CONNECT, device); return true; } boolean disconnect(BluetoothDevice device) { enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission"); int connectionState = mStateMachine.getConnectionState(device); if (connectionState != BluetoothProfile.STATE_CONNECTED && connectionState != BluetoothProfile.STATE_CONNECTING) { return false; } mStateMachine.sendMessage(A2dpSinkStateMachine.DISCONNECT, device); return true; } public List getConnectedDevices() { enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); return mStateMachine.getConnectedDevices(); } List getDevicesMatchingConnectionStates(int[] states) { enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); return mStateMachine.getDevicesMatchingConnectionStates(states); } int getConnectionState(BluetoothDevice device) { enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); return mStateMachine.getConnectionState(device); } public boolean setPriority(BluetoothDevice device, int priority) { enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission"); Settings.Global.putInt(getContentResolver(), Settings.Global.getBluetoothA2dpSrcPriorityKey(device.getAddress()), priority); if (DBG) { Log.d(TAG,"Saved priority " + device + " = " + priority); } return true; } public int getPriority(BluetoothDevice device) { enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission"); int priority = Settings.Global.getInt(getContentResolver(), Settings.Global.getBluetoothA2dpSrcPriorityKey(device.getAddress()), BluetoothProfile.PRIORITY_UNDEFINED); return priority; } /** * Called by AVRCP controller to provide information about the last user intent on CT. * * If the user has pressed play in the last attempt then A2DP Sink component will grant focus to * any incoming sound from the phone (and also retain focus for a few seconds before * relinquishing. On the other hand if the user has pressed pause/stop then the A2DP sink * component will take the focus away but also notify the stack to throw away incoming data. */ public void informAvrcpPassThroughCmd(BluetoothDevice device, int keyCode, int keyState) { if (mStateMachine != null) { if (keyCode == AvrcpControllerService.PASS_THRU_CMD_ID_PLAY && keyState == AvrcpControllerService.KEY_STATE_RELEASED) { mStateMachine.sendMessage(A2dpSinkStateMachine.EVENT_AVRCP_CT_PLAY); } else if ((keyCode == AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE || keyCode == AvrcpControllerService.PASS_THRU_CMD_ID_STOP) && keyState == AvrcpControllerService.KEY_STATE_RELEASED) { mStateMachine.sendMessage(A2dpSinkStateMachine.EVENT_AVRCP_CT_PAUSE); } } } /** * Called by AVRCP controller to provide information about the last user intent on TG. * * Tf the user has pressed pause on the TG then we can preempt streaming music. This is opposed * to when the streaming stops abruptly (jitter) in which case we will wait for sometime before * stopping playback. */ public void informTGStatePlaying(BluetoothDevice device, boolean isPlaying) { if (mStateMachine != null) { if (!isPlaying) { mStateMachine.sendMessage(A2dpSinkStateMachine.EVENT_AVRCP_TG_PAUSE); } else { mStateMachine.sendMessage(A2dpSinkStateMachine.EVENT_AVRCP_TG_PLAY); } } } synchronized boolean isA2dpPlaying(BluetoothDevice device) { enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); if (DBG) { Log.d(TAG, "isA2dpPlaying(" + device + ")"); } return mStateMachine.isPlaying(device); } BluetoothAudioConfig getAudioConfig(BluetoothDevice device) { enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); return mStateMachine.getAudioConfig(device); } //Binder object: Must be static class or memory leak may occur private static class BluetoothA2dpSinkBinder extends IBluetoothA2dpSink.Stub implements IProfileServiceBinder { private A2dpSinkService mService; private A2dpSinkService getService() { if (!Utils.checkCaller()) { Log.w(TAG,"A2dp call not allowed for non-active user"); return null; } if (mService != null && mService.isAvailable()) { return mService; } return null; } BluetoothA2dpSinkBinder(A2dpSinkService svc) { mService = svc; } public boolean cleanup() { mService = null; return true; } public boolean connect(BluetoothDevice device) { A2dpSinkService service = getService(); if (service == null) return false; return service.connect(device); } public boolean disconnect(BluetoothDevice device) { A2dpSinkService service = getService(); if (service == null) return false; return service.disconnect(device); } public List getConnectedDevices() { A2dpSinkService service = getService(); if (service == null) return new ArrayList(0); return service.getConnectedDevices(); } public List getDevicesMatchingConnectionStates(int[] states) { A2dpSinkService service = getService(); if (service == null) return new ArrayList(0); return service.getDevicesMatchingConnectionStates(states); } public int getConnectionState(BluetoothDevice device) { A2dpSinkService service = getService(); if (service == null) return BluetoothProfile.STATE_DISCONNECTED; return service.getConnectionState(device); } public boolean isA2dpPlaying(BluetoothDevice device) { A2dpSinkService service = getService(); if (service == null) return false; return service.isA2dpPlaying(device); } public boolean setPriority(BluetoothDevice device, int priority) { A2dpSinkService service = getService(); if (service == null) return false; return service.setPriority(device, priority); } public int getPriority(BluetoothDevice device) { A2dpSinkService service = getService(); if (service == null) return BluetoothProfile.PRIORITY_UNDEFINED; return service.getPriority(device); } public BluetoothAudioConfig getAudioConfig(BluetoothDevice device) { A2dpSinkService service = getService(); if (service == null) return null; return service.getAudioConfig(device); } }; @Override public void dump(StringBuilder sb) { super.dump(sb); if (mStateMachine != null) { mStateMachine.dump(sb); } } }