package org.safs.android.messenger; import java.io.CharArrayWriter; import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.Hashtable; import java.util.List; import java.util.Properties; import java.util.UUID; import org.safs.android.messenger.client.MessageResult; import org.safs.sockets.DebugListener; import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.os.Messenger; import android.os.Parcelable; import android.util.Log; /** * This class contains methods to handle message of big size.<br> * At the message-sender side, the message will be divided to multiples parcels. These multiples<br> * parcels contain an ID to indicate to which message they belong; They contains an index to<br> * indicate their order; They also contains a filed to indicate if it is the last parcel.<br> * * At the message-receiver side, the multiple parcels will be assembled to one message.<br> * * <p>Message-Sender:<br> * {@link org.safs.android.messenger.MessengerService}<br> * {@link org.safs.android.messenger.client.MessengerRunner}<br> * * <p>Message-Receiver:<br> * {@link org.safs.android.messenger.MessengerHandler}<br> * {@link org.safs.android.messenger.client.MessengerHandler}<br> * * @see org.safs.android.messenger.MessengerService * @see org.safs.android.messenger.client.MessengerRunner * @see org.safs.android.messenger.MessengerHandler * @see org.safs.android.messenger.client.MessengerHandler * * @author Lei Wang, SAS Institute, Inc * @since APR 25, 2013 * */ public abstract class MultipleParcelsHandler extends Handler{ public String TAG = getClass().getName(); private DebugListener debugListener = null; private MultipleParcelListener multipleParcelListener = null; public static final int INITIAL_MESSAGE_CACHE_SIZE = 10; public static final int INITIAL_PARCEL_CACHE_SIZE = 5; /** time to wait for acknowledgment of parcel in milliseconds*/ public static final int TIMEOUT_WAIT_FOR_PARCEL_ACKNOWLEDGMENT = 5000; public MultipleParcelsHandler(Looper looper, MultipleParcelListener multipleParcelListener) { super(looper); this.multipleParcelListener = multipleParcelListener; if(multipleParcelListener instanceof DebugListener){ this.debugListener = (DebugListener) multipleParcelListener; } } public MultipleParcelsHandler(MultipleParcelListener multipleParcelListener) { super(); this.multipleParcelListener = multipleParcelListener; if(multipleParcelListener instanceof DebugListener){ this.debugListener = (DebugListener) multipleParcelListener; } } protected void debug(String message){ if(debugListener==null){ Log.d(TAG, message); }else{ debugListener.onReceiveDebug(message); } } /* ============================================================================================ * === Message-Receiver Side === * === Following fields and methods are used to handle and assemble small parcels === * === to create a whole message. === * === Message sender should never touch them!!! === * ============================================================================================*/ private static Hashtable<String, ParcelBuffer> messageReceivedCache = new Hashtable<String, ParcelBuffer>(INITIAL_MESSAGE_CACHE_SIZE); private String __last_handled_message_id = null; /** * This method will accumulate small parcels of one message, store them into<br> * a cache for future assembly.<br> * * Message.obj will hold the final result of message:<br> * If the message is not divided into parcels, this method will do no thing.<br> * If the message is divided into parcels, this method will store them into<br> * a cache {@link #messageReceivedCache} and assemble them and assign it to <br> * parameter msg.obj when the last parcel arrives. Each time a parcel arrives<br> * and be stored in the cache {@link #messageReceivedCache}, an acknowledgment<br> * will be sent back to the message-sender.<br> * * The message is sent out by method {@link #sendMessageAsMultipleParcels(Messenger, Message, Object)}<br> * at the message-sender side.<br> * * @param msg Message * @return Boolean, if we have finished handling all the parcels of a whole message. * * @see #sendMessageAsMultipleParcels(Messenger, Message, Object) */ public synchronized boolean assembleSmallParcels(Message msg) throws Exception{ Bundle dataBundle = msg.getData(); String messageID = null; int messageIndex = -1; int totalNumber = -1; ParcelBuffer parcelBuffer = null; SmallParcel smallParcel = null; boolean finished = false; // debug("dataBundle is "+dataBundle); if(MessageUtil.isSmallParcelOfWholeMessage(dataBundle)){ //The message is too big so that it is divided into small parcels, we should //accumulate them into a cache for future assembly messageID = MessageUtil.getParcelableIDFromSmallParcel(dataBundle); messageIndex = MessageUtil.getParcelableIndexFromSmallParcel(dataBundle); totalNumber = MessageUtil.getParcelableTotalNumberFromSmallParcel(dataBundle); debug("handling Small Parcel, messageID="+messageID+"; messageIndex="+messageIndex+"; totalNumber="+totalNumber); if(messageID==null) return finished; if(messageID.equals(__last_handled_message_id) && MessageUtil.getParcelableResentParcelFromSmallParcel(dataBundle)){ //If the message has been handled and its parcels arrive again, ignore it. return finished; } smallParcel = new SmallParcel(msg, messageIndex); parcelBuffer = messageReceivedCache.get(messageID); if(parcelBuffer==null){ parcelBuffer = new ParcelBuffer(smallParcel, totalNumber); messageReceivedCache.put(messageID, parcelBuffer); }else{ parcelBuffer.addParcel(smallParcel); } //Send acknowledgment that one parcel has been received. sendAcknowledgment(messageID, messageIndex); if(parcelBuffer.receivedAllParcels()){ debug("Trying to assemble message, messageID="+messageID); //Assign the assembled data to input parameter msg.obj msg.obj = parcelBuffer.assembleParcels().object; debug("assembly finished for message, messageID="+messageID); messageReceivedCache.remove(messageID); __last_handled_message_id = messageID; finished = true; } }else{ //else, nothing needs to do, just send back the result. finished = true; } return finished; } /** * This class is used to wrap a small parcel of a whole message. * */ protected class SmallParcel implements Comparable<SmallParcel>{ /** The order of parcel in one whole message.*/ private int index = -1; /** The type of parcel of one whole message.*/ private int msgType = -1; /** The content of parcel, which is part of one whole message.*/ private Parcelable object = null;//Parcelable containing String or char[] or other types public SmallParcel(){} public SmallParcel(Message message, int index){ this.index = index; this.msgType = message.what; try{ this.object = (Parcelable) message.obj; }catch(Exception e){ debug("Can't set a part of message, due to "+e.getClass().getName()+":"+e.getMessage()); } } public int compareTo(SmallParcel another) { //Will be sorted according orderID return index-another.index; } public int getIndex() { return index; } public void setIndex(int index) { this.index = index; } public int getMsgType() { return msgType; } public void setMsgType(int msgType) { this.msgType = msgType; } public Object getObject() { return object; } public void setObject(Parcelable object) { this.object = object; } public Object getRealObject() { if(object==null) return null; if(isStringType()){ return MessageUtil.getParcelableMessage(object); }else if(isCharArrayType()){ return MessageUtil.getParcelableProps(object); }else{ return object; } } public boolean isStringType(){ return (MessageUtil.ID_ENGINE_RESULT==msgType || MessageUtil.ID_ENGINE_DEBUG==msgType || MessageUtil.ID_ENGINE_EXCEPTION==msgType || MessageUtil.ID_ENGINE_MESSAGE==msgType || MessageUtil.ID_ENGINE_DISPATCHFILE==msgType ); } public boolean isCharArrayType(){ return (MessageUtil.ID_ENGINE_RESULTPROPS==msgType || MessageUtil.ID_ENGINE_DISPATCHPROPS==msgType ); } /** * This method is used to concatenate the contents of two parcels.<br> * It will append the content provided by parameter 'another' to this<br> * SmallParcel object's content.<br> * * @param another SmallParcel, whose content will be added to this parcel. * @return SmallParcel, contains the concatenated content. */ public SmallParcel add(SmallParcel another) throws Exception{ //If this parcel has no type, assign one for it. if(this.msgType==-1){ this.msgType = another.msgType; } //We need to add parcel of same type, and in order if(this.msgType==another.msgType && another.index>this.index){ this.index = another.index; if(isStringType()){ object = MessageUtil.assembleParcelMessage(this.object, another.object); }else if(isCharArrayType()){ object = MessageUtil.assembleParcelProps(this.object, another.object); }else{ debug("The message type is "+this.msgType+", we don't know how to assemble."); } }else{ //else we ignore the parcel. debug("wrong parcel to add, or the order is wrong."); } return this; } } protected class ParcelBuffer{ private List<SmallParcel> parcels = new ArrayList<SmallParcel>(INITIAL_PARCEL_CACHE_SIZE); private int totalParcelNumber = 0; public ParcelBuffer(SmallParcel parcel, int totalNumber){ addParcel(parcel); totalParcelNumber = totalNumber; } public synchronized int addParcel(SmallParcel parcel){ parcels.add(parcel); return parcels.size(); } public synchronized int getTotalParcelNumber(){ return parcels.size(); } /** * We need to sort all the small parcels according to parcel's index<br> * before assembling them. This method is called in {@link #assembleParcels()}<br> * * @see #assembleParcels() */ public synchronized void sortBuffer(){ Collections.sort(parcels); } /** * @return boolean, true if all the parcels have been received. */ public synchronized boolean receivedAllParcels(){ return parcels.size()==totalParcelNumber; } /** * [Note] This method should NOT be called if {@link #receivedAllParcels()} return false.<br> * Assemble all parcels stored in this buffer.<br> * * @return {@link SmallParcel}, the assembled parcel containing content of all parcels. * @see #receivedAllParcels() */ public synchronized SmallParcel assembleParcels() throws Exception{ SmallParcel wholeMessage = new SmallParcel(); if(parcels.size()==0){ throw new Exception("There is no parcels in the parcel buffer!!!"); } //To keep the received parcels in order. sortBuffer(); for(int i=0;i<parcels.size();i++){ // debug("assembling part "+parcels.get(i).index); wholeMessage.add(parcels.get(i)); } return wholeMessage; } } protected void sendAcknowledgment(String messageID){ multipleParcelListener.onAllParcelsHaveBeenHandled(messageID); } protected void sendAcknowledgment(String messageID, int index){ multipleParcelListener.onParcelHasBeenHandled(messageID, index); } /** * The subclass of this class must implement this method to handle message.<br> */ abstract protected void handleWholeMessage(Message msg); /* ============================================================================================ * === Message-Sender side === * === Following fields and methods are used to generate multiple parcels of one message === * === Message receiver should never touch them!!! === * ============================================================================================ */ private static String __last_unique_key = ""; /** * Routine is used to create a unique ID String key that can be used by "message-sender" * to identify different small parcels of the same message.<br> * This method is thread-safe: it guarantees that multiple threads can get unique ID.<br> * * @return unique String suitable to be the key for the item. */ protected String getUniqueKey() { String timestamp = ""; synchronized (__last_unique_key) { do {timestamp = UUID.randomUUID().toString(); } while (timestamp.equals(__last_unique_key)); __last_unique_key = timestamp; } return timestamp; } /** * This cache is used to store the message (small parcels) that we have sent out.<br> * The key is the messageID, the value is a set of small parcels (part of a message).<br> * If some parcels are lost, we can get them and send again.<br> * This cache needs to be cleared when all the parcels arrive at the receiver side.<br> */ private static Hashtable<String, Hashtable<Integer, Message>> messageSendedCache = new Hashtable<String, Hashtable<Integer, Message>>(INITIAL_MESSAGE_CACHE_SIZE); /** * Store parcel (part of a message) into a cache according to the message ID.<br> * If the parcel is not a part of a whole message, it will not be stored in the cache<br> * We use {@link MessageUtil#isSmallParcelOfWholeMessage(Bundle)} to test if this<br> * parcel is a part of message.<br> * * @param parcel, Message, one parcel of a whole message, * it should contain a data(ID,index,totalNumber) * @return String, the messageID of the parcel stored. */ protected synchronized String storeParcelMessage(Message parcel){ String messageID = null; //Verify that this parcel's has correct format as a part of whole message if(!MessageUtil.isSmallParcelOfWholeMessage(parcel.getData())){ debug("This parcel is not part of message! We will not store it in the cache."); return messageID; } messageID = MessageUtil.getParcelableIDFromSmallParcel(parcel.getData()); Hashtable<Integer, Message> parcels = messageSendedCache.get(messageID); if(parcels==null){ parcels = new Hashtable<Integer, Message>(INITIAL_PARCEL_CACHE_SIZE); messageSendedCache.put(messageID, parcels); } int index = MessageUtil.getParcelableIndexFromSmallParcel(parcel.getData()); parcels.put(Integer.valueOf(index), parcel); debug("Stored parcel, messageID="+messageID+", index="+index); return messageID; } /** * Clear the cache {@link #messageSendedCache} */ protected synchronized void clearParcelMessage(String messageID){ debug("Clearing message, messageID="+messageID); messageSendedCache.remove(messageID); notifyAll(); debug("notifying with 'all parcels arrived' messageID="+messageID); } /** * Clear one parcel of a message from the cache {@link #messageSendedCache} */ protected synchronized void clearParcelMessage(String messageID, int index){ debug("Clearing parcel, messageID="+messageID+", index="+index); Hashtable<Integer, Message> parcels = messageSendedCache.get(messageID); if(parcels!=null) parcels.remove(Integer.valueOf(index)); notifyAll(); debug("notifying with 'one parcel arrived' messageID="+messageID+" index="+index); } /** * According to messageId to get a set of parcels of a whole messagefrom<br> * the cache {@link #messageSendedCache}.<br> * * @param messageID, String, represent a message * @return Message[], an array of parcels of a message. */ protected synchronized Message[] getParcelMessage(String messageID){ Hashtable<Integer, Message> parcels = messageSendedCache.get(messageID); Message[] messages = null; if(parcels!=null) messages = parcels.values().toArray(new Message[0]); return messages; } /** * According to messageId and index to get one parcel of a whole message from<br> * the cache {@link #messageSendedCache}.<br> * * @param messageID, String, represent a message. * @param index, int, the index of the parcel. * @return Message, one parcel of a message. */ protected synchronized Message getParcelMessage(String messageID, int index){ Hashtable<Integer, Message> parcels = messageSendedCache.get(messageID); Message message = null; if(parcels!=null){ message = parcels.get(Integer.valueOf(index)); } return message; } /** * It is used to test if the cache {@link #messageSendedCache} still contains a<br> * certain message indicated by parameter messageID.<br> * * @see #clearParcelMessage(String) * @see #clearParcelMessage(String, int) */ protected synchronized boolean containMessageWithoutAck(String messageID){ if(messageID==null) return false; Hashtable<Integer, Message> parcels = messageSendedCache.get(messageID); return (parcels!=null && parcels.size()>0); } /** * Calculate the total number of parcels the message will be divided.<br> */ protected int getTotalNumberOfParcels(int dataLength, int parcelLength){ if((dataLength%parcelLength)==0) return dataLength/parcelLength; return dataLength/parcelLength +1; } /** * Divide message's content into small parcels if the message content size is too large.<br> * If the content size is bigger than {@link MessageUtil#MAX_TRANSFER_BYTE_SIZE}, it will<br> * be divided into small parcels, wrapped to a Message object and put into a List to return.<br> * If the content size is smaller than {@link MessageUtil#MAX_TRANSFER_BYTE_SIZE}, it will<br> * be set to the original Message object and put into a List to return.<br> * * @param msg Message, the original message to send. It contains 'what' and 'replyTo'. * @param props Properties, the real content to send through Message. * @return List of Message, to send. Never null and contain at the least one Message. * * @see MessageUtil */ protected List<Message> divideMessageIntoSmallPieces(Message msg, Properties props) throws Exception{ CharArrayWriter chars = new CharArrayWriter(); props.store(chars, "ResultProperties"); char[] buffer = chars.toCharArray(); return divideMessageIntoSmallPieces(msg, buffer); } /** * Divide message's content into small parcels if the message content size is too large.<br> * If the content size is bigger than {@link MessageUtil#MAX_TRANSFER_BYTE_SIZE}, it will<br> * be divided into small parcels, wrapped to a Message object and put into a List to return.<br> * If the content size is smaller than {@link MessageUtil#MAX_TRANSFER_BYTE_SIZE}, it will<br> * be set to the original Message object and put into a List to return.<br> * * @param msg Message, the original message to send. It contains 'what' and 'replyTo'. * @param buffer char[], the real content to send through Message. * @return List of Message, to send. Never null and contain at the least one Message. * * @see MessageUtil */ protected List<Message> divideMessageIntoSmallPieces(Message msg, char[] buffer) throws Exception{ List<Message> messages = new ArrayList<Message>(); if(buffer==null){ messages.add(msg); }else{ debug("Trying to send message of size "+buffer.length); if(buffer.length>MessageUtil.MAX_TRANSFER_BYTE_SIZE){ //Divide the message to multiple small parcels //Generate an ID for those small parcels so that they can be identified as one same message. String messageID = getUniqueKey(); debug("The message will be sent as multiple parcels, belonging to same message, messageID="+messageID); int totalParcelsNumber = getTotalNumberOfParcels(buffer.length,MessageUtil.SMALL_TRANSFER_BYTE_SIZE); CharArrayWriter smallParcel = null; Message smallParcelMessage = null; int count = 0; int len = MessageUtil.SMALL_TRANSFER_BYTE_SIZE; for(int i=0;i<buffer.length; ){ //Obtain a new Message to carry a part of content of original message //we need to obtain from the original one to keep the Message#what,Message#replyTo smallParcelMessage = Message.obtain(msg); //Let the new Message to carry part of the chars smallParcel = new CharArrayWriter(MessageUtil.SMALL_TRANSFER_BYTE_SIZE); if(i+MessageUtil.SMALL_TRANSFER_BYTE_SIZE>=buffer.length){ //This is the last part of whole message len = buffer.length - i; }else{ len = MessageUtil.SMALL_TRANSFER_BYTE_SIZE; } smallParcel.write(buffer, i, len); i += len; smallParcelMessage.obj = MessageUtil.setParcelableProps(smallParcel.toCharArray()); //Set extra information to data of the new Message, so that the message receiver can //assemble the different small parcels to create a whole message. smallParcelMessage.setData(MessageUtil.setBundleOfSmallParcel(messageID, count++, totalParcelsNumber)); //debug("Adding small parcel to List for message messageID="+messageID+"; parcelCount="+count+"; size="+len); messages.add(smallParcelMessage); } }else{ debug("The message will be sent as one parcel."); msg.obj = MessageUtil.setParcelableProps(buffer); messages.add(msg); } } return messages; } /** * Divide message's content into small parcels if the message content size is too large.<br> * If the content size is bigger than {@link MessageUtil#MAX_TRANSFER_BYTE_SIZE}, it will<br> * be divided into small parcels, wrapped to a Message object and put into a List to return.<br> * If the content size is smaller than {@link MessageUtil#MAX_TRANSFER_BYTE_SIZE}, it will<br> * be set to the original Message object and put into a List to return.<br> * * @param msg Message, the original message to send. It contains 'what' and 'replyTo'. * @param message String, the real content to send through Message. * @return List of Message, to send. Never null and contain at the least one Message. * * @see MessageUtil */ protected List<Message> divideMessageIntoSmallPieces(Message msg, String message) throws Exception{ List<Message> messages = new ArrayList<Message>(); if(message==null){ messages.add(msg); }else{ int messageLength = message.length(); debug("Trying to send message of size "+messageLength); if(messageLength > MessageUtil.MAX_TRANSFER_BYTE_SIZE){ //Divide the message to multiple small parcels //Generate an ID for those small parcels so that they can be identified as one same message. String messageID = getUniqueKey(); debug("The message will be sent as multiple parcels, belonging to same message, messageID="+messageID); int totalParcelsNumber = getTotalNumberOfParcels(messageLength,MessageUtil.SMALL_TRANSFER_BYTE_SIZE); String smallParcel = null; Message smallParcelMessage = null; int count = 0; int len = MessageUtil.SMALL_TRANSFER_BYTE_SIZE; for(int i=0;i<messageLength; ){ //Obtain a new Message to carry a part of content of original message //we need to obtain from the original one to keep the Message#what,Message#replyTo smallParcelMessage = Message.obtain(msg); //Let the new Message to carry part of the chars if(i+MessageUtil.SMALL_TRANSFER_BYTE_SIZE>=messageLength){ //This is the last part of whole message len = messageLength - i; }else{ len = MessageUtil.SMALL_TRANSFER_BYTE_SIZE; } smallParcel = message.substring(i, i+len); i += len; smallParcelMessage.obj = MessageUtil.setParcelableMessage(smallParcel); //Set extra information to data of the new Message, so that the message receiver can //assemble the different small parcels to create a whole message. smallParcelMessage.setData(MessageUtil.setBundleOfSmallParcel(messageID, count++, totalParcelsNumber)); //debug("Adding small parcel to List for message messageID="+messageID+"; parcelCount="+count+"; size="+len); messages.add(smallParcelMessage); } }else{ debug("The message will be sent as one parcel."); msg.obj = MessageUtil.setParcelableMessage(message); messages.add(msg); } } return messages; } /** * If the messag's size is bigger than {@link MessageUtil#MAX_TRANSFER_BYTE_SIZE},<br> * Send the message as multiple parcels<br> * and wait for acknowledgment of the each parcel, if no acknowledgment arrive within<br> * timeout {@value #TIMEOUT_WAIT_FOR_PARCEL_ACKNOWLEDGMENT}, all the parcels in cache<br> * {@link #messageSendedCache} of a message will be re-sent out.<br> * * At the message-receiver side, the parcels will be assembled by method {@link #assembleSmallParcels(Message)}<br> * * @param msg, Message, a wrapper for the object. * @param message, Object, the object to send * @return boolean, true if the message is sent out. * * @see #sendServiceResult(Properties) * @see #sendServiceResult(MessageResult) * @see #assembleSmallParcels(Message) */ public boolean sendMessageAsMultipleParcels(Messenger mService, Message msg, Object object){ try{ List<Message> messages = null; if(object instanceof Properties){ messages = divideMessageIntoSmallPieces(msg, (Properties) object); }else if(object instanceof String){ messages = divideMessageIntoSmallPieces(msg, (String) object); }else if(object instanceof char[]){ messages = divideMessageIntoSmallPieces(msg, (char[]) object); }else{ //If we don't know the object's type, we don't know how to divide it. //just simply assign it directly to msg.obj, we need to implement new code //to handle it. debug("object's type is '"+object.getClass().getName()+"', NEED new code to handle."); messages = new ArrayList<Message>(); //TODO Make sure to convert the object to a parcelable object before assign it to msg.obj // msg.obj = MessageUtil.setParcelableMessage(object); messages.add(msg); } String messageID = null; //If there is only 1 message in list messages, that means we didn't send it as multiple parcels for(int i=0;i<messages.size();i++){ //Before sent out the message, we store it into the cache messageID = storeParcelMessage(messages.get(i)); // if(i!=1) mService.send(messages.get(i));//Don't send the 2nd parcel intentionally to test lost parcel. mService.send(messages.get(i)); } //Wait for the acknowledgment of the 'sent-message'. //If we receive an acknowledgment, that's good. //If we didn't receive an acknowledgment within timeout, we will send all parcels //stored in cache for message (indicated by messageID) long time = 0; synchronized (this) { while(containMessageWithoutAck(messageID)){ time = new Date().getTime(); debug("waitting for acknowledgments..."); wait(TIMEOUT_WAIT_FOR_PARCEL_ACKNOWLEDGMENT); if(new Date().getTime()>=(time+TIMEOUT_WAIT_FOR_PARCEL_ACKNOWLEDGMENT)){ debug("notified by timeout, re-send parcels."); if(containMessageWithoutAck(messageID)){ sendMessageOfLostParcel(mService, messageID); } }else{ debug("notified by ack, no need to re-send parcels."); } } } return true; }catch(Exception x){ debug("Failed to send to MessengerService due to "+org.safs.sockets.Message.getStackTrace(x)); } return false; } /** * According to the messageID, we will get the message from cache {@link #messageSendedCache}<br> * and send it out.<br> * We should set the field {@link MessageUtil#BUNDLE_SMALL_RESENT_PARCEL} to true,<br> * so that 'message-receiver' will know it is a message being resent by 'sender'<br> * * @see #getParcelMessage(String, int) * @see MessageUtil#addParcelableResentParcelFromSmallParcel(Parcelable, boolean) */ public boolean sendMessageOfLostParcel(Messenger mService, String messageID){ try{ Message[] messages = getParcelMessage(messageID); if(messages !=null){ for(int i=0;i<messages.length;i++){ //We add a field to let 'receiver' to know it is a parcel being resent MessageUtil.addParcelableResentParcelFromSmallParcel(messages[i].getData(),true); mService.send(messages[i]); } return true; }else{ return false; } }catch(Exception x){ debug("Failed to send to MessengerService due to "+org.safs.sockets.Message.getStackTrace(x)); } return false; } /** * According to the messageID and index, we will get the message from cache {@link #messageSendedCache}<br> * and send it out.<br> * We should set the field {@link MessageUtil#BUNDLE_SMALL_RESENT_PARCEL} to true,<br> * so that 'message-receiver' will know it is a message being resent by 'sender'<br> * * @see #getParcelMessage(String, int) */ public boolean sendMessageOfLostParcel(Messenger mService, String messageID, int index){ try{ Message message = getParcelMessage(messageID, index); if(message !=null){ //We add a field to let 'receiver' to know it is a parcel being resent MessageUtil.addParcelableResentParcelFromSmallParcel(message.getData(),true); mService.send(message); return true; }else{ return false; } }catch(Exception x){ debug("Failed to send to MessengerService due to "+org.safs.sockets.Message.getStackTrace(x)); } return false; } /* ============================================================================================ * === Message-Sender/Receiver side === * === Following fields and methods are both by sender and receiver. === * ============================================================================================ */ /** * Override method from {@link Handler} * We will handle the small parcels of a whole message. Make sure no parcels * are lost and assemble them in order. */ public void handleMessage(Message msg){ //==================== Sender Side Begin ===================================== //Sender get an 'acknowledgment of message sent back from receiver, we just handle it and return. switch (msg.what){ case MessageUtil.ID_PARCEL_ACKNOWLEDGMENT: //Clear the parcel cache of sent-message at the sender side clearParcelMessage(MessageUtil.getParcelableMessage((Parcelable)msg.obj), msg.arg1); return; case MessageUtil.ID_ALL_PARCELS_ACKNOWLEDGMENT: //Clear the parcel cache of sent-message at the sender side clearParcelMessage(MessageUtil.getParcelableMessage((Parcelable)msg.obj)); return; } //==================== Sender Side End ===================================== //==================== Receiver Side Begin ===================================== try { //If we haven't finished assemble all parcels, we don't send out. if(!assembleSmallParcels(msg)) return; } catch (Exception e) { debug(MessageUtil.getStackTrace(e)); } handleWholeMessage(msg); //After handled message, send acknowledgment to tell the sender to clear the cache if(MessageUtil.isSmallParcelOfWholeMessage(msg.getData())){ String messageID = MessageUtil.getParcelableIDFromSmallParcel(msg.getData()); sendAcknowledgment(messageID); } //==================== Receiver Side End ===================================== } }