Create a sync extension using scripts

This document will talk about developing sync extension using script. The example provided in the guide was used to develop IoTool extension to send data to IoTool 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 "IoToolCloud" and/or "IoTCloud". 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 "IoToolCloud" and/or "IoTCloud" 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

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.iotcloud;

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.preference.PreferenceManager;
import android.support.v4.app.NotificationCompat;
import android.widget.Toast;

import org.apache.http.NameValuePair;
import org.apache.http.message.BasicNameValuePair;

import java.io.File;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.List;

import io.senlab.iotool.library.base.IoToolConstants;
import io.senlab.iotool.library.communication.CommFunctions;
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 IoTool Cloud server.
 * @author Ljubo
 *
 */
public class IoToolSyncServiceIoToolCloud extends Service {
	protected static final String serviceName = "IoToolSyncServiceIoToolCloud";

	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 String url = "http://localhost/";
	private String userName = "";
	private String userPass = "";
	private String db = "";

	private boolean useSSL;
	private String sslPass;

	private boolean sendDataMode = true;

	private boolean receivingData = false;

	private boolean showNewestData = false;
	private boolean waitForCurrentData = false;

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

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

	private long delayBroadcast = 0;

	@Override
    public int onStartCommand(Intent intent, int flags, int startId) {
		if(intent == null){
    		stop(1);
    	}

    	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();
				//TODO get preferences from preference class
				int sendInterval = getSendIntervalInteger(parseSendInterval(sp.getString(getString(R.string.preference_key_sendinterval), getString(R.string.preference_default_sendinterval)))) * 1000;

				useSSL = sp.getBoolean(getString(R.string.preference_key_usessl), getResources().getBoolean(R.bool.preference_default_usessl));
				sslPass = sp.getString(getString(R.string.preference_key_sslpass), getString(R.string.preference_default_ssl_pass));

				url = prepareURL(sp.getString(getString(R.string.preference_key_serveraddress), getString(R.string.preference_default_serveraddress)), useSSL);
				if(url.length()==0) url = "http://localhost/";

				db = sp.getString(getString(R.string.preference_key_database), getString(R.string.preference_default_database));
				userName = sp.getString(getString(R.string.preference_key_databaseuser), getString(R.string.preference_default_databaseuser));
				userPass = sp.getString(getString(R.string.preference_key_databasepass), getString(R.string.preference_default_databasepass));

				alertMissingServerData(url, db, userName, userPass);

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

				showNewestData = sp.getBoolean(getString(R.string.preference_key_show_newest_data), getResources().getBoolean(R.bool.preference_default_show_newest_data));

				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, IoToolSyncServiceIoToolCloudPreferences.class);
					i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
					startActivity(i);
				} else {
					if (sendDataMode) {
						sendAgain = true;
						scheduleSend();
						scheduleSendFiles();
					} else {
						broadcastAndReceiveRunnable = new BroadcastAndReceiveRunnable(this);
						scheduleBroadcastAndReceive();
					}
				}
    		} 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<IoToolSyncServiceIoToolCloud> w;

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

        public void onReceive(Context context, Intent intent) {
        	if(intent==null || !intent.hasExtra(IoToolConstants.ServiceCommandType))
        		return;
        	IoToolSyncServiceIoToolCloud 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("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;
		}
		sendinterval = number + unit;
		return sendinterval;
	}

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

	private void alertMissingServerData(String url, String database, String databaseuser, String databasepass) {
		if (url.trim().length()<1) Toast.makeText(this, getResources().getString(R.string.pref_error_enter_url), Toast.LENGTH_SHORT).show();
		if (database.trim().length()<1) Toast.makeText(this, getResources().getString(R.string.pref_error_enter_database), Toast.LENGTH_SHORT).show();
		if (databaseuser.trim().length()<1) Toast.makeText(this, getResources().getString(R.string.pref_error_enter_databaseuser), Toast.LENGTH_SHORT).show();
		if (databasepass.trim().length()<1) Toast.makeText(this, getResources().getString(R.string.pref_error_enter_databasepass), Toast.LENGTH_SHORT).show();
	}

	public String prepareURL(String serverAddress, boolean useSSL) {
		String tmp = "";
		if (useSSL) tmp += "https://";
		else tmp += "http://";
		tmp += serverAddress;
		tmp += "/android/";
		return tmp;
	}

	public String getShortURL() {
		String tmp = url;
		tmp = tmp.substring(tmp.indexOf("/")+2);
		tmp = tmp.substring(0, tmp.indexOf("/"));
		return tmp;
	}

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

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

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

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

		public void run() {
			IoToolSyncServiceIoToolCloud 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<IoToolSyncServiceIoToolCloud> w;

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

    	@Override
    	protected String doInBackground(Void... params) {
    		IoToolSyncServiceIoToolCloud 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.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 sendDataToIoToolCloud(SyncList syncList) throws IOException {
		//TODO send sensor data to cloud
		Boolean commit = false;
		String urlString = url+"iotool_v1_savedata.php";
		String answer = "";
		List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>(2);
		nameValuePairs.add(new BasicNameValuePair("db", db));
		nameValuePairs.add(new BasicNameValuePair("un", userName));
		nameValuePairs.add(new BasicNameValuePair("up", userPass));
		nameValuePairs.add(new BasicNameValuePair("sensorcount", Integer.toString(syncList.getCount())));
		for (int i=0; i<syncList.getCount(); i++) {
			SyncItem syncItem = syncList.getItem(i);
			nameValuePairs.add(new BasicNameValuePair("db"+i, syncItem.getReadingID()));
			StringBuilder data = new StringBuilder();
			if (syncItem.data.size()>0) data.append(syncItem.data.get(0));
			for (int j=1; j<syncItem.data.size(); j++) data.append("#"+syncItem.data.get(j));
			String compressedData = "";
			try {
				compressedData = SyncFunctions.GZIPAndBase64Encode(data.toString());
			} catch (IOException e) {
				syncList.getItem(i).ok = false;
			}
			nameValuePairs.add(new BasicNameValuePair("data"+i, compressedData));
		}
		answer = CommFunctions.postHttpRequest(urlString, nameValuePairs, useSSL, sslPass, this);
//		Log.w("IoToolSendData","answer send data:"+answer.trim());
		SyncMonitorFunctions.broadcastMessageWithCurrentTimestamp(this, "Data sent to server "+getShortURL()+", user = "+userName+", server answer = "+answer.trim());
		if (answer.trim().compareTo("commit") == 0) commit = true;
		return commit;
	}

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

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

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

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

	    public void run() {
	    	IoToolSyncServiceIoToolCloud 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.execute();
    		sfo.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
    	} else {
    		stop(8);
    	}
    }

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

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

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

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

				if (a.userName.length() > 0) {
					a.sendFilesToIoToolCloud();
				}

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

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

	private boolean sendFilesToIoToolCloud() {
		//TODO send files to the cloud
		boolean noFileTransferErrors = true;
		String webSubDir = "Data";
		File[] syncFiles = SyncFunctions.getForSyncFiles(syncProperties, syncList);
		for (File syncFile : syncFiles) {
			if (serviceRunning) {
				try {
//					Log.v("test", "start transfer " + syncFile.toString());
					boolean transferOK = CommFunctions.postFile(url+"iotool_v1_ulfile.php", syncProperties.appDir.toString()+"/Data/"+syncFile, webSubDir, userName, userPass, db, useSSL, sslPass, this);
//					Log.v("test", "end transfer transferOK=" + transferOK + " " + syncFile.toString());
					SyncMonitorFunctions.broadcastMessageWithCurrentTimestamp(this, "File transfer "+(transferOK ? "succesful" : "failed")+", filename: "+syncFile);
					if (transferOK) SyncFunctions.markSentFile(syncProperties, syncFile);
					else noFileTransferErrors = false;
				} catch (Exception e) {
					noFileTransferErrors = false;
				}
			}
		}
		if ((!syncProperties.liveSession) && noFileTransferErrors) allFilesSent = true;
		return noFileTransferErrors;
	}

	// ------------------------------ 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<IoToolSyncServiceIoToolCloud> w;

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

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

	public void broadcastAndReceiveData() {
		if (waitForCurrentData) {
			if (!receivingData) {
				waitForData();
			}
		} else {
			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<IoToolSyncServiceIoToolCloud> w;

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

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

				a.getDataFromIoToolCloud();

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

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

	private void getDataFromIoToolCloud() {
		//TODO look in the data received
		String answer = "";
		try {
			if (showNewestData) if (syncProperties.currentTime < (System.currentTimeMillis()-(4*downloadInterval))) {
				String tanswer = "";

				List<NameValuePair> tnameValuePairs = new ArrayList<NameValuePair>(2);
				tnameValuePairs.add(new BasicNameValuePair("db", db));
				tnameValuePairs.add(new BasicNameValuePair("un", userName));
				tnameValuePairs.add(new BasicNameValuePair("up", userPass));
				tanswer = CommFunctions.postHttpRequest(url+"iotool_v1_get_last_time.php", tnameValuePairs, useSSL, sslPass, this);

				long tlastData = 0;
				try {
					tlastData = Long.parseLong(tanswer.trim());
				} catch (Exception e) {
					tlastData = 0;
				}

				if (syncProperties.currentTime < (tlastData-downloadInterval)) {
					delayBroadcast = downloadInterval - broadcastInterval;
					SyncFunctions.setNewTime(syncProperties, syncList, tlastData-(downloadInterval-100));
				}
			}

			long timestart = syncProperties.currentTime - broadcastInterval;
			long timestop = syncProperties.currentTime + downloadInterval;

//					Log.v("test", "get new data from "+timestart+" to "+timestop);
			SyncMonitorFunctions.broadcastMessageWithCurrentTimestamp(this, "Get new data from " + SyncMonitorFunctions.timestampToString(timestart) + " to " + SyncMonitorFunctions.timestampToString(timestop) + ", server = " + getShortURL() + ", user = " + userName);

			List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>(2);
			nameValuePairs.add(new BasicNameValuePair("db", db));
			nameValuePairs.add(new BasicNameValuePair("sensorcount", Integer.toString(syncList.getCount())));
			nameValuePairs.add(new BasicNameValuePair("un", userName));
			nameValuePairs.add(new BasicNameValuePair("up", userPass));
			for(int i=0; i<syncList.getCount(); i++) {
				nameValuePairs.add(new BasicNameValuePair("mt"+i, syncList.getItem(i).getReadingID()));
			}
			nameValuePairs.add(new BasicNameValuePair("start", Long.toString(timestart)));
			nameValuePairs.add(new BasicNameValuePair("stop", Long.toString(timestop)));
			answer = CommFunctions.postHttpRequest(url+"iotool_v1_data.php", nameValuePairs, useSSL, sslPass, this);

			String[] ansarr = answer.trim().split("~");
			SyncMonitorFunctions.broadcastMessageWithCurrentTimestamp(this, "Server answer = " + ansarr[0]);

			if (ansarr[0].compareTo("ok") == 0) {
				String[] data = ansarr[1].split(":");

				for (int i=0; i<syncList.getCount(); i++) {
					if (data[i].indexOf("#") > 0) {
						int dataCount = Integer.parseInt(data[i].substring(0, data[i].indexOf("#")));
						if (dataCount>0) {
							data[i] = data[i].substring(data[i].indexOf("#")+1);
							String[] dataarr = data[i].split("#");
							for (String s : dataarr) syncList.getItem(i).data.add(s.replace(";",SyncConstants.DataDelimiter));
						}
					}
				}
			}
		} catch (Exception e) {
			//e.printStackTrace();
		}
	}

	// ------------------------------ WAIT FOR DATA ------------------------------

	public void waitForData() {
		if (serviceRunning) {
			WaitForDataOperation wo = new WaitForDataOperation(this);
			wo.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
		} else {
			stop(14);
		}
	}

	private static class WaitForDataOperation extends AsyncTask<Void, Void, String> {
		private WeakReference<IoToolSyncServiceIoToolCloud> w;

		public WaitForDataOperation(IoToolSyncServiceIoToolCloud a) {
			w = new WeakReference<IoToolSyncServiceIoToolCloud>(a);
		}

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

			if(a!=null) {
				SyncMonitorFunctions.broadcastMessageWithCurrentTimestamp(a, "Checking if data available on server");

				a.notifyReceiving();
				a.receivingData = true;

				a.waitForDataOnIoToolCloud();

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

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

	private void waitForDataOnIoToolCloud() {
		//TODO manipulate with data after wait for it to be received
		String answer = "";
		try {
			List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>(2);
			nameValuePairs.add(new BasicNameValuePair("db", db));
			nameValuePairs.add(new BasicNameValuePair("un", userName));
			nameValuePairs.add(new BasicNameValuePair("up", userPass));
			answer = CommFunctions.postHttpRequest(url+"iotool_v1_get_last_time.php", nameValuePairs, useSSL, sslPass, this);

			long lastData = 0;
			try {
				lastData = Long.parseLong(answer.trim());
			} catch (Exception e) {
				lastData = 0;
			}

			if (lastData > syncProperties.currentTime) {
				SyncMonitorFunctions.broadcastMessageWithCurrentTimestamp(this, "Found new data on server");
				delayBroadcast = downloadInterval - broadcastInterval;
				if (waitForCurrentData) SyncFunctions.setNewTime(syncProperties, syncList, lastData-(downloadInterval-100));
				waitForCurrentData = false;
			}
		} catch (Exception e) {
		}
	}
}

"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.iotcloud;

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.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 IoToolSyncServiceIoToolCloudPreferences extends AppCompatPreferenceActivity {

	private PreferenceManager pm;

	private EditTextPreference ServerHttp;
	//	private EditTextPreference ServerFolder;
	private EditTextPreference Database;
	private EditTextPreference DatabaseUser;
	private EditTextPreference DatabasePass;
	private CheckBoxPreference ShowNewestData;
	private EditTextPreference SendInterval;

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

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

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

	    pm = getPreferenceManager();

	    initPreferences();

		//TODO this hides certain preferences
		//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)));
		//everyone uses iotool database
		((PreferenceCategory) pm.findPreference(getString(R.string.preference_key_category_server_connectivity_settings))).removePreference((EditTextPreference) pm.findPreference(getString(R.string.preference_key_database)));

		SendInterval.setOnPreferenceChangeListener(new OnSendIntervalPrefChangeListener(this));

		DatabaseUser.setOnPreferenceChangeListener(new OnDatabaseUserPrefChangeListener(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(), IoToolSyncServiceIoToolCloud.serviceName+" "+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 object that will be manipulated
		ServerHttp = (EditTextPreference) pm.findPreference(getString(R.string.preference_key_serveraddress));
		Database = (EditTextPreference) pm.findPreference(getString(R.string.preference_key_database));
		DatabaseUser = (EditTextPreference) pm.findPreference(getString(R.string.preference_key_databaseuser));
		DatabasePass = (EditTextPreference) pm.findPreference(getString(R.string.preference_key_databasepass));
		ShowNewestData = (CheckBoxPreference) pm.findPreference(getString(R.string.preference_key_show_newest_data));
		SendInterval = (EditTextPreference) pm.findPreference(getString(R.string.preference_key_sendinterval));
	}

	private void loadSettings() {
    }

	private void saveSettings() {
    	try {
			//TODO change settings before saving

			if (!IoToolFunctions.isValidEmail(DatabaseUser.getText().toString()))
				Toast.makeText(getApplicationContext(), getString(R.string.invalid_database_username), Toast.LENGTH_LONG).show();

			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<IoToolSyncServiceIoToolCloudPreferences> w;

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

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

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

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

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

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

			return false;
		}
	}

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

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

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

			if(a!=null) {
				String email = newValue.toString();
				if (IoToolFunctions.isValidEmail(email)) return true;
				else {
					//if(a.UseServer.isChecked())
					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.iotcloud;

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 IoToolSyncServiceIoToolCloudProvider extends ContentProvider {
	//TODO change provider name and its properties
	static final String PROVIDER_NAME = "io.senlab.iotool.extension.iotcloud.IoToolSyncServiceIoToolCloudProvider";

	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, "IoTool Cloud", "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);
		}
	}
}