However, by the time I developed the 1st
app, it still seemed like a lot of code. Of course this kind of an app is meant
to do a lot more than just say ‘Hello World’. It’s power is best utilized in developing
games, I would say.
Like the poker
game that is available in the Samsung Android developer documentation.
So, What are the steps to start with?
Step 1: You first need to create a channel. I do this when I initialize my service. I have written a service that gets an instance
of the ChordManager.
public void
initialize(IChordServiceListener csListener) {
if (mChord != null) {
return;
}
mChord =
ChordManager.getInstance(this);
mListener = csListener;
mChord.setTempDirectory(chordFilePath);
mChord.setHandleEventLooper(getMainLooper());
…
}
Note that I have got an ChordManager here
and associated a temporary file path and a the app’s main looper.
Step 2:
Next, You would want a way to join the channel So, let us implement the
joinChannel() method and leaveChannel() method as its complement.
This is after the ChordManager is up and
running.
public IChordChannel joinChannel(String
channelName) {
if (channelName
== null || channelName.equals("")) {
mPrivateChannelName = MYCHANNEL;
} else {
mPrivateChannelName =
channelName;
}
IChordChannel channelInst = mChord.joinChannel(mPrivateChannelName, mChannelListener);
if (null == channelInst) {
return null;
}
return channelInst;
}
This seems straight forward. MYCHANNEL is a
constant declared earlier as public static String MYCHANNEL = "com.sai.CHANNELONE";
If the caller does not pass a channel name,
the default one declared would be used.
While joining a channel, we need to pass
the Channel name and also an associated listener that is of the type IChordChannelListener.
So next step would be implement this
listener.
Note the leave channel code would be like
this:
public void
leaveChannel() {
mChord.leaveChannel(mPrivateChannelName);
mPrivateChannelName = "";
}
Step 3: Implement the IChordChannelListener.
All the 9 methods of this have to be
implemented. These are the methods that would be called back when a file is
sent or data is received etc. And the actual implementation for this would be
in the Activity that is invoking the ChordManager service.
Here, based on the example from Samsung, I
have also implemented these methods mainly as a way of calling a listener that
is implemented by the activity invoking it.
@Override
public void onDataReceived(String arg0, String arg1, String arg2,
byte[][] arg3) {
if (!CHORD_APITEST_MESSAGE_TYPE.equals(payloadType))
return;
byte[] buf = payload[0];
if (null != mListener)
mListener.onReceiveMessage(fromNode,
fromChannel, new String(buf));
}
@Override
public void onFileChunkReceived(String fromNode, String fromChannel, String
fileName,
String hash, String
fileType, String exchangeId, long fileSize, long offset) {
if (null != mListener) {
int progress = (int)(offset * 100 / fileSize);
mListener.onFileProgress(false, fromNode, fromChannel, progress, exchangeId);
}
}
@Override
public void onFileChunkSent(String toNode, String toChannel, String fileName,
String hash, String
fileType, String exchangeId, long fileSize, long offset,
long chunckSize) {
if (null != mListener) {
int progress = (int)(offset * 100 / fileSize);
mListener.onFileProgress(true, toNode, toChannel, progress, exchangeId);
}
}
@Override
public void onFileFailed(String node, String channel, String fileName,
String hash, String
exchangeId, int reason) {
switch (reason) {
case ERROR_FILE_REJECTED: {
if (null != mListener) {
mListener.onFileCompleted(IChordServiceListener.REJECTED, node,
channel,
exchangeId,
fileName);
}
break;
}
case ERROR_FILE_CANCELED: {
if (null != mListener) {
mListener.onFileCompleted(IChordServiceListener.CANCELLED, node,
channel,
exchangeId,
fileName);
}
break;
}
case ERROR_FILE_CREATE_FAILED:
case ERROR_FILE_NO_RESOURCE:
default:
if (null != mListener) {
mListener.onFileCompleted(IChordServiceListener.FAILED, node,
channel,
exchangeId,
fileName);
}
break;
}
}
@Override
public void onFileReceived(String fromNode, String fromChannel, String fileName,
String hash, String fileType,
String exchangeId, long fileSize, String tmpFilePath) {
String savedName = fileName;
int i =
savedName.lastIndexOf(".");
String name =
savedName.substring(0, i);
String ext =
savedName.substring(i);
Log.d("CHORDSERVICE", "onFileReceived : " + fileName);
Log.d("CHORDSERVICE", "onFileReceived : " + name + "
" + ext);
File targetFile = new File(chordFilePath, savedName);
int index = 0;
while
(targetFile.exists()) {
savedName = name + "_" + index + ext;
targetFile = new File(chordFilePath, savedName);
index++;
Log.d("CHORDSERVICE", "onFileReceived : " + savedName);
}
File srcFile = new File(tmpFilePath);
srcFile.renameTo(targetFile);
if (null != mListener) {
mListener.onFileCompleted(IChordServiceListener.RECEIVED, fromNode, fromChannel,
exchangeId, savedName);
}
}
@Override
public void onFileSent(String toNode, String toChannel, String fileName,
String hash, String
fileType, String exchangeId) {
if (null != mListener) {
mListener.onFileCompleted(IChordServiceListener.SENT, toNode, toChannel,
exchangeId, fileName);
}
}
@Override
public void onFileWillReceive(String fromNode, String fromChannel, String fileName,
String hash, String
fileType, String exchangeId, long fileSize) {
File targetdir = new File(chordFilePath);
if
(!targetdir.exists()) {
targetdir.mkdirs();
}
//verifying if the external storage is avaiable
StatFs stat = new StatFs(chordFilePath);
long blockSize =
stat.getBlockSize();
long totalBlocks =
stat.getAvailableBlocks();
long
availableMemory = blockSize * totalBlocks;
if
(availableMemory < fileSize) {
rejectFile(fromChannel,
exchangeId);
if (null != mListener)
mListener.onFileCompleted(IChordServiceListener.FAILED, fromNode,
fromChannel,
exchangeId,
fileName);
return;
}
if (null != mListener)
mListener.onFileWillReceive(fromNode,
fromChannel, fileName, exchangeId);
}
//This is called whenever a node joins a channel
@Override
public void onNodeJoined(String fromNode, String fromChannel) {
Log.v("CHORDSERVICE", "onNodeJoined(), fromNode : " + fromNode + ",
fromChannel : "
+ fromChannel);
if (null != mListener)
mListener.onNodeEvent(fromNode,
fromChannel, true);
}
//This is called whenever a node leaves the channel
@Override
public void onNodeLeft(String fromNode, String fromChannel) {
Log.v("CHORDSERVICE", "onNodeJoined(), fromNode : " + fromNode + ",
fromChannel : "
+ fromChannel);
if (null != mListener)
mListener.onNodeEvent(fromNode,
fromChannel, false);
}
};
Step 4: What should happen when a Node
joins the channel and leaves the channel is also dictated by the methods
overridden above.
In all of the above, you see that the
Listener (a new one declared in the Service class) is what is passed in by the
activity.
i.e. in the ChordService class, I have declared a new
Listener – IChordServiceListener. And what does the listener do? It declares methods that the activity
that called the ChordService should implement. They are related what should the
activity do on receiving a message, when a node joins or leaves, when a network
state changes etc.
So, IChordServiceListener is becoming the glue
between the implementation in the activity and the call back methods with the ChordService to
the IChordChannelListener.
Here is IChordServiceListener declaration:
public interface
IChordServiceListener {
void
onReceiveMessage(String node, String channel, String message);
void
onFileWillReceive(String node, String channel, String fileName, String
exchangeId);
void
onFileProgress(boolean bSend, String node, String channel, int progress,
String exchangeId);
public static final int SENT = 0;
public static final int RECEIVED = 1;
public static final int CANCELLED = 2;
public static final int REJECTED = 3;
public static final int FAILED = 4;
void
onFileCompleted(int reason, String node, String channel,
String exchangeId,
String fileName);
void
onNodeEvent(String node, String channel, boolean bJoined);
void
onNetworkDisconnected();
void onUpdateNodeInfo(String
nodeName, String ipAddress);
void
onConnectivityChanged();
}
Step 5: Now that we have implemented the
IChordChannelListener, we have 2 more Listeners that we need to deal with. One
is the INetworkListener.
This is implemented in the initialize
method itself as follows:
mChord.setNetworkListener(new INetworkListener() {
@Override
public void onConnected(int interfaceType) {
if (null != mListener) {
mListener.onConnectivityChanged();
}
}
@Override
public void onDisconnected(int interfaceType) {
if (null != mListener) {
mListener.onConnectivityChanged();
}
}
});
Step 6: The only other listener we need to
Implement is the IChordManagerListener. This is typically used by the
ChordManager when it starts. Here is the
implementation for the same.
public int start() {
return mChord.start(ChordManager.INTERFACE_TYPE_WIFI, new IChordManagerListener() {
@Override
public void onError(int arg0) {
}
@Override
public void onNetworkDisconnected() {
if (null != mListener)
mListener.onNetworkDisconnected();
}
@Override
public void onStarted(String name, int reason) {
if (null != mListener)
mListener.onUpdateNodeInfo(name, mChord.getIp());
if (STARTED_BY_RECONNECTION == reason) {
return;
}
IChordChannel channel
= mChord.joinChannel(ChordManager.PUBLIC_CHANNEL,
mChannelListener);
}
});
}
Note that I have made an assumption that the
ChordManager is going to use only Wifi as the mode of communication between
nodes for simplicity sake.
With most of the above code , the basic aspects of the
ChordService are ready for being invoked by any calling activity. There are a
few aspects around binding to a service, unbinding to a service etc. I have in
the code but not explaining here as it is not an aspect of the Chord Android SDK. It is more of a
basic android service.
The complete code for this along with the activity
that invokes the ChordService will be part of the next article in the series.
So, with all this basic exploration, I am curious to
know if any of you have entered the Samsung Android Contest and also to see
the plethora of ways this SDK
has been used innovatively.