Create a sync extension for IoTool using MQTT

This document will talk about developing sync extension using MQTT connection. The example provided in the guide was used to develop IMB Watson cloud extension to send data to IBM Watson Cloud.

General things

Before we start developing extensions we need to understand, that there is a possibility that some of the developed extension could be used worldwide. Therefore me need to ensure that the extension do not interfere with each other. Because of that we need make sure that the packages, classes etc. A general idea was described in this document, however this document is used for sensor extensions. Because of that we need to address differences between then. First of there is no service ID to name. Second, package names, class names, etc. should contain name that resembles service ID and should follow the same naming rules. In this example is "IBMWatson". Sync extension also has to use the same classes (service, provider and preferences). Both have similar implementation of preferences and provider (Sync extension has less options to set in provider, but usually a lot more preferences to set).

Note: Further instances of your chosen name will be referred as serviceID.

How to proceed

Just make a copy of the demo project and make sure that you change names that contain "IBMWatson" to you chosen name.

Note: Any change to the code may be needed only at the sections which start with "//TODO".

Libraries

Demo project uses 3 libraries made by us:

  • IoToolLibrarySync.arr

  • IoToolLibraryBase.arr

  • IoToolLibraryComms.arr

It also uses 2 packages made by third party companies:

  • org.eclipse.paho.client.mqttv3

  • org.eclipse.paho.android.service

Classes

"IoToolSyncService"+ serviceID +"IoT.java"

Define and implements all necessary setup and processing of data to send. In demo code packages this class already implements all extension functionality such as connecting to a cloud, responding to the application commands etc.

package io.senlab.iotool.extension.ibmwatsoncloud;

import android.Manifest;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.database.sqlite.SQLiteDatabase;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Environment;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.preference.PreferenceManager;
import android.support.v4.app.NotificationCompat;
import android.util.Log;
import android.widget.Toast;

import org.eclipse.paho.android.service.MqttAndroidClient;
import org.eclipse.paho.client.mqttv3.IMqttActionListener;
import org.eclipse.paho.client.mqttv3.IMqttToken;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.MqttMessage;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.lang.ref.WeakReference;
import java.util.Calendar;
import java.util.GregorianCalendar;

import io.senlab.iotool.library.base.IoToolConstants;
import io.senlab.iotool.library.sync.SyncConstants;
import io.senlab.iotool.library.sync.SyncItem;
import io.senlab.iotool.library.sync.SyncMonitorFunctions;
import io.senlab.iotool.library.sync.SyncFunctions;
import io.senlab.iotool.library.sync.SyncList;
import io.senlab.iotool.library.sync.SyncProperties;

/**
 * This service sends/receives sensor data to/from a IBM Watson IoT server.
 *
 */
public class IoToolSyncServiceIBMWatsonIoT extends Service {

	//Mqtt connection variables
	MqttAndroidClient client;

	Handler handlerr;
	String hostName;
	String username;
	String password;
	String port;
	String clientIdentification;
	boolean cleanSession;
	String qualityOfService;
	String topic;

    //condensed parameter
    boolean sendCondensedData;

	Handler handlerToast = null; //handles Toast Message

	protected static final String serviceName = "IoToolSyncServiceIBMWatsonIoT";

	private boolean serviceRunning = false;

	private boolean sendingData = false;
	private boolean sendingDataFiles = false;

	private NotificationManager mNM;

    private Notification notification;
    private PendingIntent contentIntent;
    private ConnectServiceCommandReceiver commandReceiver;

	private SyncList syncList;
	private SyncProperties syncProperties;

    private Handler sendHandler = new Handler();
    private SendRunnable sendRunnable;
    private Handler sendFilesHandler = new Handler();
    private SendFilesRunnable sendFilesRunnable;
	private boolean allFilesSent = false;
	private boolean sendAgain = false;

	private boolean sendDataMode = true;

	private boolean receivingData = false;

	private final long downloadInterval = 10000;
	private final long broadcastInterval = 1000;

	private Handler broadcastHandler = new Handler();
	private BroadcastAndReceiveRunnable broadcastAndReceiveRunnable;

	private long delayBroadcast = 0;
	public void onCreate() {
		super.onCreate();
		handlerr = new Handler();
	}
	@Override
    public int onStartCommand(Intent intent, int flags, int startId) {
		if(intent == null){
    		stop(1);
    	}

		Log.v("test","TESTNI IZPIS");

    	if (!serviceRunning) {
    		try {
        		serviceRunning = true;
        		commandReceiver = new ConnectServiceCommandReceiver(this);
        		registerReceiver(commandReceiver, new IntentFilter(IoToolConstants.ServiceCommand));

				mNM = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);

				CharSequence text = getResources().getString(R.string.service_sync_notif_started);

				// Set the icon, scrolling text and timestamp
				//notification = new Notification(R.drawable.status_waiting, text, System.currentTimeMillis());
				NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
				notification = builder.setContentIntent(contentIntent)
						.setSmallIcon(R.drawable.status_waiting)
						.setWhen(System.currentTimeMillis())
						.setAutoCancel(true)
						.setContentTitle(serviceName)
						.setContentText(text)
						.build();

				// The PendingIntent to launch our activity if the user selects this notification
				contentIntent = PendingIntent.getActivity(this, 0, new Intent(), 0);

				// Set the info for the views that show in the notification panel.
				//notification.setLatestEventInfo(this, serviceName, text, contentIntent);

				startForeground(R.string.foreground_service_id, notification);

				sendRunnable = new SendRunnable(this);
				sendFilesRunnable = new SendFilesRunnable(this);

				SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());

				sp.edit().putBoolean(getString(getResources().getIdentifier("preference_key_allowtracking", "string", getPackageName())),intent.getBooleanExtra(IoToolConstants.AllowAnalytics, true)).commit();

                int sendInterval;
                String interval = sp.getString(getString(R.string.preference_key_sendinterval), getString(R.string.preference_default_sendinterval));
                if (interval.trim().endsWith("ms")){
                    sendInterval = Integer.valueOf(interval.substring(0,interval.length()-2));
                }else{
                     sendInterval= getSendIntervalInteger(parseSendInterval(sp.getString(getString(R.string.preference_key_sendinterval), getString(R.string.preference_default_sendinterval)))) * 1000;
                }


				handlerToast = new Handler() {
					public void handleMessage(Message msg) {
						if(msg.arg1 == 1)
							Toast.makeText(getApplicationContext(),getResources().getString(R.string.pref_error_enter_params), Toast.LENGTH_LONG).show();
					}
				};

				boolean sendFiles = sp.getBoolean(getString(R.string.preference_key_send_files), getResources().getBoolean(R.bool.preference_default_send_files));

				sendDataMode = intent.getBooleanExtra(SyncConstants.SendDataExtra, true);

				boolean liveSession = intent.getBooleanExtra(SyncConstants.LiveSessionExtra, true);
				int session = intent.getIntExtra(SyncConstants.SessionIDExtra, -1);
				long sessionTimeFrom;
				long sessionTimeTo;
				if (session<0) sessionTimeFrom = 0;
				else sessionTimeFrom = intent.getLongExtra(SyncConstants.SessionTimeFromExtra, 0);
				if (liveSession) {
					Calendar cal = new GregorianCalendar();
					cal.setTimeInMillis(System.currentTimeMillis());
					cal.add(Calendar.YEAR, 10);
					sessionTimeTo = cal.getTimeInMillis();
				} else {
					sessionTimeTo = intent.getLongExtra(SyncConstants.SessionTimeToExtra, 0);
					sendInterval = 500;
				}

				File appDir = new File(Environment.getExternalStorageDirectory().toString()+"/"+intent.getStringExtra(IoToolConstants.FileDirExtra));
                if (! appDir.exists()) appDir.mkdir();

				long currentTime = System.currentTimeMillis() - downloadInterval;
				if (intent.hasExtra(IoToolConstants.TimeStampExtra)) currentTime = intent.getLongExtra(IoToolConstants.TimeStampExtra, currentTime);

				syncList = new SyncList(intent.getStringArrayExtra(SyncConstants.SyncListExtra));
				syncProperties = new SyncProperties(serviceName, appDir, sendInterval, liveSession, sessionTimeFrom, sessionTimeTo, session, sendFiles, currentTime);

				boolean permissions = true;
				if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
					if(checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) permissions = false;
				}
				if (!permissions) {
					Intent i = new Intent();
					i.setClass(this, IoToolSyncServiceIBMWatsonIoTPreferences.class);
					i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
					startActivity(i);
				} else {
					if (sendDataMode) {
						sendAgain = true;
						scheduleSend();
					} else {

						Toast.makeText(this, getResources().getString(R.string.pref_error_receive_data_not_supported), Toast.LENGTH_SHORT).show();
						stop(2);
					}
				}
    		} catch (Exception e) {
    			e.printStackTrace();
    			stop(4);
    			return START_NOT_STICKY;
			}
    	}

        // We want this service to continue running until it is explicitly
        // stopped, so return sticky.
        return START_STICKY;
    }

	@Override
	public IBinder onBind(final Intent intent) {
		return null;
	}


	@Override
	public void onDestroy() {
		super.onDestroy();
		if(commandReceiver!=null)
			unregisterReceiver(commandReceiver);
	}

	public void stop(int cause) {
    	serviceRunning = false;
Log.v("test", "try to stop service "+cause);

		if (sendDataMode) {
			if ((!sendingData) &amp;& (!sendingDataFiles)) {
Log.v("test", "stopping service");
				sendAgain = false;

				try {
					sendHandler.removeCallbacks(sendRunnable);
				} catch(Exception e) {
				}

				try {
					sendFilesHandler.removeCallbacks(sendFilesRunnable);
				} catch(Exception e) {
				}

				// Make sure our notification is gone.
				stopForeground(true);

				stopSelf();
			}
		} else {
Log.v("test", "stopping service");
			try {
				broadcastHandler.removeCallbacks(broadcastAndReceiveRunnable);
			} catch(Exception e) {
			}
			broadcastAndReceiveRunnable = null;

			// Make sure our notification is gone.
			stopForeground(true);

			stopSelf();
		}
	}

	public static class ConnectServiceCommandReceiver extends BroadcastReceiver {
		private WeakReference<IoToolSyncServiceIBMWatsonIoT> w;

		public ConnectServiceCommandReceiver(IoToolSyncServiceIBMWatsonIoT a) {
			w = new WeakReference<IoToolSyncServiceIBMWatsonIoT>(a);
		}

        public void onReceive(Context context, Intent intent) {
        	if(intent==null || !intent.hasExtra(IoToolConstants.ServiceCommandType))
        		return;
        	IoToolSyncServiceIBMWatsonIoT a = w.get();

        	if(a!=null) {
            	if (intent.getStringExtra(IoToolConstants.ServiceCommandType).equals(IoToolConstants.ServiceCommandStopAllServices) ||
						intent.getStringExtra(IoToolConstants.ServiceCommandType).equals(SyncConstants.CommandStopSyncService)) {
					a.stop(5);
				} else if (intent.getStringExtra(IoToolConstants.ServiceCommandType).equals(SyncConstants.CommandSetTime)) {
					if (a.serviceRunning) SyncFunctions.setNewTime(a.syncProperties, a.syncList, intent.getLongExtra(IoToolConstants.TimeStampExtra, 0));
            	}
        	}
        }
    }

	public void notifyWaiting() {
		if ((!sendingData) && (!sendingDataFiles)) {
			SyncFunctions.notifyWaiting(this, mNM, notification, contentIntent, serviceName, R.string.foreground_service_id);
		}
	}

	public void notifySending() {
		SyncFunctions.notifySending(this, mNM, notification, contentIntent, serviceName, R.string.foreground_service_id);
	}

	public void notifyReceiving() {
		SyncFunctions.notifyReceiving(this, mNM, notification, contentIntent, serviceName, R.string.foreground_service_id);
	}

	public static String parseSendInterval(String sendinterval) {
		String unit = "s";
        if (sendinterval.trim().endsWith("ms")) unit = "ms";
		if (sendinterval.trim().endsWith("m")) unit = "m";
		if (sendinterval.trim().endsWith("h")) unit = "h";
		if (sendinterval.trim().endsWith("d")) unit = "d";
		Integer number;
		try {
			number = Integer.parseInt(sendinterval.replaceAll("[\\D]", ""));
			if (number<1) number = 10;
		} catch (Exception e) {
			number = 10;
		}
        if (unit.equals("ms") && number < 200){
            number = 200; //minimum half second
        }
		sendinterval = number + unit;
		return sendinterval;
	}

	public int getSendIntervalInteger(String sendinterval) {
		Integer number = Integer.parseInt(sendinterval.replaceAll("[\\D]", ""));
        Double numberMs = Double.valueOf(number);
		if (sendinterval.endsWith("m")) number *= 60;
		else if (sendinterval.endsWith("h")) number *= 3600;
		else if (sendinterval.endsWith("d")) number *= 86400;
		return number;
	}


	// ------------------------------ SEND DATA ------------------------------

	public void scheduleSend() {
        if (sendAgain) sendHandler.postDelayed(sendRunnable, syncProperties.sendInterval);
    }

	private static class SendRunnable implements Runnable {
		private WeakReference<IoToolSyncServiceIBMWatsonIoT> w;

		public SendRunnable(IoToolSyncServiceIBMWatsonIoT a) {
			w = new WeakReference<IoToolSyncServiceIBMWatsonIoT>(a);
		}

		public void run() {
			IoToolSyncServiceIBMWatsonIoT a = w.get();
			if(a!=null) {
				a.sendData();
			}
		}
	}

	public void sendData() {
		if (serviceRunning) {
Log.v("test", "try to start send data operation");
			SendOperation so = new SendOperation(this);
			//so.execute(sendList);
			so.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
		} else {
			stop(6);
		}
	}

	private static class SendOperation extends AsyncTask<Void, Void, String> {
    	private WeakReference<IoToolSyncServiceIBMWatsonIoT> w;

    	public SendOperation(IoToolSyncServiceIBMWatsonIoT a) {
    		w = new WeakReference<IoToolSyncServiceIBMWatsonIoT>(a);
    	}

    	@Override
    	protected String doInBackground(Void... params) {
    		IoToolSyncServiceIBMWatsonIoT a = w.get();

    		if(a!=null) {
				a.sendingData = true;
//    			Log.d("IoToolSendData","sendingData = true");
    			a.notifySending();

				SQLiteDatabase dbSync = null;

    			try {
    				try{
    					File syncFolder = new File(a.syncProperties.appDir.toString()+"/System");
    					if(!syncFolder.exists()){
    						syncFolder.mkdir();
    					}
    				}catch(Exception ex){
    					ex.printStackTrace();
    				}

    				dbSync = SyncFunctions.getSyncDB(a.syncProperties);

    				Boolean commit = false;

					Boolean newdata = SyncFunctions.readData(a.syncList, a.syncProperties, dbSync);

    				if (newdata) { //new SSL connection
						commit = a.sendDataToIBMCloud(a.syncList);
        	            //commit = a.sendDataToIoToolCloud(a.syncList);
    				} else {
						if (a.syncProperties.liveSession) {
							SyncMonitorFunctions.broadcastMessageWithCurrentTimestamp(a, "No more data found!");
						} else {
							if (a.allFilesSent) {
								a.serviceRunning = false;
								SyncFunctions.broadcastSessionUploaded(a, a.syncProperties);
							}
						}
					}

    				if (commit) SyncFunctions.markSentData(a.syncList, a.syncProperties, dbSync);

    				dbSync.close();
    			} catch (Exception e) {
    				e.printStackTrace();
    				Log.d("IoToolSendData","AsyncTask upload crashed");
    				try {
    					dbSync.close();
    				} catch (Exception e2) {}
    			}

    			a.sendingData = false;
    			a.notifyWaiting();

    			if (!a.serviceRunning) {
    				a.stop(7);
    			} else {
    				a.scheduleSend();
    			}

    		}
    		return null;
    	}
    }


	public boolean sendDataToIBMCloud(SyncList syncList) throws IOException{
		if (isMqttConnectedNormal()) {//if connected, send data to server
            mqttPrepareDataForPublishToIBMIot(syncList);
			return true;
		}else {
			mqttConnectToIBMIot();//connect to server
			return false;
		}
	}

	//connect to ibm
	private void mqttConnectToIBMIot() {
		//TODO check if all parameters are valid
		SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
		//get IBM parameters from settings
		hostName= sp.getString(getString(R.string.preference_key_hostname),"");
		username = sp.getString(getString(R.string.preference_key_username),"");
		password = sp.getString(getString(R.string.preference_key_password),"");
		port = sp.getString(getString(R.string.preference_key_port),"");
		clientIdentification = sp.getString(getString(R.string.preference_key_client_identification),"");
		cleanSession = sp.getBoolean(getString(R.string.preference_key_clean_session),true);
		qualityOfService = sp.getString(getString(R.string.preference_key_quality_of_service),"");
		topic = sp.getString(getString(R.string.preference_key_topic),"");
        sendCondensedData = sp.getBoolean(getString(R.string.preference_key_condensed), false);

		boolean paramsOK = true;
		if (hostName.trim().length()<1) paramsOK = false;
		if (port.trim().length()<1) paramsOK = false;
		if (username.trim().length()<1) paramsOK = false;
		if (password.trim().length()<1) paramsOK = false;
		if (clientIdentification.trim().length()<1) paramsOK = false;
		if (topic.trim().length()<1) paramsOK = false;
		if (qualityOfService.trim().length()<1) paramsOK = false;

		if (paramsOK) {
			//TODO create MQTT connection
			client = new MqttAndroidClient(getApplication(), hostName+":"+port,
					clientIdentification);
			try {
				MqttConnectOptions options = new MqttConnectOptions();
				options.setCleanSession(cleanSession);
				options.setUserName(username);
				options.setPassword(password.toCharArray());
				IMqttToken token = client.connect(options,null,new IMqttActionListener() {
					@Override
					public void onSuccess(IMqttToken asyncActionToken) {
						// We are connected
						Log.d(getApplication().toString(), "onSuccess");
						SyncMonitorFunctions.broadcastMessageWithCurrentTimestamp(getApplication(), "Connection to server successful: "+hostName+", user = "+username+", server answer = "+"successful");

					}
					@Override
					public void onFailure(final IMqttToken asyncActionToken, final Throwable exception) {
						// Something went wrong e.g. connection timeout or firewall problems
						Log.d(getApplication().toString()+" "+exception.getStackTrace().toString(), "onFailure");
						SyncMonitorFunctions.broadcastMessageWithCurrentTimestamp(getApplication(), "Connection to server failed: "+hostName+", user = "+username+", server answer = "+"failed");
					}
				});
			} catch (MqttException e) {
				e.printStackTrace();
			}
		} else {
			SyncMonitorFunctions.broadcastMessageWithCurrentTimestamp(getApplication(), getString(R.string.pref_error_enter_params));
			Message msg = handlerToast.obtainMessage();
			msg.arg1 = 1;
			handlerToast.sendMessage(msg);
		}
	}

	//publish to ibm
    private void mqttPrepareDataForPublishToIBMIot(SyncList syncList) {
		//TODO prepare data to send
		String payload = "";
		String newDataToAddToPayload = "";

        for (int i = 0; i < syncList.getCount(); i++) {
            SyncItem syncItem = syncList.getItem(i);
            //if we have data
            if (syncItem.data.size() > 0) {
                if (!sendCondensedData) {//if condensed is turned of, send just last data

                    if (i != syncList.getCount()-1){ //if not last sensor data
                        payload += "\"" + syncItem.getReadingID() + "\":" + syncItem.data.get(syncItem.data.size() - 1).split(",")[1]+","; //get last data from list
                    }else {
                        payload += "\"" + syncItem.getReadingID() + "\":" + syncItem.data.get(syncItem.data.size() - 1).split(",")[1]; //get last data from list
                        mqqtPublishDataToIBMIot(payload);
                        payload = "";
                    }
                } else {

                    for (int j = 0; j < syncItem.data.size(); j++) {
                        newDataToAddToPayload = "";
                        newDataToAddToPayload += "\"" + "timestamp" + j + "\":" + syncItem.data.get(j).split(",")[0] + ",";
                        if (j != syncItem.data.size() - 1) {
                            newDataToAddToPayload += "\"" + syncItem.getReadingID() + j + "\":" + syncItem.data.get(j).split(",")[1] + ",";
                        } else {//dont ad comma on last data
                            newDataToAddToPayload += "\"" + syncItem.getReadingID() + j + "\":" + syncItem.data.get(j).split(",")[1];
                        }

                        try {
                            int totalSize = payload.getBytes("UTF-8").length + newDataToAddToPayload.getBytes("UTF-8").length;
                            //Log.d("total size",String.valueOf(totalSize));
                            if (totalSize > 131072) { //in case our message gets bigger than IBM limit, send it
                                if (payload.endsWith(",")){
                                    payload = payload.substring(0,payload.length()-1); //remove ","
                                }
                                mqqtPublishDataToIBMIot(payload);
                                //set payload to new data
                                payload = newDataToAddToPayload;
                            } else {
                                payload += newDataToAddToPayload; //if size is samller, add to payload
                            }
                        } catch (UnsupportedEncodingException e) {
                            e.printStackTrace();
                        }

                    }
                }
            }
        }

        if (sendCondensedData) { //if condensed sending is turned on, send rest of the payload
            mqqtPublishDataToIBMIot(payload);
        }
	}

	private void mqqtPublishDataToIBMIot (String payload){
		//TODO sen data via MQTT
		try{ //send to IBM
			payload = "{\"d\":{"+payload+"}}"; //encapsulate for ibm
			byte [] encodedPayload = payload.getBytes("UTF-8");
			Log.v("Size", String.valueOf(encodedPayload.length));

			MqttMessage message = new MqttMessage(encodedPayload);

			message.setQos(Integer.parseInt(qualityOfService));

			IMqttToken token = client.publish(topic, message);
			token.setActionCallback(new IMqttActionListener() {
				@Override
				public void onSuccess(IMqttToken asyncActionToken) {
					SyncMonitorFunctions.broadcastMessageWithCurrentTimestamp(getApplication(), "Publishing to broker successful: "+hostName+", user = "+username+", server answer = "+"successful");
					System.out.println("Publishing to broker successful");
				}
				@Override
				public void onFailure(IMqttToken asyncActionToken, Throwable exception) {
					SyncMonitorFunctions.broadcastMessageWithCurrentTimestamp(getApplication(), "Publishing to broker failed: "+hostName+", user = "+username+", server answer = "+"failed");
					System.out.println("Publishing to broker failed");
				}
			});

		} catch (UnsupportedEncodingException | MqttException | NullPointerException e) {
			e.printStackTrace();
		}
	}

	private boolean isMqttConnectedNormal() {
		Log.d(getApplication().toString(), ".isMqttConnected() entered");
		boolean connected = false;
		try {
			if ((client != null) && (client.isConnected())) {
				connected = true;
			}
		} catch (Exception e) {
			// swallowing the exception as it means the client is not connected
		}
		Log.d(getApplication().toString(), ".isMqttConnected() - returning " + connected);
		return connected;
	}

	// ------------------------------ SEND FILES ------------------------------

	public void scheduleSendFiles() {
		if (syncProperties.sendFiles && sendAgain) sendFilesHandler.postDelayed(sendFilesRunnable, syncProperties.sendInterval);
	}

	private static class SendFilesRunnable implements Runnable {
		private WeakReference<IoToolSyncServiceIBMWatsonIoT> w;

		public SendFilesRunnable(IoToolSyncServiceIBMWatsonIoT a) {
			w = new WeakReference<IoToolSyncServiceIBMWatsonIoT>(a);
		}

	    public void run() {
	    	IoToolSyncServiceIBMWatsonIoT a = w.get();
	    	if(a!=null) {
		    	a.sendFiles();
	    	}
	    }
	}

    public void sendFiles() {
    	if (serviceRunning) {
Log.v("test", "try to start send files operation");
    		SendFilesOperation sfo = new SendFilesOperation(this);

    		sfo.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
    	} else {
    		stop(8);
    	}
    }

	private static class SendFilesOperation extends AsyncTask<Void, Void, String> {
		private WeakReference<IoToolSyncServiceIBMWatsonIoT> w;

		public SendFilesOperation(IoToolSyncServiceIBMWatsonIoT a) {
			w = new WeakReference<IoToolSyncServiceIBMWatsonIoT>(a);
		}

		@Override
		protected String doInBackground(Void... params) {
			IoToolSyncServiceIBMWatsonIoT a = w.get();

			if(a!=null) {
				a.sendingDataFiles = true;
				a.notifySending();


				a.sendingDataFiles = false;
				a.notifyWaiting();

				if (!a.serviceRunning) {
					a.stop(9);
				} else {
					a.scheduleSendFiles();
				}
			}
			return null;
		}
	}

	// ------------------------------ BROADCAST AND RECEIVE DATA ------------------------------

	public void scheduleBroadcastAndReceive() {
		if (delayBroadcast > 0) {
			delayBroadcast -= broadcastInterval;
			broadcastHandler.postDelayed(broadcastAndReceiveRunnable, 2*broadcastInterval);
		} else broadcastHandler.postDelayed(broadcastAndReceiveRunnable, broadcastInterval);
	}

	private static class BroadcastAndReceiveRunnable implements Runnable {
		private WeakReference<IoToolSyncServiceIBMWatsonIoT> w;

		public BroadcastAndReceiveRunnable(IoToolSyncServiceIBMWatsonIoT a) {
			w = new WeakReference<IoToolSyncServiceIBMWatsonIoT>(a);
		}

		public void run() {
			IoToolSyncServiceIBMWatsonIoT a = w.get();
			if(a!=null) {
				if (a.serviceRunning) {
					a.broadcastAndReceiveData();
				} else {
					a.stop(12);
				}
			}
		}
	}

	public void broadcastAndReceiveData() {
		if (syncList.allDataEmpty()) {
			if (!receivingData) {
				Log.v("test", "no data available for broadcast getting new from server, currentTime="+syncProperties.currentTime);
				SyncMonitorFunctions.broadcastMessageWithCurrentTimestamp(this, "No data available for broadcast getting new from server, selected time = "+SyncMonitorFunctions.timestampToString(syncProperties.currentTime));
				receiveData();
			}
		} else if (!receivingData) {
			Log.v("test", "data available for broadcast, currentTime="+syncProperties.currentTime);
			SyncMonitorFunctions.broadcastMessageWithCurrentTimestamp(this, "Data available for broadcast, time = "+SyncMonitorFunctions.timestampToString(syncProperties.currentTime));

			SyncFunctions.broadcastDataUpToCurrentTime(this, syncProperties, syncList);

			syncProperties.currentTime += broadcastInterval;
		}

		scheduleBroadcastAndReceive();
	}

	// ------------------------------ RECEIVE DATA ------------------------------

	public void receiveData() {
		if (serviceRunning) {
			ReceiveOperation ro = new ReceiveOperation(this);
			ro.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
		} else {
			stop(13);
		}
	}

	private static class ReceiveOperation extends AsyncTask<Void, Void, String> {
		private WeakReference<IoToolSyncServiceIBMWatsonIoT> w;

		public ReceiveOperation(IoToolSyncServiceIBMWatsonIoT a) {
			w = new WeakReference<IoToolSyncServiceIBMWatsonIoT>(a);
		}

		@Override
		protected String doInBackground(Void... params) {
			IoToolSyncServiceIBMWatsonIoT a = w.get();
			if(a!=null) {
				a.notifyReceiving();
				a.receivingData = true;


				a.receivingData = false;
				a.notifyWaiting();

				if (!a.serviceRunning) {
					a.stop(11);
				}
			}
			return null;
		}
	}
}

"IoToolSyncService"+ serviceID +"IoTPreferences.java"

This class implements preferences for a sync extension which are accessible from IoTool app. Any additional preferences could be added or removed. These are usually related to keys and parameters that relate to connection to the cloud of your choosing.

package io.senlab.iotool.extension.ibmwatsoncloud;

import java.lang.ref.WeakReference;

import android.Manifest;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.preference.CheckBoxPreference;
import android.preference.EditTextPreference;
import android.preference.ListPreference;
import android.preference.Preference;
import android.preference.PreferenceCategory;
import android.preference.PreferenceManager;
import android.util.Log;
import android.widget.Toast;

import io.senlab.iotool.library.base.AppCompatPreferenceActivity;
import io.senlab.iotool.library.base.IoToolConstants;
import io.senlab.iotool.library.base.IoToolFunctions;
import io.senlab.iotool.library.sync.SyncFunctions;

public class IoToolSyncServiceIBMWatsonIoTPreferences extends AppCompatPreferenceActivity {

	private PreferenceManager pm;

	private EditTextPreference SendInterval;

	private EditTextPreference hostname;
	private EditTextPreference username;
	private EditTextPreference password;
	private EditTextPreference port;
	private EditTextPreference clientIdentification;
	private EditTextPreference topic;

	@Override
    public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);

		getSupportActionBar().setTitle(SyncFunctions.getSyncServiceFullName(this, IoToolSyncServiceIBMWatsonIoT.serviceName, getPackageName()));

	    // Load the preferences from an XML resource
	    addPreferencesFromResource(R.xml.datatransferservicepreferences);

	    pm = getPreferenceManager();

	    initPreferences();

		//TODO
		//hide various server settings
		((PreferenceCategory) pm.findPreference(getString(R.string.preference_key_category_server_connectivity_settings))).removePreference((CheckBoxPreference) pm.findPreference(getString(R.string.preference_key_usessl)));
		((PreferenceCategory) pm.findPreference(getString(R.string.preference_key_category_server_connectivity_settings))).removePreference((EditTextPreference) pm.findPreference(getString(R.string.preference_key_sslpass)));

		SendInterval.setOnPreferenceChangeListener(new OnSendIntervalPrefChangeListener(this));

		loadSettings();
	}

	public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
		switch (requestCode) {
			case IoToolConstants.PERMISSIONS_REQUEST_CODE: {
				boolean alert = false;
				// If request is cancelled, the result arrays are empty.
				if (grantResults.length > 0) {
					for (int grantResult : grantResults) {
						if (grantResult != PackageManager.PERMISSION_GRANTED) alert = true;
					}
				} else alert = true;
				if (alert) {
					Toast.makeText(getApplicationContext(), "DataTransfer"+" "+getString(R.string.request_permission_explanation), Toast.LENGTH_LONG).show();
					finish();
				}
				return;
			}
		}

	}

	@Override
	public void onResume() {
		super.onResume();

		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
			boolean requestPermissions = false;
			if(checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) requestPermissions = true;
			if(requestPermissions) {
				//Need to delay this so the UI can be displayed all the way
				(new Handler()).postDelayed(new Runnable() {
					@Override
					public void run() {
						if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
							requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, IoToolConstants.PERMISSIONS_REQUEST_CODE);
						}
					}
				}, IoToolConstants.PERMISSIONS_DIALOG_DISPLAY_TIME_MILLIS);
			}
		}
	}

    @Override
	public void onAttachedToWindow() {
    	super.onAttachedToWindow();
    	IoToolFunctions.onAttachedToWindow(this);
	}

	@Override
	public void onBackPressed() {
		super.onBackPressed();
		saveSettings();
	}

	@Override
	public void onDestroy() {
		super.onDestroy();
		new SaveSettingsInBackground(this).execute();
	}

    @Override
    public void onConfigurationChanged(Configuration newConfig) {
    	super.onConfigurationChanged(newConfig);
    }

	private void initPreferences() {
		//TODO initialize objects. You only need objects for manipulating values in them.
		SendInterval = (EditTextPreference) pm.findPreference(getString(R.string.preference_key_sendinterval));

		hostname = (EditTextPreference)pm.findPreference(getString(R.string.preference_key_hostname));
		username = (EditTextPreference)pm.findPreference(getString(R.string.preference_key_username));
		password = (EditTextPreference)pm.findPreference(getString(R.string.preference_key_password));
		port = (EditTextPreference) pm.findPreference(getString(R.string.preference_key_port));
		clientIdentification = (EditTextPreference) pm.findPreference(getString(R.string.preference_key_client_identification));
		topic = (EditTextPreference) pm.findPreference(getString(R.string.preference_key_topic));


	}

	private void loadSettings() {
    }

	private void saveSettings() {
    	try {
			//TODO change settings if they are wrong
			if(SendInterval.getText().toString().toUpperCase().equals(""))
				SendInterval.setText(getString(R.string.preference_default_sendinterval));

    		System.gc();
		} catch (Exception e) {
			e.printStackTrace();
			Log.e("IoTool Log", "Error stack trace on save:" +e.getMessage());
		}
    }

    private static class SaveSettingsInBackground extends AsyncTask<Void, Void, Void> {
		private WeakReference<IoToolSyncServiceIBMWatsonIoTPreferences> w;

		public SaveSettingsInBackground(IoToolSyncServiceIBMWatsonIoTPreferences a) {
			w = new WeakReference<IoToolSyncServiceIBMWatsonIoTPreferences>(a);
		}
		protected Void doInBackground(Void... params) {
			IoToolSyncServiceIBMWatsonIoTPreferences a = w.get();

			if(a!=null) {
				a.saveSettings();
			}
			return null;
		}
    }

	private static class OnSendIntervalPrefChangeListener implements Preference.OnPreferenceChangeListener {
		private WeakReference<IoToolSyncServiceIBMWatsonIoTPreferences> w;

		public OnSendIntervalPrefChangeListener(IoToolSyncServiceIBMWatsonIoTPreferences a) {
			w = new WeakReference<IoToolSyncServiceIBMWatsonIoTPreferences>(a);
		}

		public boolean onPreferenceChange(Preference preference, Object newValue) {
			IoToolSyncServiceIBMWatsonIoTPreferences a = w.get();

			if(a!=null) {
				((EditTextPreference) preference).setText(IoToolSyncServiceIBMWatsonIoT.parseSendInterval(newValue.toString()));
			}

			return false;
		}
	}

	private static class OnDatabaseUserPrefChangeListener implements Preference.OnPreferenceChangeListener {
		private WeakReference<IoToolSyncServiceIBMWatsonIoTPreferences> w;

		public OnDatabaseUserPrefChangeListener(IoToolSyncServiceIBMWatsonIoTPreferences a) {
			w = new WeakReference<IoToolSyncServiceIBMWatsonIoTPreferences>(a);
		}

		public boolean onPreferenceChange(Preference preference, Object newValue) {
			IoToolSyncServiceIBMWatsonIoTPreferences a = w.get();

			if(a!=null) {
				String email = newValue.toString();
				if (IoToolFunctions.isValidEmail(email)) return true;
				else {
					Toast.makeText(a.getApplicationContext(), a.getString(R.string.invalid_database_username), Toast.LENGTH_SHORT).show();
					return false;
				}
			} else {
				return false;
			}
		}
	}
}

"IoToolSyncService"+ serviceID +"IoTProvider.java"

It must provide info for sync properties. Info is stored in two arrays, one for headers and one for actual data.

Properties:

  • IoToolConstants.PROVIDER_ROWID - unique row id (integer)

  • IoToolConstants.PROVIDER_PROFILES_NAME - profile name

  • IoToolConstants.PROVIDER_PROFILES_DEVELOPER - developer name

package io.senlab.iotool.extension.ibmwatsoncloud;

import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.database.SQLException;
import android.net.Uri;

import io.senlab.iotool.library.base.IoToolConstants;

public class IoToolSyncServiceIBMWatsonIoTProvider extends ContentProvider {
	//TODO change provider name and properties
	static final String PROVIDER_NAME = "io.senlab.iotool.extension.ibmwatsoncloud.IoToolSyncServiceIBMWatsonIoTProvider";

	static final String[] properties_header = new String[] {IoToolConstants.PROVIDER_ROWID, IoToolConstants.PROVIDER_PROPERTIES_NAME, IoToolConstants.PROVIDER_PROPERTIES_DEVELOPER};
	static final Object[] properties = new Object[] {1, "IBM Watson IoT", "SenLab"};

	static final int PROPERTIES = 1;

	static final UriMatcher uriMatcher;
	static{
		uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
		uriMatcher.addURI(PROVIDER_NAME, "properties", PROPERTIES);
	}

	@Override
	public boolean onCreate() {
		return true;
   	}

	@Override
	public Uri insert(Uri uri, ContentValues values) {
		throw new SQLException("Failed to add a record into " + uri);
	}

	@Override
	public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
		MatrixCursor mc = null;
		switch (uriMatcher.match(uri)){
			case PROPERTIES:
				mc = new MatrixCursor(properties_header);
				mc.addRow(properties);
				break;
			default:
				throw new IllegalArgumentException("Unknown URI " + uri);
		}
		return mc;
	}

	@Override
	public int delete(Uri uri, String selection, String[] selectionArgs) {
		return 0;
	}

	@Override
	public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
		return 0;
	}

	@Override
	public String getType(Uri uri) {
		switch (uriMatcher.match(uri)){
			case PROPERTIES:
				return "vnd.android.cursor.dir/vnd.iotool.properties";
			default:
				throw new IllegalArgumentException("Unsupported URI: " + uri);
		}
	}
}