package database; import android.app.Activity; import android.content.SharedPreferences; import android.os.AsyncTask; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.util.Log; import android.widget.Toast; import org.apache.http.HttpResponse; import org.apache.http.NameValuePair; import org.apache.http.client.HttpClient; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.HttpPut; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.message.BasicNameValuePair; import org.apache.http.protocol.HTTP; import org.domogik.domodroid13.R; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import java.io.BufferedOutputStream; import java.io.BufferedWriter; import java.io.IOException; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.Timer; import java.util.TimerTask; import javax.net.ssl.HttpsURLConnection; import Entity.Entity_Feature; import Entity.Entity_Map; import Entity.Entity_client; import misc.tracerengine; import mq.ZMQReqMessage; import rinor.Events_manager; import rinor.Rest_com; import rinor.Rinor_event; import rinor.Stats_Com; //import com.orhanobut.logger.Logger; public class WidgetUpdate { //implements Serializable { private static WidgetUpdate instance; private static final long serialVersionUID = 1L; private SharedPreferences sharedparams; private boolean activated; private static Activity activity; private static DomodroidDB domodb; private final String mytag = this.getClass().getName(); private TimerTask doAsynchronousTask; private tracerengine Tracer = null; private ArrayList<Cache_Feature_Element> cache = new ArrayList<>(); private Boolean locked = false; private Boolean timer_flag = false; public Boolean ready = false; private Handler mapView = null; private Events_manager eventsManager = null; private static Handler myselfHandler = null; private int last_ticket = -1; private int last_position = -1; private Timer timer = null; private int callback_counts = 0; private Boolean init_done = false; private Boolean sleeping = false; private static Stats_Com stats_com = null; private String login; private String password; private Boolean SSL; private float api_version; private String last_device_update; private String last_sensor_update; // // Table of handlers to notify // pos 0 = Main // pos 1 = Map // pos 2 = MapView private static final Handler[] parent = new Handler[3]; /* * This class is a background engine * On instantiation, it connects to Rinor server, and submit queries * each 'update' timer, to update local database values for all known devices * When variable 'activated' is set to false, the thread is kept alive, * but each timer is ignored (no more requests to server...) * When variable 'activated' is true, each timer generates a database update with server's response */ /* * New concept introduced by Doume at 2013/02/15 * This engine will maintain a cache of state values * This cache will be updated after each request to server (in parallel to database during transition phase) * When a value change, this engine will notify each client having subscribed to the device * for an immediate screen update * so, clients have not to use anymore a timer to display the changes * May be in future, this engine will also use Rest events with server, to avoid * use of timer and delayed updates */ /******************************************************************************* * Internal Constructor *******************************************************************************/ private WidgetUpdate() { super(); //com.orhanobut.logger.Logger.init("WidgetUpdate").methodCount(0); } public static WidgetUpdate getInstance() { if (instance == null) { Log.i("WidgetUpdate", "Creating instance........................"); instance = new WidgetUpdate(); } return instance; } public Boolean init(tracerengine Trac, final Activity activity, SharedPreferences params) { Boolean result = false; if (init_done) { Log.w("WidgetUpdate", "init already done"); return true; } stats_com = Stats_Com.getInstance(); //Create a statistic counter, with all 0 values sleeping = false; this.sharedparams = params; this.Tracer = Trac; WidgetUpdate.activity = activity; activated = true; login = params.getString("http_auth_username", "Anonymous"); password = params.getString("http_auth_password", ""); SSL = params.getBoolean("ssl_activate", false); api_version = sharedparams.getFloat("API_VERSION", 0); last_device_update = sharedparams.getString("last_device_update", "1900-01-01 00:00:00"); last_sensor_update = sharedparams.getString("last_sensor_update", "1900-01-01 00:00:00"); /* if(Tracer != null) { if(Tracer.DBEngine_running) { try { finalize(); } catch (Throwable t) {} } Tracer.DBEngine_running = true; //To avoid multiple engines running for same Activity } */ Tracer.d(mytag, "Initial start requested...."); domodb = new DomodroidDB(Tracer, activity, params); domodb.owner = mytag; timer_flag = false; ready = false; /* if(parent[0] != null) { parent[0].sendEmptyMessage(8000); //Ask main to display message } */ Tracer.d(mytag, "cache engine starting timer for periodic cache update"); Timer(); //and initiate the cyclic timer //Commented to avoid launching 2 request one from the initial timer and the other below //new UpdateThread().execute(); //And force an immediate refresh on init this.callback_counts = 0; //To force a refresh /* Boolean said = false; int max_time_for_sync = 15 * 1000; // On initial cache initialization, return an error // if cannot connect in 15 seconds // Probably wrong URL ? int sync_duration = 0; while (! ready) { if(! said) { Tracer.d(mytag,"cache engine not yet ready : Wait a bit !"); said=true; } try{ Thread.sleep(200); // Wait 0,2 second } catch (Exception e) {}; sync_duration += 200; if(sync_duration > max_time_for_sync) { Tracer.d(mytag,"cache engine not synced after "+(max_time_for_sync/1000)+" seconds !"); sync_duration = 0; //return result; //false, if sync not success } } if(parent[0] != null) { parent[0].sendEmptyMessage(8999); //hide Toast message } Tracer.d(mytag,"cache engine ready !"); */ // Cache contains list of existing devices, now ! ///////// Create an handler to exchange with Events_Manager/////////////////// myselfHandler = new Handler() { @Override public void handleMessage(Message msg) { //This handler will receive notifications from Events_Manager and from waitingThread running in background // 1 message => 1 event : so, it's serialized ! if (msg.what == 8999) { // Cache engine being ready, we can start events manager Tracer.d(mytag, "Main thread handler : Cache engine is now ready...."); if (eventsManager == null) { eventsManager = Events_manager.getInstance(activity); } eventsManager.init(Tracer, myselfHandler, cache, sharedparams, instance); /* if(parent[0] != null) { parent[0].sendEmptyMessage(8999); //Forward event to Main } */ } else if (msg.what == 9900) { if (eventsManager != null) { callback_counts++; Rinor_event event = eventsManager.get_event(); if (event != null) { Tracer.v(mytag, "Event from Events_Manager found, ticket : " + event.ticket_id + " # " + event.item); if (last_ticket == -1) { last_ticket = event.item; //Initial synchro on item } else { if (!((last_ticket + 1) == event.item)) { Tracer.d(mytag, "Handler event lost ? expected # " + (last_ticket + 1) + " Received # " + event.item); } last_ticket = event.item; } mapView = null; //mapView will be set by update_cache_device if at least one mini widget has to be notified update_cache_device(event.device_id, event.key, event.Value, event.Timestamp); if (mapView != null) { //It was a mini widget, not yet notified : do it now.. try { Tracer.i(mytag, "Handler send a notification to MapView"); mapView.sendEmptyMessage(9997); //notify the group of widgets a new value is there } catch (Exception e) { Tracer.e(mytag, e.toString()); } } //event = eventsManager.get_event(); //Try to get the next... } // if event } else { Tracer.d(mytag, "No Events_Manager known ! ! ! "); } } else if (msg.what == 9901) { // Events_Manager thread is dead.... Toast.makeText(activity, R.string.event_manager_die, Toast.LENGTH_LONG).show(); eventsManager = null; init_done = false; Tracer.i(mytag, "No more Events_Manager now ! ! ! "); //Clean all resources domodb = null; if (timer != null) timer.cancel(); timer = null; doAsynchronousTask = null; System.gc(); Tracer.i(mytag, "We can really go to end..."); try { this.finalize(); } catch (Throwable t) { Tracer.e(mytag, t.toString()); } } else if (msg.what == 9902) { //Time out processed callback_counts++; } else if (msg.what == 9903) { //New or update device detected by MQ //Notify on main screen Tracer.i(mytag, "Handler send a notification to MainView"); //disable the dialog or replace by a toast: here the old dialog //AlertDialog.Builder dialog_device_update = new AlertDialog.Builder(activity); //dialog_device_update.setTitle(activity.getText(R.string.domogik_information)); //dialog_device_update.setMessage(activity.getText(R.string.device_update_message)); //if (!(activity).isFinishing()) { // dialog_device_update.show(); //} //disable the dialog or replace by a toast: here the toast // Display message something changed since last update activity.runOnUiThread(new Runnable() { public void run() { Toast.makeText(activity, activity.getText(R.string.device_update_message), Toast.LENGTH_LONG).show(); } }); //todo notify on map screen if a device change. } } }; ///////// and pass to it now/////////////////// Tracer.d(mytag, "Waiting thread started to notify main when cache ready !"); new waitingThread().execute(); Tracer.d(mytag, "cache engine initialized !"); init_done = true; result = true; return result; } /* * Allow callers to set their handler in table */ public void set_handler(Handler parent, int type) { //type = 0 if View , or 1 if Map, or 2 if MapView if ((type >= 0) && (type <= 2)) WidgetUpdate.parent[type] = parent; } public void cancel() { activated = false; if (timer != null) timer.cancel(); if (eventsManager != null) eventsManager.Destroy(); if (stats_com != null) stats_com.cancel(); stats_com = null; Tracer.d(mytag, "cache engine cancel requested : Waiting for events_manager dead !"); } public void Disconnect(int type) { String name = "???"; if (type == 0) name = "Main"; else if (type == 1) name = "Map"; else if (type == 2) name = "MapView"; //Tracer.d(mytag,"Disconnect requested by "+name); // Purge all clients matching this container, as soon cache is unlocked while (locked) { //Somebody else is updating list... try { Thread.sleep(10); //Standby 10 milliseconds } catch (Exception e) { Tracer.e(mytag, e.toString()); } } locked = true; for (int i = 0; i < cache.size(); i++) { Cache_Feature_Element cache_entry = cache.get(i); ArrayList<Entity_client> clients_list = null; ArrayList<Entity_client> temp_list = null; if (cache_entry != null) { clients_list = cache_entry.clients_list; } if (clients_list != null) { int cs = clients_list.size(); //Tracer.i(mytag, "Processing cache entry # "+i+" <"+cache_entry.DevId+"> clients # = "+cs); if (cs > 0) { temp_list = cache_entry.clone_clients_list(); int deleted = 0; for (int j = 0; j < cs; j++) { //Tracer.i(mytag, " Processing client "+j+"/"+(cs-1)); Entity_client curclient = null; try { curclient = clients_list.get(j); } catch (Exception e) { Tracer.e(mytag, " Exception on client # " + j); curclient = null; } //check each connected client pointed by list if (curclient != null) { int cat = curclient.getClientType(); if (cat == type) { //This client was owned by requestor... Remove it from list Tracer.i(mytag, "remove client # " + j + " <" + curclient.getName() + "> from list " + name); curclient.setClientId(-1); //note client disconnected curclient.setClientType(-1); //this entry is'nt owned by anybody curclient.setHandler(null); //And must not be notified anymore temp_list.remove(j - deleted); deleted++; if (temp_list.size() == 0) { //List is empty : remove it from the device entry temp_list = null; break; } } } } //End of loop on clients list, for a cache entry cache_entry.clients_list = temp_list; } } //Next cache entry } // End of loop on cache items locked = false; //dump_cache(); //During development, help to debug ! } public JSONArray dump_cache_to_json() throws JSONException { JSONArray json_dump_cache = new JSONArray(); int size = cache.size(); Tracer.v(mytag, "Dump of Cache , size = " + cache.size()); while (locked) { //Somebody else is updating list... try { Thread.sleep(10); //Standby 10 milliseconds } catch (Exception e) { Tracer.e(mytag, e.toString()); } } locked = true; for (int i = 0; i < size; i++) { Cache_Feature_Element cache_entry = cache.get(i); ArrayList<Entity_client> clients_list = null; if (cache_entry == null) { Tracer.e(mytag, "Cache entry # " + i + " empty ! "); } else { JSONObject json_dump_current_cache = new JSONObject(); clients_list = cache_entry.clients_list; int clients_list_size = 0; if (clients_list != null) clients_list_size = clients_list.size(); Tracer.v(mytag, "Cache entry # " + i + " DevID : " + cache_entry.DevId + " Skey : " + cache_entry.skey + " Clients # :" + clients_list_size + " Value= :" + cache_entry.Value); json_dump_current_cache.put("id", cache_entry.DevId); json_dump_current_cache.put("last_value", cache_entry.Value); json_dump_current_cache.put("last_received", cache_entry.Value_timestamp); json_dump_cache.put(json_dump_current_cache); } } locked = false; return json_dump_cache; } public void dump_cache() { String[] name = new String[]{"Main ", "Map ", "MapView", "??? "}; int size = cache.size(); Tracer.v(mytag, "Dump of Cache , size = " + cache.size()); while (locked) { //Somebody else is updating list... try { Thread.sleep(10); //Standby 10 milliseconds } catch (Exception e) { Tracer.e(mytag, e.toString()); } } locked = true; for (int i = 0; i < size; i++) { Cache_Feature_Element cache_entry = cache.get(i); ArrayList<Entity_client> clients_list = null; if (cache_entry == null) { Tracer.e(mytag, "Cache entry # " + i + " empty ! "); } else { clients_list = cache_entry.clients_list; int clients_list_size = 0; if (clients_list != null) clients_list_size = clients_list.size(); Tracer.v(mytag, "Cache entry # " + i + " DevID : " + cache_entry.DevId + " Skey : " + cache_entry.skey + " Clients # :" + clients_list_size + " Value= :" + cache_entry.Value); if (clients_list_size > 0) { for (int j = 0; j < clients_list_size; j++) { if (clients_list.get(j) == null) break; int cat = clients_list.get(j).getClientType(); String client_name = clients_list.get(j).getName(); Handler h = clients_list.get(j).getClientHandler(); String state = "connected"; if (h == null) state = "zombie"; String type = "widget"; if (clients_list.get(j).is_Miniwidget()) type = "mini widget"; int ctype = clients_list.get(j).getClientType(); if (ctype == -1) ctype = 3; Tracer.v(mytag, " ==> entry : " + j + " owner : " + name[ctype] + " client name : " + client_name + " type = " + type + " state = " + state); } } } } //End of loop on clients list, for a cache entry locked = false; Tracer.i(mytag, "End of cache dump "); } /* * Method allowing external methods to force a refresh */ public void refreshNow() { if (doAsynchronousTask != null) doAsynchronousTask.run(); //To force immediate refresh } public void resync() { //May be URL has been changed : force engine to reconstruct cache Disconnect(0); Disconnect(1); Disconnect(2); locked = true; cache = null; cache = new ArrayList<>(); ready = false; refreshNow(); //To reconstruct cache Tracer.d(mytag, "state engine resync : waiting for initial setting of cache !"); Boolean said = false; int counter = 0; while (!ready) { if (!said) { Tracer.d(mytag, "cache engine not yet ready : Wait a bit !"); said = true; } try { Thread.sleep(100); counter++; if (counter > 100) { // 10 seconds elapsed //finalize(); } } catch (Exception e) { Tracer.e(mytag, e.toString()); } } Tracer.d(mytag, "cache engine ready after resync !"); locked = false; } /* * This method should only be called once, to create and arm a cyclic timer */ private void Timer() { timer = new Timer(); final Handler loc_handler = new Handler(); if (timer_flag) return; //Don't run many cyclic timers ! doAsynchronousTask = new TimerTask() { @Override public void run() { Runnable myTH = new Runnable() { public void run() { if (activated) { try { Tracer.d(mytag, "new UpdateThread().execute() from timer"); new UpdateThread().execute(); //on timer } catch (Exception e) { Tracer.e(mytag, e.toString()); } } } //End of run methodTimer }; // End of runnable bloc try { loc_handler.post(myTH); //To avoid exception on ICS } catch (Exception e) { Tracer.e(mytag, e.toString()); } } }; // and arm the timer to do automatically this each 'update' seconds timer_flag = true; //Cyclic timer is running... if (timer != null) { //timer.schedule(doAsynchronousTask, 0, 125 * 1000); // for tests with Events_Manager // 2'05 is a bit more than events timeout by server (2') // dame but using the user option timer timer.schedule(doAsynchronousTask, 0, sharedparams.getInt("UPDATE_TIMER", 300) * 1000); } } /* * Method to freeze/wakeup server exchanges and cache state ( on Pause / Resume, by example ) */ public void set_sleeping() { Tracer.d(mytag, "Pause requested..."); if (eventsManager != null) { eventsManager.set_sleeping(); } if (stats_com != null) stats_com.set_sleeping(); sleeping = true; } public void wakeup() { //ADD try catch due to an android error message in dev console on a DROID RAZR i (smi) try { Tracer.d(mytag, "Wake up requested..."); if (eventsManager != null) { eventsManager.wakeup(); } if (stats_com != null) stats_com.wakeup(); sleeping = false; //TODO : if sleep period too long (> 1'50) , we must force a refresh of cache values, because events have been 'masked' ! if ((eventsManager != null) && eventsManager.cache_out_of_date) { callback_counts = 0; new UpdateThread().execute(); //Force an immediate cache refresh on wakeup eventsManager.cache_out_of_date = false; } if (ready) { //Notify cache is ready if (parent[0] != null) { parent[0].sendEmptyMessage(8999); } if (parent[1] != null) { parent[1].sendEmptyMessage(8999); } if (parent[2] != null) { parent[2].sendEmptyMessage(8999); } } } catch (Exception e) { e.printStackTrace(); //Tracer.e(mytag, "Crash cause by: " + e.toString()); } } private class waitingThread extends AsyncTask<Void, Integer, Void> { @Override protected Void doInBackground(Void... params) { Boolean said = false; int counter = 0; while (!ready) { if (!said) { Tracer.d(mytag, "cache engine not yet ready : Wait a bit !"); said = true; } try { Thread.sleep(100); counter++; if (counter > 100) { said = false; counter = 0; } } catch (Exception e) { Tracer.e(mytag, e.toString()); } } if (myselfHandler != null) { //Tracer.d(mytag,"cache engine ready ! Notify it...."); myselfHandler.sendEmptyMessage(8999); // cache engine ready..... } if (parent[0] != null) { Tracer.d(mytag, "cache engine ready ! Notify Main activity...."); parent[0].sendEmptyMessage(8999); //hide Toast message } else { Tracer.d(mytag, "cache engine ready ! No Main activity...."); } if (parent[1] != null) { Tracer.d(mytag, "cache engine ready ! Notify Map activity...."); parent[1].sendEmptyMessage(8999); //hide Toast message } else { Tracer.d(mytag, "cache engine ready ! No Map activity...."); } if (parent[2] != null) { Tracer.d(mytag, "cache engine ready ! Notify MapView activity...."); parent[2].sendEmptyMessage(8999); //hide Toast message } else { Tracer.d(mytag, "cache engine ready ! No MapView activity...."); } Tracer.d(mytag, "cache engine ready ! Exiting Waiting thread ...."); return null; } } private class UpdateThread extends AsyncTask<Void, Integer, Void> { @Override protected Void doInBackground(Void... params) { // Added by Doume to correctly release resources when exiting if (!activated) { Tracer.d(mytag, "UpdateThread frozen...."); } else { if (sleeping) { return null; //Will check stats in 2 minutes } if (callback_counts > 0) { if (Tracer != null) Tracer.d(mytag, "Events detected since last loop = " + callback_counts + " No stats !"); //myselfHandler.sendEmptyMessage(9900); //Force to drain the pending stack events callback_counts = 0; return null; } if (Tracer != null) Tracer.d(mytag, "Request to server for stats update..."); String request = sharedparams.getString("UPDATE_URL", null); String URL = sharedparams.getString("URL", "1.1.1.1"); //Because in old time it was the full request that was used and saved, not only the real UPDATE_PATH //like "https://192.168.0.1:40406/rest/sensor" instead of just "sensor" try { request = request.replace(URL, ""); } catch (Exception e) { e.printStackTrace(); request = sharedparams.getString("UPDATE_URL", null); } Tracer.i(mytag, "urlupdate saved = " + request); if (request != null) { JSONObject json_widget_state = null; JSONArray json_widget_state_0_4 = new JSONArray(); stats_com.add(Stats_Com.STATS_SEND, request.length()); try { if (api_version <= 0.6f) { //Set timeout very high as tickets is a long process json_widget_state = Rest_com.connect_jsonobject(activity, Tracer, request, 300000); if (json_widget_state == null || (json_widget_state.toString().equals("{}"))) { // Cannot get data_type from Rinor server..... Tracer.e(mytag, "Cannot get data_type from Rinor server....."); activity.runOnUiThread(new Runnable() { public void run() { Toast.makeText(activity, R.string.rest_error_getting_sensors, Toast.LENGTH_LONG).show(); } }); Bundle b = new Bundle(); //Notify error to parent Dialog b.putString("message", "datatype"); Message msg = new Message(); msg.setData(b); //stop(); //todo stop Widgetupdate //handler.sendMessage(msg); return null; } Tracer.d(mytag, "json_widget_state for <0.6 API=" + json_widget_state.toString()); } else if (api_version >= 0.7f) { json_widget_state = new JSONObject(); //todo change by == when device.get will work for 0.5 // else if (api_version == 0.7f) { //get all sensors if (api_version < 0.9f) { //get all sensors from rest json_widget_state_0_4 = Rest_com.connect_jsonarray(activity, Tracer, request, 30000); if (json_widget_state_0_4 == null || (json_widget_state_0_4.toString().equals("[]"))) { // Cannot get data_type from Rinor server..... Tracer.e(mytag, "Cannot get sensors from Rinor server....."); activity.runOnUiThread(new Runnable() { public void run() { Toast.makeText(activity, R.string.rest_error_getting_sensors, Toast.LENGTH_LONG).show(); } }); Bundle b = new Bundle(); //Notify error to parent Dialog b.putString("message", "datatype"); Message msg = new Message(); msg.setData(b); //todo stop Widgetupdate //handler.sendMessage(msg); return null; } } else if (api_version == 0.9f) { //load timestamp apps was closed String sensor_saved_timestamp = sharedparams.getString("sensor_saved_timestamp", "0"); Log.e("#124 sensor_timestamp", sensor_saved_timestamp); //Modify request to match the timestamp String request_since; //if timestamp is null or 0 get the full sensor list if (sensor_saved_timestamp.equals("0")) { request_since = request; } else { request_since = request + "since/" + sensor_saved_timestamp; } JSONArray json_widget_state_0_6 = Rest_com.connect_jsonarray(activity, Tracer, request_since, 30000); if (json_widget_state_0_6 == null || (json_widget_state_0_6.toString().equals("[]"))) { // Cannot get data_type from Rinor server..... Tracer.e(mytag, "Cannot get sensors from Rinor server....."); activity.runOnUiThread(new Runnable() { public void run() { Toast.makeText(activity, R.string.rest_error_getting_sensors, Toast.LENGTH_LONG).show(); } }); Bundle b = new Bundle(); //Notify error to parent Dialog b.putString("message", "datatype"); Message msg = new Message(); msg.setData(b); //todo stop Widgetupdate //handler.sendMessage(msg); return null; } Tracer.d(mytag, "json_widget_state for 0.9 API=" + json_widget_state_0_6.toString()); String strJson = sharedparams.getString("sensor_saved_value", "0"); if (strJson != null) { //TODO load stored last_value JSONArray jsonData = new JSONArray(strJson); Log.e("#124 json saved", jsonData.toString()); String tmp_key; for (int i = 0; i < json_widget_state_0_6.length(); i++) { json_widget_state_0_4.put(json_widget_state_0_6.getJSONObject(i)); Log.e("#124", "json creating from domogik: " + json_widget_state_0_6.getJSONObject(i).toString()); Log.e("#124", "json creating from domogik: " + json_widget_state_0_4.length()); } //TODO remove when ok // Display message something changed since last update final JSONArray finalJson_widget_state_0_ = json_widget_state_0_6; activity.runOnUiThread(new Runnable() { public void run() { Toast.makeText(activity, "json length from rest sensor/since= " + finalJson_widget_state_0_.length(), Toast.LENGTH_SHORT).show(); } }); for (int i = 0; i < jsonData.length(); i++) { json_widget_state_0_4.put(jsonData.getJSONObject(i)); Log.e("#124", "json creating from saved: " + jsonData.getJSONObject(i).toString()); Log.e("#124", "json creating from saved: " + json_widget_state_0_4.length()); } //#124 Todo compare saved value and load value from domogik to remove old ones. //TODO remove when ok //Display message something changed since last update final JSONArray finalJson_widget_state_0_1 = jsonData; activity.runOnUiThread(new Runnable() { public void run() { Toast.makeText(activity, "json length from from saved: " + finalJson_widget_state_0_1.length(), Toast.LENGTH_SHORT).show(); } }); Log.e("#124", "json combined: " + json_widget_state_0_4.toString()); //TODO remove when ok final JSONArray finalJson_widget_state_0_2 = json_widget_state_0_4; activity.runOnUiThread(new Runnable() { public void run() { Toast.makeText(activity, "json total: " + finalJson_widget_state_0_2.length(), Toast.LENGTH_SHORT).show(); } }); } else { Tracer.d(mytag, "#124 json saved is null"); activity.runOnUiThread(new Runnable() { public void run() { Toast.makeText(activity, "json saved is null", Toast.LENGTH_SHORT).show(); } });//get all sensors from rest json_widget_state_0_4 = Rest_com.connect_jsonarray(activity, Tracer, request, 30000); if (json_widget_state_0_4 == null || (json_widget_state_0_4.toString().equals("[]"))) { // Cannot get data_type from Rinor server..... Tracer.e(mytag, "Cannot get sensors from Rinor server....."); activity.runOnUiThread(new Runnable() { public void run() { Toast.makeText(activity, R.string.rest_error_getting_sensors, Toast.LENGTH_LONG).show(); } }); Bundle b = new Bundle(); //Notify error to parent Dialog b.putString("message", "datatype"); Message msg = new Message(); msg.setData(b); //todo stop Widgetupdate //handler.sendMessage(msg); return null; } } } // Create a false JSONObject like if it was domomgik 0.3 //(meaning provide value in an stats: array containing a list of value in jsonobject format) json_widget_state.put("stats", json_widget_state_0_4); Tracer.d(mytag, "json_widget_state for API >0.7 =" + json_widget_state.toString()); //todo move this part in 0.8 api under when MQ.Get will work if (api_version >= 0.8f) { SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); Date timestamplast_device_update; Date timestamplast_update = new Date(); boolean newer = false; try { timestamplast_device_update = df.parse(last_device_update); } catch (Exception e) { Tracer.e(mytag, "No saved date or error parsing it"); timestamplast_device_update = new Date(); } try { JSONArray json_device_state_0_4 = new JSONArray(); //change url to get device instead of sensor request = request.replace("sensor", "device"); //Grab device list if (api_version == 0.8f) { json_device_state_0_4 = Rest_com.connect_jsonarray(activity, Tracer, request, 30000); if (json_widget_state_0_4 == null || (json_widget_state_0_4.toString().equals("[]"))) { // Cannot get data_type from Rinor server..... Tracer.e(mytag, "Cannot get device list from Rinor server....."); activity.runOnUiThread(new Runnable() { public void run() { Toast.makeText(activity, R.string.rest_error_getting_device, Toast.LENGTH_LONG).show(); } }); Bundle b = new Bundle(); //Notify error to parent Dialog b.putString("message", "datatype"); Message msg = new Message(); msg.setData(b); //todo stop Widgetupdate //handler.sendMessage(msg); return null; } } else if (api_version >= 0.9f) { //todo be sure last_device_update is in the right timestamp format??? request = request + "since/" + last_device_update; json_device_state_0_4 = Rest_com.connect_jsonarray(activity, Tracer, request, 30000); if (json_widget_state_0_4 == null || (json_widget_state_0_4.toString().equals("[]"))) { // Cannot get data_type from Rinor server..... Tracer.e(mytag, "Cannot get sensors from Rinor server....."); activity.runOnUiThread(new Runnable() { public void run() { Toast.makeText(activity, R.string.rest_error_getting_device, Toast.LENGTH_LONG).show(); } }); Bundle b = new Bundle(); //Notify error to parent Dialog b.putString("message", "datatype"); Message msg = new Message(); msg.setData(b); //todo stop Widgetupdate //handler.sendMessage(msg); return null; } } Tracer.d(mytag, "json_widget_device for 0.8 API=" + json_device_state_0_4.toString()); //test if info_changed: for (int i = 0; i < json_device_state_0_4.length(); i++) { try { String last_update = json_device_state_0_4.getJSONObject(i).getString("info_changed"); timestamplast_update = df.parse(last_update); //compare to latest update if (timestamplast_update.compareTo(timestamplast_device_update) > 0) { newer = true; timestamplast_device_update = timestamplast_update; Tracer.v(mytag, "device info_changed at: " + timestamplast_update.toString()); } } catch (ParseException E) { //timestamplast_update = new Date(); Tracer.d(mytag, "Exception info_changed:" + E); } } if (newer) { //Display message something changed since last update activity.runOnUiThread(new Runnable() { public void run() { Toast.makeText(activity, R.string.device_update_message, Toast.LENGTH_LONG).show(); } }); } } catch (Exception e) { Tracer.e(mytag, "Error trying to parse /device and info_changed"); } } }/* else if (api_version >= 0.8f) { //todo will use this when device.get will work json_widget_state = zmqrequest(); }*/ } catch (final Exception e) { //stats request cannot be completed (broken link or terminal in standby ?) //Will retry automatically in 2'05, if no events received Tracer.e(mytag, "get stats : Rinor error <" + e.getMessage() + ">"); //Toast not available in asynctask // TODO handle "Host name may not be null" to avoid white page in domodroid activity.runOnUiThread(new Runnable() { public void run() { Toast.makeText(activity, R.string.Error + e.toString(), Toast.LENGTH_SHORT).show(); } }); return null; } //Tracer.d(mytag,"UPDATE_URL = "+ sharedparams.getString("UPDATE_URL", null)); //Tracer.d(mytag,"result : "+ json_widget_state.toString()); if (json_widget_state != null) { stats_com.add(Stats_Com.STATS_RCV, json_widget_state.toString().length()); // cache engine : update cache with new values... int updated_items = update_cache(json_widget_state); //if(updated_items > 0) { //domodb.insertFeatureState(json_widget_state); //} ready = true; //Accept subscribing, now ! } } } return null; } } /* * Methods concerning cache management * (Doume, 2013/02/15) */ /* * Private method to update values in cache, and eventually notify all connected clients * Parameter : result of Rest request (multiple stats) */ private int update_cache(JSONObject json_widget_state) { int updated_items = 0; Boolean to_process = false; int dev_id = 0; String skey = null; String Val = null; String Value_timestamp = null; JSONArray itemArray = null; if (json_widget_state == null) { if (parent[0] != null) { parent[0].sendEmptyMessage(8001); //Ask main to display message } return 0; } //Tracer.i(mytag, "Cache update : stats result <"+json_widget_state.toString()+">"); try { itemArray = json_widget_state.getJSONArray("stats"); } catch (Exception e) { Tracer.e(mytag, "Cache update : No stats result !"); Tracer.e(mytag, e.toString()); if (parent[0] != null) { parent[0].sendEmptyMessage(8001); //Ask main to display message } return 0; } if (itemArray == null) { if (parent[0] != null) { parent[0].sendEmptyMessage(8001); //Ask main to display message } return 0; } try { if (itemArray.getJSONObject(0).getString("Error").length() > 0) { if (parent[0] != null) { parent[0].sendEmptyMessage(8002); //Ask main to display message } return 0; } } catch (JSONException e) { Tracer.e(mytag, e.toString()); } mapView = null; for (int i = 0; i < itemArray.length(); i++) { //force to process to true for all item and then to false if something wrong //then if false it will not update the cache value, avoiding display of null value. to_process = true; //Retrieve Json infos try { if (api_version <= 0.6f) { dev_id = itemArray.getJSONObject(i).getInt("device_id"); } else if (api_version >= 0.7f) { //todo try to avoid problem with MQ dev_id = itemArray.getJSONObject(i).getInt("id"); //dev_id = itemArray.getJSONObject(i).getInt("device_id"); } } catch (Exception e) { Tracer.e(mytag, "Cache update : No feature id ! "); Tracer.e(mytag, e.toString()); to_process = false; } try { if (api_version <= 0.6f) { skey = itemArray.getJSONObject(i).getString("skey"); } else if (api_version >= 0.7f) { //todo try to avoid problem with MQ skey = ""; //skey = itemArray.getJSONObject(i).getString("reference"); } } catch (Exception e) { Tracer.e(mytag, "Cache update : No skey ! "); Tracer.e(mytag, e.toString()); to_process = false; } try { if (api_version <= 0.6f) { Val = itemArray.getJSONObject(i).getString("value"); } else if (api_version >= 0.7f) { String temp = itemArray.getJSONObject(i).getString("last_value"); if (temp.equals(null) || temp.equals("null")) { to_process = false; } else { Val = temp; } } } catch (Exception e) { Tracer.e(mytag, "Cache update : No value ! "); Tracer.e(mytag, e.toString()); to_process = false; } try { if (api_version <= 0.6f) { Value_timestamp = itemArray.getJSONObject(i).getString("timestamp"); } else if (api_version >= 0.7f) { //todo try to avoid problem with MQ Value_timestamp = itemArray.getJSONObject(i).getString("last_received"); //dev_id = itemArray.getJSONObject(i).getInt("device_id"); } } catch (Exception e) { Tracer.e(mytag, "Cache update : No feature id ! "); Tracer.e(mytag, e.toString()); to_process = false; } // Try to put this in cache, now if (to_process) { Boolean item_updated = update_cache_device(dev_id, skey, Val, Value_timestamp); //insert, update or ignore new value for this feature if (item_updated) updated_items++; } } // end of for loop on stats result if (mapView != null) { //At least 1 mini widget has to be notified // send a 'group' notification to MapView ! try { Tracer.i(mytag, "cache engine send a unique notification to MapView"); mapView.sendEmptyMessage(9997); //notify the group of widgets a new value is there } catch (Exception e) { Tracer.e(mytag, e.toString()); } } mapView = null; Tracer.i(mytag, "cache size = " + cache.size()); return updated_items; } /* * Update device value in cache, and eventually notify clients about change * This sequence must be protected against concurrent access */ private Boolean update_cache_device(int dev_id, String skey, String Val, String Value_timestamp) { if (cache == null) return false; //synchronized(this) { while (locked) { //Somebody else is updating list... //Tracer.i(mytag, "cache engine locked : wait !"); try { Thread.sleep(100); //Standby 10 milliseconds } catch (Exception e) { Tracer.e(mytag, e.toString()); } } locked = true; //Take the lock Boolean result = false; int cache_position = -1; cache_position = locate_device(dev_id, skey, last_position); // Try to retrieve it in cache from the last accessed position // because 'stats' return them always in the same order if (cache_position >= 0) { //device found last_position = cache_position; //Keep the position, for next search if ((cache.get(cache_position).Value.equals(Val))) { //value not changed Tracer.i(mytag, "cache engine no value change for (" + dev_id + ") (" + skey + ") (" + Val + ") with timestamp=" + Value_timestamp); } else { //value changed : has to notify clients.... Tracer.i(mytag, "cache engine update value changed for (" + dev_id + ") (" + skey + ") (" + Val + ") with timestamp=" + Value_timestamp); cache.get(cache_position).Value = Val; cache.get(cache_position).Value_timestamp = Value_timestamp; result = true; if (cache.get(cache_position).clients_list != null) { for (int j = 0; j < cache.get(cache_position).clients_list.size(); j++) { //Notify each connected client Handler client = cache.get(cache_position).clients_list.get(j).getClientHandler(); if (client != null) { cache.get(cache_position).clients_list.get(j).setValue(Val); //update the session structure with new value cache.get(cache_position).clients_list.get(j).setTimestamp(Value_timestamp); //update the session structure with new value if (cache.get(cache_position).clients_list.get(j).is_Miniwidget()) { // This client is a mapView's miniwidget // Don't' notify it immediately // A unique notification will be done by Handler, or higher level after all updates processed ! mapView = client; } else { // It's not a mini_widget : notify it now try { Tracer.i(mytag, "cache engine send (" + Val + ") to client <" + cache.get(cache_position).clients_list.get(j).getName() + ">"); client.sendEmptyMessage(9999); //notify the widget a new value is ready for display } catch (Exception e) { Tracer.e(mytag, e.toString()); } } } // test of valid client handler } } } } else { // device not yet exist in cache // when creating a new cache entry, it can't have clients ! Tracer.i(mytag, "cache engine inserting (" + dev_id + ") (" + skey + ") (" + Val + ")"); Cache_Feature_Element device = new Cache_Feature_Element(dev_id, skey, Val, Value_timestamp); cache.add(device); result = true; } locked = false; return result; //} // End protected bloc } private int locate_device(int dev_id, String skey, int from) { if (cache.size() == 0) return -1; //empty cache int pos = from + 1; if (pos >= cache.size() || pos < 0) pos = 0; //Check if following entry in cache is the good one... for (int i = pos; i < cache.size(); i++) { if ((cache.get(i).DevId == dev_id) && (cache.get(i).skey.equals(skey))) { return i; //Bingo, the next one was the good one ! } } // If here, it's because from the location 'from + 1', till end of cache, the device was not found ! // Search from the beginning of table for (int i = 0; i <= pos; i++) { if ((cache.get(i).DevId == dev_id) && (cache.get(i).skey.equals(skey))) { return i; //Bingo, found ! } } return -1; //Not found in cache ! } /* * Method offered to clients, to subscribe to a device/skey value-changed event * The client must provide a Handler, to be notified * Parameter : Object Entity_client containing references to device , and handler for callbacks * Result : false if subscription failed (already exist, or unknown device ) * true : subscription accepted : Entity_client contains resulting state */ public Boolean subscribe(Entity_client client) { int device = -1; String skey = ""; Boolean result = false; if (client == null) return result; Handler h = client.getClientHandler(); if (h == null) return result; device = client.getDevId(); skey = client.getskey(); Tracer.i(mytag, "cache engine subscription requested by <" + client.getName() + "> Device (" + device + ") (" + skey + ")"); if (!ready) { Tracer.i(mytag, "cache engine not yet ready : reject !"); return result; } while (locked) { //Somebody else is updating list... Tracer.i(mytag, "cache engine locked : wait !"); try { Thread.sleep(100); //Standby 10 milliseconds } catch (Exception e) { Tracer.e(mytag, e.toString()); } } locked = true; //Take the lock for (int i = 0; i < cache.size(); i++) { if ((cache.get(i).DevId == device) && (cache.get(i).skey.equals(skey))) { //found device in list client.setValue(cache.get(i).Value); //return current stat value client.setTimestamp(cache.get(i).Value_timestamp); //return current stat Value_timestamp // Try to add this client to list cache.get(i).add_client(client); //The client structure will contain also last known value for this device Tracer.i(mytag, "cache engine subscription done for <" + client.getName() + "> Device (" + device + ") (" + skey + ") Value : " + cache.get(i).Value); result = true; break; } // not the good one : check next } //loop to search this device in cache locked = false; //dump_cache(); return result; } public void unsubscribe(Entity_client client) { int device = -1; String skey = ""; Boolean result = false; if (client == null) return; device = client.getDevId(); skey = client.getskey(); Tracer.i(mytag, "cache engine release subscription requested by <" + client.getName() + "> Device (" + device + ") (" + skey + ")"); while (locked) { //Somebody else is updating list... try { Thread.sleep(10); //Standby 10 milliseconds } catch (Exception e) { Tracer.e(mytag, e.toString()); } } locked = true; for (int i = 0; i < cache.size(); i++) { if ((cache.get(i).DevId == device) && (cache.get(i).skey.equals(skey))) { //found device in list client.setValue(cache.get(i).Value); //return current stat value client.setTimestamp(cache.get(i).Value_timestamp); //return current stat value // Try to remove this client from list cache.get(i).remove_client(client); Tracer.i(mytag, "cache engine release subscription OK for <" + client.getName() + "> Device (" + device + ") (" + skey + ")"); result = true; break; } // not the good one : check next } //loop to search this device in cache client.setClientId(-1); //subscribing not located... locked = false; } /* * Some methods to help widgets for database access (they don't have anymore to connect to DomodroidDB ! * */ public void descUpdate(int id, String new_desc, String type) { if (domodb == null) { Tracer.d(mytag, "domodb is null"); this.init(Tracer, activity, sharedparams); } boolean changed = false; try { if (Abstract.Connectivity.IsInternetAvailable()) { String url = null; Boolean SSL = false; if (Abstract.Connectivity.on_prefered_Wifi) { //If connected to default SSID use local adress url = sharedparams.getString("URL", "1.1.1.1"); SSL = sharedparams.getBoolean("ssl_activate", false); } else { //If not connected to default SSID use external adress url = sharedparams.getString("external_URL", "1.1.1.1"); SSL = sharedparams.getBoolean("ssl_external_activate", false); } //Todo Move this method somewhere else and make it reusable. if (!SSL) { try { Entity_Feature feature = domodb.requestFeaturesbyid(Integer.toString(id)); HttpClient httpclient = new DefaultHttpClient(); HttpPut httpput = new HttpPut(url + "device/" + feature.getDevId()); List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>(1); nameValuePairs.add(new BasicNameValuePair("description", new_desc)); httpput.setEntity(new UrlEncodedFormEntity(nameValuePairs, HTTP.UTF_8)); HttpResponse response = httpclient.execute(httpput); Tracer.d(mytag, "Renaming to Domogik without SSL response=" + response.getStatusLine().toString()); changed = true; } catch (IOException e) { Tracer.e(mytag, "Renaming to Domogik without SSL error " + e.toString()); } } else { try { Entity_Feature feature = domodb.requestFeaturesbyid(Integer.toString(id)); HttpsURLConnection urlConnection = Abstract.httpsUrl.setUpHttpsConnection(url + "device/" + feature.getDevId(), login, password); urlConnection.setRequestMethod("PUT"); List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>(1); nameValuePairs.add(new BasicNameValuePair("description", new_desc)); String result = null; urlConnection.setDoOutput(true); OutputStream os = new BufferedOutputStream(urlConnection.getOutputStream()); BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(os, "UTF-8")); writer.write(Abstract.httpsUrl.getQuery(nameValuePairs)); writer.flush(); writer.close(); os.close(); int responseCode = urlConnection.getResponseCode(); Tracer.d(mytag, "Renaming to Domogik with SSL response=" + responseCode); changed = true; } catch (IOException e) { Tracer.e(mytag, "Renaming to Domogik with SSL error " + e.toString()); } } if (changed) { //update db domodb.update_name(id, new_desc, type); //store last update in prefs for next start SharedPreferences.Editor prefEditor = sharedparams.edit(); SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); Date tempdate = new Date(); prefEditor.putString("last_device_update", df.format(tempdate)); prefEditor.commit(); } } else { Tracer.e(mytag, "NO CONNECTION"); activity.runOnUiThread(new Runnable() { public void run() { Toast.makeText(activity, R.string.no_connection, Toast.LENGTH_LONG).show(); } }); } } catch (Exception e) { Tracer.e(mytag, e.toString()); } } /* * This one allow Widgets to permut (for moving up or down) */ public void move_one_feature_association(int id, int place_id, String place_type, String order) { domodb.move_one_feature_association(id, place_id, place_type, order); } /* * This one allow area to permut (for moving up or down) */ public void move_one_area(int id, int place_id, String place_type, String order) { domodb.move_one_area(id, place_id, place_type, order); } /* * This one allow room to permut (for moving up or down) */ public void move_one_room(int id, int place_id, String place_type, String order) { domodb.move_one_room(id, place_id, place_type, order); } /* * This one allow MapView to clean all widgets from a map */ public void cleanFeatureMap(String map_name) { domodb.cleanFeatureMap(map_name); } /* * Obtain the list of feature located on a map */ public Entity_Map[] getMapFeaturesList(String currentmap) { return domodb.requestFeatures(currentmap); } /* * Obtain the list of map switches located on a map */ public Entity_Map[] getMapSwitchesList(String currentmap) { return domodb.requestMapSwitches(currentmap); } /* * Put feature at coordinates on a map */ public void insertFeatureMap(int id, int posx, int posy, String mapname) { domodb.insertFeatureMap(id, posx, posy, mapname); } /* * remove an area/room/icon or feature by id and type */ public void remove_one_things(int id, String type) { //Correct #209O in a better way. //seems domodb not create because widgetupdate not init. //in case domogik MQ problem if (domodb == null) { Tracer.d(mytag, "domodb is null"); this.init(Tracer, activity, sharedparams); } switch (type) { case "area": domodb.remove_one_area(id); break; case "room": domodb.remove_one_room(id); break; case "icon": domodb.remove_one_icon(id); break; case "feature": domodb.remove_one_feature(id); break; } } /* * remove an icon with his id and type */ public void remove_one_icon(int id, String place_type) { if (domodb == null) { Tracer.d(mytag, "domodb is null"); this.init(Tracer, activity, sharedparams); } domodb.remove_one_icon(id, place_type); } /* * remove a feature_association with his id place_id and place_type */ public void remove_one_feature_association(int id, int place_id, String place_type) { if (domodb == null) { Tracer.d(mytag, "domodb is null"); this.init(Tracer, activity, sharedparams); } domodb.remove_one_feature_association(id, place_id, place_type); } /* * remove a feature_map with his coordinates and mapname */ public void remove_one_FeatureMap(int id, int posx, int posy, String mapname) { if (domodb == null) { Tracer.d(mytag, "domodb is null"); this.init(Tracer, activity, sharedparams); } domodb.remove_one_FeatureMap(id, posx, posy, mapname); } public void remove_one_feature_in_FeatureMap(int id) { if (domodb == null) { Tracer.d(mytag, "domodb is null"); this.init(Tracer, activity, sharedparams); } domodb.remove_one_feature_in_FeatureMap(id); } /* * remove a place with his place_id and place_type */ public void remove_one_place_type_in_Featureassociation(int place_id, String place_type) { if (domodb == null) { Tracer.d(mytag, "domodb is null"); this.init(Tracer, activity, sharedparams); } domodb.remove_one_place_type_in_Featureassociation(place_id, place_type); } public static Entity_Feature[] requestFeatures() { return domodb.requestFeatures(); } public static Activity getactivity() { return activity; } public JSONObject zmqrequest() throws JSONException { ZMQReqMessage REQ = new ZMQReqMessage(myselfHandler); String ip = sharedparams.getString("MQaddress", ""); // TODO : use a R. for the default value String port = sharedparams.getString("MQreq_repport", "40410"); // TODO : use a R. for the default value final String pub_url = "tcp://" + ip + ":" + port; Log.d(mytag, "req address : " + pub_url); JSONArray json_widget_state_0_5 = new JSONArray(); REQ.execute(pub_url, "device.get"); REQ = null; Tracer.json(mytag, json_widget_state_0_5.toString()); JSONObject json_widget_state = new JSONObject(); // Create a false jsonarray like if it was domomgik 0.3 //(meaning provide value in an stats: array containing a list of value in jsonobject format) json_widget_state.put("stats", json_widget_state_0_5); Tracer.d(mytag, "json_widget_state for 0.8 API="); Tracer.json(mytag, json_widget_state.toString()); return json_widget_state; } }