NOTE:

NOTE: Of late, I have been getting requests for very trivial problems that many of you are facing in your day-to-day work. This blog is not to solve your "project" problems - surely not a "Support" site.
I just love to share my knowledge in my spare time and would appreciate any questions or feedback on the articles and code I have shared. I also do appreciate thought-provoking questions that would lead me to write more articles and share.
But please do not put your day-to-day trivial problems here. Even if you do, you most probably would not get a response here.
Thanks

Search This Blog

x

Saturday, 31 August 2013

The Chord Android SDK Starter Program - Part 1


 With all of the knowledge shared with you in the last 3 articles, I wanted to develop a very basic Android app – almost like a “Hello World” App using the Chord Android SDK.

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.