diff options
author | Andrew Comminos <andrew@comminos.com> | 2015-10-23 07:09:20 +0300 |
---|---|---|
committer | Andrew Comminos <andrew@comminos.com> | 2015-10-23 07:09:20 +0300 |
commit | 551e79877c292ee786f4aa76eb3b9818ec0892d7 (patch) | |
tree | 2f5f00c212b278cf22ac3a87ae6cc4185a3b0686 | |
parent | 523e9357752a68024e1525a8354a804d76eedf5f (diff) |
Make JumbleBinder a local proxy, migrate most functionality to JumbleService to reduce coupling.
4 files changed, 552 insertions, 429 deletions
diff --git a/src/androidTest/java/com/morlunk/jumble/test/JumbleServiceTest.java b/src/androidTest/java/com/morlunk/jumble/test/JumbleServiceTest.java index d309f55..0776335 100644 --- a/src/androidTest/java/com/morlunk/jumble/test/JumbleServiceTest.java +++ b/src/androidTest/java/com/morlunk/jumble/test/JumbleServiceTest.java @@ -21,7 +21,6 @@ import android.content.Intent; import android.os.RemoteException; import android.test.ServiceTestCase; -import com.morlunk.jumble.JumbleBinder; import com.morlunk.jumble.JumbleService; import com.morlunk.jumble.model.Server; @@ -45,11 +44,10 @@ public class JumbleServiceTest extends ServiceTestCase<JumbleService> { intent.putExtra(JumbleService.EXTRAS_SERVER, DUMMY_SERVER); startService(intent); JumbleService service = getService(); - JumbleBinder binder = (JumbleBinder) service.onBind(intent); - assertFalse(binder.isReconnecting()); - assertNull(binder.getConnectionError()); - assertEquals(JumbleService.ConnectionState.DISCONNECTED, binder.getConnectionState()); - assertEquals(DUMMY_SERVER, binder.getConnectedServer()); + assertFalse(service.isReconnecting()); + assertNull(service.getConnectionError()); + assertEquals(JumbleService.ConnectionState.DISCONNECTED, service.getConnectionState()); + assertEquals(DUMMY_SERVER, service.getConnectedServer()); } } diff --git a/src/main/java/com/morlunk/jumble/IJumbleService.java b/src/main/java/com/morlunk/jumble/IJumbleService.java new file mode 100644 index 0000000..6e149d0 --- /dev/null +++ b/src/main/java/com/morlunk/jumble/IJumbleService.java @@ -0,0 +1,225 @@ +/* + * 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; + +import com.morlunk.jumble.model.IChannel; +import com.morlunk.jumble.model.IUser; +import com.morlunk.jumble.model.Message; +import com.morlunk.jumble.model.Server; +import com.morlunk.jumble.net.JumbleUDPMessageType; +import com.morlunk.jumble.util.IJumbleObserver; +import com.morlunk.jumble.util.JumbleException; + +import java.util.List; + +/** + * A public interface for clients to communicate with a {@link JumbleService}. + * The long-term goal for this class is to migrate of the complexity out of this class into a + * JumbleProtocol class that is owned by a {@link com.morlunk.jumble.net.JumbleConnection}. + * <br><br> + * Calls are not guaranteed to be thread-safe, so only call the binder from the main thread. + * Service state changes related to connection state are only guaranteed to work if isConnected() + * is checked to be true. + * <br><br> + * If not explicitly stated in the method documentation, any call that depends on connection state + * will throw IllegalStateException if disconnected or not synchronized. + */ +public interface IJumbleService { + /** + * Returns the current connection state of the service. + * @return one of {@link JumbleService.ConnectionState}. + */ + JumbleService.ConnectionState getConnectionState(); + + /** + * If the {@link JumbleService} disconnected due to an error, returns that error. + * @return The error causing disconnection. If the last disconnection was successful or a + * connection has yet to be established, returns null. + */ + JumbleException getConnectionError(); + + /** + * Returns the reconnection state of the {@link JumbleService}. + * @return true if the service will attempt to automatically reconnect in the future. + */ + boolean isReconnecting(); + + /** + * Cancels any future reconnection attempts. Does nothing if reconnection is not in progress. + */ + void cancelReconnect(); + + /** + * @return the latency in milliseconds for the TCP connection. + * @throws IllegalStateException if not connected. + */ + long getTCPLatency(); + + /** + * @return the latency in milliseconds for the UDP connection. + * @throws IllegalStateException if not connected. + */ + long getUDPLatency(); + + /** + * @return the maximum bandwidth in bps for audio allowed by the server, or -1 if not set. + * @throws IllegalStateException if not synchronized. + */ + int getMaxBandwidth(); + + /** + * @return the current bandwidth in bps for audio sent to the server, or a negative integer + * if unknown (prior to connection or after disconnection). + * @throws IllegalStateException if not synchronized. + */ + int getCurrentBandwidth(); + + /** + * Returns the protocol version returned by the server in the format 0xAABBCC, where AA + * indicates the major version, BB indicates the minor version, and CC indicates the patch + * version. This is the same formatting used by the Mumble protocol in big-endian format. + * @return the current bandwidth in bps for audio sent to the server, or a negative integer + * if unknown (prior to connection or after disconnection). + * @throws IllegalStateException if not synchronized. + */ + int getServerVersion(); + + /** + * @return a user-readable string with the server's Mumble release info. + * @throws IllegalStateException if not synchronized. + */ + String getServerRelease(); + + /** + * @return a user-readable string with the server's OS name. + * @throws IllegalStateException if not connected. + */ + String getServerOSName(); + + /** + * @return a user-readable string with the server's OS version. + * @throws IllegalStateException if not synchronized. + */ + String getServerOSVersion(); + + /** + * Returns the current user's session. Set during server synchronization. + * @return an integer identifying the current user's connection. + * @throws IllegalStateException if not synchronized. + */ + int getSession(); + + /** + * Returns the current user. Set during server synchronization. + * @return the {@link IUser} representing the current user. + * @throws IllegalStateException if not synchronized. + */ + IUser getSessionUser(); + + /** + * Returns the user's current channel. + * @return the {@link IChannel} representing the user's current channel. + * @throws IllegalStateException if not synchronized. + */ + IChannel getSessionChannel(); + + /** + * @return the server that Jumble is currently connected to (or attempted connection to). + */ + Server getConnectedServer(); + + /** + * Retrieves the user with the given session ID. + * @param session An integer ID identifying a user's session. See {@link IUser#getSession()}. + * @return A user with the given session, or null if not found. + * @throws IllegalStateException if not synchronized. + */ + IUser getUser(int session); + + /** + * Retrieves the channel with the given ID. + * @param id An integer ID identifying a channel. See {@link IChannel#getId()}. + * @return A channel with the given session, or null if not found. + * @throws IllegalStateException if not synchronized. + */ + IChannel getChannel(int id); + + /** + * @return the root channel of the server. + * @throws IllegalStateException if not synchronized. + */ + IChannel getRootChannel(); + + int getPermissions(); + + int getTransmitMode(); + + JumbleUDPMessageType getCodec(); + + boolean usingBluetoothSco(); + + void enableBluetoothSco(); + + void disableBluetoothSco(); + + boolean isTalking(); + + void setTalkingState(boolean talking); + + void joinChannel(int channel); + + void moveUserToChannel(int session, int channel); + + void createChannel(int parent, String name, String description, int position, boolean temporary); + + void sendAccessTokens(List<String> tokens); + + void requestBanList(); + + void requestUserList(); + + void requestPermissions(int channel); + + void requestComment(int session); + + void requestAvatar(int session); + + void requestChannelDescription(int channel); + + void registerUser(int session); + + void kickBanUser(int session, String reason, boolean ban); + + Message sendUserTextMessage(int session, String message); + + Message sendChannelTextMessage(int channel, String message, boolean tree); + + void setUserComment(int session, String comment); + + void setPrioritySpeaker(int session, boolean priority); + + void removeChannel(int channel); + + void setMuteDeafState(int session, boolean mute, boolean deaf); + + void setSelfMuteDeafState(boolean mute, boolean deaf); + + void registerObserver(IJumbleObserver observer); + + void unregisterObserver(IJumbleObserver observer); +} diff --git a/src/main/java/com/morlunk/jumble/JumbleBinder.java b/src/main/java/com/morlunk/jumble/JumbleBinder.java deleted file mode 100644 index b0db425..0000000 --- a/src/main/java/com/morlunk/jumble/JumbleBinder.java +++ /dev/null @@ -1,412 +0,0 @@ -/* - * 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; - -import android.os.Binder; -import android.util.Log; - -import com.morlunk.jumble.exception.AudioException; -import com.morlunk.jumble.model.Channel; -import com.morlunk.jumble.model.IChannel; -import com.morlunk.jumble.model.IUser; -import com.morlunk.jumble.model.Message; -import com.morlunk.jumble.model.Server; -import com.morlunk.jumble.model.User; -import com.morlunk.jumble.net.JumbleConnection; -import com.morlunk.jumble.net.JumbleTCPMessageType; -import com.morlunk.jumble.net.JumbleUDPMessageType; -import com.morlunk.jumble.protobuf.Mumble; -import com.morlunk.jumble.util.IJumbleObserver; -import com.morlunk.jumble.util.JumbleException; - -import java.util.ArrayList; -import java.util.List; - -/** - * A public interface for clients to communicate with a {@link JumbleService}. - * The long-term goal for this class is to migrate of the complexity out of this class into a - * JumbleProtocol class that is owned by a {@link com.morlunk.jumble.net.JumbleConnection}. - * <br><br> - * Calls are not guaranteed to be thread-safe, so only call the binder from the main thread. - * Service state changes related to connection state are only guaranteed to work if isConnected() - * is checked to be true. - * <br><br> - * If not explicitly stated in the method documentation, any call that depends on connection state - * will throw IllegalStateException if disconnected or not synchronized. - */ -public class JumbleBinder extends Binder { - private final JumbleService mService; - - protected JumbleBinder(JumbleService service) { - mService = service; - } - - /** - * Returns the current connection state of the service. - * @return one of {@link com.morlunk.jumble.JumbleService.ConnectionState}. - */ - public JumbleService.ConnectionState getConnectionState() { - return mService.getConnectionState(); - } - - /** - * If the {@link JumbleService} disconnected due to an error, returns that error. - * @return The error causing disconnection. If the last disconnection was successful or a - * connection has yet to be established, returns null. - */ - public JumbleException getConnectionError() { - JumbleConnection connection = mService.getConnection(); - return connection != null ? connection.getError() : null; - } - - /** - * Returns the reconnection state of the {@link JumbleService}. - * @return true if the service will attempt to automatically reconnect in the future. - */ - public boolean isReconnecting() { - return mService.isReconnecting(); - } - - /** - * Cancels any future reconnection attempts. Does nothing if reconnection is not in progress. - */ - public void cancelReconnect() { - mService.setReconnecting(false); - } - - /** - * @return the latency in milliseconds for the TCP connection. - * @throws IllegalStateException if not connected. - */ - public long getTCPLatency() { - return mService.getConnection().getTCPLatency(); - } - - /** - * @return the latency in milliseconds for the UDP connection. - * @throws IllegalStateException if not connected. - */ - public long getUDPLatency() { - return mService.getConnection().getUDPLatency(); - } - - /** - * @return the maximum bandwidth in bps for audio allowed by the server, or -1 if not set. - * @throws IllegalStateException if not synchronized. - */ - public int getMaxBandwidth() { - return mService.getConnection().getMaxBandwidth(); - } - - /** - * @return the current bandwidth in bps for audio sent to the server, or a negative integer - * if unknown (prior to connection or after disconnection). - * @throws IllegalStateException if not synchronized. - */ - public int getCurrentBandwidth() { - return mService.getAudioHandler().getCurrentBandwidth(); - } - - /** - * Returns the protocol version returned by the server in the format 0xAABBCC, where AA - * indicates the major version, BB indicates the minor version, and CC indicates the patch - * version. This is the same formatting used by the Mumble protocol in big-endian format. - * @return the current bandwidth in bps for audio sent to the server, or a negative integer - * if unknown (prior to connection or after disconnection). - * @throws IllegalStateException if not synchronized. - */ - public int getServerVersion() { - return mService.getConnection().getServerVersion(); - } - - /** - * @return a user-readable string with the server's Mumble release info. - * @throws IllegalStateException if not synchronized. - */ - public String getServerRelease() { - return mService.getConnection().getServerRelease(); - } - - /** - * @return a user-readable string with the server's OS name. - * @throws IllegalStateException if not connected. - */ - public String getServerOSName() { - return mService.getConnection().getServerOSName(); - } - - /** - * @return a user-readable string with the server's OS version. - * @throws IllegalStateException if not synchronized. - */ - public String getServerOSVersion() { - return mService.getConnection().getServerOSVersion(); - } - - /** - * Returns the current user's session. Set during server synchronization. - * @return an integer identifying the current user's connection. - * @throws IllegalStateException if not synchronized. - */ - public int getSession() { - return mService.getConnection().getSession(); - } - - /** - * Returns the current user. Set during server synchronization. - * @return the {@link IUser} representing the current user. - * @throws IllegalStateException if not synchronized. - */ - public IUser getSessionUser() { - return mService.getModelHandler().getUser(getSession()); - } - - /** - * Returns the user's current channel. - * @return the {@link IChannel} representing the user's current channel. - * @throws IllegalStateException if not synchronized. - */ - public IChannel getSessionChannel() { - IUser user = getSessionUser(); - if (user != null) - return user.getChannel(); - throw new IllegalStateException("Session user should be set post-synchronization!"); - } - - /** - * @return the server that Jumble is currently connected to (or attempted connection to). - */ - public Server getConnectedServer() { - return mService.getConnectedServer(); - } - - /** - * Retrieves the user with the given session ID. - * @param session An integer ID identifying a user's session. See {@link IUser#getSession()}. - * @return A user with the given session, or null if not found. - * @throws IllegalStateException if not synchronized. - */ - public IUser getUser(int session) { - return mService.getModelHandler().getUser(session); - } - - /** - * Retrieves the channel with the given ID. - * @param id An integer ID identifying a channel. See {@link IChannel#getId()}. - * @return A channel with the given session, or null if not found. - * @throws IllegalStateException if not synchronized. - */ - public IChannel getChannel(int id) { - return mService.getModelHandler().getChannel(id); - } - - /** - * @return the root channel of the server. - * @throws IllegalStateException if not synchronized. - */ - public IChannel getRootChannel() { - return getChannel(0); - } - - public int getPermissions() { - return mService.getModelHandler().getPermissions(); - } - - public int getTransmitMode() { - return mService.getAudioHandler().getTransmitMode(); - } - - public JumbleUDPMessageType getCodec() { - return mService.getConnection().getCodec(); - } - - public boolean usingBluetoothSco() { - return mService.getBluetoothReceiver().isBluetoothScoOn(); - } - - public void enableBluetoothSco() { - mService.getBluetoothReceiver().startBluetoothSco(); - } - - public void disableBluetoothSco() { - mService.getBluetoothReceiver().stopBluetoothSco(); - } - - public boolean isTalking() { - return mService.getAudioHandler().isRecording(); - } - - public void setTalkingState(boolean talking) { - if (getSessionUser().isSelfMuted() || getSessionUser().isMuted()) - return; - - if (mService.getAudioHandler().getTransmitMode() != Constants.TRANSMIT_PUSH_TO_TALK) { - Log.w(Constants.TAG, "Attempted to set talking state when not using PTT"); - return; - } - - try { - mService.getAudioHandler().setTalking(talking); - } catch (AudioException e) { - mService.logError(e.getMessage()); - } - } - - public void joinChannel(int channel) { - moveUserToChannel(getSession(), channel); - } - - public void moveUserToChannel(int session, int channel) { - Mumble.UserState.Builder usb = Mumble.UserState.newBuilder(); - usb.setSession(session); - usb.setChannelId(channel); - mService.getConnection().sendTCPMessage(usb.build(), JumbleTCPMessageType.UserState); - } - - public void createChannel(int parent, String name, String description, int position, boolean temporary) { - Mumble.ChannelState.Builder csb = Mumble.ChannelState.newBuilder(); - csb.setParent(parent); - csb.setName(name); - csb.setDescription(description); - csb.setPosition(position); - csb.setTemporary(temporary); - mService.getConnection().sendTCPMessage(csb.build(), JumbleTCPMessageType.ChannelState); - } - - public void sendAccessTokens(final List<String> tokens) { - mService.getConnection().sendAccessTokens(tokens); - } - - public void requestBanList() { - throw new UnsupportedOperationException("Not yet implemented"); // TODO - } - - public void requestUserList() { - throw new UnsupportedOperationException("Not yet implemented"); // TODO - } - - public void requestPermissions(int channel) { - Mumble.PermissionQuery.Builder pqb = Mumble.PermissionQuery.newBuilder(); - pqb.setChannelId(channel); - mService.getConnection().sendTCPMessage(pqb.build(), JumbleTCPMessageType.PermissionQuery); - } - - public void requestComment(int session) { - Mumble.RequestBlob.Builder rbb = Mumble.RequestBlob.newBuilder(); - rbb.addSessionComment(session); - mService.getConnection().sendTCPMessage(rbb.build(), JumbleTCPMessageType.RequestBlob); - } - - public void requestAvatar(int session) { - Mumble.RequestBlob.Builder rbb = Mumble.RequestBlob.newBuilder(); - rbb.addSessionTexture(session); - mService.getConnection().sendTCPMessage(rbb.build(), JumbleTCPMessageType.RequestBlob); - } - - public void requestChannelDescription(int channel) { - Mumble.RequestBlob.Builder rbb = Mumble.RequestBlob.newBuilder(); - rbb.addChannelDescription(channel); - mService.getConnection().sendTCPMessage(rbb.build(), JumbleTCPMessageType.RequestBlob); - } - - public void registerUser(int session) { - Mumble.UserState.Builder usb = Mumble.UserState.newBuilder(); - usb.setSession(session); - usb.setUserId(0); - mService.getConnection().sendTCPMessage(usb.build(), JumbleTCPMessageType.UserState); - } - - public void kickBanUser(int session, String reason, boolean ban) { - Mumble.UserRemove.Builder urb = Mumble.UserRemove.newBuilder(); - urb.setSession(session); - urb.setReason(reason); - urb.setBan(ban); - mService.getConnection().sendTCPMessage(urb.build(), JumbleTCPMessageType.UserRemove); - } - - public Message sendUserTextMessage(int session, String message) { - Mumble.TextMessage.Builder tmb = Mumble.TextMessage.newBuilder(); - tmb.addSession(session); - tmb.setMessage(message); - mService.getConnection().sendTCPMessage(tmb.build(), JumbleTCPMessageType.TextMessage); - - User self = mService.getModelHandler().getUser(getSession()); - User user = mService.getModelHandler().getUser(session); - List<User> users = new ArrayList<User>(1); - users.add(user); - return new Message(getSession(), self.getName(), new ArrayList<Channel>(0), new ArrayList<Channel>(0), users, message); - } - - public Message sendChannelTextMessage(int channel, String message, boolean tree) { - Mumble.TextMessage.Builder tmb = Mumble.TextMessage.newBuilder(); - if(tree) tmb.addTreeId(channel); - else tmb.addChannelId(channel); - tmb.setMessage(message); - mService.getConnection().sendTCPMessage(tmb.build(), JumbleTCPMessageType.TextMessage); - - User self = mService.getModelHandler().getUser(getSession()); - Channel targetChannel = mService.getModelHandler().getChannel(channel); - List<Channel> targetChannels = new ArrayList<Channel>(); - targetChannels.add(targetChannel); - return new Message(getSession(), self.getName(), targetChannels, tree ? targetChannels : new ArrayList<Channel>(0), new ArrayList<User>(0), message); - } - - public void setUserComment(int session, String comment) { - Mumble.UserState.Builder usb = Mumble.UserState.newBuilder(); - usb.setSession(session); - usb.setComment(comment); - mService.getConnection().sendTCPMessage(usb.build(), JumbleTCPMessageType.UserState); - } - - public void setPrioritySpeaker(int session, boolean priority) { - Mumble.UserState.Builder usb = Mumble.UserState.newBuilder(); - usb.setSession(session); - usb.setPrioritySpeaker(priority); - mService.getConnection().sendTCPMessage(usb.build(), JumbleTCPMessageType.UserState); - } - - public void removeChannel(int channel) { - Mumble.ChannelRemove.Builder crb = Mumble.ChannelRemove.newBuilder(); - crb.setChannelId(channel); - mService.getConnection().sendTCPMessage(crb.build(), JumbleTCPMessageType.ChannelRemove); - } - - public void setMuteDeafState(int session, boolean mute, boolean deaf) { - Mumble.UserState.Builder usb = Mumble.UserState.newBuilder(); - usb.setSession(session); - usb.setMute(mute); - usb.setDeaf(deaf); - if (!mute) usb.setSuppress(false); - mService.getConnection().sendTCPMessage(usb.build(), JumbleTCPMessageType.UserState); - } - - public void setSelfMuteDeafState(boolean mute, boolean deaf) { - Mumble.UserState.Builder usb = Mumble.UserState.newBuilder(); - usb.setSelfMute(mute); - usb.setSelfDeaf(deaf); - mService.getConnection().sendTCPMessage(usb.build(), JumbleTCPMessageType.UserState); - } - - public void registerObserver(IJumbleObserver observer) { - mService.registerObserver(observer); - } - - public void unregisterObserver(IJumbleObserver observer) { - mService.unregisterObserver(observer); - } -} diff --git a/src/main/java/com/morlunk/jumble/JumbleService.java b/src/main/java/com/morlunk/jumble/JumbleService.java index 4d358f0..127763f 100644 --- a/src/main/java/com/morlunk/jumble/JumbleService.java +++ b/src/main/java/com/morlunk/jumble/JumbleService.java @@ -25,6 +25,7 @@ import android.content.IntentFilter; import android.media.AudioManager; import android.net.ConnectivityManager; import android.net.NetworkInfo; +import android.os.Binder; import android.os.Build; import android.os.Bundle; import android.os.Handler; @@ -35,10 +36,15 @@ 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; +import com.morlunk.jumble.model.IUser; +import com.morlunk.jumble.model.Message; import com.morlunk.jumble.model.Server; import com.morlunk.jumble.model.TalkState; import com.morlunk.jumble.model.User; import com.morlunk.jumble.net.JumbleConnection; +import com.morlunk.jumble.net.JumbleUDPMessageType; import com.morlunk.jumble.util.IJumbleObserver; import com.morlunk.jumble.util.JumbleException; import com.morlunk.jumble.net.JumbleTCPMessageType; @@ -50,9 +56,10 @@ import com.morlunk.jumble.util.JumbleLogger; import java.security.Security; import java.security.cert.X509Certificate; +import java.util.ArrayList; import java.util.List; -public class JumbleService extends Service implements JumbleConnection.JumbleConnectionListener, JumbleLogger, BluetoothScoReceiver.Listener { +public class JumbleService extends Service implements IJumbleService, JumbleConnection.JumbleConnectionListener, JumbleLogger, BluetoothScoReceiver.Listener { static { // Use Spongy Castle for crypto implementation so we can create and manage PKCS #12 (.p12) certificates. @@ -234,7 +241,7 @@ public class JumbleService extends Service implements JumbleConnection.JumbleCon return new JumbleBinder(this); } - private void connect() { + protected void connect() { try { setReconnecting(false); mConnectionState = ConnectionState.DISCONNECTED; @@ -576,8 +583,7 @@ public class JumbleService extends Service implements JumbleConnection.JumbleCon * Exposes the current connection. The current connection is set once an attempt to connect to * a server is made, and remains set until a subsequent connection. It remains available * after disconnection to provide information regarding the terminated connection. - * @return The active {@link JumbleConnection}, or null if a connection has not been - * established yet. + * @return The active {@link JumbleConnection}. */ public JumbleConnection getConnection() { return mConnection; @@ -588,7 +594,7 @@ public class JumbleService extends Service implements JumbleConnection.JumbleCon * to a server, and destroyed upon disconnection. * @return the active AudioHandler, or null if there is no active connection. */ - public AudioHandler getAudioHandler() { + private AudioHandler getAudioHandler() { if (!isSynchronized()) throw new IllegalStateException("Not synchronized"); if (mAudioHandler == null && mConnectionState == ConnectionState.CONNECTED) @@ -601,7 +607,7 @@ public class JumbleService extends Service implements JumbleConnection.JumbleCon * valid for the lifetime of a connection. * @return the active ModelHandler, or null if there is no active connection. */ - public ModelHandler getModelHandler() { + private ModelHandler getModelHandler() { if (!isSynchronized()) throw new IllegalStateException("Not synchronized"); if (mModelHandler == null && mConnectionState == ConnectionState.CONNECTED) @@ -614,24 +620,317 @@ public class JumbleService extends Service implements JumbleConnection.JumbleCon * @return The {@link BluetoothScoReceiver} attached to this service. * @throws IllegalStateException if not synchronized or disconnected. */ - public BluetoothScoReceiver getBluetoothReceiver() { + private BluetoothScoReceiver getBluetoothReceiver() { if (!isSynchronized()) throw new IllegalStateException("Not synchronized"); return mBluetoothReceiver; } + @Override + public JumbleService.ConnectionState getConnectionState() { + return mConnectionState; + } + + @Override + public JumbleException getConnectionError() { + JumbleConnection connection = getConnection(); + return connection != null ? connection.getError() : null; + } + + @Override public boolean isReconnecting() { return mReconnecting; } - public ConnectionState getConnectionState() { - return mConnectionState; + @Override + public void cancelReconnect() { + setReconnecting(false); + } + + @Override + public long getTCPLatency() { + return getConnection().getTCPLatency(); + } + + @Override + public long getUDPLatency() { + return getConnection().getUDPLatency(); + } + + @Override + public int getMaxBandwidth() { + return getConnection().getMaxBandwidth(); + } + + @Override + public int getCurrentBandwidth() { + return getAudioHandler().getCurrentBandwidth(); + } + + @Override + public int getServerVersion() { + return getConnection().getServerVersion(); + } + + @Override + public String getServerRelease() { + return getConnection().getServerRelease(); + } + + @Override + public String getServerOSName() { + return getConnection().getServerOSName(); + } + + @Override + public String getServerOSVersion() { + return getConnection().getServerOSVersion(); + } + + @Override + public int getSession() { + return getConnection().getSession(); + } + + @Override + public IUser getSessionUser() { + return getModelHandler().getUser(getSession()); + } + + @Override + public IChannel getSessionChannel() { + IUser user = getSessionUser(); + if (user != null) + return user.getChannel(); + throw new IllegalStateException("Session user should be set post-synchronization!"); } + @Override public Server getConnectedServer() { return mServer; } + @Override + public IUser getUser(int session) { + return getModelHandler().getUser(session); + } + + @Override + public IChannel getChannel(int id) { + return getModelHandler().getChannel(id); + } + + @Override + public IChannel getRootChannel() { + return getChannel(0); + } + + @Override + public int getPermissions() { + return getModelHandler().getPermissions(); + } + + @Override + public int getTransmitMode() { + return getAudioHandler().getTransmitMode(); + } + + @Override + public JumbleUDPMessageType getCodec() { + return getConnection().getCodec(); + } + + @Override + public boolean usingBluetoothSco() { + return getBluetoothReceiver().isBluetoothScoOn(); + } + + @Override + public void enableBluetoothSco() { + getBluetoothReceiver().startBluetoothSco(); + } + + @Override + public void disableBluetoothSco() { + getBluetoothReceiver().stopBluetoothSco(); + } + + @Override + public boolean isTalking() { + return getAudioHandler().isRecording(); + } + + @Override + public void setTalkingState(boolean talking) { + if (getSessionUser().isSelfMuted() || getSessionUser().isMuted()) + return; + + if (getAudioHandler().getTransmitMode() != Constants.TRANSMIT_PUSH_TO_TALK) { + Log.w(Constants.TAG, "Attempted to set talking state when not using PTT"); + return; + } + + try { + getAudioHandler().setTalking(talking); + } catch (AudioException e) { + logError(e.getMessage()); + } + } + + @Override + public void joinChannel(int channel) { + moveUserToChannel(getSession(), channel); + } + + @Override + public void moveUserToChannel(int session, int channel) { + Mumble.UserState.Builder usb = Mumble.UserState.newBuilder(); + usb.setSession(session); + usb.setChannelId(channel); + getConnection().sendTCPMessage(usb.build(), JumbleTCPMessageType.UserState); + } + + @Override + public void createChannel(int parent, String name, String description, int position, boolean temporary) { + Mumble.ChannelState.Builder csb = Mumble.ChannelState.newBuilder(); + csb.setParent(parent); + csb.setName(name); + csb.setDescription(description); + csb.setPosition(position); + csb.setTemporary(temporary); + getConnection().sendTCPMessage(csb.build(), JumbleTCPMessageType.ChannelState); + } + + @Override + public void sendAccessTokens(final List<String> tokens) { + getConnection().sendAccessTokens(tokens); + } + + @Override + public void requestBanList() { + throw new UnsupportedOperationException("Not yet implemented"); // TODO + } + + @Override + public void requestUserList() { + throw new UnsupportedOperationException("Not yet implemented"); // TODO + } + + @Override + public void requestPermissions(int channel) { + Mumble.PermissionQuery.Builder pqb = Mumble.PermissionQuery.newBuilder(); + pqb.setChannelId(channel); + getConnection().sendTCPMessage(pqb.build(), JumbleTCPMessageType.PermissionQuery); + } + + @Override + public void requestComment(int session) { + Mumble.RequestBlob.Builder rbb = Mumble.RequestBlob.newBuilder(); + rbb.addSessionComment(session); + getConnection().sendTCPMessage(rbb.build(), JumbleTCPMessageType.RequestBlob); + } + + @Override + public void requestAvatar(int session) { + Mumble.RequestBlob.Builder rbb = Mumble.RequestBlob.newBuilder(); + rbb.addSessionTexture(session); + getConnection().sendTCPMessage(rbb.build(), JumbleTCPMessageType.RequestBlob); + } + + @Override + public void requestChannelDescription(int channel) { + Mumble.RequestBlob.Builder rbb = Mumble.RequestBlob.newBuilder(); + rbb.addChannelDescription(channel); + getConnection().sendTCPMessage(rbb.build(), JumbleTCPMessageType.RequestBlob); + } + + @Override + public void registerUser(int session) { + Mumble.UserState.Builder usb = Mumble.UserState.newBuilder(); + usb.setSession(session); + usb.setUserId(0); + getConnection().sendTCPMessage(usb.build(), JumbleTCPMessageType.UserState); + } + + @Override + public void kickBanUser(int session, String reason, boolean ban) { + Mumble.UserRemove.Builder urb = Mumble.UserRemove.newBuilder(); + urb.setSession(session); + urb.setReason(reason); + urb.setBan(ban); + getConnection().sendTCPMessage(urb.build(), JumbleTCPMessageType.UserRemove); + } + + @Override + public Message sendUserTextMessage(int session, String message) { + Mumble.TextMessage.Builder tmb = Mumble.TextMessage.newBuilder(); + tmb.addSession(session); + tmb.setMessage(message); + getConnection().sendTCPMessage(tmb.build(), JumbleTCPMessageType.TextMessage); + + User self = getModelHandler().getUser(getSession()); + User user = getModelHandler().getUser(session); + List<User> users = new ArrayList<User>(1); + users.add(user); + return new Message(getSession(), self.getName(), new ArrayList<Channel>(0), new ArrayList<Channel>(0), users, message); + } + + @Override + public Message sendChannelTextMessage(int channel, String message, boolean tree) { + Mumble.TextMessage.Builder tmb = Mumble.TextMessage.newBuilder(); + if(tree) tmb.addTreeId(channel); + else tmb.addChannelId(channel); + tmb.setMessage(message); + getConnection().sendTCPMessage(tmb.build(), JumbleTCPMessageType.TextMessage); + + User self = getModelHandler().getUser(getSession()); + Channel targetChannel = getModelHandler().getChannel(channel); + List<Channel> targetChannels = new ArrayList<Channel>(); + targetChannels.add(targetChannel); + return new Message(getSession(), self.getName(), targetChannels, tree ? targetChannels : new ArrayList<Channel>(0), new ArrayList<User>(0), message); + } + + @Override + public void setUserComment(int session, String comment) { + Mumble.UserState.Builder usb = Mumble.UserState.newBuilder(); + usb.setSession(session); + usb.setComment(comment); + getConnection().sendTCPMessage(usb.build(), JumbleTCPMessageType.UserState); + } + + @Override + public void setPrioritySpeaker(int session, boolean priority) { + Mumble.UserState.Builder usb = Mumble.UserState.newBuilder(); + usb.setSession(session); + usb.setPrioritySpeaker(priority); + getConnection().sendTCPMessage(usb.build(), JumbleTCPMessageType.UserState); + } + + @Override + public void removeChannel(int channel) { + Mumble.ChannelRemove.Builder crb = Mumble.ChannelRemove.newBuilder(); + crb.setChannelId(channel); + getConnection().sendTCPMessage(crb.build(), JumbleTCPMessageType.ChannelRemove); + } + + @Override + public void setMuteDeafState(int session, boolean mute, boolean deaf) { + Mumble.UserState.Builder usb = Mumble.UserState.newBuilder(); + usb.setSession(session); + usb.setMute(mute); + usb.setDeaf(deaf); + if (!mute) usb.setSuppress(false); + getConnection().sendTCPMessage(usb.build(), JumbleTCPMessageType.UserState); + } + + @Override + public void setSelfMuteDeafState(boolean mute, boolean deaf) { + Mumble.UserState.Builder usb = Mumble.UserState.newBuilder(); + usb.setSelfMute(mute); + usb.setSelfDeaf(deaf); + getConnection().sendTCPMessage(usb.build(), JumbleTCPMessageType.UserState); + } + public void registerObserver(IJumbleObserver observer) { mCallbacks.registerObserver(observer); } @@ -640,6 +939,7 @@ public class JumbleService extends Service implements JumbleConnection.JumbleCon mCallbacks.unregisterObserver(observer); } + /** * The current connection state of the service. */ @@ -660,9 +960,21 @@ public class JumbleService extends Service implements JumbleConnection.JumbleCon /** * The connection was lost due to either a kick/ban or socket I/O error. * Jumble may be reconnecting in this state. - * @see JumbleBinder#isReconnecting() - * @see JumbleBinder#cancelReconnect() + * @see #isReconnecting() + * @see #cancelReconnect() */ CONNECTION_LOST } + + public static class JumbleBinder extends Binder { + private final IJumbleService mService; + + private JumbleBinder(IJumbleService service) { + mService = service; + } + + public IJumbleService getService() { + return mService; + } + } } |