/** ** Copyright (C) SAS Institute, All rights reserved. ** General Public License: http://www.opensource.org/licenses/gpl-license.php **/ package org.safs.android.messenger; import android.os.Bundle; import android.os.Message; import android.os.Parcelable; /** * * @author Carl Nagle, SAS Institute, Inc. * @since FEB 04, 2012 (CANAGL) Initial version * <br> APR 25, 2013 (LeiWang) Add some methods to assist handling message of big size. */ public class MessageUtil extends org.safs.sockets.Message{ /** Server: the service is shutting down abnormally and will not be available. */ public static final int ID_SERVER_SHUTDOWN = 0; /** Client: Register the local app client. */ public static final int ID_REGISTER_ENGINE = 1; /** Client: UnRegister the local app client. */ public static final int ID_UNREGISTER_ENGINE = 2; /** Client: local app is Ready to receive commands. */ public static final int ID_ENGINE_READY = 3; /** Server: Notify local app a File is available for processing. */ public static final int ID_ENGINE_DISPATCHFILE = 4; /** Server: Notify local app a serialized Properties Object is available for processing. */ public static final int ID_ENGINE_DISPATCHPROPS = 5; /** Client: local app is Running/Processing the File or Properties dispatched. */ public static final int ID_ENGINE_RUNNING = 6; /** Client: local app is returning simple processing results: (statuscode, statusinfo). */ public static final int ID_ENGINE_RESULT = 7; /** Client: local app is returning a serialized Properties object containing results. */ public static final int ID_ENGINE_RESULTPROPS = 8; /** Client: local app is sending a generic Message. Server: remote TCP client is sending a generic Message. */ public static final int ID_ENGINE_MESSAGE = 9;// send to engine /** Client: local app is sending a debug message. Server: send local app a server debug message. */ public static final int ID_ENGINE_DEBUG = 10; /** Client: local app is sending an Exception message. */ public static final int ID_ENGINE_EXCEPTION = 11; /** Server: a remote TCP client has connected. */ public static final int ID_SERVER_CONNECTED = 12; /** Server: a remote TCP client has disconnected. */ public static final int ID_SERVER_DISCONNECTED = 13; /** Server: a remote TCP client has requested a shutdown. Client: the local appl has shutdown and should be sent no more messages. */ public static final int ID_ENGINE_SHUTDOWN = 14; /** * If the message's size is bigger than {@value #MAX_TRANSFER_BYTE_SIZE} bytes,<br> * the message will be divided into small parcels. To make sure all parcels will<br> * arrive at the 'message-receiver', we need this acknowledgment.<br> * Used by 'message-receiver' to send acknowledgment for all parcels arrived */ public static final int ID_ALL_PARCELS_ACKNOWLEDGMENT = 50; /** * If the message's size is bigger than {@value #MAX_TRANSFER_BYTE_SIZE} bytes,<br> * the message will be divided into small parcels. To make sure all parcels will<br> * arrive at the 'message-receiver', we need this acknowledgment.<br> * Used by 'message-receiver' to send acknowledgment for one parcel arrived */ public static final int ID_PARCEL_ACKNOWLEDGMENT = 51; /** key used to extract generic String message from Bundle. */ public static final String BUNDLE_MESSAGE = "message"; /** key used to extract serialized Properties object from Bundle. */ public static final String BUNDLE_PROPERTIES = "properties"; public static final String SERVICE_CONNECT_INTENT = "org.safs.android.messenger.Connect"; public static final String SERVICE_SHUTDOWN_INTENT = "org.safs.android.messenger.Shutdown"; /** * This field defines the max size can be transfered through Message Service in SAFS.<br> * <p> * Android Message: The Binder transaction buffer has a limited fixed size, currently 1Mb, * which is shared by all transactions in progress for the process. Consequently * TransactionTooLargeException can be thrown when there are many transactions * in progress even when most of the individual transactions are of moderate size. * </p> * <p> * So the real max size of data can be transfered is always smaller than that 1Mb. * Here we just give 0.25Mb as the max size to be permitted to transfer. But this also * may cause TransactionTooLargeException if a lot of transactions is in progress. * In future, we may adjust this value dynamically? * </p> * it is used to decide if a message should be transferred by a few parcels<br> */ public static final int MAX_TRANSFER_BYTE_SIZE = 256*1024; /** * This field defines the block's size to be transfered, if the whole message's size<br> * is bigger than {@value #MAX_TRANSFER_BYTE_SIZE}<br> * it is used when transferring a message by a few parcels<br> */ public static final int SMALL_TRANSFER_BYTE_SIZE = MAX_TRANSFER_BYTE_SIZE/4; /** * key used to extract from Bundle a String value, which is message's ID<br> * it is used when transferring a message by a few parcels<br> */ public static final String BUNDLE_SMALL_PARCEL_ID = "smallparcelid"; /** * key used to extract from Bundle an int value, which is the index of parcels of a message<br> * it is used when transferring a message by a few parcels<br> */ public static final String BUNDLE_SMALL_PARCEL_INDEX = "smallparcelindex"; /** * key used to extract from Bundle an int value, which indicates the total number of<br> * parcels of a whole message to be sent.<br> * it is used when transferring a message by a few parcels<br> */ public static final String BUNDLE_SMALL_PARCEL_TOTALNUMBER = "smallparceltotalnumber"; /** * key used to extract from Bundle an boolean value, which indicates if this parcel<br> * is sent again or not by 'message-sender'.<br> * it is used when transferring a message by a few parcels<br> */ public static final String BUNDLE_SMALL_RESENT_PARCEL = "smallparcelresent"; /** * Create a Parcelable Bundle containing a String message for transport to the test package engine. * The String message is stored via Bundle.putString using {@link #BUNDLE_MESSAGE} as the key for the item. * @param message to send across processes. * @return Parcelable Bundle * @see Bundle#putString(String, String) * @see Bundle#getString(String) */ public static Parcelable setParcelableMessage(String message){ Bundle bundle = new Bundle(); bundle.putString(BUNDLE_MESSAGE, message); bundle.setClassLoader(Bundle.class.getClassLoader()); return bundle; } /** * Create a Parcelable Bundle containing a char[] for transport to the test package engine. * The char[] message is stored via Bundle.putCharArray using {@link #BUNDLE_PROPERTIES} as the key for the item. * The original intent here is to store Java Properties in the Bundle that were serialized via * the Java Properties.store method.<br> * NOTE: This has not yet been tested! * @param char[] (Properties) to send across processes. * @return Parcelable Bundle * @see Bundle#putCharArray(String, char[]) * @see Bundle#getCharArray(String) */ public static Parcelable setParcelableProps(char[] bytes){ Bundle bundle = new Bundle(); bundle.putCharArray(BUNDLE_PROPERTIES, bytes); bundle.setClassLoader(Bundle.class.getClassLoader()); return bundle; } /** * Create a Parcelable Bundle containing 3 parameters: String, int, int for transport from the test package engine.<br> * It will be used by {@link Message#setData(Bundle)} for sending a part of whole message from engine.<br> * The String message is stored via Bundle.putCharArray using {@link #BUNDLE_SMALL_PARCEL_ID} as the key for the item.<br> * The int message is stored via Bundle.putCharArray using {@link #BUNDLE_SMALL_PARCEL_INDEX} as the key for the item.<br> * The int message is stored via Bundle.putCharArray using {@link #BUNDLE_SMALL_PARCEL_TOTALNUMBER} as the key for the item.<br> * * @param ID String, the message's ID. * @param index int, the index of this parcel of the whole message. * @param totalNumber int, indicate the total number of parcels will be sent for a whole message.. * @return Parcelable Bundle * @see Bundle#putCharArray(String, char[]) * @see Bundle#getCharArray(String) * @see #getParcelableIDFromSmallParcel(Parcelable) * @see #getParcelableIndexFromSmallParcel(Parcelable) * @see #getParcelableTotalNumberFromSmallParcel(Parcelable) * @see #isSmallParcelOfWholeMessage(Bundle) */ public static Bundle setBundleOfSmallParcel(String ID, int index, int totalNumber){ Bundle bundle = new Bundle(); bundle.putString(BUNDLE_SMALL_PARCEL_ID, ID); bundle.putInt(BUNDLE_SMALL_PARCEL_INDEX, index); bundle.putInt(BUNDLE_SMALL_PARCEL_TOTALNUMBER, totalNumber); bundle.setClassLoader(Bundle.class.getClassLoader()); return bundle; } public static Bundle addParcelableResentParcelFromSmallParcel(Parcelable parcelable, boolean isResent){ Bundle bundle = (Bundle)parcelable; bundle.putBoolean(BUNDLE_SMALL_RESENT_PARCEL, isResent); return bundle; } /** * Test is the bundle contains the information of small parcel of a whole message.<br> * * @param dataBundle, Bundle, get from the data property of a Message object. * @return boolean, true, if the dataBundle contains the information of small parcel. * @see #setBundleOfSmallParcel(String, int, int) * @see #getParcelableIDFromSmallParcel(Parcelable) * @see #getParcelableIndexFromSmallParcel(Parcelable) * @see #getParcelableTotalNumberFromSmallParcel(Parcelable) */ public static boolean isSmallParcelOfWholeMessage(Bundle dataBundle){ return (dataBundle!=null && dataBundle.containsKey(BUNDLE_SMALL_PARCEL_ID) && dataBundle.containsKey(BUNDLE_SMALL_PARCEL_INDEX) && dataBundle.containsKey(BUNDLE_SMALL_PARCEL_TOTALNUMBER) ); } /** * Extract the String message received from the test package engine. * The String message is retrieved via Bundle.getString using {@link #BUNDLE_MESSAGE} as the key for the item. * @param Parcelable Bundle received from the TCP Messenger Service. * @return String message, if present * @see Bundle#putString(String, String) * @see Bundle#getString(String) */ public static String getParcelableMessage(Parcelable parcelable){ Bundle bundle = (Bundle)parcelable; return bundle.getString(BUNDLE_MESSAGE); } /** * Extract a char[] received from the test package engine. * The char[] message is extracted via Bundle.getCharArray using {@link #BUNDLE_PROPERTIES} as the key for the item. * The original intent here is to retrieve Java Properties stored as a char[] suitable for deserialization * via the Java Properties.load method.<br> * NOTE: This has not yet been tested! * @param Parcelable Bundle received from the test package engine * @return char[] suitable for Java Properties.load * @see Bundle#putCharArray(String, char[]) * @see Bundle#getCharArray(String) */ public static char[] getParcelableProps(Parcelable parcelable){ Bundle bundle = (Bundle)parcelable; return bundle.getCharArray(BUNDLE_PROPERTIES); } /** * Extract a String received from the test package engine. * The String message is extracted via Bundle.getCharArray using {@link #BUNDLE_SMALL_PARCEL_ID} as the key for the item. * * @param Parcelable Bundle received from the test package engine * @return String represents the ID of a message, generated at the engine side. * @see Bundle#putCharArray(String, char[]) * @see Bundle#getCharArray(String) * @see #setBundleOfSmallParcel(String, int, int) */ public static String getParcelableIDFromSmallParcel(Parcelable parcelable){ Bundle bundle = (Bundle)parcelable; return bundle.getString(BUNDLE_SMALL_PARCEL_ID); } /** * Extract a int received from the test package engine. * The int message is extracted via Bundle.getCharArray using {@link #BUNDLE_SMALL_PARCEL_INDEX} as the key for the item. * * @param Parcelable Bundle received from the test package engine * @return int represents the index of a parcel of a whole message, generated at the engine side. * @see Bundle#putCharArray(String, char[]) * @see Bundle#getCharArray(String) * @see #setBundleOfSmallParcel(String, int, int) */ public static int getParcelableIndexFromSmallParcel(Parcelable parcelable){ Bundle bundle = (Bundle)parcelable; return bundle.getInt(BUNDLE_SMALL_PARCEL_INDEX); } /** * Extract an int received from the test package engine. * The int message is extracted via Bundle.getCharArray using {@link #BUNDLE_SMALL_PARCEL_TOTALNUMBER} as the key for the item. * * @param Parcelable Bundle received from the test package engine * @return int the total number of parcels to be sent for one message, generated at the engine side. * @see Bundle#putCharArray(String, char[]) * @see Bundle#getCharArray(String) * @see #setBundleOfSmallParcel(String, int, int) */ public static int getParcelableTotalNumberFromSmallParcel(Parcelable parcelable){ Bundle bundle = (Bundle)parcelable; return bundle.getInt(BUNDLE_SMALL_PARCEL_TOTALNUMBER); } public static boolean getParcelableResentParcelFromSmallParcel(Parcelable parcelable){ Bundle bundle = (Bundle)parcelable; return bundle.getBoolean(BUNDLE_SMALL_RESENT_PARCEL); } /** * Concatenate two Parcelable Bundles containing String to form one Parcelable.<br> * The String message is stored via Bundle.putCharArray using {@link #BUNDLE_MESSAGE} as the key for the item.<br> * * @param one Parcelable Bundle, containing String message * @param another Parcelable Bundle, containing String message * @return Parcelable Bundle, including Parcelable one and another. * @see Bundle#putCharArray(String, char[]) * @see Bundle#getCharArray(String) */ public static Parcelable assembleParcelMessage(Parcelable one, Parcelable another){ Bundle bundle = new Bundle(); String message = ""; String tmp = null; if(one!=null){ tmp = ((Bundle)one).getString(BUNDLE_MESSAGE); if(tmp!=null) message += tmp; } if(another!=null){ tmp = ((Bundle)another).getString(BUNDLE_MESSAGE); if(tmp!=null) message += tmp; } bundle.putString(BUNDLE_MESSAGE, message); bundle.setClassLoader(Bundle.class.getClassLoader()); return bundle; } /** * Concatenate two Parcelable Bundles containing char[] to form one Parcelable.<br> * The char[] message is stored via Bundle.putCharArray using {@link #BUNDLE_PROPERTIES} as the key for the item.<br> * * @param one Parcelable Bundle, containing char[] message * @param another Parcelable Bundle, containing char[] message * @return Parcelable Bundle, including Parcelable one and another. * @see Bundle#putCharArray(String, char[]) * @see Bundle#getCharArray(String) */ public static Parcelable assembleParcelProps(Parcelable one, Parcelable another){ Bundle bundle = new Bundle(); char[] characters = null; char[] characters1 = null; char[] characters2 = null; int length = 0; if(one!=null){ characters1 = ((Bundle)one).getCharArray(BUNDLE_PROPERTIES); if(characters1!=null) length += characters1.length; } if(another!=null){ characters2 = ((Bundle)another).getCharArray(BUNDLE_PROPERTIES); if(characters2!=null) length += characters2.length; } characters = new char[length]; if(characters1!=null && characters2!=null){ System.arraycopy(characters1, 0, characters, 0, characters1.length); System.arraycopy(characters2, 0, characters, characters1.length, characters2.length); }else if(characters1!=null && characters2==null){ System.arraycopy(characters1, 0, characters, 0, characters1.length); }else if(characters1==null && characters2!=null){ System.arraycopy(characters2, 0, characters, 0, characters2.length); } bundle.putCharArray(BUNDLE_PROPERTIES, characters); bundle.setClassLoader(Bundle.class.getClassLoader()); return bundle; } }