/** * Copyright (c) 2013, Sana * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of the Sana nor the * names of its contributors may be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL Sana BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package org.sana.android.service.impl; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.BufferedReader; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.Writer; import java.lang.reflect.Field; import java.lang.reflect.Type; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.Hashtable; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.ListIterator; import java.util.HashMap; import java.util.Map; import java.util.Queue; import java.util.PriorityQueue; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.TimeUnit; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.Executors; import org.apache.http.HttpHost; import org.apache.http.HttpResponse; import org.apache.http.auth.AuthScope; import org.apache.http.auth.Credentials; import org.apache.http.auth.UsernamePasswordCredentials; import org.apache.http.client.CredentialsProvider; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.impl.client.AbstractHttpClient; import org.apache.http.impl.client.BasicCredentialsProvider; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.util.EntityUtils; import org.sana.R; import org.sana.android.Constants; import org.sana.android.activity.EncounterList; import org.sana.android.activity.MainActivity; import org.sana.android.app.Locales; import org.sana.android.app.NotificationFactory; import org.sana.android.content.ModelContext; import org.sana.android.content.ModelEntity; import org.sana.android.content.Uris; import org.sana.android.content.Intents; import org.sana.android.content.core.PatientWrapper; import org.sana.android.db.ModelWrapper; import org.sana.android.net.MDSInterface; import org.sana.android.net.MDSInterface2; import org.sana.android.provider.EncounterTasks; import org.sana.android.provider.Encounters; import org.sana.android.provider.BaseContract; import org.sana.android.provider.Patients; import org.sana.android.provider.Procedures; import org.sana.android.provider.Subjects; import org.sana.android.service.QueueManager; import org.sana.android.util.Logf; import org.sana.android.util.SanaUtil; import org.sana.api.task.EncounterTask; import org.sana.core.Model; import org.sana.core.Patient; import org.sana.core.Procedure; import org.sana.net.Response; import org.sana.core.Subject; import org.sana.net.http.HttpTaskFactory; import org.sana.net.http.handler.EncounterResponseHandler; import org.sana.net.http.handler.EncounterTaskResponseHandler; import org.sana.net.http.handler.PatientResponseHandler; import org.sana.util.DateUtil; import com.google.gson.FieldNamingPolicy; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.reflect.TypeToken; import com.google.gson.stream.JsonReader; import android.app.Notification; import android.app.PendingIntent; import android.app.Service; import android.content.ContentValues; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; import android.database.Cursor; import android.net.Uri; import android.os.Binder; import android.os.Build; import android.os.Bundle; import android.os.Environment; import android.os.Handler; import android.os.HandlerThread; import android.os.IBinder; import android.os.Looper; import android.os.Message; import android.os.Process; import android.preference.PreferenceManager; import android.support.v4.content.LocalBroadcastManager; import android.support.v4.app.NotificationCompat; import android.text.TextUtils; import android.util.Log; /** * Implementation of the dispatch server as an Android service. * * @author Sana Development * */ public class DispatchService extends Service{ public static final String TAG = DispatchService.class.getSimpleName(); static final int REQUEST = 0; static final int RESPONSE = 1; public static final int IN_PROGRESS = 0; public static final int COMPLETE = 1; public static final int UPLOAD_RESPONSE = 1; public static final String RESPONSE_NOTIFICATION_ID = "notification_id"; public static final String RESPONSE_CODE = "code"; public static final int NO_BROADCAST = -1; public class DispatchCallback implements Handler.Callback{ PatientResponseHandler pHandler = new PatientResponseHandler(); /** * Message passed should have the following values * what - REQUEST(0) or RESPONSE(1) * arg1 - startId passed to startService() * arg2 - Uri descriptor * obj - Uri string of the intent * data - additional query/post parameters * * Request will be sent from initial call to handle message which * will trigger a second callback placed on the Handler for the * response. Hence, Response may not be handled in the same order as * the original request. */ @Override public boolean handleMessage(Message msg) { return true; } } //TODO Refactor this out. abstract static class SyncHandler<T> { SyncHandler(){} public abstract List<ContentValues> values(Response<T> response); public Response<T> fromJson(Message msg){ Log.d("JSONHandler<T>", msg.obj.toString()); Type type = new TypeToken<Response<T>>(){}.getType(); Gson gson = new GsonBuilder() .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) .setDateFormat("yyyy-MM-dd HH:mm:ss") .create(); Response<T> response = gson.fromJson(msg.obj.toString(), type); Log.d("JSONHandler<T>", msg.obj.toString()); return response; } public Response<T> fromJson(String json){ return null; } public abstract ContentValues[] values(T t); } final SyncHandler<List<Procedure>> procedureListHandler = new SyncHandler<List<Procedure>>(){ @Override public List<ContentValues> values(Response<List<Procedure>> response) { List<ContentValues> list = new ArrayList<ContentValues>(); for(Procedure p: response.getMessage()){ ContentValues vals = new ContentValues(); vals.put(Procedures.Contract.UUID, p.getUuid()); vals.put(Procedures.Contract.TITLE, p.getDescription()); vals.put(Procedures.Contract.AUTHOR, p.getAuthor()); vals.put(Procedures.Contract.VERSION, p.getVersion()); vals.put(Procedures.Contract.PROCEDURE, p.getSrc()); list.add(vals); } return list; } @Override public ContentValues[] values(List<Procedure> t) { // TODO Auto-generated method stub return null; } }; final SyncHandler<Collection<Patient>> patientListHandler = new SyncHandler<Collection<Patient>>(){ public Response<Collection<Patient>> fromJson(Message msg){ Type type = new TypeToken<Response<List<Patient>>>(){}.getType(); Gson gson = new GsonBuilder() .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) .setDateFormat(DispatchService.this.getString(R.string.cfg_format_date_value)) .create(); Response<Collection<Patient>> response = gson.fromJson(msg.obj.toString(), type); return response; } public Response<Collection<Patient>> fromJson(String json){ Type type = new TypeToken<Response<List<Patient>>>(){}.getType(); Gson gson = new GsonBuilder() .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) .setDateFormat(DispatchService.this.getString(R.string.cfg_format_date_value)) .create(); Response<Collection<Patient>> response = gson.fromJson(json, type); return response; } @Override public List<ContentValues> values(Response<Collection<Patient>> response) { List<ContentValues> list = new ArrayList<ContentValues>(); for(Patient p: response.getMessage()){ Log.d(TAG, p.system_id); ContentValues vals = new ContentValues(); vals.put(Patients.Contract.GIVEN_NAME, p.getGiven_name()); vals.put(Patients.Contract.FAMILY_NAME, p.getFamily_name()); vals.put(Patients.Contract.GENDER, p.getGender()); vals.put(Patients.Contract.LOCATION, p.getLocation().getUuid()); vals.put(Patients.Contract.UUID, p.getUuid()); vals.put(Patients.Contract.PATIENT_ID, p.system_id); vals.put(Patients.Contract.DOB, p.getDob().toString()); if(p.getImage() != null && (p.getImage().getPath().endsWith("jpg") || p.getImage().getPath().endsWith("png"))){ try{ File dir = ModelContext.getExternalFilesDir(Subjects.CONTENT_URI); //String path = media.getAbsolutePath() + "/" + p.getImage().toASCIIString(); File f = new File(dir, p.getImage().toASCIIString()); if(f.isDirectory()){ Log.d(TAG, "deleting erroneous directory " + f.getAbsolutePath()); f.delete(); } if(f.exists()){ // no need to download again Log.d(TAG, "File exists " + f.getAbsolutePath()); if(f.isDirectory()) f.delete(); else vals.put(Patients.Contract.IMAGE, Uri.fromFile(f).toString()); } else { // TODO Add image download here Log.d(TAG, "Need to download file to: " + f.getAbsolutePath()); if(f.exists() && f.isDirectory()) f.delete(); f.getParentFile().mkdirs(); boolean result = MDSInterface2.getFile(DispatchService.this, "media/" + p.getImage().toASCIIString(), f); Log.d(TAG, "Download success: " + result); vals.put(Patients.Contract.IMAGE, Uri.fromFile(f).toString()); } } catch(Exception e){ Log.e(TAG, e.getMessage()); e.printStackTrace(); } } else { try{ File dir = ModelContext.getExternalFilesDir(Subjects.CONTENT_URI); Log.d(TAG, dir.getAbsolutePath()); File f = new File(dir, p.getImage().toASCIIString()); if(f.isDirectory() && f.delete()){ Log.d(TAG, "deleting erroneous directory " + f.getAbsolutePath()); } } catch(Exception e){ Log.e(TAG, e.getMessage()); e.printStackTrace(); } } list.add(vals); } return list; } @Override public ContentValues[] values(Collection<Patient> t) { // TODO Auto-generated method stub return null; } }; final SyncHandler<Collection<EncounterTask>> encounterTasksHandler = new SyncHandler<Collection<EncounterTask>>(){ @Override public List<ContentValues> values(Response<Collection<EncounterTask>> response) { List<ContentValues> values = new ArrayList<ContentValues>(response.message.size()); for(EncounterTask task:response.message){ ContentValues value = new ContentValues(); value.put(EncounterTasks.Contract.UUID , task.uuid); value.put(EncounterTasks.Contract.DUE_DATE , DateUtil.format (task.due_on)); value.put(EncounterTasks.Contract.PROCEDURE , task.procedure.uuid); value.put(EncounterTasks.Contract.SUBJECT , task.subject.uuid ); value.put(EncounterTasks.Contract.ENCOUNTER, task.encounter.uuid); value.put(EncounterTasks.Contract.OBSERVER , task.assigned_to.uuid); value.put(EncounterTasks.Contract.STATUS , task.getStatus()); values.add(value); } return values; } @Override public ContentValues[] values(Collection<EncounterTask> t) { ContentValues[] values = new ContentValues[t.size()]; Iterator<EncounterTask> iterator = t.iterator(); int index = 0; while(iterator.hasNext()){ EncounterTask task = iterator.next(); ContentValues value = new ContentValues(); value.put(EncounterTasks.Contract.UUID , task.uuid); value.put(EncounterTasks.Contract.DUE_DATE , DateUtil.format (task.due_on)); value.put(EncounterTasks.Contract.PROCEDURE , task.procedure.uuid); value.put(EncounterTasks.Contract.SUBJECT , task.subject.uuid ); if(task.encounter != null) value.put(EncounterTasks.Contract.ENCOUNTER, task.encounter.uuid); value.put(EncounterTasks.Contract.OBSERVER , task.assigned_to.uuid); value.put(EncounterTasks.Contract.STATUS , task.getStatus()); values[index] = value; index++; } return values; } }; EncounterTaskResponseHandler eTaskHandler = new EncounterTaskResponseHandler(); static final int create = 0x00000001; static final int read = 0x00000010; static final int update = 0x00000100; static final int delete = 0x000001000; static final int CREATE = new Intent(Intents.ACTION_CREATE).filterHashCode(); static final int READ = new Intent(Intents.ACTION_READ).filterHashCode(); static final int UPDATE = new Intent(Intents.ACTION_UPDATE).filterHashCode(); static final int DELETE = new Intent(Intents.ACTION_DELETE).filterHashCode(); static final IntentFilter filter = new IntentFilter(); static{ filter.addAction(Intents.ACTION_CREATE); filter.addAction(Intents.ACTION_READ); filter.addAction(Intents.ACTION_UPDATE); filter.addAction(Intents.ACTION_DELETE); filter.addDataScheme("content"); filter.addDataAuthority("org.sana.provider", null); } static final IntentFilter pkgFilter = new IntentFilter(); static{ pkgFilter.addAction(Intents.ACTION_READ); pkgFilter.addAction(Intents.ACTION_UPDATE); pkgFilter.addDataScheme("package"); } public static final String PKG = "application/vnd.android.package-archive"; public static final int PKG_MASK = 0x00000000; static final AtomicInteger sNotificationCount = new AtomicInteger(0); // Callback we pass to the Handler Thread to execute the requests. protected Handler.Callback getHandlerCallback(){ return new Handler.Callback() { /* * (non-Javadoc) * @see android.os.Handler.Callback#handleMessage(android.os.Message) */ /** * Message passed should have the following values * what - REQUEST(0) or RESPONSE(1) * arg1 - startId passed to startService() * arg2 - Uri descriptor * obj - Uri string of the intent * data - additional query/post parameters * * Request will be sent from initial call to handle message which * will trigger a second callback placed on the Handler for the * response. Hence, Response may not be handled in the same order as * the original request. */ @Override public boolean handleMessage(Message msg) { if(msg != null) Logf.D(TAG, "handleMessage()", String.format( "Message what: %d, arg1: %d, arg2: %d", msg.what,msg.arg1,msg.arg2)); else{ Logf.W(TAG, "handleMessage()", "Null message. Was it supposed to be?"); return true; } DispatchService.this.handleRequestStart(1,msg.what); int what = msg.what; int arg1 = msg.arg1; int arg2 = msg.arg2; Object obj = (msg.obj != null)? msg.obj: null; Bundle data = msg.getData(); if(data != null) data = new Bundle(data); int startId = msg.arg1; int mode = msg.arg2; // set up for results ContentValues[] values = null; int index = 0; Cursor c = null; // Result broadcast Content String bcastMessage = ""; String bcastMessages = null; int bcastCode = NO_BROADCAST; try { SharedPreferences preferences = PreferenceManager .getDefaultSharedPreferences(DispatchService.this); String username = preferences.getString( Constants.PREFERENCE_EMR_USERNAME, Constants.DEFAULT_USERNAME); String password = preferences.getString( Constants.PREFERENCE_EMR_PASSWORD, Constants.DEFAULT_PASSWORD); switch (msg.arg2) { case REQUEST: Log.i(TAG, "...handleMessage(Message) + Got a " + "REQUEST"); HttpUriRequest request = null; HttpResponse httpResponse = null; String responseString = null; HttpClient client = HttpTaskFactory.CLIENT_FACTORY.produce(); Intent intent = Intent.parseUri(msg.obj.toString(), 0); String action = intent.getAction(); int flags = intent.getFlags(); // set the request method. String method = "GET"; if (action.contains("CREATE")) method = "POST"; else if (action.contains("READ")) method = "GET"; else if (action.contains("UPDATE")) method = "PUT"; else if (action.contains("DELTE")) method = "DELETE"; Log.d(TAG, "...handleMessage()" + String.format("Method: %s", method)); Uri msgUri = intent.getData(); Log.d(TAG,"..." + msgUri.getSchemeSpecificPart()); String path = ""; String query = ""; if(msgUri != null){ path = msgUri.getPath(); query = msgUri.getEncodedQuery(); } if(!Uris.isEmpty(msgUri)){ // Broadcast that something is happening broadcastResult(msgUri, Response.Code.CONTINUE.code, R.string.general_uploading); } URI uri = MDSInterface2.getURI(DispatchService.this, path, query); Logf.D(TAG, "handleMessage()", "method: " + method + ", uri: " + uri); // Set up for results ContentValues update = new ContentValues(); switch (msg.what) { case Uris.ITEM_FILE: if(MDSInterface2.getFile(DispatchService.this, msgUri)){ Log.d(TAG, "....File download success: " + msgUri); } else { Log.d(TAG, "....File download fail: " + msgUri); } bcastCode = NO_BROADCAST; break; case Uris.ENCOUNTER_DIR: // TODO implement as a query from msg.data break; case Uris.ENCOUNTER_UUID: case Uris.ENCOUNTER_ITEM: // TODO Allows GET or POST if (method.equals("GET")) request = new HttpGet(uri); else if (method.equals("POST")) { try{ boolean encounterPost = MDSInterface2 .postProcedureToDjangoServer( intent.getData(), DispatchService.this); // Send notification to notification bar // Notification intent Intent notifyIntent = new Intent( DispatchService.this, EncounterList.class); // Sets the Activity to start in a new, empty // task notifyIntent .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); if (encounterPost) { update.put( Encounters.Contract.UPLOAD_STATUS, QueueManager.UPLOAD_STATUS_SUCCESS); DispatchService.this.getContentResolver() .update(intent.getData(), update, null, null); //DispatchService.this.notify( // R.string.upload_success, // notifyIntent); //DispatchService.this.notifyForeground( // UPLOAD_RESPONSE, // R.string.upload_success, // notifyIntent); Locales.updateLocale(DispatchService.this, getString(R.string.force_locale)); bcastMessage = getString(R.string.upload_success); bcastCode = 200; } else { update.put( Encounters.Contract.UPLOAD_STATUS, QueueManager.UPLOAD_STATUS_FAILURE); DispatchService.this.getContentResolver() .update(intent.getData(), update, null, null); addFailedToQueue(what, arg1, arg2, obj, data, msgUri); //DispatchService.this.notifyForeground( // UPLOAD_RESPONSE, // R.string.upload_fail, // notifyIntent); Locales.updateLocale(DispatchService.this, getString(R.string.force_locale)); bcastMessage = getString(R.string.upload_fail); bcastCode = 400; } } catch (Exception e){ addFailedToQueue(what, arg1, arg2, obj, data, msgUri); Log.e(TAG, "POST failed: " + msgUri); Log.e(TAG,"...." + e.getMessage()); Locales.updateLocale(DispatchService.this, getString(R.string.force_locale)); bcastMessage = getString(R.string.upload_fail); bcastCode = 400; } } break; case Uris.OBSERVATION_DIR: // TODO implement as a query from msg.data break; case Uris.OBSERVATION_UUID: // TODO Allows GET or POST if (method.equals("GET")) request = new HttpGet(uri); else if (method.equals("POST")) { request = new HttpPost(uri); } break; case Uris.PROCEDURE_DIR: // TODO Allows GET only for updating procedures from // repository //uri = MDSInterface2.getURI(DispatchService.this, ); // only allows get request = new HttpGet(uri); break; case Uris.PROCEDURE_UUID: // TODO Allows GET. This should pull raw xml if (method.equals("GET")) request = new HttpGet(uri); break; case Uris.SUBJECT_DIR: try { //List<ContentValues> content = new ArrayList<ContentValues>(); PatientResponseHandler pHandler = new PatientResponseHandler(); Response<Collection<Patient>> patientListResponse = MDSInterface2.apiGet(uri,username,password, pHandler); bcastCode = createOrUpdateSubjects(patientListResponse.message, startId); bcastMessage = ""; Log.d(TAG, "" +Uris.SUBJECT_DIR+"...code " + bcastCode); } catch (Exception e) { Log.e(TAG,"...." + e.getMessage()); Locales.updateLocale(DispatchService.this, getString(R.string.force_locale)); //bcastMessage = e.getMessage(); bcastCode = 400; } break; case Uris.SUBJECT_UUID: case Uris.SUBJECT_ITEM: Patient patient = PatientWrapper.get (DispatchService.this, msgUri); PatientResponseHandler pHandler = new PatientResponseHandler(); Response<Collection<Patient>> patientResponse = null; Log.d(TAG, "...method=" + method + ", " + "data=" + msgUri); if (method.equals("GET")) request = new HttpGet(uri); else if (method.equals("POST")) { patientResponse = MDSInterface2.postPatient( DispatchService.this, patient, username,password, pHandler); Log.d(TAG, "...response: code=" + patientResponse.getCode() + ", data=" + patientResponse.getMessage()); bcastCode = createOrUpdateSubjects( patientResponse.message, startId); // If successful create, we need to swap the // client side uuid out with the server side // value if(bcastCode == 200) { bcastCode = 201; List<Patient> pList = new ArrayList<Patient> (patientResponse.getMessage()); Patient p = pList.get(0); String uuid = ModelWrapper.getUuid(msgUri, DispatchService.this.getContentResolver()); if(!uuid.equalsIgnoreCase(p.getUuid())){ int isTemp = intent.getFlags() & Intents.FLAG_REPLACE; if(isTemp == 0){ DispatchService.this .getContentResolver() .delete(msgUri,null,null); } Uri u = Uris.withAppendedUuid (Subjects.CONTENT_URI, p.getUuid()); bcastMessage = u.toString(); } else { bcastMessage = msgUri.toString(); } } Log.d(TAG, "...method=" + method + ", " + "data=" + msgUri); } else if (method.equals("PUT")) { patientResponse = MDSInterface2.updatePatient( DispatchService.this, patient, username,password, pHandler); Log.d(TAG, "...response: code=" + patientResponse .getCode() + ", data=" + patientResponse.getMessage()); bcastCode = patientResponse.getCode(); if(patientResponse.code == 200) { bcastMessage = msgUri.toString(); } } break; case Uris.ENCOUNTER_TASK_DIR: if (method.equals("GET")){ EncounterTaskResponseHandler handler = new EncounterTaskResponseHandler(); Collection<EncounterTask> objs = Collections.emptyList(); try { Response<Collection<EncounterTask>> response = MDSInterface2.apiGet(uri,username,password,handler); objs = response.message; Log.i(TAG, "GET EncounterTask: Returned " + "n=" + objs.size()); bcastCode = createOrUpdateEncounterTasks(response.message, startId); } catch (Exception e) { Log.w(TAG, "GET failed: " + uri .toASCIIString()); Log.w(TAG,"...." + e.getMessage()); e.printStackTrace(); Locales.updateLocale(DispatchService.this, getString(R.string.force_locale)); bcastMessage = e.getMessage();//getString(R.string.upload_fail); bcastCode = 400; } } break; case Uris.ENCOUNTER_TASK_ITEM: case Uris.ENCOUNTER_TASK_UUID: try{ if (method.equals("PUT")) { Log.i(TAG, "....Updating task: " + msgUri); Bundle form = data.getBundle("form"); Log.d(TAG, "....form size: " + ((form != null)? form.size():"NULL FORM")); Response<Collection<EncounterTask>> e = MDSInterface2.syncUpdate(DispatchService.this, msgUri, username, password, form, null, eTaskHandler); Log.d(TAG, "....UPDATE " + e.status +" --> " + e.message); if(e.code != 200) addFailedToQueue(what, arg1, arg2, obj, data, msgUri); else bcastMessage = getString(R.string.upload_success); } } catch(Exception e){ addFailedToQueue(what, arg1, arg2, obj, data, msgUri); Log.e(TAG, "PUT failed: " + msgUri); Log.e(TAG,"...." + e.getMessage()); Locales.updateLocale(DispatchService.this, getString(R.string.force_locale)); bcastMessage = e.getMessage(); bcastCode = 400; } break; case Uris.PACKAGE_DIR: Logf.D(TAG, "handleMessage(Message)", "PACKAGE update request"); uri = MDSInterface2.getURI(DispatchService.this,DispatchService.this .getString(R.string.path_app)); request = new HttpGet(uri); break; default: } Log.d(TAG, "...REQUEST: " + msgUri); Log.d(TAG, "... code="+ bcastCode); if(bcastCode != NO_BROADCAST) broadcastResult(intent.getData(),bcastCode,bcastMessage); break; case RESPONSE: Logf.I(TAG, "handleResponse(Message)", "Got a RESPONSE: " + msg.obj); Response<?> response; List<ContentValues> content = new ArrayList<ContentValues>(); if (msg.obj == null) { Logf.W(TAG, "handleResponse(Message)", "NULL RESPONSE"); break; } switch (msg.what) { case Uris.ENCOUNTER_DIR: // TODO implement as a query from msg.data break; case Uris.ENCOUNTER_ITEM: case Uris.ENCOUNTER_UUID: Logf.D(TAG, String.valueOf(msg.obj)); break; case Uris.OBSERVATION_DIR: // TODO implement as a query from msg.data break; case Uris.OBSERVATION_ITEM: case Uris.OBSERVATION_UUID: // TODO Allows GET or POST break; case Uris.PROCEDURE_DIR: /* Response<List<Procedure>> p = procedureListHandler.fromJson(msg); content = procedureListHandler.values(p); for(ContentValues v: content){ ModelWrapper.insertOrUpdate(Procedures.CONTENT_URI, v, getContentResolver()); } */ break; case Uris.PROCEDURE_ITEM: case Uris.PROCEDURE_UUID: // TODO Allows GET. This should pull raw xml break; case Uris.SUBJECT_DIR: /* for (ContentValues v : content) { ModelWrapper.insertOrUpdate( Subjects.CONTENT_URI, v, getContentResolver()); } */ // getContentResolver().notifyChange(Subjects.CONTENT_URI, // null); break; case Uris.SUBJECT_ITEM: case Uris.SUBJECT_UUID: // TODO Response<Patient> patientResponse; break; case Uris.PACKAGE_DIR: Logf.D(TAG, "handleMessage(Message)", "PACKAGE update response: " + msg.obj); // This will download and install new apk break; default: } break; default: } DispatchService.this.handleRequestComplete(1); } catch (Exception e) { e.printStackTrace(); } //stopSelf(msg.arg1); return true; } }; } //////////////////////////////////////////////////////////////////////////// // //////////////////////////////////////////////////////////////////////////// int mStartMode; // indicates how to behave if the service is killed final IBinder mBinder = new Binder(); // interface for clients that bind boolean mAllowRebind; // indicates whether onRebind should be used private Looper mServiceLooper; private Handler mHandler; private boolean initialized = false; private NotificationFactory mNotificationFactory; private AtomicInteger numNotifications = new AtomicInteger(0); private QueueControl failQueue = new QueueControl(); //////////////////////////////////////////////////////////////////////////// // Begin Overridden methods //////////////////////////////////////////////////////////////////////////// /* * (non-Javadoc) * @see android.app.Service#onCreate() */ @Override public void onCreate() { super.onCreate(); Log.i(TAG, "onCreate()"); HandlerThread thread = new HandlerThread("dispatcher", Process.THREAD_PRIORITY_BACKGROUND); thread.start(); // Get the HandlerThread's Looper and use it for our Handler mServiceLooper = thread.getLooper(); mHandler = new Handler(mServiceLooper, getHandlerCallback()); if(!initialized) initialized = checkInit(); mNotificationFactory = NotificationFactory.getInstance(this); mNotificationFactory.setContentTitle(R.string.network_alert); } /* (non-Javadoc) * @see android.app.Service#onBind(android.content.Intent) */ @Override public IBinder onBind(Intent intent) { return mBinder; } /* (non-Javadoc) * @see android.app.Service#onBind(android.content.Intent) */ @Override public void onRebind(Intent intent) { super.onRebind(intent); } @Override public boolean onUnbind(Intent intent) { return super.onUnbind(intent); } @Override public int onStartCommand(Intent intent, int flags, int startId) { Log.i(TAG, "onStartCommand()" + ((intent != null)? intent.getAction(): null)); Log.d(TAG, "..." + intent); if (intent != null) { // handleCommand(intent); // We want this service to continue running until it is explicitly // stopped, so return sticky. int matchesPackage = pkgFilter.match(getContentResolver(), intent, false, TAG); int matchesModel = filter.match(getContentResolver(), intent, false, TAG); int what = Uris.getDescriptor(intent.getData()); Log.d(TAG, String.format("...Message what: %d, match: %d, pkg: %d", what, matchesModel, matchesPackage)); // msg.what <=> [REQUEST|RESPONSE] and msg.arg1 <=> startId if (matchesModel >= 0) { Message msg = mHandler.obtainMessage(what, intent.toUri(Intent.URI_INTENT_SCHEME)); msg.arg1 = startId; msg.arg2 = REQUEST; Bundle data = intent.getExtras(); Log.d(TAG,"onStartCommand() --> " + ((data != null)?data.size():"NULL")); msg.setData(intent.getExtras()); mHandler.sendMessage(msg); } else if (matchesPackage >= 0) { Message msg = mHandler.obtainMessage(what, intent.toUri(Intent.URI_INTENT_SCHEME)); msg.arg1 = startId; msg.arg2 = REQUEST; msg.setData(intent.getExtras()); mHandler.sendMessage(msg); } else { Log.e(TAG, String.format( "Unrecognized message. what: %d, match: %d", what, matchesPackage)); //stopSelf(startId); } } else { Log.w(TAG, "onStartCommand(): Null intent. Are we resuming?"); //stopSelf(startId); } mStartMode = START_STICKY; return START_STICKY; } @Override public void onDestroy(){ Logf.D(TAG, "onDestroy()", "...finishing"); try{ if(!failQueue.isEmpty()){ Log.w(TAG, "RESEND queue is not empty"); } failQueue.cancel(); mNotificationFactory.cancelAll(); } catch(Exception e){ e.printStackTrace(); } super.onDestroy(); } @Override public boolean stopService(Intent intent){ Logf.I(TAG, "stopService()", (intent != null)? intent.toUri(Intent.URI_INTENT_SCHEME): "null"); return super.stopService(intent); /* if(intent != null){ Uri data = intent.getData(); int what = -1; if(!Uris.isEmpty(data)){ what = Uris.getDescriptor(data); if(failQueue.contains(what)){ return true; } } */ /* failQueue.cancel(); try{ int notification = intent.getIntExtra(RESPONSE_NOTIFICATION_ID, 0); if (notification != 0){ mNotificationFactory.cancel(notification); if(sNotificationCount.decrementAndGet() == 0){ mNotificationFactory.cancelAll(); mNotificationFactory = null; stopForeground(true); return super.stopService(intent); } } } catch(Exception e){ e.printStackTrace(); } } else { mNotificationFactory = null; return super.stopService(intent); } return false; */ } private final boolean checkInit(){ Logf.D(TAG, "initialize()", "Entering"); SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(getBaseContext()); int version = 0; String dbKey = getString(R.string.cfg_db_init); String dbVersion = getString(R.string.cfg_db_version); // check whether the db is initialized and create if not boolean doInit = preferences.getBoolean(dbKey, false); Logf.D(TAG, "initialize()", "dbs initialized: " + doInit); if(!doInit){ getContentResolver().acquireContentProviderClient(Procedures.CONTENT_URI).release(); getContentResolver().delete(Procedures.CONTENT_URI, null, null); SanaUtil.loadDefaultDatabase(getBaseContext()); preferences.edit().putBoolean(dbKey, true) .putInt(dbVersion, getResources().getInteger(R.integer.cfg_db_version_value)) .putBoolean(dbKey, true) .commit(); } return true; } //TODO Refactor these out /** * Returns the number of ms until the next sync should occur * * @param uri * @return */ private final long nextSync(Uri uri){ final String METHOD = "delta(Uri)"; SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(DispatchService.this); String key = "last_sync"; switch(Uris.getDescriptor(uri)){ default: key = "last_sync"; } long now = new Date().getTime(); long tdelta = Long.valueOf(getString(R.string.sync_delta)); long delta = now - preferences.getLong(key, 0); return delta; } private final void resetSync(Uri uri){ final String METHOD = "resetSync(Uri)"; SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(DispatchService.this); String key = null; switch(Uris.getDescriptor(uri)){ default: key = "last_sync"; } preferences.edit().putLong(key, new Date().getTime()).commit(); } // The dispatch server URI URI uHost = null; HttpClient mClient = null; HttpHost mHost = null; protected void build() throws URISyntaxException{ URI uri = MDSInterface2.getRoot(this); mHost = new HttpHost(uri.getHost(),uri.getPort(),uri.getScheme()); CredentialsProvider credsProvider = new BasicCredentialsProvider(); AuthScope authScope = new AuthScope(mHost.getHostName(), mHost.getPort()); Credentials creds = new UsernamePasswordCredentials("username", "password"); credsProvider.setCredentials(authScope,creds); mClient = new DefaultHttpClient(); ((AbstractHttpClient) mClient).getCredentialsProvider().setCredentials( authScope, creds); } protected final void notifyForeground(int id, int resID, Intent notifyIntent){ // Pending intent used to launch the notification intent PendingIntent actionIntent = PendingIntent.getActivity( getBaseContext(), 0, notifyIntent, PendingIntent.FLAG_UPDATE_CURRENT ); // Always force the locale before we send the notification Locales.updateLocale(getBaseContext(), getString(R.string.force_locale)); Notification notification = mNotificationFactory .setContentIntent(actionIntent) .setContentText(resID) .setNumber(numNotifications.incrementAndGet()) .build(); sNotificationCount.incrementAndGet(); startForeground(id, notification); } protected final void notify(int resID, Intent notifyIntent){ Log.d(TAG, "notify(...) " + resID +", " + notifyIntent.toUri(Intent.URI_INTENT_SCHEME)); // Pending intent used to launch the notification intent PendingIntent actionIntent = PendingIntent.getActivity( getBaseContext(), 0, notifyIntent, PendingIntent.FLAG_UPDATE_CURRENT ); // Always force the locale before we send the notification Locales.updateLocale(getBaseContext(), getString(R.string.force_locale)); mNotificationFactory .setContentIntent(actionIntent) .setContentText(resID) .doNotify(); } protected final void notify(int resID, int code, Intent notifyIntent){ Log.d(TAG, "notify(...) " + resID + ", " + notifyIntent.toUri(Intent.URI_INTENT_SCHEME)); // Pending intent used to launch the notification intent PendingIntent actionIntent = PendingIntent.getActivity( getBaseContext(), 0, notifyIntent, PendingIntent.FLAG_UPDATE_CURRENT ); // Always force the locale before we send the notification Locales.updateLocale(getBaseContext(), getString(R.string.force_locale)); mNotificationFactory .setContentIntent(actionIntent) .setContentText(resID, code) .doNotify(); } protected final void broadcastResult(Uri data, int code, String message){ Log.i(TAG,"broadcastResult() code=" + code + ", uri=" + data + ", message=" + message); Intent broadcast = new Intent(Response.RESPONSE,data); broadcast.putExtra(Response.MESSAGE, message); broadcast.putExtra(Response.CODE, code); LocalBroadcastManager.getInstance(this.getApplicationContext()).sendBroadcast(broadcast); } protected final void broadcastResult(Uri data, int code, int message){ Locales.updateLocale(DispatchService.this, getString(R.string.force_locale)); this.broadcastResult(data, code, getString(message)); } protected void handleEncounterTasks(List<EncounterTasks> tasks){ } protected void handleSubject(Patient subject){ } static <T> T readJSONStream(HttpURLConnection url) throws IOException{ T t = null; JsonReader reader = new JsonReader(new InputStreamReader(url.getInputStream())); Type type = new TypeToken<T>(){}.getType(); t = new Gson().fromJson(reader, type); return t; } static <T> T readJSONStream(HttpURLConnection url, Context context) throws IOException{ T t = null; JsonReader reader = new JsonReader(new InputStreamReader(url.getInputStream())); final Gson gson = new GsonBuilder() .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) .setDateFormat(context.getString(R.string.cfg_format_date_value)) .create(); Type type = new TypeToken<T>(){}.getType(); return gson.fromJson(reader, type); } static <T> T readJSONStream(String string, Context context) throws IOException{ final Gson gson = new GsonBuilder() .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) .setDateFormat(context.getString(R.string.cfg_format_date_value)) .create(); Type type = new TypeToken<T>(){}.getType(); return gson.fromJson(string, type); } public static String readCharStream(HttpURLConnection url) throws IOException{ InputStreamReader in = null; StringBuilder builder = new StringBuilder(); char[] buf = new char[80]; in = new InputStreamReader(url.getInputStream()); while(in.read(buf) > -1){ builder.append(buf); } return builder.toString().trim(); } public static Uri readByteStream(HttpURLConnection url, String output) throws IOException{ File file = new File(output); OutputStream out = null; InputStream in = null; try{ in = new BufferedInputStream(url.getInputStream()); out = (BufferedOutputStream) new BufferedOutputStream(new FileOutputStream(file)); byte[] buffer = new byte[1024]; while(in.read(buffer) > -1){ out.write(buffer); } } finally { in.close(); out.close(); } return Uri.fromFile(new File(output)); } private final void notifyPackageManager(final Context context, Uri apk){ Intent intent = new Intent(Intent.ACTION_VIEW); intent.setDataAndType(apk, "application/vnd.android.package-archive"); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); // without this flag android returned a intent error! context.startActivity(intent); } private ContentValues[] toArray(List<ContentValues> values){ int size = (values != null)? values.size():0; ContentValues[] array = new ContentValues[size]; int index = 0; while(index < size){ array[index] = values.get(index); index++; } return array; } public final boolean exists(Uri uri, Model obj){ boolean exists = false; Cursor c = null; try{ Uri target = Uri.parse(uri + "/" + obj.uuid); c = getContentResolver().query(target,null,null,null,null); if(c!= null && c.getCount() == 1){ exists = true; } } finally { if(c != null) c.close(); } return exists; } //TODO Handle the modified part public Uri getFileIfNotExistsOrNotModified(URI remote, File dir, ContentValues vals, int startId) { Log.i(TAG, "getFileIfNotExists() remote=" + remote.toASCIIString() +", dir=" +dir.getPath() + ", startId=" + startId); Uri result = Uri.EMPTY; try{ File local = new File(dir, remote.toASCIIString()); if(local.isDirectory()){ Log.w(TAG, "....deleting erroneous directory " + local.getAbsolutePath()); local.delete(); } if(!local.exists()){ try{ Log.d(TAG, "....Need to download file to: " + local.getAbsolutePath()); if(local.isDirectory()) local.delete(); local.getParentFile().mkdirs(); // Try to fetch and add to ContentVals if success result = Uri.fromFile(local); sendFileGetDispatchSelf(result,startId); vals.put(Patients.Contract.IMAGE, result.toString()); } catch(Exception e){ Log.w(TAG, "....Something went wront getting" + remote.toASCIIString()); e.printStackTrace(); result = Uri.EMPTY; } } else { // no need to download again // TODO Check file size/mod time Log.d(TAG, "....File exists " + local.getAbsolutePath()); } } catch(Exception e){ e.printStackTrace(); } return result; } public final int createOrUpdateSubjects(Collection<Patient> t, int startId) { Log.i(TAG, "createOrUpdatePatients(Collection<Patient>,int)"); int size = (t != null)?t.size():0; Log.d(TAG,"...size="+size); // return a 404 not found code if size is zero if(size == 0) return Response.Code.NOT_FOUND.code; Uri result = Uri.EMPTY; final File dir = ModelContext.getExternalFilesDir(Subjects.CONTENT_URI); // Containers for instances that must be inserted or updated List<ContentValues> insert = new ArrayList<ContentValues>(); List<ModelEntity> update = new ArrayList<ModelEntity>(); // Begin process of iterating over the list int index = 0; Iterator<Patient> iterator = t.iterator(); while(iterator.hasNext()){ Patient p = iterator.next(); ContentValues vals = new ContentValues(); vals.put(Patients.Contract.GIVEN_NAME, p.getGiven_name()); vals.put(Patients.Contract.FAMILY_NAME, p.getFamily_name()); vals.put(Patients.Contract.GENDER, p.getGender()); vals.put(Patients.Contract.LOCATION, p.getLocation().getUuid()); vals.put(Patients.Contract.PATIENT_ID, p.system_id); vals.put(Patients.Contract.DOB, DateUtil.format(p.getDob())); //////////////////////////////////////////////////////////// // Handle images //////////////////////////////////////////////////////////// File file = new File(dir, p.getImage().toASCIIString()); // check that file is valid if(file != null && (file.getPath().endsWith("jpg") || file.getPath().endsWith("png"))) { getFileIfNotExistsOrNotModified(p.getImage(), dir, vals, startId); } // Don't add uuid initially if(!exists(Subjects.CONTENT_URI, p)){ vals.put(Patients.Contract.UUID, p.uuid); insert.add(vals); } else { update.add( new ModelEntity( Uris.withAppendedUuid(Subjects.CONTENT_URI, p.uuid), vals)); } } // Handle the insert(s) int inserted = getContentResolver().bulkInsert(Subjects.CONTENT_URI, toArray(insert)); Log.d(TAG, "....inserted=" + inserted); // Handle the update(s) int updated = 0; for(ModelEntity me:update){ updated += getContentResolver().update(me.getUri(), me.getEntityValues(),null,null); } Log.d(TAG, "....updates=" + updated); // Successful return a 200 code return Response.Code.OK.code; } public final int createOrUpdateEncounterTasks(Collection<EncounterTask> t, int startId) { Log.i(TAG, "createOrUpdateEncounterTasks() size=" +((t != null)?t.size():"null")); int result = 400; ContentValues[] values = null; Map<String,Subject> subjects = new HashMap<String,Subject>(); List<ContentValues> insert = new ArrayList<ContentValues>(); List<ModelEntity> update = new ArrayList<ModelEntity>(); Iterator<EncounterTask> iterator = t.iterator(); int index = 0; while(iterator.hasNext()){ EncounterTask task = iterator.next(); ContentValues value = new ContentValues(); value.put(EncounterTasks.Contract.UUID , task.uuid); value.put(EncounterTasks.Contract.DUE_DATE , DateUtil.format(task.due_on)); value.put(EncounterTasks.Contract.PROCEDURE , task.procedure.uuid); value.put(EncounterTasks.Contract.SUBJECT , task.subject.uuid ); subjects.put(task.subject.uuid, task.subject); if(task.encounter != null) value.put(EncounterTasks.Contract.ENCOUNTER, task.encounter.uuid); value.put(EncounterTasks.Contract.OBSERVER , task.assigned_to.uuid); value.put(EncounterTasks.Contract.STATUS , task.getStatus()); if (task.completed != null) { value.put(EncounterTasks.Contract.COMPLETED, DateUtil.format(task.completed)); } if (task.started != null) { value.put(EncounterTasks.Contract.STARTED, DateUtil.format(task.started)); } if(!exists(EncounterTasks.CONTENT_URI, task)) insert.add(value); else update.add( new ModelEntity( Uris.withAppendedUuid(EncounterTasks.CONTENT_URI, task.uuid), value)); } int inserted = getContentResolver().bulkInsert(EncounterTasks.CONTENT_URI, toArray(insert)); Log.d(TAG, "....inserted=" + inserted); Log.d(TAG, "....updates=" + update.size()); int updated = 0; for(ModelEntity me:update){ updated += getContentResolver().update(me.getUri(),me.getEntityValues(),null,null); } Log.d(TAG, "....updated=" + updated); //createOrUpdateSubjects(patients.values(), startId); result = 200; return result; } final Handler.Callback updateCheckRunnable(Handler handler) throws URISyntaxException{ Intent intent = new Intent(getString(R.string.intent_action_read)); intent.setType("application/vnd.android.package-archive"); URI uri = MDSInterface2.getRoot(DispatchService.this); final Message message = handler.obtainMessage(RESPONSE); try { final URL url = uri.toURL(); return new Handler.Callback() { @Override public boolean handleMessage(Message msg) { try { HttpURLConnection connection = (HttpURLConnection) url.openConnection(); Response<String> response = readJSONStream(connection); message.obj = response; return true; } catch (IOException e) { e.printStackTrace(); } return false; }}; } catch (MalformedURLException e1) { throw new IllegalArgumentException(e1); } } public final void sendFileGetDispatchSelf(Uri file, int startId){ Intent intent = new Intent(Intents.ACTION_READ, file); Message msg = mHandler.obtainMessage(Uris.ITEM_FILE, intent.toUri(Intent.URI_INTENT_SCHEME)); msg.arg1 = startId; msg.arg2 = REQUEST; mHandler.sendMessage(msg); } final MessageFactory MSG_FACTORY = new MessageFactory(); public static class MessageFactory{ public MessageFactory(){} public Message obtainRequest(Handler handler, String action, Uri uri){ Message msg = handler.obtainMessage(REQUEST, action.hashCode(), Uris.getDescriptor(uri)); return msg; } public Message obtainRequest(Handler handler, int who, Intent intent){ int what = Uris.getDescriptor(intent.getData()); Message msg = handler.obtainMessage(what, intent.toUri(Intent.URI_INTENT_SCHEME)); msg.arg1 = intent.filterHashCode(); msg.arg2 = REQUEST; msg.obj = intent.toUri(Intent.URI_INTENT_SCHEME); return msg; } public Message obtainResponse(Message orig, Object obj){ Message msg = Message.obtain(orig); msg.what = RESPONSE; msg.obj = obj; return msg; } } public final void handleFailResend(MessageHolder message){ Log.i(TAG, "handleFailedResend() " + message.what); Message msg = mHandler.obtainMessage(message.what, message.arg1, message.arg2, message.object); if(message.data != null) msg.setData(message.data); msg.sendToTarget(); } public final void addFailedToQueue(Message message){ Log.i(TAG, "addFailedToQueue() " + message.what); addFailedToQueue(message.what,message.arg1,message.arg2,message.obj,message.getData(),Uri.EMPTY); } public final void addFailedToQueue(int what, int arg1, int arg2, Object object, Bundle data){ Log.i(TAG, "addFailedToQueue() " + what); addFailedToQueue(what,arg1,arg2,object,data,Uri.EMPTY); } public final void addFailedToQueue(int what, int arg1, int arg2, Object object, Bundle data, Uri uri){ Log.i(TAG, "addFailedToQueue(...Uri) " + what); MessageHolder holder = new MessageHolder(); holder.what = what; holder.arg1 = arg1; holder.arg2 = arg2; holder.object = object; if(data != null) { holder.data = new Bundle(data); } holder.uri = uri; Log.d(TAG,"....Queue size: " + failQueue.size()); Log.d(TAG,"....adding 1 item"); failQueue.add(holder); Log.d(TAG,"....Queue size: " + failQueue.size()); failQueue.start(); failQueue.resend(); } public final void handleRequestStart(int id, int code){ Log.i(TAG, "handleRequestStart():" + id); int resID = R.string.general_network_active; Intent notifyIntent = new Intent(DispatchService.this, MainActivity.class); //notifyIntent.addCategory(Intent.CATEGORY_HOME); notifyIntent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); notifyIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); notify(resID,code,notifyIntent); } public final void handleRequestStart(int id){ Log.i(TAG, "handleRequestStart():" + id); int resID = R.string.general_network_active; Intent notifyIntent = new Intent(DispatchService.this, MainActivity.class); //notifyIntent.addCategory(Intent.CATEGORY_HOME); notifyIntent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); notifyIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); notify(resID,notifyIntent); } public final void handleRequestComplete(int id){ Log.i(TAG, "handleRequestComplete():" + id); int count = sNotificationCount.decrementAndGet(); int resID = (count > 0)? R.string.general_network_active: R.string.general_network_inactive; Intent notifyIntent = new Intent(DispatchService.this, MainActivity.class); //notifyIntent.addCategory(Intent.CATEGORY_HOME); notifyIntent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); notifyIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); notify(resID,notifyIntent); } public final void handleQueueEmpty(){ Intent notifyIntent = new Intent(getApplicationContext(), EncounterList.class); notifyForeground(COMPLETE, R.string.upload_fail, notifyIntent); } public final void handleResponse(Message message){ } class MessageHolder implements Comparable<MessageHolder>{ public MessageHolder(){} public MessageHolder(MessageHolder message){} public MessageHolder(Message message){ what = message.what; arg1 = message.arg1; arg2 = message.arg2; object = message.obj; if(message.getData() != null) data = new Bundle(message.getData()); } Intent intent = null; int priority = 0; int what = -1; int arg1 = -1; int arg2 = -1; Object object = null; Bundle data = new Bundle(); Uri uri = Uri.EMPTY; public int compareTo(MessageHolder another){ if(this.what == another.what){ return another.priority - this.priority; } else { return another.priority - this.priority; } } public boolean equals(MessageHolder message){ return (what == message.what); } } class QueueControl{ //LinkedList<Integer> queue = new LinkedList<Integer>(); LinkedList<Uri> queue = new LinkedList<Uri>(); //Hashtable<Integer,MessageHolder> pending = new Hashtable<Integer,MessageHolder>(); Hashtable<Uri,MessageHolder> pending = new Hashtable<Uri,MessageHolder>(); //Hashtable<Integer,ArrayList<MessageHolder>> pending = Hashtable<Integer,ArrayList<MessageHolder>>(); private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); AtomicBoolean halt = new AtomicBoolean(false); public void resend(){ Log.i(TAG, "QueueControl resend()"); if(halt.get()){ return; } final Runnable sender = new Runnable(){ public void run(){ Log.d(TAG, "QueueControl sender.run()"); synchronized(queue){ Uri head = queue.poll(); if(head != null){ MessageHolder next = pending.remove(head); handleFailResend(next); } } } }; final ScheduledFuture handle = scheduler.schedule(sender, 15, TimeUnit.SECONDS); } public boolean isEmpty(){ boolean empty = true; synchronized(queue){ empty = (queue.size() > 0); } return empty; } public void add(MessageHolder message){ Log.i(TAG, "QueueControl add()"); synchronized(queue){ queue.add(message.uri); pending.put(message.uri, message); } halt.set(false); } public final void cancel(){ Log.i(TAG, "QueueControl cancel()"); halt.set(true); } public boolean contains(Uri what){ boolean contains = false; synchronized(queue){ contains = queue.contains(what); } return contains; } public int size(){ int size = -1; synchronized(queue){ size = queue.size(); } return size; } public MessageHolder[] toArray(){ MessageHolder[] array = new MessageHolder[pending.size()]; synchronized(queue){ int index = 0; for(MessageHolder message:pending.values()){ array[index] = new MessageHolder(message); } } return array; } public final void start(){ Log.i(TAG, "QueueControl start()"); halt.set(false); } } }