Loading media/java/android/media/AudioManager.java +127 −1 Original line number Diff line number Diff line Loading @@ -690,6 +690,132 @@ public class AudioManager { } } //==================================================================== // Bluetooth SCO control /** * @hide * TODO unhide for SDK * Sticky broadcast intent action indicating that the bluetoooth SCO audio * connection state has changed. The intent contains on extra {@link EXTRA_SCO_AUDIO_STATE} * indicating the new state which is either {@link #SCO_AUDIO_STATE_DISCONNECTED} * or {@link #SCO_AUDIO_STATE_CONNECTED} * * @see #startBluetoothSco() */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_SCO_AUDIO_STATE_CHANGED = "android.media.SCO_AUDIO_STATE_CHANGED"; /** * @hide * TODO unhide for SDK * Extra for intent {@link #ACTION_SCO_AUDIO_STATE_CHANGED} containing the new * bluetooth SCO connection state. */ public static final String EXTRA_SCO_AUDIO_STATE = "android.media.extra.SCO_AUDIO_STATE"; /** * @hide * TODO unhide for SDK * Value for extra {@link #EXTRA_SCO_AUDIO_STATE} indicating that the * SCO audio channel is not established */ public static final int SCO_AUDIO_STATE_DISCONNECTED = 0; /** * @hide * TODO unhide for SDK * Value for extra {@link #EXTRA_SCO_AUDIO_STATE} indicating that the * SCO audio channel is established */ public static final int SCO_AUDIO_STATE_CONNECTED = 1; /** * @hide * TODO unhide for SDK * Value for extra {@link #EXTRA_SCO_AUDIO_STATE} indicating that * there was an error trying to obtain the state */ public static final int SCO_AUDIO_STATE_ERROR = -1; /** * @hide * TODO unhide for SDK * Indicates if current platform supports use of SCO for off call use cases. * Application wanted to use bluetooth SCO audio when the phone is not in call * must first call thsi method to make sure that the platform supports this * feature. * @return true if bluetooth SCO can be used for audio when not in call * false otherwise * @see #startBluetoothSco() */ public boolean isBluetoothScoAvailableOffCall() { return mContext.getResources().getBoolean( com.android.internal.R.bool.config_bluetooth_sco_off_call); } /** * @hide * TODO unhide for SDK * Start bluetooth SCO audio connection. * <p>Requires Permission: * {@link android.Manifest.permission#MODIFY_AUDIO_SETTINGS}. * <p>This method can be used by applications wanting to send and received audio * to/from a bluetooth SCO headset while the phone is not in call. * <p>As the SCO connection establishment can take several seconds, * applications should not rely on the connection to be available when the method * returns but instead register to receive the intent {@link #ACTION_SCO_AUDIO_STATE_CHANGED} * and wait for the state to be {@link #SCO_AUDIO_STATE_CONNECTED}. * <p>As the connection is not guaranteed to succeed, applications must wait for this intent with * a timeout. * <p>When finished with the SCO connection or if the establishment times out, * the application must call {@link #stopBluetoothSco()} to clear the request and turn * down the bluetooth connection. * <p>Even if a SCO connection is established, the following restrictions apply on audio * output streams so that they can be routed to SCO headset: * - the stream type must be {@link #STREAM_VOICE_CALL} or {@link #STREAM_BLUETOOTH_SCO} * - the format must be mono * - the sampling must be 16kHz or 8kHz * <p>The following restrictions apply on input streams: * - the format must be mono * - the sampling must be 8kHz * * <p>Note that the phone application always has the priority on the usage of the SCO * connection for telephony. If this method is called while the phone is in call * it will be ignored. Similarly, if a call is received or sent while an application * is using the SCO connection, the connection will be lost for the application and NOT * returned automatically when the call ends. * @see #stopBluetoothSco() * @see #ACTION_SCO_AUDIO_STATE_CHANGED */ public void startBluetoothSco(){ IAudioService service = getService(); try { service.startBluetoothSco(mICallBack); } catch (RemoteException e) { Log.e(TAG, "Dead object in startBluetoothSco", e); } } /** * @hide * TODO unhide for SDK * Stop bluetooth SCO audio connection. * <p>Requires Permission: * {@link android.Manifest.permission#MODIFY_AUDIO_SETTINGS}. * <p>This method must be called by applications having requested the use of * bluetooth SCO audio with {@link #startBluetoothSco()} * when finished with the SCO connection or if the establishment times out. * @see #startBluetoothSco() */ public void stopBluetoothSco(){ IAudioService service = getService(); try { service.stopBluetoothSco(mICallBack); } catch (RemoteException e) { Log.e(TAG, "Dead object in stopBluetoothSco", e); } } /** * Request use of Bluetooth SCO headset for communications. * <p> Loading media/java/android/media/AudioService.java +195 −0 Original line number Diff line number Diff line Loading @@ -240,6 +240,15 @@ public class AudioService extends IAudioService.Stub { // The last process to have called setMode() is at the top of the list. private ArrayList <SetModeDeathHandler> mSetModeDeathHandlers = new ArrayList <SetModeDeathHandler>(); // List of clients having issued a SCO start request private ArrayList <ScoClient> mScoClients = new ArrayList <ScoClient>(); // BluetoothHeadset API to control SCO connection private BluetoothHeadset mBluetoothHeadset; // Bluetooth headset connection state private boolean mBluetoothHeadsetConnected; /////////////////////////////////////////////////////////////////////////// // Construction /////////////////////////////////////////////////////////////////////////// Loading Loading @@ -267,12 +276,17 @@ public class AudioService extends IAudioService.Stub { AudioSystem.setErrorCallback(mAudioSystemCallback); loadSoundEffects(); mBluetoothHeadsetConnected = false; mBluetoothHeadset = new BluetoothHeadset(context, mBluetoothHeadsetServiceListener); // Register for device connection intent broadcasts. IntentFilter intentFilter = new IntentFilter(Intent.ACTION_HEADSET_PLUG); intentFilter.addAction(BluetoothA2dp.ACTION_SINK_STATE_CHANGED); intentFilter.addAction(BluetoothHeadset.ACTION_STATE_CHANGED); intentFilter.addAction(Intent.ACTION_DOCK_EVENT); intentFilter.addAction(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED); context.registerReceiver(mReceiver, intentFilter); // Register for media button intent broadcasts. Loading Loading @@ -705,6 +719,10 @@ public class AudioService extends IAudioService.Stub { mSetModeDeathHandlers.add(0, hdlr); hdlr.setMode(mode); } if (mode != AudioSystem.MODE_NORMAL) { clearAllScoClients(); } } } int streamType = getActiveStreamType(AudioManager.USE_DEFAULT_STREAM_TYPE); Loading Loading @@ -909,6 +927,157 @@ public class AudioService extends IAudioService.Stub { } } /** @see AudioManager#startBluetoothSco() */ public void startBluetoothSco(IBinder cb){ if (!checkAudioSettingsPermission("startBluetoothSco()")) { return; } ScoClient client = getScoClient(cb); client.incCount(); } /** @see AudioManager#stopBluetoothSco() */ public void stopBluetoothSco(IBinder cb){ if (!checkAudioSettingsPermission("stopBluetoothSco()")) { return; } ScoClient client = getScoClient(cb); client.decCount(); } private class ScoClient implements IBinder.DeathRecipient { private IBinder mCb; // To be notified of client's death private int mStartcount; // number of SCO connections started by this client ScoClient(IBinder cb) { mCb = cb; mStartcount = 0; } public void binderDied() { synchronized(mScoClients) { Log.w(TAG, "SCO client died"); int index = mScoClients.indexOf(this); if (index < 0) { Log.w(TAG, "unregistered SCO client died"); } else { clearCount(true); mScoClients.remove(this); } } } public void incCount() { synchronized(mScoClients) { requestScoState(BluetoothHeadset.AUDIO_STATE_CONNECTED); if (mStartcount == 0) { try { mCb.linkToDeath(this, 0); } catch (RemoteException e) { // client has already died! Log.w(TAG, "ScoClient incCount() could not link to "+mCb+" binder death"); } } mStartcount++; } } public void decCount() { synchronized(mScoClients) { if (mStartcount == 0) { Log.w(TAG, "ScoClient.decCount() already 0"); } else { mStartcount--; if (mStartcount == 0) { mCb.unlinkToDeath(this, 0); } requestScoState(BluetoothHeadset.AUDIO_STATE_DISCONNECTED); } } } public void clearCount(boolean stopSco) { synchronized(mScoClients) { mStartcount = 0; mCb.unlinkToDeath(this, 0); if (stopSco) { requestScoState(BluetoothHeadset.AUDIO_STATE_DISCONNECTED); } } } public int getCount() { return mStartcount; } public IBinder getBinder() { return mCb; } public int totalCount() { synchronized(mScoClients) { int count = 0; int size = mScoClients.size(); for (int i = 0; i < size; i++) { count += mScoClients.get(i).getCount(); } return count; } } private void requestScoState(int state) { if (totalCount() == 0 && mBluetoothHeadsetConnected && AudioService.this.mMode == AudioSystem.MODE_NORMAL) { if (state == BluetoothHeadset.AUDIO_STATE_CONNECTED) { mBluetoothHeadset.startVoiceRecognition(); } else { mBluetoothHeadset.stopVoiceRecognition(); } } } } public ScoClient getScoClient(IBinder cb) { synchronized(mScoClients) { ScoClient client; int size = mScoClients.size(); for (int i = 0; i < size; i++) { client = mScoClients.get(i); if (client.getBinder() == cb) return client; } client = new ScoClient(cb); mScoClients.add(client); return client; } } public void clearAllScoClients() { synchronized(mScoClients) { int size = mScoClients.size(); for (int i = 0; i < size; i++) { mScoClients.get(i).clearCount(false); } } } private BluetoothHeadset.ServiceListener mBluetoothHeadsetServiceListener = new BluetoothHeadset.ServiceListener() { public void onServiceConnected() { if (mBluetoothHeadset != null && mBluetoothHeadset.getState() == BluetoothHeadset.STATE_CONNECTED) { mBluetoothHeadsetConnected = true; } } public void onServiceDisconnected() { if (mBluetoothHeadset != null && mBluetoothHeadset.getState() == BluetoothHeadset.STATE_DISCONNECTED) { mBluetoothHeadsetConnected = false; clearAllScoClients(); } } }; /////////////////////////////////////////////////////////////////////////// // Internal methods Loading Loading @@ -1577,11 +1746,14 @@ public class AudioService extends IAudioService.Stub { AudioSystem.DEVICE_STATE_UNAVAILABLE, address); mConnectedDevices.remove(device); mBluetoothHeadsetConnected = false; clearAllScoClients(); } else if (!isConnected && state == BluetoothHeadset.STATE_CONNECTED) { AudioSystem.setDeviceConnectionState(device, AudioSystem.DEVICE_STATE_AVAILABLE, address); mConnectedDevices.put(new Integer(device), address); mBluetoothHeadsetConnected = true; } } else if (action.equals(Intent.ACTION_HEADSET_PLUG)) { int state = intent.getIntExtra("state", 0); Loading Loading @@ -1614,6 +1786,29 @@ public class AudioService extends IAudioService.Stub { mConnectedDevices.put( new Integer(AudioSystem.DEVICE_OUT_WIRED_HEADPHONE), ""); } } } else if (action.equals(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED)) { int state = intent.getIntExtra(BluetoothHeadset.EXTRA_AUDIO_STATE, BluetoothHeadset.STATE_ERROR); synchronized (mScoClients) { if (!mScoClients.isEmpty()) { switch (state) { case BluetoothHeadset.AUDIO_STATE_CONNECTED: state = AudioManager.SCO_AUDIO_STATE_CONNECTED; break; case BluetoothHeadset.AUDIO_STATE_DISCONNECTED: state = AudioManager.SCO_AUDIO_STATE_DISCONNECTED; break; default: state = AudioManager.SCO_AUDIO_STATE_ERROR; break; } if (state != AudioManager.SCO_AUDIO_STATE_ERROR) { Intent newIntent = new Intent(AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED); newIntent.putExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, state); mContext.sendStickyBroadcast(newIntent); } } } } } } Loading media/java/android/media/IAudioService.aidl +4 −0 Original line number Diff line number Diff line Loading @@ -82,4 +82,8 @@ interface IAudioService { void registerMediaButtonEventReceiver(in ComponentName eventReceiver); void unregisterMediaButtonEventReceiver(in ComponentName eventReceiver); void startBluetoothSco(IBinder cb); void stopBluetoothSco(IBinder cb); } Loading
media/java/android/media/AudioManager.java +127 −1 Original line number Diff line number Diff line Loading @@ -690,6 +690,132 @@ public class AudioManager { } } //==================================================================== // Bluetooth SCO control /** * @hide * TODO unhide for SDK * Sticky broadcast intent action indicating that the bluetoooth SCO audio * connection state has changed. The intent contains on extra {@link EXTRA_SCO_AUDIO_STATE} * indicating the new state which is either {@link #SCO_AUDIO_STATE_DISCONNECTED} * or {@link #SCO_AUDIO_STATE_CONNECTED} * * @see #startBluetoothSco() */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_SCO_AUDIO_STATE_CHANGED = "android.media.SCO_AUDIO_STATE_CHANGED"; /** * @hide * TODO unhide for SDK * Extra for intent {@link #ACTION_SCO_AUDIO_STATE_CHANGED} containing the new * bluetooth SCO connection state. */ public static final String EXTRA_SCO_AUDIO_STATE = "android.media.extra.SCO_AUDIO_STATE"; /** * @hide * TODO unhide for SDK * Value for extra {@link #EXTRA_SCO_AUDIO_STATE} indicating that the * SCO audio channel is not established */ public static final int SCO_AUDIO_STATE_DISCONNECTED = 0; /** * @hide * TODO unhide for SDK * Value for extra {@link #EXTRA_SCO_AUDIO_STATE} indicating that the * SCO audio channel is established */ public static final int SCO_AUDIO_STATE_CONNECTED = 1; /** * @hide * TODO unhide for SDK * Value for extra {@link #EXTRA_SCO_AUDIO_STATE} indicating that * there was an error trying to obtain the state */ public static final int SCO_AUDIO_STATE_ERROR = -1; /** * @hide * TODO unhide for SDK * Indicates if current platform supports use of SCO for off call use cases. * Application wanted to use bluetooth SCO audio when the phone is not in call * must first call thsi method to make sure that the platform supports this * feature. * @return true if bluetooth SCO can be used for audio when not in call * false otherwise * @see #startBluetoothSco() */ public boolean isBluetoothScoAvailableOffCall() { return mContext.getResources().getBoolean( com.android.internal.R.bool.config_bluetooth_sco_off_call); } /** * @hide * TODO unhide for SDK * Start bluetooth SCO audio connection. * <p>Requires Permission: * {@link android.Manifest.permission#MODIFY_AUDIO_SETTINGS}. * <p>This method can be used by applications wanting to send and received audio * to/from a bluetooth SCO headset while the phone is not in call. * <p>As the SCO connection establishment can take several seconds, * applications should not rely on the connection to be available when the method * returns but instead register to receive the intent {@link #ACTION_SCO_AUDIO_STATE_CHANGED} * and wait for the state to be {@link #SCO_AUDIO_STATE_CONNECTED}. * <p>As the connection is not guaranteed to succeed, applications must wait for this intent with * a timeout. * <p>When finished with the SCO connection or if the establishment times out, * the application must call {@link #stopBluetoothSco()} to clear the request and turn * down the bluetooth connection. * <p>Even if a SCO connection is established, the following restrictions apply on audio * output streams so that they can be routed to SCO headset: * - the stream type must be {@link #STREAM_VOICE_CALL} or {@link #STREAM_BLUETOOTH_SCO} * - the format must be mono * - the sampling must be 16kHz or 8kHz * <p>The following restrictions apply on input streams: * - the format must be mono * - the sampling must be 8kHz * * <p>Note that the phone application always has the priority on the usage of the SCO * connection for telephony. If this method is called while the phone is in call * it will be ignored. Similarly, if a call is received or sent while an application * is using the SCO connection, the connection will be lost for the application and NOT * returned automatically when the call ends. * @see #stopBluetoothSco() * @see #ACTION_SCO_AUDIO_STATE_CHANGED */ public void startBluetoothSco(){ IAudioService service = getService(); try { service.startBluetoothSco(mICallBack); } catch (RemoteException e) { Log.e(TAG, "Dead object in startBluetoothSco", e); } } /** * @hide * TODO unhide for SDK * Stop bluetooth SCO audio connection. * <p>Requires Permission: * {@link android.Manifest.permission#MODIFY_AUDIO_SETTINGS}. * <p>This method must be called by applications having requested the use of * bluetooth SCO audio with {@link #startBluetoothSco()} * when finished with the SCO connection or if the establishment times out. * @see #startBluetoothSco() */ public void stopBluetoothSco(){ IAudioService service = getService(); try { service.stopBluetoothSco(mICallBack); } catch (RemoteException e) { Log.e(TAG, "Dead object in stopBluetoothSco", e); } } /** * Request use of Bluetooth SCO headset for communications. * <p> Loading
media/java/android/media/AudioService.java +195 −0 Original line number Diff line number Diff line Loading @@ -240,6 +240,15 @@ public class AudioService extends IAudioService.Stub { // The last process to have called setMode() is at the top of the list. private ArrayList <SetModeDeathHandler> mSetModeDeathHandlers = new ArrayList <SetModeDeathHandler>(); // List of clients having issued a SCO start request private ArrayList <ScoClient> mScoClients = new ArrayList <ScoClient>(); // BluetoothHeadset API to control SCO connection private BluetoothHeadset mBluetoothHeadset; // Bluetooth headset connection state private boolean mBluetoothHeadsetConnected; /////////////////////////////////////////////////////////////////////////// // Construction /////////////////////////////////////////////////////////////////////////// Loading Loading @@ -267,12 +276,17 @@ public class AudioService extends IAudioService.Stub { AudioSystem.setErrorCallback(mAudioSystemCallback); loadSoundEffects(); mBluetoothHeadsetConnected = false; mBluetoothHeadset = new BluetoothHeadset(context, mBluetoothHeadsetServiceListener); // Register for device connection intent broadcasts. IntentFilter intentFilter = new IntentFilter(Intent.ACTION_HEADSET_PLUG); intentFilter.addAction(BluetoothA2dp.ACTION_SINK_STATE_CHANGED); intentFilter.addAction(BluetoothHeadset.ACTION_STATE_CHANGED); intentFilter.addAction(Intent.ACTION_DOCK_EVENT); intentFilter.addAction(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED); context.registerReceiver(mReceiver, intentFilter); // Register for media button intent broadcasts. Loading Loading @@ -705,6 +719,10 @@ public class AudioService extends IAudioService.Stub { mSetModeDeathHandlers.add(0, hdlr); hdlr.setMode(mode); } if (mode != AudioSystem.MODE_NORMAL) { clearAllScoClients(); } } } int streamType = getActiveStreamType(AudioManager.USE_DEFAULT_STREAM_TYPE); Loading Loading @@ -909,6 +927,157 @@ public class AudioService extends IAudioService.Stub { } } /** @see AudioManager#startBluetoothSco() */ public void startBluetoothSco(IBinder cb){ if (!checkAudioSettingsPermission("startBluetoothSco()")) { return; } ScoClient client = getScoClient(cb); client.incCount(); } /** @see AudioManager#stopBluetoothSco() */ public void stopBluetoothSco(IBinder cb){ if (!checkAudioSettingsPermission("stopBluetoothSco()")) { return; } ScoClient client = getScoClient(cb); client.decCount(); } private class ScoClient implements IBinder.DeathRecipient { private IBinder mCb; // To be notified of client's death private int mStartcount; // number of SCO connections started by this client ScoClient(IBinder cb) { mCb = cb; mStartcount = 0; } public void binderDied() { synchronized(mScoClients) { Log.w(TAG, "SCO client died"); int index = mScoClients.indexOf(this); if (index < 0) { Log.w(TAG, "unregistered SCO client died"); } else { clearCount(true); mScoClients.remove(this); } } } public void incCount() { synchronized(mScoClients) { requestScoState(BluetoothHeadset.AUDIO_STATE_CONNECTED); if (mStartcount == 0) { try { mCb.linkToDeath(this, 0); } catch (RemoteException e) { // client has already died! Log.w(TAG, "ScoClient incCount() could not link to "+mCb+" binder death"); } } mStartcount++; } } public void decCount() { synchronized(mScoClients) { if (mStartcount == 0) { Log.w(TAG, "ScoClient.decCount() already 0"); } else { mStartcount--; if (mStartcount == 0) { mCb.unlinkToDeath(this, 0); } requestScoState(BluetoothHeadset.AUDIO_STATE_DISCONNECTED); } } } public void clearCount(boolean stopSco) { synchronized(mScoClients) { mStartcount = 0; mCb.unlinkToDeath(this, 0); if (stopSco) { requestScoState(BluetoothHeadset.AUDIO_STATE_DISCONNECTED); } } } public int getCount() { return mStartcount; } public IBinder getBinder() { return mCb; } public int totalCount() { synchronized(mScoClients) { int count = 0; int size = mScoClients.size(); for (int i = 0; i < size; i++) { count += mScoClients.get(i).getCount(); } return count; } } private void requestScoState(int state) { if (totalCount() == 0 && mBluetoothHeadsetConnected && AudioService.this.mMode == AudioSystem.MODE_NORMAL) { if (state == BluetoothHeadset.AUDIO_STATE_CONNECTED) { mBluetoothHeadset.startVoiceRecognition(); } else { mBluetoothHeadset.stopVoiceRecognition(); } } } } public ScoClient getScoClient(IBinder cb) { synchronized(mScoClients) { ScoClient client; int size = mScoClients.size(); for (int i = 0; i < size; i++) { client = mScoClients.get(i); if (client.getBinder() == cb) return client; } client = new ScoClient(cb); mScoClients.add(client); return client; } } public void clearAllScoClients() { synchronized(mScoClients) { int size = mScoClients.size(); for (int i = 0; i < size; i++) { mScoClients.get(i).clearCount(false); } } } private BluetoothHeadset.ServiceListener mBluetoothHeadsetServiceListener = new BluetoothHeadset.ServiceListener() { public void onServiceConnected() { if (mBluetoothHeadset != null && mBluetoothHeadset.getState() == BluetoothHeadset.STATE_CONNECTED) { mBluetoothHeadsetConnected = true; } } public void onServiceDisconnected() { if (mBluetoothHeadset != null && mBluetoothHeadset.getState() == BluetoothHeadset.STATE_DISCONNECTED) { mBluetoothHeadsetConnected = false; clearAllScoClients(); } } }; /////////////////////////////////////////////////////////////////////////// // Internal methods Loading Loading @@ -1577,11 +1746,14 @@ public class AudioService extends IAudioService.Stub { AudioSystem.DEVICE_STATE_UNAVAILABLE, address); mConnectedDevices.remove(device); mBluetoothHeadsetConnected = false; clearAllScoClients(); } else if (!isConnected && state == BluetoothHeadset.STATE_CONNECTED) { AudioSystem.setDeviceConnectionState(device, AudioSystem.DEVICE_STATE_AVAILABLE, address); mConnectedDevices.put(new Integer(device), address); mBluetoothHeadsetConnected = true; } } else if (action.equals(Intent.ACTION_HEADSET_PLUG)) { int state = intent.getIntExtra("state", 0); Loading Loading @@ -1614,6 +1786,29 @@ public class AudioService extends IAudioService.Stub { mConnectedDevices.put( new Integer(AudioSystem.DEVICE_OUT_WIRED_HEADPHONE), ""); } } } else if (action.equals(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED)) { int state = intent.getIntExtra(BluetoothHeadset.EXTRA_AUDIO_STATE, BluetoothHeadset.STATE_ERROR); synchronized (mScoClients) { if (!mScoClients.isEmpty()) { switch (state) { case BluetoothHeadset.AUDIO_STATE_CONNECTED: state = AudioManager.SCO_AUDIO_STATE_CONNECTED; break; case BluetoothHeadset.AUDIO_STATE_DISCONNECTED: state = AudioManager.SCO_AUDIO_STATE_DISCONNECTED; break; default: state = AudioManager.SCO_AUDIO_STATE_ERROR; break; } if (state != AudioManager.SCO_AUDIO_STATE_ERROR) { Intent newIntent = new Intent(AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED); newIntent.putExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, state); mContext.sendStickyBroadcast(newIntent); } } } } } } Loading
media/java/android/media/IAudioService.aidl +4 −0 Original line number Diff line number Diff line Loading @@ -82,4 +82,8 @@ interface IAudioService { void registerMediaButtonEventReceiver(in ComponentName eventReceiver); void unregisterMediaButtonEventReceiver(in ComponentName eventReceiver); void startBluetoothSco(IBinder cb); void stopBluetoothSco(IBinder cb); }