diff --git a/app/src/main/java/org/emstrack/ambulance/MainActivity.java b/app/src/main/java/org/emstrack/ambulance/MainActivity.java index ea914902..df129749 100644 --- a/app/src/main/java/org/emstrack/ambulance/MainActivity.java +++ b/app/src/main/java/org/emstrack/ambulance/MainActivity.java @@ -62,6 +62,7 @@ public class MainActivity extends AppCompatActivity { private ImageView onlineIcon; private ImageView trackingIcon; private LocationChangeBroadcastReceiver receiver; + private CallPromptReceiver promptReceiver; private int requestingToStreamLocation; public class LocationChangeBroadcastReceiver extends BroadcastReceiver { @@ -93,6 +94,29 @@ public void onReceive(Context context, Intent intent ) { } }; + public class CallPromptReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + Log.i(TAG, "received broadcast for call prompt"); + if (intent != null) { + final String action = intent.getAction(); + + if (action.equals(AmbulanceForegroundService.BroadcastActions.PROMPT_CALL_ACCEPT)) { + Log.i(TAG, "PROMPT_CALL_ACCEPT"); + + String callId = intent.getStringExtra("CALLID"); + String callDetails = intent.getStringExtra("CALL_DETAILS"); + + createAcceptDialog(callId, callDetails); + } else if (action.equals(AmbulanceForegroundService.BroadcastActions.PROMPT_CALL_END)) { + Log.i(TAG, "PROMPT_CALL_END"); + + createEndDialog(); + } + } + } + }; + /** * @param savedInstanceState */ @@ -102,6 +126,8 @@ protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); + + // Identifier text headerText = (TextView) findViewById(R.id.headerText); @@ -281,6 +307,8 @@ public void onReceive(Context context, Intent intent) { } + + } } }); @@ -333,6 +361,8 @@ public void onReceive(Context context, Intent intent) { tabLayout.getTabAt(1).setIcon(R.drawable.ic_hospital); tabLayout.getTabAt(2).setIcon(R.drawable.ic_globe); + + } public boolean canWrite() { @@ -457,6 +487,13 @@ public void onResume() { receiver = new LocationChangeBroadcastReceiver(); getLocalBroadcastManager().registerReceiver(receiver, filter); + Log.i(TAG, "Creating CallPromptReceiver"); + + IntentFilter promptFilter = new IntentFilter(); + promptFilter.addAction(AmbulanceForegroundService.BroadcastActions.PROMPT_CALL_ACCEPT); + promptFilter.addAction(AmbulanceForegroundService.BroadcastActions.PROMPT_CALL_END); + promptReceiver = new CallPromptReceiver(); + getLocalBroadcastManager().registerReceiver(promptReceiver, promptFilter); } @Override @@ -468,6 +505,12 @@ public void onPause() { getLocalBroadcastManager().unregisterReceiver(receiver); receiver = null; } + + // Unregister prompt receiver + if (promptReceiver != null) { + getLocalBroadcastManager().unregisterReceiver(promptReceiver); + promptReceiver = null; + } } public void panicPopUp() { @@ -501,4 +544,71 @@ private LocalBroadcastManager getLocalBroadcastManager() { return LocalBroadcastManager.getInstance(this); } + private void createAcceptDialog(final String callId, final String callDetails) { + + Log.i(TAG, "Creating accept dialog"); + + // Use the Builder class for convenient dialog construction + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setMessage(callDetails) + .setTitle("Accept Incoming Call?") + .setPositiveButton("Accept", new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + Toast.makeText(MainActivity.this, "Call accepted", Toast.LENGTH_SHORT).show(); + + Log.i(TAG, "Call accepted"); + + Intent serviceIntent = new Intent(MainActivity.this, AmbulanceForegroundService.class); + serviceIntent.setAction(AmbulanceForegroundService.Actions.CALL_ACCEPTED); + serviceIntent.putExtra("CALLID", callId); + startService(serviceIntent); + + } + }) + .setNegativeButton("Decline", new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + Toast.makeText(MainActivity.this, "Call declined", Toast.LENGTH_SHORT).show(); + + Log.i(TAG, "Call declined"); + + Intent serviceIntent = new Intent(MainActivity.this, AmbulanceForegroundService.class); + serviceIntent.setAction(AmbulanceForegroundService.Actions.CALL_DECLINED); + startService(serviceIntent); + } + }); + // Create the AlertDialog object and return it + builder.create().show(); + + } + + private void createEndDialog() { + + Log.i(TAG, "Creating end call dialog"); + + // Use the Builder class for convenient dialog construction + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle("Finish Current Call?") + .setPositiveButton("Accept", new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + Toast.makeText(MainActivity.this, "Call ending", Toast.LENGTH_SHORT).show(); + + Log.i(TAG, "Call ending"); + + Intent serviceIntent = new Intent(MainActivity.this, AmbulanceForegroundService.class); + serviceIntent.setAction(AmbulanceForegroundService.Actions.CALL_FINISHED); + startService(serviceIntent); + + } + }) + .setNegativeButton("Decline", new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + Toast.makeText(MainActivity.this, "Continuing call", Toast.LENGTH_SHORT).show(); + + Log.i(TAG, "Call not finished"); + } + }); + // Create the AlertDialog object and return it + builder.create().show(); + } + } \ No newline at end of file diff --git a/app/src/main/java/org/emstrack/ambulance/fragments/AmbulanceFragment.java b/app/src/main/java/org/emstrack/ambulance/fragments/AmbulanceFragment.java index 460e1a11..f46e9c29 100644 --- a/app/src/main/java/org/emstrack/ambulance/fragments/AmbulanceFragment.java +++ b/app/src/main/java/org/emstrack/ambulance/fragments/AmbulanceFragment.java @@ -146,14 +146,14 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa // TODO: REMOVE, JUST FOR TESTING // Add geofence - Log.i(TAG, "Adding geofence"); - Intent serviceIntent = new Intent(getActivity(), - AmbulanceForegroundService.class); - serviceIntent.setAction(AmbulanceForegroundService.Actions.GEOFENCE_START); - serviceIntent.putExtra("LATITUDE", (float) ambulance.getLocation().getLatitude()); - serviceIntent.putExtra("LONGITUDE", (float) ambulance.getLocation().getLongitude()); - serviceIntent.putExtra("RADIUS", 50.f); - getActivity().startService(serviceIntent); +// Log.i(TAG, "Adding geofence"); +// Intent serviceIntent = new Intent(getActivity(), +// AmbulanceForegroundService.class); +// serviceIntent.setAction(AmbulanceForegroundService.Actions.GEOFENCE_START); +// serviceIntent.putExtra("LATITUDE", (float) 32.881150); +// serviceIntent.putExtra("LONGITUDE", (float) -117.238200); +// serviceIntent.putExtra("RADIUS", 50.f); +// getActivity().startService(serviceIntent); } diff --git a/app/src/main/java/org/emstrack/ambulance/services/AmbulanceForegroundService.java b/app/src/main/java/org/emstrack/ambulance/services/AmbulanceForegroundService.java index f17d9f6a..e20a5089 100644 --- a/app/src/main/java/org/emstrack/ambulance/services/AmbulanceForegroundService.java +++ b/app/src/main/java/org/emstrack/ambulance/services/AmbulanceForegroundService.java @@ -1,11 +1,13 @@ package org.emstrack.ambulance.services; import android.annotation.SuppressLint; +import android.app.AlertDialog; import android.app.Notification; import android.app.NotificationChannel; import android.app.NotificationManager; import android.app.PendingIntent; import android.content.Context; +import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; import android.graphics.Bitmap; @@ -46,12 +48,15 @@ import org.eclipse.paho.client.mqttv3.MqttException; import org.eclipse.paho.client.mqttv3.MqttMessage; import org.emstrack.ambulance.LoginActivity; +import org.emstrack.ambulance.MainActivity; import org.emstrack.ambulance.R; +import org.emstrack.ambulance.fragments.AmbulanceFragment; import org.emstrack.ambulance.util.Geofence; import org.emstrack.ambulance.util.LocationFilter; import org.emstrack.ambulance.util.LocationUpdate; import org.emstrack.models.Ambulance; import org.emstrack.models.AmbulancePermission; +import org.emstrack.models.Call; import org.emstrack.models.Hospital; import org.emstrack.models.HospitalPermission; import org.emstrack.models.Location; @@ -65,9 +70,11 @@ import java.util.Date; import java.util.HashMap; import java.util.Iterator; +import java.util.LinkedList; import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Queue; import java.util.TimeZone; import java.util.UUID; import java.util.concurrent.atomic.AtomicInteger; @@ -133,6 +140,12 @@ public class AmbulanceForegroundService extends BroadcastService implements Mqt private GeofencingClient fenceClient; private PendingIntent geofenceIntent; + private String currCallId; + // is the user currently on a call + private boolean onCall = false; + // hold the callIds of pending calls + private Queue pendingCalls; + public class Actions { public final static String START_SERVICE = "org.emstrack.ambulance.ambulanceforegroundservice.action.START_SERVICE"; public final static String LOGIN = "org.emstrack.ambulance.ambulanceforegroundservice.action.LOGIN"; @@ -147,7 +160,13 @@ public class Actions { public final static String LOGOUT = "org.emstrack.ambulance.ambulanceforegroundservice.action.LOGOUT"; public final static String GEOFENCE_START = "org.emstrack.ambulance.ambulanceforegroundservice.action.GEOFENCE_START"; public final static String GEOFENCE_STOP = "org.emstrack.ambulance.ambulanceforegroundservice.action.GEOFENCE_STOP"; + public final static String GEOFENCE_ENTER = "org.emstrack.ambulance.ambulanceforegroundservice.action.GEOFENCE_ENTER"; + public final static String GEOFENCE_EXIT = "org.emstrack.ambulance.ambulanceforegroundservice.action.GEOFENCE_EXIT"; public final static String STOP_SERVICE = "org.emstrack.ambulance.ambulanceforegroundservice.action.STOP_SERVICE"; + public final static String CALL_ACCEPTED = "org.emstrack.ambulance.ambulanceforegroundservice.action.CALL_ACCEPTED"; + public final static String CALL_DECLINED = "org.emstrack.ambulance.ambulanceforegroundservice.action.CALL_DECLINED"; + public final static String CALL_ONGOING = "org.emstrack.ambulance.ambulanceforegroundservice.action.CALL_ONGOING"; + public final static String CALL_FINISHED = "org.emstrack.ambulance.ambulanceforegroundservice.action.CALL_FINISHED"; } public class BroadcastExtras { @@ -164,6 +183,8 @@ public class BroadcastActions { public final static String CONNECTIVITY_CHANGE = "org.emstrack.ambulance.ambulanceforegroundservice.broadcastaction.CONNECTIVITY_CHANGE"; public final static String SUCCESS = "org.emstrack.ambulance.ambulanceforegroundservice.broadcastaction.SUCCESS"; public final static String FAILURE = "org.emstrack.ambulance.ambulanceforegroundservice.broadcastaction.FAILURE"; + public final static String PROMPT_CALL_ACCEPT = "org.emstrack.ambulance.ambulanceforegroundservice.broadcastaction.PROMPT_CALL_ACCEPT"; + public final static String PROMPT_CALL_END = "org.emstrack.ambulance.ambulanceforegroundservice.broadcastaction.PROMPT_CALL_END"; } public static class ProfileClientException extends Exception { @@ -251,6 +272,8 @@ public void onCreate() { // Initialize geofence client fenceClient = LocationServices.getGeofencingClient(this); + // initialize list of pending calls + pendingCalls = new LinkedList(); } @Override @@ -294,7 +317,7 @@ public int onStartCommand(Intent intent, int flags, int startId) { stopServiceIntent.setAction(Actions.STOP_SERVICE); PendingIntent stopServicePendingIntent = PendingIntent.getService(AmbulanceForegroundService.this, 0, stopServiceIntent, 0); - + // Icon Bitmap icon = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher); @@ -513,6 +536,9 @@ public void onFailure(Bundle extras) { // stop requesting location updates stopLocationUpdates(uuid); + // force a call to end + finishCall(currCallId, uuid); + } else if (intent.getAction().equals(Actions.UPDATE_AMBULANCE)) { Log.i(TAG, "UPDATE_AMBULANCE Foreground Intent"); @@ -551,26 +577,112 @@ public void onFailure(Bundle extras) { Log.i(TAG, "GEOFENCE_START Foreground Intent"); // Retrieve latitude and longitude - // TODO: consider how this works with multiple geofences + boolean isHospital = intent.getBooleanExtra("GEOFENCE_TYPE", false); Float latitude = intent.getFloatExtra("LATITUDE", 0.f); Float longitude = intent.getFloatExtra("LONGITUDE", 0.f); - Float radius = intent.getFloatExtra("RADIUS", 5.f); + Float radius = intent.getFloatExtra("RADIUS", 50.f); - startGeofence(uuid, new Geofence(new Location(latitude, longitude), radius)); + startGeofence(uuid, new Geofence(new Location(latitude, longitude), radius, isHospital)); } else if (intent.getAction().equals(Actions.GEOFENCE_STOP)) { Log.i(TAG, "GEOFENCE_STOP Foreground Intent"); // Retrieve request ids - // TODO: consider how this works with mutliple geofences - // TODO: find somewhere to create intent to remove String requestId = intent.getStringExtra("REQUESTID"); List requestIds = new ArrayList(); requestIds.add(requestId); stopGeofence(uuid, requestIds); + } else if (intent.getAction().equals(Actions.CALL_ACCEPTED)) { + + Log.i(TAG, "CALL_ACCEPTED Foreground Intent"); + + // get the ambulance that accepted the call and the call id + String callId = intent.getStringExtra("CALLID"); + + // next steps to publish information to server (steps 3, 4) + replyToAcceptCall(callId, uuid); + + } else if (intent.getAction().equals(Actions.CALL_DECLINED)) { + + Log.i(TAG, "CALL_DECLINED Foreground Intent"); + + // get next call in queue and prompt user again + if (!pendingCalls.isEmpty()) { + + Log.i(TAG, "Prompting user for next call"); + + String nextCallId = pendingCalls.poll(); + subscribeToCall(nextCallId, uuid); + } else { + + Log.i(TAG, "No more pending calls"); + + onCall = false; + } + + } else if (intent.getAction().equals(Actions.CALL_ONGOING)) { + + Log.i(TAG, "CALL_ONGOING Foreground Intent"); + + // get the ambulance that has an ongoing call and the call id + String callId = intent.getStringExtra("CALLID"); + float latitude = intent.getFloatExtra("LATITUDE", 0.f); + float longitude = intent.getFloatExtra("LONGITUDE", 0.f); + + // next steps to publish information to server (step + replyToOngoingCall(callId, uuid, latitude, longitude); + + } else if (intent.getAction().equals(Actions.CALL_FINISHED)) { + + Log.i(TAG, "CALL_FINISHED Foreground Intent"); + + finishCall(currCallId, uuid); + + } else if (intent.getAction().equals(Actions.GEOFENCE_ENTER)) { + + Log.i(TAG, "GEOFENCE_ENTER Foreground Intent"); + + // get list of geofence ids that were entered + String[] triggeredGeofences = intent.getStringArrayExtra("TRIGGERED_GEOFENCES"); + + // check if the triggered geofences were hospitals or not + for (String geoId : triggeredGeofences) { + Geofence triggeredGeofence = _geofences.get(geoId); + + if (triggeredGeofence.isHospital()) { + replyToGeofenceTransitions(currCallId, uuid, true, true); + } else { + replyToGeofenceTransitions(currCallId, uuid, true, false); + } + } + + // true == entering geofence + //replyToGeofenceTransitions(currCallId, uuid, true); + + } else if (intent.getAction().equals(Actions.GEOFENCE_EXIT)) { + + Log.i(TAG, "GEOFENCE_EXIT Foreground Intent"); + + // get list of geofence ids that were exited + String[] triggeredGeofences = intent.getStringArrayExtra("TRIGGERED_GEOFENCES"); + + // check if the triggered geofences were hospitals or not + for (String geoId : triggeredGeofences) { + Geofence triggeredGeofence = _geofences.get(geoId); + + if (triggeredGeofence.isHospital()) { + replyToGeofenceTransitions(currCallId, uuid, false, true); + } else { + replyToGeofenceTransitions(currCallId, uuid, false, false); + } + } + + // false = not entering geofence + // replyToGeofenceTransitions(currCallId, uuid, false); + } else Log.i(TAG, "Unknown Intent"); @@ -1733,6 +1845,356 @@ public void messageArrived(String topic, MqttMessage message) { } + // subscribe to call stauses + try { + Log.i(TAG, "Subscribing to statuses"); + + final String clientId = profileClient.getClientId(); + + profileClient.subscribe(String.format("ambulance/%1$d/call/+/status", ambulanceId), + 2, new MqttProfileMessageCallback() { + @Override + public void messageArrived(String topic, MqttMessage message) { + + // Keep subscription to calls to make sure we receive latest updates + + Log.i(TAG, "Retrieving stauses"); + + try { + // parse the status + String status = new String(message.getPayload()); + Log.i(TAG, "payload is: " + status); + + // check if message is "requested" + // literally has to be "requested" in quotes + if (status.equalsIgnoreCase("\"requested\"")) { + // retrieve call id + Log.i(TAG, "STATUS MESSAGE TOPIC: " + topic); + + // the call id is the 3rd value + final String callId = topic.split("/")[3]; + + if (!onCall) { + onCall = true; + + // subscribe to call data then prompt user to accept + subscribeToCall(callId, uuid); + + } else { + // add call id to pendingCalls + pendingCalls.add(callId); + } + + Log.i(TAG, "STATUS CALL ID: " + callId); + + } else { + Log.i(TAG, "DID NOT GET REQUESTED STATUS"); + } + + + } catch (Exception e) { + + Log.i(TAG, "Could not parse status"); + + // Broadcast failure + Intent localIntent = new Intent(BroadcastActions.FAILURE); + localIntent.putExtra(BroadcastExtras.MESSAGE, getString(R.string.couldNotParseStatus)); + sendBroadcastWithUUID(localIntent, uuid); + } + } + }); + } catch (MqttException e) { + + Log.d(TAG, "Could not subscribe to statuses"); + + // Broadcast failure + Intent localIntent = new Intent(BroadcastActions.FAILURE); + localIntent.putExtra(BroadcastExtras.MESSAGE, getString(R.string.couldNotSubscribeToStatuses)); + sendBroadcastWithUUID(localIntent, uuid); + } + + } + + public void subscribeToCall(final String callId, final String uuid) { + + final MqttProfileClient profileClient = getProfileClient(this); + + currCallId = callId; + + try { + + Log.i(TAG, "Subscribing to call data"); + + profileClient.subscribe(String.format("call/%1$s/data", callId), + 2, new MqttProfileMessageCallback() { + @Override + public void messageArrived(String topic, MqttMessage message) { + + // Keep subscription to calls to make sure we receive latest updates + + Log.i(TAG, "Retrieving call data"); + + // parse call id data + GsonBuilder gsonBuilder = new GsonBuilder(); + gsonBuilder.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES); + Gson gson = gsonBuilder.create(); + + try { + + // Parse and set call id data + Call call = gson + .fromJson(new String(message.getPayload()), + Call.class); + + String callDetails = String.format("Priority: %1$s\n" + + "%2$s %3$s, %4$s %5$s", call.getPriority(), + call.getStreet(), call.getNumber(), call.getCity(), + call.getZipcode()); + + // create intent to prompt user + Intent callPromptIntent = new Intent(BroadcastActions.PROMPT_CALL_ACCEPT); + callPromptIntent.putExtra("CALLID", callId); + callPromptIntent.putExtra("CALL_DETAILS", callDetails); + callPromptIntent.putExtra("LATITUDE", (float) call.getLocation().getLatitude()); + callPromptIntent.putExtra("LONGITUDE", (float) call.getLocation().getLongitude()); + sendBroadcastWithUUID(callPromptIntent, uuid); + + // used to skip asking user to accept call +// Intent localIntent = new Intent(AmbulanceForegroundService.this, +// AmbulanceForegroundService.class); +// localIntent.setAction(Actions.CALL_ACCEPTED); +// localIntent.putExtra("CALLID", callId); +// startService(localIntent); + + } catch (Exception e) { + + Log.i(TAG, "Could not parse call id update."); + + // Broadcast failure + Intent localIntent = new Intent(BroadcastActions.FAILURE); + localIntent.putExtra(BroadcastExtras.MESSAGE, getString(R.string.couldNotParseCallData)); + sendBroadcastWithUUID(localIntent, uuid); + + } + } + }); + } catch (MqttException e) { + + Log.d(TAG, "Could not subscribe to call id data"); + + // Broadcast failure + Intent localIntent = new Intent(BroadcastActions.FAILURE); + localIntent.putExtra(BroadcastExtras.MESSAGE, getString(R.string.couldNotSubscribe, "data for call " + callId)); + sendBroadcastWithUUID(localIntent, uuid); + } + } + + public void publishToPath(final String payload, final String path, final String uuid) { + + MqttProfileClient profileClient = getProfileClient(this); + + try { + + Log.i(TAG, "publishing " + payload + " to " + path); + + profileClient.publish(path, payload, 2, false); + + } catch (MqttException e) { + + Log.d(TAG, path); + + Intent localIntent = new Intent(BroadcastActions.FAILURE); + localIntent.putExtra(BroadcastExtras.MESSAGE, getString(R.string.couldNotPublish, path)); + sendBroadcastWithUUID(localIntent, uuid); + } + } + + // handles steps 3 and 4 of Accepting Calls + public void replyToAcceptCall(final String callId, String uuid) { + Log.i(TAG, "Replying to server with username, client id, and call id"); + + MqttProfileClient profileClient = getProfileClient(AmbulanceForegroundService.this); + + Ambulance ambulance = getAmbulance(); + + if (ambulance != null) { + + String path = String.format("user/%1$s/client/%2$s/ambulance/%3$s/data", + profileClient.getUsername(), profileClient.getClientId(), callId); + + String payload = String.format("{\"location_client\": %1$s}", profileClient.getClientId()); + + // step 3: publish client id to server + publishToPath(payload, path, uuid); + + // start streaming location + startLocationUpdates(uuid, false); + + // step 4: publish accepted to server + path = String.format("user/%1$s/client/%2$s/" + + "ambulance/%3$s/call/%4$s/status", + profileClient.getUsername(), profileClient.getClientId(), ambulance.getId(), callId); + + // publish "Accepted" to server + publishToPath("\"Accepted\"", path, uuid); + + try { + profileClient.subscribe(String.format("ambulance/%1$d/call/%2$s/status", + ambulance.getId(), callId), + 2, new MqttProfileMessageCallback() { + @Override + public void messageArrived(String topic, MqttMessage message) { + + String status = new String(message.getPayload()); + + if (status.equalsIgnoreCase("\"Ongoing\"")) { + + Log.i(TAG, "Received ongoing"); + + // step 5 + Intent localIntent = new Intent(AmbulanceForegroundService.this, + AmbulanceForegroundService.class); + localIntent.setAction(Actions.CALL_ONGOING); + localIntent.putExtra("CALLID", callId); + startService(localIntent); + } + } + }); + } catch (MqttException e) { + + String subscribePath = String.format("Could not subscribe to ambulance/%1$s/call/%2$s/status", + ambulance.getId(), callId); + + Log.d(TAG, subscribePath); + + Intent localIntent = new Intent(BroadcastActions.FAILURE); + localIntent.putExtra(BroadcastExtras.MESSAGE, path); + sendBroadcastWithUUID(localIntent, uuid); + } + + } else { + Log.d(TAG, "Ambulance not found while in replyToAcceptCall()"); + + Intent localIntent = new Intent(BroadcastActions.FAILURE); + localIntent.putExtra(BroadcastExtras.MESSAGE, getString(R.string.couldNotFindAmbulance)); + sendBroadcastWithUUID(localIntent, uuid); + } + } + + // handles steps 6 to 7 + public void replyToOngoingCall(final String callId, final String uuid, float latitude, float longitude) { + // step 6 & 7 + Log.i(TAG, "Replying to server with username, client id, and call id"); + + // TODO: ask Mauricio if we need to check if online or not + final MqttProfileClient profileClient = getProfileClient(AmbulanceForegroundService.this); + + final Ambulance ambulance = getAmbulance(); + + // step 6 + // subscribe to call id + // Add geofence + Log.i(TAG, "Adding geofence"); + Intent serviceIntent = new Intent(AmbulanceForegroundService.this, + AmbulanceForegroundService.class); + serviceIntent.setAction(AmbulanceForegroundService.Actions.GEOFENCE_START); + serviceIntent.putExtra("GEOFENCE_TYPE", false); + serviceIntent.putExtra("LATITUDE", latitude); + serviceIntent.putExtra("LONGITUDE", longitude); + serviceIntent.putExtra("RADIUS", 50.f); + startService(serviceIntent); + + // step 7 + if (ambulance != null) { + + String path = String.format("user/%1$s/client/%2$s/ambulance/%3$s/data", + profileClient.getUsername(), profileClient.getClientId(), callId); + + // step 7: publish patient bound to server + publishToPath("patient bound", path, uuid); + + } else { + + Log.d(TAG, "Ambulance not found while in replyToOngoingCall()"); + + Intent localIntent = new Intent(BroadcastActions.FAILURE); + localIntent.putExtra(BroadcastExtras.MESSAGE, getString(R.string.couldNotFindAmbulance)); + sendBroadcastWithUUID(localIntent, uuid); + } + } + + /* + ** + */ + public void replyToGeofenceTransitions(String callId, String uuid, boolean enter, boolean isHospital) { + final MqttProfileClient profileClient = getProfileClient(AmbulanceForegroundService.this); + + final Ambulance ambulance = getAmbulance(); + + // step 7 + if (ambulance != null) { + + if (!isHospital) { + + String path = String.format("user/%1$s/client/%2$s/ambulance/%3$s/data", + profileClient.getUsername(), profileClient.getClientId(), callId); + + String payload = enter ? "at patient" : "hospital bound"; + + Log.i(TAG, "user status: " + payload); + + // step 8: publish other status updates to server + publishToPath(payload, path, uuid); + } else { + + if (enter) { + + Log.i(TAG, "User has entered hospital"); + + // create intent to prompt user to end call + Intent callPromptIntent = new Intent(BroadcastActions.PROMPT_CALL_END); + callPromptIntent.putExtra("CALLID", callId); + sendBroadcastWithUUID(callPromptIntent, uuid); + } else { + // user is leaving the hospital + + Log.i(TAG, "User is leaving hospital"); + } + } + + } else { + Log.d(TAG, "Ambulance not found while in replyToTransition()"); + + Intent localIntent = new Intent(BroadcastActions.FAILURE); + localIntent.putExtra(BroadcastExtras.MESSAGE, getString(R.string.couldNotFindAmbulance)); + sendBroadcastWithUUID(localIntent, uuid); + } + } + + public void finishCall(final String callId, final String uuid) { + Log.i(TAG, "Ending call"); + + final MqttProfileClient profileClient = getProfileClient(AmbulanceForegroundService.this); + + final Ambulance ambulance = getAmbulance(); + + if (ambulance != null) { + + String path = String.format("user/%1$s/client/%2$s/ambulance/%3$s/call" + + "/%4$s/status", + profileClient.getUsername(), profileClient.getClientId(), ambulance.getId(), + callId); + + // step 9: publish "Finished" to server + publishToPath("\"Finished\"", path, uuid); + + } else { + Log.d(TAG, "Ambulance not found while in finishCall()"); + + Intent localIntent = new Intent(BroadcastActions.FAILURE); + localIntent.putExtra(BroadcastExtras.MESSAGE, getString(R.string.couldNotFindAmbulance)); + sendBroadcastWithUUID(localIntent, uuid); + } } /** @@ -1779,6 +2241,8 @@ public void removeAmbulance(boolean reconnect) { // Unsubscribe to ambulance data profileClient.unsubscribe("ambulance/" + ambulanceId + "/data"); + // TODO: unsubscribe from statuses and call data + } catch (MqttException exception) { Log.d(TAG, "Could not unsubscribe to 'ambulance/" + ambulanceId + "/data'"); } @@ -2454,6 +2918,7 @@ private PendingIntent getGeofencePendingIntent() { return geofenceIntent; } + private void startGeofence(final String uuid, final Geofence geofence) { Log.d(TAG,String.format("GEOFENCE(%1$s, %2$f)", geofence.getLocation().toString(), geofence.getRadius())); @@ -2475,7 +2940,8 @@ public void onSuccess(LocationSettingsResponse locationSettingsResponse) { Log.i(TAG, "Adding GEOFENCE."); - fenceClient.addGeofences(getGeofencingRequest(geofence.build(id)), getGeofencePendingIntent()) + fenceClient.addGeofences(getGeofencingRequest(geofence.build(id)), + getGeofencePendingIntent()) .addOnSuccessListener(new OnSuccessListener() { @Override @@ -2541,11 +3007,10 @@ private void removeGeofences(final String uuid) { .addOnSuccessListener(new OnSuccessListener() { @Override public void onSuccess(Void aVoid) { - // Geofences removed Log.i(TAG, "GEOFENCES REMOVED."); - // Clear all ids + // Clear all geofence ids _geofences.clear(); // Broadcast success diff --git a/app/src/main/java/org/emstrack/ambulance/services/GeofenceBroadcastReceiver.java b/app/src/main/java/org/emstrack/ambulance/services/GeofenceBroadcastReceiver.java index db960149..6bd3d715 100644 --- a/app/src/main/java/org/emstrack/ambulance/services/GeofenceBroadcastReceiver.java +++ b/app/src/main/java/org/emstrack/ambulance/services/GeofenceBroadcastReceiver.java @@ -9,7 +9,14 @@ import com.google.android.gms.location.Geofence; import com.google.android.gms.location.GeofencingEvent; +import org.eclipse.paho.client.mqttv3.MqttException; +import org.emstrack.ambulance.R; +import org.emstrack.ambulance.fragments.AmbulanceFragment; import org.emstrack.models.Ambulance; +import org.emstrack.mqtt.MqttProfileClient; + +import java.util.ArrayList; +import java.util.List; public class GeofenceBroadcastReceiver extends BroadcastReceiver { @@ -40,10 +47,39 @@ public void onReceive(Context context, Intent intent) { // Get the transition type. int geofenceTransition = geofencingEvent.getGeofenceTransition(); + List triggeringGeofences = geofencingEvent.getTriggeringGeofences(); + List geofenceIds = new ArrayList(); + + if (triggeringGeofences != null) { + for (Geofence geofence : triggeringGeofences) { + + String geoId = geofence.getRequestId(); + geofenceIds.add(geoId); + + Log.i(TAG, "TRIGGERED GEOFENCE: " + geoId); + } + } + + String[] triggeredGeofences = geofenceIds.toArray(new String[geofenceIds.size()]); + if (geofenceTransition == Geofence.GEOFENCE_TRANSITION_ENTER || geofenceTransition == Geofence.GEOFENCE_TRANSITION_EXIT) { if (geofenceTransition == Geofence.GEOFENCE_TRANSITION_ENTER) { + + // broadcast change on ambulance status when it enters geofence + Intent localIntent = new Intent(context, AmbulanceForegroundService.class); + localIntent.setAction(AmbulanceForegroundService.Actions.GEOFENCE_ENTER); + localIntent.putExtra("TRIGGERED_GEOFENCES", triggeredGeofences); + context.startService(localIntent); + Log.i(TAG, "GEOFENCE_TRIGGERED: ENTER"); } else if (geofenceTransition == Geofence.GEOFENCE_TRANSITION_EXIT) { + + // broadcast change on ambulance status when it exits geofence + Intent localIntent = new Intent(context, AmbulanceForegroundService.class); + localIntent.setAction(AmbulanceForegroundService.Actions.GEOFENCE_EXIT); + localIntent.putExtra("TRIGGERED_GEOFENCES", triggeredGeofences); + context.startService(localIntent); + Log.i(TAG, "GEOFENCE_TRIGGERED: EXIT"); } diff --git a/app/src/main/java/org/emstrack/ambulance/util/Geofence.java b/app/src/main/java/org/emstrack/ambulance/util/Geofence.java index 5da8f1e4..26e422a3 100644 --- a/app/src/main/java/org/emstrack/ambulance/util/Geofence.java +++ b/app/src/main/java/org/emstrack/ambulance/util/Geofence.java @@ -14,16 +14,20 @@ public class Geofence { Location location; float radius; + boolean isHospital; - public Geofence(Location location, float radius) { + public Geofence(Location location, float radius, boolean isHospital) { this.location = location; this.radius = radius; + this.isHospital = isHospital; } public Location getLocation() { return location; } public float getRadius() { return radius; } + public boolean isHospital() { return isHospital; } + public com.google.android.gms.location.Geofence build(String id) { return build(id, GEOFENCE_TRANSITION_ENTER | GEOFENCE_TRANSITION_EXIT); } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 24e92049..d67bb836 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -131,6 +131,14 @@ About: Failed to add geofence Failed to remove geofence. + Could not subscribe to calls + Could not parse call + Could not subscribe to statuses + Could not parse status + Could not publish to %1$s + Could not subscribe to %1$s + Could not find ambulance + Could not parse call data @string/dispatch diff --git a/models/src/main/java/org/emstrack/models/Call.java b/models/src/main/java/org/emstrack/models/Call.java new file mode 100644 index 00000000..aa2054cb --- /dev/null +++ b/models/src/main/java/org/emstrack/models/Call.java @@ -0,0 +1,213 @@ +package org.emstrack.models; + +/** + * Created by Leon on 5/8/2018. + */ + +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class Call { + + private Integer id; + private String status; + private String details; + private String priority; + private String number; + private String street; + private String unit; + private String neighborhood; + private String city; + private String state; + private String zipcode; + private String country; + private Location location; + private String createdAt; + private Date pendingAt; + private Date startedAt; + private Date endedAt; + private Date comment; + private Integer updatedBy; + private String updatedOn; + private List ambulancecallSet = null; + private List patientSet = null; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getStatus() { + return status; + } + + public void setStatus(String status) { + this.status = status; + } + + public String getDetails() { + return details; + } + + public void setDetails(String details) { + this.details = details; + } + + public String getPriority() { + return priority; + } + + public void setPriority(String priority) { + this.priority = priority; + } + + public String getNumber() { + return number; + } + + public void setNumber(String number) { + this.number = number; + } + + public String getStreet() { + return street; + } + + public void setStreet(String street) { + this.street = street; + } + + public Object getUnit() { + return unit; + } + + public void setUnit(String unit) { + this.unit = unit; + } + + public Object getNeighborhood() { + return neighborhood; + } + + public void setNeighborhood(String neighborhood) { + this.neighborhood = neighborhood; + } + + public String getCity() { + return city; + } + + public void setCity(String city) { + this.city = city; + } + + public String getState() { + return state; + } + + public void setState(String state) { + this.state = state; + } + + public String getZipcode() { + return zipcode; + } + + public void setZipcode(String zipcode) { + this.zipcode = zipcode; + } + + public String getCountry() { + return country; + } + + public void setCountry(String country) { + this.country = country; + } + + public Location getLocation() { + return location; + } + + public void setLocation(Location location) { + this.location = location; + } + + public String getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(String createdAt) { + this.createdAt = createdAt; + } + + public Object getPendingAt() { + return pendingAt; + } + + public void setPendingAt(Date pendingAt) { + this.pendingAt = pendingAt; + } + + public Object getStartedAt() { + return startedAt; + } + + public void setStartedAt(Date startedAt) { + this.startedAt = startedAt; + } + + public Object getEndedAt() { + return endedAt; + } + + public void setEndedAt(Date endedAt) { + this.endedAt = endedAt; + } + + public Object getComment() { + return comment; + } + + public void setComment(Date comment) { + this.comment = comment; + } + + public Integer getUpdatedBy() { + return updatedBy; + } + + public void setUpdatedBy(Integer updatedBy) { + this.updatedBy = updatedBy; + } + + public String getUpdatedOn() { + return updatedOn; + } + + public void setUpdatedOn(String updatedOn) { + this.updatedOn = updatedOn; + } + + public List getAmbulancecallSet() { + return ambulancecallSet; + } + + public void setAmbulancecallSet(List ambulancecallSet) { + this.ambulancecallSet = ambulancecallSet; + } + + public List getPatientSet() { + return patientSet; + } + + public void setPatientSet(List patientSet) { + this.patientSet = patientSet; + } + +} diff --git a/models/src/test/java/org/emstrack/models/ModelsUnitTest.java b/models/src/test/java/org/emstrack/models/ModelsUnitTest.java index 5b14448f..822e6da6 100644 --- a/models/src/test/java/org/emstrack/models/ModelsUnitTest.java +++ b/models/src/test/java/org/emstrack/models/ModelsUnitTest.java @@ -583,4 +583,44 @@ public void test_hospital() throws Exception { } } + + @Test + public void test_call() throws Exception { + + double epsilon = 1e-4; + + Call call = new Call(); + + Gson gson = new Gson(); + + String to_json = gson.toJson(call); + + Call from_json = gson.fromJson(to_json, Call.class); + System.out.println("to_json = " + to_json + "'"); + + Integer expectedId = call.getId(); + Integer answerId = from_json.getId(); + assertEquals(expectedId, answerId); + + String expectedString = call.getCity(); + String answerString = from_json.getCity(); + assertEquals(expectedString, answerString); + + expectedString = call.getDetails(); + answerString = from_json.getDetails(); + assertEquals(expectedString, answerString); + + //expectedString = call.get(); + answerString = from_json.getCity(); + assertEquals(expectedString, answerString); + + expectedString = call.getCountry(); + answerString = from_json.getCity(); + assertEquals(expectedString, answerString); + + expectedString = call.getCountry(); + answerString = from_json.getCity(); + assertEquals(expectedString, answerString); + + } }