Author | Date | Revision |
---|---|---|
Knight | 2016-02-05 | Initial release |
- Vision Statement
- Requirements
- Iteration #1
- Iteration #1
- Design
- Coding Standards
- Project Summary and Retrospective
For the tech-savvy introvert who would rather have a text conversation than interact with a human in the real word Jaw bridges the gap. Jaw allows you to talk to anyone at a party, without all the social awkwardness (like eye contact). Unlike conventional messaging apps, Jaw only works with other Jaw users in your immediate proximity. Jaw works just like conventional messaging apps.
Participant - A user who actively sends and receives messages, or passively relays messages to other participants.
Actor | Goal |
---|---|
Participant | Send messages to all other engaged participants |
Potentially see a list of all participants | |
Receive messages from all participant | |
Potentially send/receive direct messages from particular participants | |
Story ID | Story | Story Points | Priority | Status |
---|---|---|---|---|
S1 | Participants will choose usernames to identify themselves. | 5 | 1 | |
S2 | Participants can also be identified by an avatar image. | 4 | 2 | |
S3 | Participant can enter a message. | 3 | 3 | |
S4 | Message should be sent to all other participants. | 6 | 4 | |
S5 | Participant can see a history of past messages. | 3 | 5 | |
S6 | Participant can see system messages. | 2 | 6 | |
S7 | System messages will show when participants join the conversation. | 2 | 7 | |
S8 | System messages should show when participants leave the conversation. | 2 | 8 | |
S9 | Participants will receive messages from all other participants. | 4 | 9 | |
8 | 10 | |||
7 | 11 | |||
13 | 12 | |||
3 | 13 | |||
15 | 14 | |||
16 | 15 | |||
S16 | As a participant, I would like to send direct messages to specific participants. | 3 | 16 | |
18 | 17 |
Title: Join the conversation
Use case ID: UC001
Actor: Participant
Description: This is the initial action all participants must take to join a conversation
Basic Flow
- Upon app launch, other participants are automatically found and connected.
- Participant can begin send messages to all other participants.
- Other participant messages will appear to the participant.
Alternative Flow
- No other participants are found.
- The app show a message indicating no other participants.
- The app waits for other participants to join.
Exceptions
- Connection cannot be made with other participants.
- Connection should automatically attempt to reconnect.
Open issues
- How are participants identified?
- Automatically generated ID?
- User entered handle/username?
- Avatar picture?
Story ID | Task | Estimated Hours | Actual Hours |
---|---|---|---|
S1 | Design UI | 5 | 7 |
Develop model | 8 | 5 | |
Implement network layer | 16 | ||
S2 | Design message listener | 8 | |
S4 | Implement send/receive/repeat message model | 16 | |
Use network topology to find fastest path between participants | 16 |
- UI was demoed to the client.
- Client didn't like the color scheme (it should be brighter).
- Generally the UI was well received.
Time was spent on implementing the UI layer. Instead of designing UI mockups ahead of time, design happened at the same time as implementation. This approach added some extra time because of the waisted time implementing UI elements that were later removed, but with my unfamiliarity with front-end Android code it helped guard against designing unimplementable elements.
I might need to retcon the story, since the Story Plan doesn't accurately represent the time spent during this iteration.
Project Velocity: 0
Changed color scheme to a lighter blue (at clients request).
Added automatically generated profile images (Identicons).
Due to current time requirements, the original design goal of the app to support a BLE Mesh network does not seem to be feasible. Fortunately, the app has been design to be network agnostic (the communication implementation does not affect any other aspects of the app). Therefore, pivoting to a traditional Client/Server implementation will require minimal effort, and will leave the option of implementing a BLE Mesh connection as a potential future task.
Also data seeding has been refactored into a ChatProvider with random data to better mimic a real connection.
Seed debug data button has been removed since that feature was removed in the previous iteration.
Attempting to send a message before a connection is established now throws an exception.
Assertions were added to the Message class to ensure that the peer is not set or retrieved for system messages (doing so is considered a programming error, the behavior is undefined).
We are adding a feature to allow the user to change his/her profile name Session.setCurrentPeer
:
The Socket Connection Client class had numerous areas in need of refactoring. Primarily, the class had too many responsibilities (socket creation, input, output, response handling, etc.).
The refactoring separated many of the responsibilities into separate classes:
- DataIOStreamTask: AsyncTask used to create the socket
- DataIOStream: Tuple class use to pass input streams from task to client.
- InputThread: Thread dedicated to receiving data.
- OutputThread: Thread dedicated to sending data.
public class Client extends Thread {
private static final String TAG = "Client";
private static final int PORT = 8080;
private static final String ADDRESS = "10.0.1.13";
private final Peer mPeer;
private final ClientListener mListener;
private String messageToSend = "";
private boolean connected = false;
private final Gson mGson = new Gson();
private final JsonParser mJsonParser = new JsonParser();
public Client(Peer peer, ClientListener listener) {
mPeer = peer;
mListener = listener;
}
@Override
public void run() {
Socket socket = null;
DataInputStream inputStream = null;
DataOutputStream outputStream = null;
try {
InetSocketAddress address = new InetSocketAddress(ADDRESS, PORT);
socket = new Socket();
socket.connect(address, 3000);
inputStream = new DataInputStream(socket.getInputStream());
outputStream = new DataOutputStream(socket.getOutputStream());
String handshake = JSONWrapper.wrap("connect", mPeer);
outputStream.writeUTF(handshake);
outputStream.flush();
connected = true;
while(connected) {
if (inputStream.available() > 0) {
text = inputStream.readUTF();
JsonElement jsonElement = mJsonParser.parse(text);
JsonObject jsonObject = jsonElement.getAsJsonObject();
if (jsonObject.has("connect")) {
Peer peer = mGson.fromJson(jsonObject.get("connect"), Peer.class);
mListener.onPeerConnected(peer);
} else if (jsonObject.has("disconnect")) {
Peer peer = mGson.fromJson(jsonObject.get("disconnect"), Peer.class);
mListener.onPeerDisconnected(peer);
} else if (jsonObject.has("message")) {
Message message = mGson.fromJson(jsonObject.get("message"), Message.class);
mListener.onMessageReceived(message);
} else {
Log.d(TAG, "Unrecognized payload: " + text);
}
}
if (messageToSend.length() > 0) {
outputStream.writeUTF(handshake);
outputStream.flush();
messageToSend = "";
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (outputStream != null) {
try {
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
private void sendText(String text) {
messageToSend = text;
}
public void sendMessage(Message message) {
String json = JSONWrapper.wrap("message", message);
sendText(json);
}
public void disconnect() {
connected = false;
}
public interface ClientListener {
void onFailedConnection();
void onPeerConnected(Peer peer);
void onPeerDisconnected(Peer peer);
void onMessageReceived(Message message);
}
}
public class Client implements InputThread.Listener {
private static final String TAG = "Client";
private static final int PORT = 8080;
private static final String ADDRESS = "10.0.1.13";
private final Peer mPeer;
private final ClientListener mListener;
private DataIOStream mDataIOStream;
private OutputThread mOutputThread;
private final Gson mGson = new Gson();
private final JsonParser mJsonParser = new JsonParser();
public Client(Peer peer, ClientListener listener) {
mPeer = peer;
mListener = listener;
}
public void start() {
new DataIOStreamTask().execute();
}
private void startThreads(DataIOStream dataIOStream) {
if (dataIOStream == null) {
mListener.onFailedConnection();
return;
}
mDataIOStream = dataIOStream;
InputThread inputThread = new InputThread(mDataIOStream.in(), this);
mOutputThread = new OutputThread(mDataIOStream.out());
inputThread.start();
mOutputThread.start();
String handshake = JSONWrapper.wrap("connect", mPeer);
mOutputThread.sendText(handshake);
}
public void sendMessage(Message message) {
String json = JSONWrapper.wrap("message", message);
mOutputThread.sendText(json);
}
public void disconnect() {
try {
mDataIOStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void receivedText(String text) {
JsonElement jsonElement = mJsonParser.parse(text);
JsonObject jsonObject = jsonElement.getAsJsonObject();
if (jsonObject.has("connect")) {
Peer peer = mGson.fromJson(jsonObject.get("connect"), Peer.class);
mListener.onPeerConnected(peer);
} else if (jsonObject.has("disconnect")) {
Peer peer = mGson.fromJson(jsonObject.get("disconnect"), Peer.class);
mListener.onPeerDisconnected(peer);
} else if (jsonObject.has("message")) {
Message message = mGson.fromJson(jsonObject.get("message"), Message.class);
mListener.onMessageReceived(message);
} else {
Log.d(TAG, "Unrecognized payload: " + text);
}
}
public interface ClientListener {
void onFailedConnection();
void onPeerConnected(Peer peer);
void onPeerDisconnected(Peer peer);
void onMessageReceived(Message message);
}
private class DataIOStreamTask extends AsyncTask<Void, Void, DataIOStream> {
@Override
protected DataIOStream doInBackground(Void... params) {
try {
InetSocketAddress address = new InetSocketAddress(ADDRESS, PORT);
Socket socket = new Socket();
socket.connect(address, 3000);
return new DataIOStream(socket);
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
@Override
protected void onPostExecute(DataIOStream dataIOStream) {
startThreads(dataIOStream);
}
}
}
public class DataIOStream {
final private Socket mSocket;
final private DataInputStream mInputStream;
final private DataOutputStream mOutputStream;
public DataIOStream(Socket socket) throws IOException {
mSocket = socket;
mInputStream = new DataInputStream(mSocket.getInputStream());
mOutputStream = new DataOutputStream(mSocket.getOutputStream());
}
public DataInputStream in() {
return mInputStream;
}
public DataOutputStream out() {
return mOutputStream;
}
public void close() throws IOException {
mSocket.close();
mOutputStream.close();
mInputStream.close();
}
}
class InputThread extends Thread {
private final DataInputStream mDataInputStream;
private final Listener mListener;
public InputThread(DataInputStream dataInputStream, Listener listener) {
mDataInputStream = dataInputStream;
mListener = listener;
}
public interface Listener {
void receivedText(String text);
}
@Override
public void run() {
try {
//noinspection InfiniteLoopStatement
while (true) {
String input = mDataInputStream.readUTF();
mListener.receivedText(input);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
class OutputThread extends Thread {
private final DataOutputStream mDataOutputStream;
private final Object mLock = new Object();
private String mText = "";
OutputThread(DataOutputStream dataOutputStream) {
mDataOutputStream = dataOutputStream;
}
@Override
public void run() {
try {
//noinspection InfiniteLoopStatement
while (true) {
String text;
synchronized(mLock) {
try {
mLock.wait();
} catch(InterruptedException ignored) {}
text = mText;
}
mDataOutputStream.writeUTF(text);
mDataOutputStream.flush();
}
} catch (IOException e) {
e.printStackTrace();
}
}
public void sendText(String text) {
synchronized(mLock) {
mText = text;
mLock.notify();
}
}
}
The networking design will use an iterative design to incrementally increase the range of communication. Initial design should focus on peer to peer communication (1 to 1). The network model should then focus on hub and spoke, allowing one node to act as the host and all others as clients. Finally, the model will act as a mesh network, with each node acting simultaneously as a client, host, and repeater.
Follow standard Java/Android coding standards. See https://source.android.com/source/code-style.html