diff options
author | Andrew Comminos <andrew@comminos.com> | 2015-09-26 09:04:24 +0300 |
---|---|---|
committer | Andrew Comminos <andrew@comminos.com> | 2015-09-26 09:04:24 +0300 |
commit | 17b4c9f8a787ce2245beb6e830354f27427498b0 (patch) | |
tree | 43fb63fc17311230329ab6fecb764c148220663c /src/main/java/com/morlunk/jumble | |
parent | c506f116c18a0cd72ae0e9c303390c87a4f74a7d (diff) |
Reintroduction of runtime Bluetooth connection support.
Diffstat (limited to 'src/main/java/com/morlunk/jumble')
3 files changed, 155 insertions, 64 deletions
diff --git a/src/main/java/com/morlunk/jumble/JumbleService.java b/src/main/java/com/morlunk/jumble/JumbleService.java index 4046a4f..63e4b3c 100644 --- a/src/main/java/com/morlunk/jumble/JumbleService.java +++ b/src/main/java/com/morlunk/jumble/JumbleService.java @@ -34,6 +34,7 @@ import android.os.RemoteException; import android.util.Log; import com.morlunk.jumble.audio.AudioOutput; +import com.morlunk.jumble.audio.BluetoothScoReceiver; import com.morlunk.jumble.exception.AudioException; import com.morlunk.jumble.model.Channel; import com.morlunk.jumble.model.IChannel; @@ -58,7 +59,7 @@ import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.List; -public class JumbleService extends Service implements JumbleConnection.JumbleConnectionListener, JumbleLogger { +public class JumbleService extends Service implements JumbleConnection.JumbleConnectionListener, JumbleLogger, BluetoothScoReceiver.Listener { static { // Use Spongy Castle for crypto implementation so we can create and manage PKCS #12 (.p12) certificates. @@ -151,6 +152,7 @@ public class JumbleService extends Service implements JumbleConnection.JumbleCon private int mConnectionState; private ModelHandler mModelHandler; private AudioHandler mAudioHandler; + private BluetoothScoReceiver mBluetoothReceiver; private boolean mReconnecting; @@ -255,10 +257,13 @@ public class JumbleService extends Service implements JumbleConnection.JumbleCon .setEncodeListener(mAudioInputListener) .setTalkingListener(mAudioOutputListener); mConnectionState = STATE_DISCONNECTED; + mBluetoothReceiver = new BluetoothScoReceiver(this, this); + registerReceiver(mBluetoothReceiver, new IntentFilter(AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED)); } @Override public void onDestroy() { + unregisterReceiver(mBluetoothReceiver); mCallbacks.kill(); super.onDestroy(); } @@ -397,6 +402,9 @@ public class JumbleService extends Service implements JumbleConnection.JumbleCon mModelHandler = null; mAudioHandler = null; + // Halt SCO connection on shutdown. + mBluetoothReceiver.stopBluetoothSco(); + try { mCallbacks.onDisconnected(e); } catch (RemoteException re) { @@ -473,6 +481,29 @@ public class JumbleService extends Service implements JumbleConnection.JumbleCon } /** + * Instantiates an audio handler with the current service settings, destroying any previous + * handler. Requires synchronization with the server, as the maximum bandwidth and session must + * be known. + */ + private void createAudioHandler() throws AudioException { + if (BuildConfig.DEBUG && mConnectionState != STATE_CONNECTED) { + throw new AssertionError("Attempted to instantiate audio handler when not connected!"); + } + + if (mAudioHandler != null) { + mConnection.removeTCPMessageHandler(mAudioHandler); + mConnection.removeUDPMessageHandler(mAudioHandler); + mAudioHandler.shutdown(); + } + + mAudioHandler = mAudioBuilder.initialize( + mModelHandler.getUser(mConnection.getSession()), + mConnection.getMaxBandwidth(), mConnection.getCodec()); + mConnection.addTCPMessageHandlers(mAudioHandler); + mConnection.addUDPMessageHandlers(mAudioHandler); + } + + /** * Loads all defined settings from the given bundle into the JumbleService. * Some settings may only take effect after a reconnect. * @param extras A bundle with settings. @@ -575,21 +606,38 @@ public class JumbleService extends Service implements JumbleConnection.JumbleCon // Reload audio subsystem if initialized if (mAudioHandler != null && mAudioHandler.isInitialized()) { - mConnection.removeTCPMessageHandler(mAudioHandler); - mConnection.removeUDPMessageHandler(mAudioHandler); - mAudioHandler.shutdown(); - - mAudioHandler = mAudioBuilder.initialize( - mModelHandler.getUser(mConnection.getSession()), - mConnection.getMaxBandwidth(), mConnection.getCodec()); - mConnection.addTCPMessageHandlers(mAudioHandler); - mConnection.addUDPMessageHandlers(mAudioHandler); - + createAudioHandler(); Log.i(Constants.TAG, "Audio subsystem reloaded after settings change."); } return reconnectNeeded; } + @Override + public void onBluetoothScoConnected() { + // After an SCO connection is established, audio is rerouted to be compatible with SCO. + mAudioBuilder.setBluetoothEnabled(true); + if (mAudioHandler != null) { + try { + createAudioHandler(); + } catch (AudioException e) { + e.printStackTrace(); + } + } + } + + @Override + public void onBluetoothScoDisconnected() { + // Restore audio settings after disconnection. + mAudioBuilder.setBluetoothEnabled(false); + if (mAudioHandler != null) { + try { + createAudioHandler(); + } catch (AudioException e) { + e.printStackTrace(); + } + } + } + public class JumbleBinder extends IJumbleService.Stub { @Override public int getConnectionState() throws RemoteException { @@ -715,6 +763,21 @@ public class JumbleService extends Service implements JumbleConnection.JumbleCon } @Override + public boolean usingBluetoothSco() throws RemoteException { + return mBluetoothReceiver.isBluetoothScoOn(); + } + + @Override + public void enableBluetoothSco() throws RemoteException { + mBluetoothReceiver.startBluetoothSco(); + } + + @Override + public void disableBluetoothSco() throws RemoteException { + mBluetoothReceiver.stopBluetoothSco(); + } + + @Override public boolean isTalking() throws RemoteException { return mAudioHandler != null && mAudioHandler.isRecording(); } @@ -739,12 +802,6 @@ public class JumbleService extends Service implements JumbleConnection.JumbleCon } @Override - public boolean isBluetoothAvailable() throws RemoteException { - AudioManager audioManager = (AudioManager) getSystemService(AUDIO_SERVICE); - return audioManager.isBluetoothScoOn(); - } - - @Override public void joinChannel(int channel) throws RemoteException { moveUserToChannel(getSession(), channel); } diff --git a/src/main/java/com/morlunk/jumble/audio/BluetoothScoReceiver.java b/src/main/java/com/morlunk/jumble/audio/BluetoothScoReceiver.java new file mode 100644 index 0000000..4577296 --- /dev/null +++ b/src/main/java/com/morlunk/jumble/audio/BluetoothScoReceiver.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2015 Andrew Comminos <andrew@comminos.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.morlunk.jumble.audio; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.media.AudioManager; +import android.widget.Toast; + +import com.morlunk.jumble.R; +import com.morlunk.jumble.exception.AudioInitializationException; + +/** + * Manages the state of Bluetooth SCO. + * Created by andrew on 25/09/15. + */ +public class BluetoothScoReceiver extends BroadcastReceiver { + private final Listener mListener; + private final AudioManager mAudioManager; + private boolean mBluetoothScoOn; + + public BluetoothScoReceiver(Context context, Listener listener) { + mListener = listener; + mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); + } + + @Override + public void onReceive(Context context, Intent intent) { + AudioManager am = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); + int audioState = intent.getIntExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, AudioManager.SCO_AUDIO_STATE_ERROR); + switch (audioState) { + case AudioManager.SCO_AUDIO_STATE_CONNECTED: + mBluetoothScoOn = true; + mListener.onBluetoothScoConnected(); + break; + case AudioManager.SCO_AUDIO_STATE_DISCONNECTED: + case AudioManager.SCO_AUDIO_STATE_ERROR: + am.stopBluetoothSco(); + mBluetoothScoOn = false; + mListener.onBluetoothScoDisconnected(); + break; + } + } + + public void startBluetoothSco() { + mAudioManager.startBluetoothSco(); + } + + public void stopBluetoothSco() { + mAudioManager.stopBluetoothSco(); + mBluetoothScoOn = false; + } + + public boolean isBluetoothScoOn() { + return mBluetoothScoOn; + } + + public interface Listener { + void onBluetoothScoConnected(); + void onBluetoothScoDisconnected(); + } +} diff --git a/src/main/java/com/morlunk/jumble/protocol/AudioHandler.java b/src/main/java/com/morlunk/jumble/protocol/AudioHandler.java index 84b1672..1d5273f 100644 --- a/src/main/java/com/morlunk/jumble/protocol/AudioHandler.java +++ b/src/main/java/com/morlunk/jumble/protocol/AudioHandler.java @@ -98,41 +98,6 @@ public class AudioHandler extends JumbleNetworkListener implements AudioInput.Au private final Object mEncoderLock; - private BroadcastReceiver mBluetoothReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - synchronized (mOutput) { - int audioState = intent.getIntExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, AudioManager.SCO_AUDIO_STATE_ERROR); - switch (audioState) { - case AudioManager.SCO_AUDIO_STATE_CONNECTED: - Toast.makeText(mContext, R.string.bluetooth_connected, Toast.LENGTH_LONG).show(); - mOutput.stopPlaying(); - mBluetoothOn = true; - try { - mOutput.startPlaying(AudioManager.STREAM_VOICE_CALL); - } catch (AudioInitializationException e) { - e.printStackTrace(); - mLogger.logError(e.getLocalizedMessage()); - } - break; - case AudioManager.SCO_AUDIO_STATE_DISCONNECTED: - case AudioManager.SCO_AUDIO_STATE_ERROR: - if (mOutput.isPlaying() && mBluetoothOn) - Toast.makeText(mContext, R.string.bluetooth_disconnected, Toast.LENGTH_LONG).show(); - mOutput.stopPlaying(); - try { - mOutput.startPlaying(mAudioStream); - } catch (AudioInitializationException e) { - e.printStackTrace(); - mLogger.logError(e.getLocalizedMessage()); - } - mBluetoothOn = false; - break; - } - } - } - }; - public AudioHandler(Context context, JumbleLogger logger, int audioStream, int audioSource, int sampleRate, int targetBitrate, int targetFramesPerPacket, int transmitMode, float vadThreshold, float amplitudeBoost, @@ -179,8 +144,9 @@ public class AudioHandler extends JumbleNetworkListener implements AudioInput.Au setServerMuted(self.isMuted() || self.isLocalMuted() || self.isSuppressed()); if (mTalking && !mMuted) startRecording(); - // This sticky broadcast will initialize the audio output. - mContext.registerReceiver(mBluetoothReceiver, new IntentFilter(AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED)); + // Ensure that if a bluetooth SCO connection is active, we use the VOICE_CALL stream. + // This is required by Android for compatibility with SCO. + mOutput.startPlaying(mBluetoothOn ? AudioManager.STREAM_VOICE_CALL : mAudioStream); mInitialized = true; } @@ -428,18 +394,8 @@ public class AudioHandler extends JumbleNetworkListener implements AudioInput.Au mEncoder = null; } } - if(mInitialized) { - try { - mContext.unregisterReceiver(mBluetoothReceiver); - } catch (IllegalArgumentException e) { - e.printStackTrace(); // Called if the registration failed, and we try and unregister nothing. - } - } mInitialized = false; mBluetoothOn = false; - // Restore audio manager mode - AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); - audioManager.stopBluetoothSco(); } |