package rinor; import android.app.Activity; import android.appwidget.AppWidgetManager; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.os.AsyncTask; import android.os.Build; import android.os.Handler; import android.os.Message; import android.util.Log; import android.widget.RemoteViews; import android.widget.Toast; //import com.orhanobut.logger.Logger; import org.domogik.domodroid13.R; import org.json.JSONException; import org.json.JSONObject; import org.zeromq.ZMQ; import java.util.ArrayList; import java.util.TimerTask; import database.Cache_Feature_Element; import database.JSONParser; import database.WidgetUpdate; import misc.tracerengine; public class Events_manager { private static Events_manager instance; private static Context context; private tracerengine Tracer; private static Activity activity; private Handler state_engine_handler; private Handler events_engine_handler; private ArrayList<Cache_Feature_Element> engine_cache; private int stack_in = -1; private int stack_out = -1; private int event_item = 0; private final int stack_size = 500; private ListenerThread listener = null; private Boolean alive = false; public Boolean cache_out_of_date = false; private int events_seen = 0; TimerTask doAsynchronousTask = null; private Boolean listener_running = false; private Boolean init_done = false; private Boolean com_broken = false; private Boolean sleeping = false; private float api_version; private String MQaddress; private String MQsubport; private final Rinor_event[] event_stack = new Rinor_event[stack_size]; private static Stats_Com stats_com = null; private final String mytag = this.getClass().getName(); /******************************************************************************* * Internal Constructor *******************************************************************************/ private Events_manager(final Activity activity) { super(); this.activity = activity; stats_com = Stats_Com.getInstance(); //Create a statistic counter, with all 0 values com_broken = false; } public static Events_manager getInstance(final Activity activity) { context = activity.getBaseContext(); if (instance == null) { Log.i("Events_manager", "Creating instance........................"); instance = new Events_manager(activity); } return instance; } public void init(tracerengine Trac, Handler state_engine_handler, ArrayList<Cache_Feature_Element> engine_cache, SharedPreferences params, WidgetUpdate caller) { if (init_done) return; this.Tracer = Trac; this.engine_cache = engine_cache; //todo look it to avoid crash in certain case... //setOwner(owner, state_engine_handler); api_version = params.getFloat("API_VERSION", 0); MQaddress = params.getString("MQaddress", null); MQsubport = params.getString("MQsubport", null); //The father's cache should already contain a list of devices features this.state_engine_handler = state_engine_handler; Tracer.w(mytag, "Events Manager initialized"); if (listener == null) { Tracer.w(mytag, "....start background task for events listening"); sleeping = false; start_listener(); } events_engine_handler = new Handler() { @Override public void handleMessage(Message msg) { if (msg.what == 9999) { listener = null; //It's dead stats_com = null; //let the instance to stop init_done = false; Tracer.w(mytag, "Events Manager really ending"); try { this.finalize(); } catch (Throwable t) { t.printStackTrace(); } } } }; Tracer.w(mytag, "Events Manager ready"); init_done = true; } //End of Constructor public void set_sleeping() { Tracer.d(mytag, "Pause requested..."); sleeping = true; /* if(stats_com != null) //Already done by cache engine ! stats_com.set_sleeping(); */ } public void wakeup() { Tracer.d(mytag, "Wake up requested..."); sleeping = false; } /* * Request to release all resources and die (when Main is onDestroy ! ) */ public void Destroy() { alive = false; //The end of loop into listener will generate a message // to local handler, to complete the destroy Tracer.d(mytag, "events engine Destroy() requested : Let the Listener thread to exit !"); } private void start_listener() { if (listener == null) { Runnable myrunnable = new Runnable() { public void run() { try { listener = new ListenerThread(); listener.execute(); } catch (Exception e) { e.printStackTrace(); } } }; //End of runnable Thread mylistener = new Thread(myrunnable); mylistener.run(); } } private class ListenerThread extends AsyncTask<Void, Integer, Void> { public void cancel() { //todo } @Override protected Void doInBackground(Void... params) { alive = true; sleeping = false; if (listener_running) { Tracer.e(mytag, "One ListenerThread is already running"); return null; } listener_running = true; if (stats_com == null) stats_com = Stats_Com.getInstance(); stats_com.wakeup(); //Engine is running... // First, construct the event request if (engine_cache.size() == 0) { Tracer.e(mytag, "Empty WidgetUpdate cache : cannot create ticket : ListenerThread aborted ! ! !"); return null; } //For 0.4 with zeromMQ if (api_version >= 0.7f) { if (MQaddress != null && MQsubport != null) { if (!MQaddress.equals("") && !MQsubport.equals("")) { try { //TODO find a way to know when ZeroMQ didn't response anymore. ZMQ.Context zmqContext = ZMQ.context(1); ZMQ.Socket subscriber = zmqContext.socket(ZMQ.SUB); Tracer.d(mytag, "subscriber = zmqContext.socket(ZMQ.sub)"); subscriber.setIdentity("domodroid".getBytes()); Tracer.d(mytag, "subscriber.setIdentity(domodroid.getBytes())"); subscriber.connect("tcp://" + MQaddress + ":" + MQsubport); Tracer.d(mytag, "subscriber.connect (tcp://" + MQaddress + ":" + MQsubport + ")"); subscriber.subscribe("device-stats".getBytes()); Tracer.d(mytag, "subscriber.subscribe(device-stats)"); subscriber.subscribe("device.update".getBytes()); Tracer.d(mytag, "subscriber.subscribe(device.update)"); while (alive) { while (!sleeping) { String result = subscriber.recvStr(0); Tracer.i(mytag, "MQ information receive: "); Tracer.i(mytag, result.toString()); if (result.contains("stored_value")) { try { JSONObject json_stats_04 = new JSONObject(result); Tracer.v(mytag, "MQ Parsing result to jsonobject"); //Tracer.d(mytag, json_stats_04.toString()); String ticket = "1"; String device_id = json_stats_04.get("sensor_id").toString(); String New_Value = json_stats_04.get("stored_value").toString(); String Timestamp = json_stats_04.get("timestamp").toString(); //TODO find a way to get the state_key of the feature by id=sensorid here!! String New_Key = ""; Tracer.v(mytag, "event ready : Ticket = MQ Device_id = " + device_id + " Key = " + New_Key + " Value = " + New_Value + " Timestamp = " + Timestamp); Rinor_event to_stack = new Rinor_event(Integer.parseInt(ticket), event_item, Integer.parseInt(device_id), New_Key, New_Value, Timestamp); put_event(to_stack); //Put in stack, and notify cache engine stats_com.add(Stats_Com.EVENTS_RCV, result.length()); } catch (JSONException e) { Tracer.e(mytag, "Error making the json from MQ result"); Tracer.e(mytag, e.toString()); } } else if (result.contains("device.update")) { Tracer.i(mytag, "New MQ message for device.update : " + result); stats_com.add(Stats_Com.EVENTS_RCV, result.length()); try { JSONObject json_mq_update = new JSONObject(result); Tracer.i(mytag, "New MQ message for device.update : " + json_mq_update.toString()); } catch (JSONException e) { Tracer.i(mytag, "New MQ message for device.update : " + e.toString()); } if (state_engine_handler != null) { state_engine_handler.sendEmptyMessage(9903); } } if (subscriber.getReceiveTimeOut() == 1) { break; } } } subscriber.close(); zmqContext.term(); } catch (IllegalArgumentException e) { // Say user Mq conf as a problem activity.runOnUiThread(new Runnable() { public void run() { Toast.makeText(activity, R.string.events_error_mq, Toast.LENGTH_LONG).show(); } }); } } else { // Say user Mq conf as a problem activity.runOnUiThread(new Runnable() { public void run() { Toast.makeText(activity, R.string.events_error_mq, Toast.LENGTH_LONG).show(); } }); Tracer.d(mytag, "error in MQ config"); //To avoid crash on multiple launch of dmd try { events_engine_handler.sendEmptyMessage(9999); //Notify main thread to die } catch (Exception e) { Tracer.e(mytag, "events_engine_handler crash " + e.toString()); } return null; } } else { //todo say user MQ address or port is empty //Toast.makeText(null, R.string.events_error_mq_config,Toast.LENGTH_LONG).show(); Tracer.d(mytag, "MQ adress or port is empty"); } } else if (api_version <= 0.6f) { //This is for 0.3 version //Build the list of devices concerned by ticket request String ticket_request = "events/request/new"; for (int i = 0; i < engine_cache.size(); i++) { String skey = engine_cache.get(i).skey; if (!(skey.equals("_") && !(skey.equals("command")))) { ticket_request += "/"; ticket_request += engine_cache.get(i).DevId; } } //And send it to server....to create an event ticket String request = ticket_request; JSONObject event = new JSONObject(); Boolean ack = false; Tracer.i(mytag, "ListenerThread starts the loop"); String ticket = ""; int counter_max = 5 * 60 * 1000; //5 minutes max between 2 retry int counter_current = 0; // 0 + 10 seconds per loop, at beginning int loop_time = 10000; //10 seconds per wait at beginning, and grow it till counter_max int max_time_for_ticket = 110 * 1000; //After 1'50 , ticket is dead... So, prepare to recreate another ! int sleep_time = 2 * 1000; //When sleeping, check every 2 seconds int sleep_duration = 0; com_broken = false; while (alive) { while (sleeping) { try { Thread.sleep(sleep_time); //Wait for 2s } catch (Throwable t) { t.printStackTrace(); } sleep_duration += sleep_time; //TODO 0.4 try to change this to listen when MQ is no more connected. if (sleep_duration > max_time_for_ticket) { cache_out_of_date = true; request = ticket_request; com_broken = false; //Retry immediately to reconnect } } // Exit from sleep loop ( wake up requested ) // If sleep was less 1'50, the ticket is still alive // Otherwise, the next request will be a get new ticket sleep_duration = 0; // for next sleep..... if (com_broken) { //Link is probably broken... Wait a bit before to re-submit a request to server counter_current += loop_time; if (counter_current > counter_max) counter_current = counter_max; Tracer.e(mytag, "ListenerThread waiting " + (counter_current / 1000) + " seconds before error recovery retry"); try { Thread.sleep(counter_current); //Wait for 10s, 20s, 30s, ... (max 5 minutes) } catch (Throwable t) { t.printStackTrace(); } //And try to reconnect } int error = 1; // Try to connect to server and send request stats_com.add(Stats_Com.EVENTS_SEND, request.length()); Tracer.w(mytag, "Requesting server <" + request + ">"); try { //Set timeout very high as tickets is a long process event = Rest_com.connect_jsonobject(activity, Tracer, request, 30000); //Blocking request : we must have an answer to continue... error = 0; } catch (Exception e) { error = 1; Tracer.e(mytag, "Rinor error : <" + e.getMessage() + ">"); } catch (Throwable t) { error = 2; Tracer.e(mytag, "Rinor Throwable error "); } if (error != 0) { Tracer.e(mytag, "Exception Error ==> Network probably not yet ready !"); com_broken = true; //Next retry has to be delayed, waiting for an operational link.... request = ticket_request; //Having detected a broken link, the current ticket with server is probably lost //Create a new one ! } else { //No error stats_com.add(Stats_Com.EVENTS_RCV, event.toString().length()); counter_current = 0; //One packet received : the link is operational ! com_broken = false; //no need to temporize before next send... try { ack = JSONParser.Ack(event); } catch (Exception e) { ack = false; } if (!ack) { // The server's response is'nt "OK" Tracer.w(mytag, "Event ERROR <" + event.toString() + "> : ignored !"); } else { //An event is available... //Tracer.w(mytag,"Processing event"); // First, take the ticket ID to resubmit an event request.... int list_size = 0; if (event != null) { String device_id = ""; try { list_size = event.getJSONArray("event").length(); } catch (Exception e) { Tracer.w(mytag, "Very strange message, ignored !"); request = ticket_request; break; } ticket = ""; // Process the event array for (int i = 0; i < list_size; i++) { try { ticket = event.getJSONArray("event").getJSONObject(i).getString("ticket_id"); } catch (Exception e) { Tracer.w(mytag, "Wrong event : No ticket !"); request = ticket_request; //Create a new ticket on next query, now ! break; } if ((ticket != null) && (!ticket.equals(""))) request = "events/request/get/" + ticket; //Use the ticket on next query else { ticket = ""; request = ticket_request; //Create a new ticket on next query } events_seen++; try { device_id = event.getJSONArray("event").getJSONObject(i).getString("device_id"); } catch (Exception e) { //No device_id : it's a timeout Tracer.w(mytag, "Timeout received !"); notify_engine(9902); //Time out seen break; //Force to redo the loop from while(alive) } //json_ValuesList = event.getJSONArray("event").getJSONObject(i).getJSONObject("data").getJSONArray("value"); int data_size = 0; try { data_size = event.getJSONArray("event").getJSONObject(i).getJSONArray("data").length(); } catch (Exception e) { data_size = 0; //No data ==> no values to process ! } for (int j = 0; j < data_size; j++) { try { String New_Key = event.getJSONArray("event").getJSONObject(i).getJSONArray("data").getJSONObject(j).getString("key"); String New_Value = event.getJSONArray("event").getJSONObject(i).getJSONArray("data").getJSONObject(j).getString("value"); String Timestamp = event.getJSONArray("event").getJSONObject(i).getString("timestamp"); Tracer.v(mytag, "event ready : Ticket = " + ticket + " Device_id = " + device_id + " Key = " + New_Key + " Value = " + New_Value + " Timestamp = " + Timestamp); event_item++; Rinor_event to_stack = new Rinor_event(Integer.parseInt(ticket), event_item, Integer.parseInt(device_id), New_Key, New_Value, Timestamp); put_event(to_stack); //Put in stack, and notify cache engine } catch (Exception e) { Tracer.e(mytag, "Malformed data entry ?????????????????"); } } } // End of loop on event array } // if event not null } // if ack } // if error } //Infinite loop of thread // Try to free the ticket, if available if (!ticket.equals("")) { request = "events/request/free/" + ticket; //Use the ticket # try { Tracer.w(mytag, "Freeing ticket <" + request + ">"); stats_com.add(Stats_Com.EVENTS_SEND, request.length()); event = Rest_com.connect_jsonobject(activity, Tracer, request, 30000); //Blocking request : we must have an answer to continue... stats_com.add(Stats_Com.EVENTS_RCV, event.length()); Tracer.w(mytag, "Received on free ticket = <" + event.toString() + ">"); } catch (Exception e) { e.printStackTrace(); } } } // should never reach this code..... Tracer.e(mytag, "should never reach this code....."); Tracer.e(mytag, "ListenerThread going down !!!!!!!!!!!!!!!!!"); listener_running = false; if (state_engine_handler != null) { state_engine_handler.sendEmptyMessage(9901); //I'm going down.... } //events_engine_handler.sendEmptyMessage(9999); //Notify main thread to die /*comment as it make crash domodroid with: FATAL EXCEPTION: AsyncTask #4 Process: org.domogik.domodroid13, PID: 2460 java.lang.RuntimeException: An error occurred while executing doInBackground() at android.os.AsyncTask$3.done(AsyncTask.java:309) at java.util.concurrent.FutureTask.finishCompletion(FutureTask.java:354) at java.util.concurrent.FutureTask.setException(FutureTask.java:223) at java.util.concurrent.FutureTask.run(FutureTask.java:242) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1113) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:588) at java.lang.Thread.run(Thread.java:818) Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'boolean android.os.Handler.sendEmptyMessage(int)' on a null object reference at rinor.Events_manager$ListenerThread.doInBackground(Events_manager.java:466) at rinor.Events_manager$ListenerThread.doInBackground(Events_manager.java:170) at android.os.AsyncTask$2.call(AsyncTask.java:295) at java.util.concurrent.FutureTask.run(FutureTask.java:237) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1113)  at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:588)  at java.lang.Thread.run(Thread.java:818)  */ listener = null; return null; //And die myself } } /* * Fill stack with events received from server (by ListenerThread) */ private void put_event(Rinor_event event) { //synchronized(this) { stack_in++; if (stack_in >= stack_size) stack_in = 0; if (event_stack[stack_in] == null) { //Position is free ! Tracer.d(mytag, "Event stacked at :" + stack_in); event_stack[stack_in] = event; notify_engine(9900); //An event is available } else { Tracer.w(mytag, "stack is full ! ! ! Event will be lost"); } //} // protected bloc } /* * This method works only if one client extracts elements....( WidgetUpdate handler, normally) */ public Rinor_event get_event() { stack_out++; if (stack_out >= stack_size) stack_out = 0; if (event_stack[stack_out] == null) { //Tracer.w(mytag,"Stack is empty @ "+stack_out); stack_out--; if (stack_out < 0) { stack_out = stack_size; } return null; } else { Rinor_event result = event_stack[stack_out]; Tracer.d(mytag, "Event unstacked from " + stack_out); event_stack[stack_out] = null; //free the entry return result; } } public int getAndResetEventCount() { int count = events_seen; events_seen = 0; return count; } /* * Notify WidgetUpdate that some Rinor_event is available in stack */ private void notify_engine(int what) { if (alive) { if (state_engine_handler != null) { state_engine_handler.sendEmptyMessage(what); } else { Tracer.w(mytag, "No handler to notify for " + stack_out); } } else { Tracer.w(mytag, "Trying to notify father for " + stack_out + " , while ListenerThread is in Pause state..."); } } }