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) && (!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);
}
}
}