diff --git a/app/build.gradle b/app/build.gradle
index f024ae8a..25d5ee8b 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -4,12 +4,18 @@ android {
compileSdkVersion 27
buildToolsVersion '27.0.3'
defaultConfig {
- applicationId 'org.emstrack.hospital'
- minSdkVersion 16
+ applicationId 'org.emstrack.ambulance'
+ minSdkVersion 17
targetSdkVersion 27
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+ resValue "string", "google_maps_key",
+ (project.findProperty("GOOGLE_MAPS_API_KEY") ?: "")
+ vectorDrawables.useSupportLibrary = true
+
+ // add build timestamp
+ buildConfigField "long", "TIMESTAMP", System.currentTimeMillis() + "L"
}
buildTypes {
debug {
@@ -34,15 +40,27 @@ dependencies {
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
})
- compile 'com.android.support:appcompat-v7:27.0.2'
+
+ compile 'com.google.android.gms:play-services-location:15.0.0'
+ compile 'com.google.android.gms:play-services-maps:15.0.0'
+ compile 'com.google.android.gms:play-services-gcm:15.0.0'
+
compile 'com.android.support.constraint:constraint-layout:1.0.2'
compile 'com.android.volley:volley:1.0.0'
+
compile 'com.google.code.gson:gson:2.8.0'
+
compile 'org.eclipse.paho:org.eclipse.paho.client.mqttv3:1.1.0'
compile 'org.eclipse.paho:org.eclipse.paho.android.service:1.1.1'
- compile 'com.android.support:recyclerview-v7:27.0.2'
- compile 'com.android.support:support-v4:27.0.2'
- compile 'com.daimajia.swipelayout:library:1.2.0@aar'
+
+ compile 'com.android.support:cardview-v7:27.1.0'
+ compile 'com.android.support:recyclerview-v7:27.1.0'
+ compile 'com.android.support:support-v4:27.1.0'
+ compile 'com.android.support:appcompat-v7:27.1.0'
+ compile 'com.android.support:design:27.1.0'
+
+ // compile 'com.daimajia.swipelayout:library:1.2.0@aar'
+ compile 'com.thoughtbot:expandablerecyclerview:1.3'
compile project(':models')
compile project(':mqtt')
diff --git a/app/src/androidTest/java/org/emstrack/hospital/ExampleInstrumentedTest.java b/app/src/androidTest/java/org/emstrack/ambulance/ExampleInstrumentedTest.java
similarity index 95%
rename from app/src/androidTest/java/org/emstrack/hospital/ExampleInstrumentedTest.java
rename to app/src/androidTest/java/org/emstrack/ambulance/ExampleInstrumentedTest.java
index 85a1a14e..d7e6d542 100644
--- a/app/src/androidTest/java/org/emstrack/hospital/ExampleInstrumentedTest.java
+++ b/app/src/androidTest/java/org/emstrack/ambulance/ExampleInstrumentedTest.java
@@ -1,4 +1,4 @@
-package org.emstrack.hospital;
+package org.emstrack.ambulance;
import android.content.Context;
import android.support.test.InstrumentationRegistry;
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index b94432a0..2d5911cf 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -1,20 +1,24 @@
+ package="org.emstrack.ambulance">
+
+
+
+ android:launchMode="singleTop"
+ android:theme="@style/AppTheme">
+
-
@@ -22,15 +26,43 @@
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/org/emstrack/ambulance/AmbulanceApp.java b/app/src/main/java/org/emstrack/ambulance/AmbulanceApp.java
new file mode 100644
index 00000000..837bee6a
--- /dev/null
+++ b/app/src/main/java/org/emstrack/ambulance/AmbulanceApp.java
@@ -0,0 +1,11 @@
+package org.emstrack.ambulance;
+
+import android.app.Application;
+
+/**
+ * Created by mauricio on 2/10/2018.
+ */
+
+public class AmbulanceApp extends Application {
+
+}
diff --git a/app/src/main/java/org/emstrack/ambulance/DispatcherCall.java b/app/src/main/java/org/emstrack/ambulance/DispatcherCall.java
new file mode 100644
index 00000000..789184ea
--- /dev/null
+++ b/app/src/main/java/org/emstrack/ambulance/DispatcherCall.java
@@ -0,0 +1,69 @@
+package org.emstrack.ambulance;
+
+import android.util.Log;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import static android.content.ContentValues.TAG;
+
+/**
+ * Created by sinan on 6/6/2017.
+ *
+ * This class is meant to create a call object that is sent from the dispatcher. It will contain
+ * a lot of information, so having it in its own separate class can save time.
+ */
+
+public class DispatcherCall {
+ String callString;
+ String address1 = "No Address Received";
+ static String latitude = "32.879409";
+ static String longitude = "-117.2382162";
+ String description = "No description received";
+
+ DispatcherCall(JSONObject c) {
+ try {
+ //GET EVERY SINGLE PART OF THE JSON OUT THE WAY
+ int id = c.getInt("id");
+ latitude = c.getString("latitude");
+ longitude = c.getString("longitude");
+ String name = c.getString("name");
+ int resU = c.getInt("residential_unit");
+ int stmain_number = c.getInt("stmain_number");
+ int delegation = c.getInt("delegation");
+ int zipcode = c.getInt("zipcode");
+ String city = c.getString("city");
+ String state = c.getString("state");
+ String assignment = c.getString("assignment");
+ description = c.getString("description");
+ String call_time = c.getString("call_time");
+ String departure_time = c.getString("departure_time");
+ String transfer_time = c.getString("transfer_time");
+ String hospital_time = c.getString("hospital_time");
+ String base_time = c.getString("base_time");
+ int amb = c.getInt("ambulance");
+
+ //format to save the address. This is temporary and might be changed based on server team
+ address1 = stmain_number + " street name pending " + "#" + resU + " " + city + " " + state + " " + zipcode;
+
+ //LOG EVERYTHING JUST TO MAKE SURE OF THIS
+ Log.d(TAG, "Call message received: and address is " + address1);
+ }
+ catch (JSONException e) { Log.d(TAG, "json error"); }
+
+ //save the call
+ callString = c.toString();
+ }
+
+ String getAddress() {
+ return address1;
+ }
+
+ public static String getLong() {
+ return longitude;
+ }
+
+ public static String getLatitude() {
+ return latitude;
+ }
+
+}
diff --git a/app/src/main/java/org/emstrack/ambulance/LoginActivity.java b/app/src/main/java/org/emstrack/ambulance/LoginActivity.java
new file mode 100644
index 00000000..292b350a
--- /dev/null
+++ b/app/src/main/java/org/emstrack/ambulance/LoginActivity.java
@@ -0,0 +1,488 @@
+package org.emstrack.ambulance;
+
+import android.Manifest;
+import android.annotation.SuppressLint;
+import android.app.Activity;
+import android.app.TaskStackBuilder;
+import android.content.Intent;
+import android.content.IntentSender;
+import android.content.SharedPreferences;
+import android.content.pm.PackageManager;
+import android.net.Uri;
+import android.provider.Settings;
+import android.support.annotation.NonNull;
+import android.support.v4.app.ActivityCompat;
+import android.support.v7.app.AppCompatActivity;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.Button;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.google.android.gms.common.api.ApiException;
+import com.google.android.gms.common.api.ResolvableApiException;
+import com.google.android.gms.location.LocationServices;
+import com.google.android.gms.location.LocationSettingsResponse;
+import com.google.android.gms.location.LocationSettingsStatusCodes;
+import com.google.android.gms.location.SettingsClient;
+import com.google.android.gms.tasks.OnFailureListener;
+import com.google.android.gms.tasks.OnSuccessListener;
+
+import org.emstrack.ambulance.dialogs.AlertSnackbar;
+import org.emstrack.ambulance.services.AmbulanceForegroundService;
+import org.emstrack.ambulance.services.OnServiceComplete;
+import org.emstrack.models.Ambulance;
+import org.emstrack.models.AmbulancePermission;
+import org.emstrack.models.Profile;
+import org.emstrack.mqtt.MqttProfileCallback;
+import org.emstrack.mqtt.MqttProfileClient;
+
+import java.util.List;
+
+public class LoginActivity extends AppCompatActivity implements View.OnClickListener {
+
+ private static final String TAG = LoginActivity.class.getSimpleName();
+
+ public static final String LOGOUT = "org.emstrack.ambulance.LoginActivity.LOGOUT";
+
+ private static final int REQUEST_PERMISSIONS_REQUEST_CODE = 34;
+ private static final int REQUEST_CHECK_SETTINGS = 0x1;
+
+ private SharedPreferences sharedPreferences;
+ private Button loginSubmitButton;
+ private TextView usernameField;
+ private TextView passwordField;
+ private boolean logout;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_login);
+
+ Log.d(TAG, "onCreate");
+
+ // get action
+ String action = getIntent().getAction();
+ logout = LOGOUT.equals(action);
+
+ // Find username and password from layout
+ usernameField = (TextView) findViewById(R.id.editUserName);
+ passwordField = (TextView) findViewById(R.id.editPassword);
+
+ // Retrieving credentials
+ sharedPreferences = getSharedPreferences(AmbulanceForegroundService.PREFERENCES_NAME, MODE_PRIVATE);
+
+ // Retrieve past credentials
+ usernameField.setText(sharedPreferences.getString(AmbulanceForegroundService.PREFERENCES_USERNAME, null));
+ passwordField.setText(sharedPreferences.getString(AmbulanceForegroundService.PREFERENCES_PASSWORD, null));
+
+ // Submit button
+ loginSubmitButton = (Button) findViewById(R.id.buttonLogin);
+
+ // allow keyboard to disappear on screen click
+ findViewById(R.id.relativeLayout).setOnTouchListener(new View.OnTouchListener() {
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ InputMethodManager imm = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
+ imm.hideSoftInputFromWindow(getCurrentFocus().getWindowToken(), 0);
+ return true;
+ }
+ });
+
+ }
+
+ public void disableLogin() {
+
+ // Disable login
+ loginSubmitButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+
+ // Toast to warn about check permissions
+ Toast.makeText(LoginActivity.this, "Please be patient. Checking permissions...", Toast.LENGTH_LONG).show();
+
+ }
+ });
+
+ }
+
+ public void enableLogin() {
+
+ Log.d(TAG, "enableLogin");
+
+ // Enable login
+ loginSubmitButton.setOnClickListener(this);
+
+ // Logout first?
+ if (logout) {
+
+ Log.d(TAG,"Logout first.");
+
+ // Stop foreground activity
+ Intent intent = new Intent(this, AmbulanceForegroundService.class);
+ intent.setAction(AmbulanceForegroundService.Actions.STOP_SERVICE);
+
+ // What to do when service completes?
+ new OnServiceComplete(this,
+ AmbulanceForegroundService.BroadcastActions.SUCCESS,
+ AmbulanceForegroundService.BroadcastActions.FAILURE,
+ intent) {
+
+ @Override
+ public void onSuccess(Bundle extras) {
+ Log.i(TAG, "onSuccess");
+
+ // Set logout to false
+ logout = false;
+
+ // Initialize service to make sure it gets bound to service
+ Intent intent = new Intent(LoginActivity.this, AmbulanceForegroundService.class);
+ intent.putExtra("ADD_STOP_ACTION", true);
+ intent.setAction(AmbulanceForegroundService.Actions.START_SERVICE);
+ startService(intent);
+
+ // TODO: is this safe to do asynchronously?
+
+ }
+
+ }
+ .setFailureMessage(this.getString(R.string.couldNotLogout))
+ .setAlert(new AlertSnackbar(this));
+
+ } else {
+
+ try {
+
+ // Already logged in?
+ final MqttProfileClient profileClient = AmbulanceForegroundService.getProfileClient();
+ Profile profile = profileClient.getProfile();
+ if (profile != null) {
+
+ // Get user info & remove whitespace
+ final String username = usernameField.getText().toString().trim();
+
+ // Create intent
+ Intent intent = new Intent(LoginActivity.this,
+ MainActivity.class);
+
+ /*
+ Ambulance ambulance = AmbulanceForegroundService.getAmbulance();
+ if (ambulance != null) {
+
+ Log.i(TAG, "Already logged in with ambulance");
+
+ intent.putExtra("SKIP_AMBULANCE_SELECTION", true);
+
+ } else {
+
+ Log.i(TAG, "Already logged in without ambulance");
+
+ }
+ */
+
+ Log.i(TAG, "Starting MainActivity");
+
+ // Toast
+ Toast.makeText(LoginActivity.this,
+ getResources().getString(R.string.loginSuccessMessage, username),
+ Toast.LENGTH_SHORT).show();
+
+ // initiate MainActivity
+ startActivity(intent);
+
+ return;
+ }
+
+ } catch (AmbulanceForegroundService.ProfileClientException e) {
+
+ // Initialize service to make sure it gets bound to service
+ Intent intent = new Intent(this, AmbulanceForegroundService.class);
+ intent.putExtra("ADD_STOP_ACTION", true);
+ intent.setAction(AmbulanceForegroundService.Actions.START_SERVICE);
+ startService(intent);
+
+ // TODO: is this safe to do asynchronously?
+
+ }
+
+ }
+
+ if (AmbulanceForegroundService.canUpdateLocation()) {
+
+ // Toast to warn about check permissions
+ Toast.makeText(LoginActivity.this, "Location permissions satisfied.", Toast.LENGTH_LONG).show();
+
+ } else {
+
+ // Alert to warn about check permissions
+ new AlertSnackbar(LoginActivity.this)
+ .alert("Location permissions not satisfied. Expect limited functionality.");
+
+ }
+
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+
+ Log.d(TAG, "onResume");
+
+ // Disable login
+ disableLogin();
+
+ // Can update?
+ if (AmbulanceForegroundService.canUpdateLocation()) {
+
+ // Enable login
+ enableLogin();
+
+ // Check and request permissions to retrieve locations if necessary
+ } else if (checkPermissions()) {
+
+ // Otherwise check
+ checkLocationSettings();
+
+ } else {
+
+ // requestPermissions calls checkLocationSettings if successful
+ requestPermissions();
+
+ }
+
+ }
+
+ @Override
+ public void onClick(View view) {
+
+ // Get user info & remove whitespace
+ final String username = usernameField.getText().toString().trim();
+ final String password = passwordField.getText().toString().trim();
+
+ if (username.isEmpty())
+ new AlertSnackbar(LoginActivity.this).alert(getResources().getString(R.string.error_empty_username));
+
+ else if (password.isEmpty())
+ new AlertSnackbar(LoginActivity.this).alert(getResources().getString(R.string.error_empty_password));
+
+ else {
+
+ Log.d(TAG, "Will offer credentials");
+
+ // Login at service
+ Intent intent = new Intent(LoginActivity.this, AmbulanceForegroundService.class);
+ intent.setAction(AmbulanceForegroundService.Actions.LOGIN);
+ intent.putExtra("CREDENTIALS", new String[]{username, password});
+
+ // What to do when service completes?
+ new OnServiceComplete(LoginActivity.this,
+ AmbulanceForegroundService.BroadcastActions.SUCCESS,
+ AmbulanceForegroundService.BroadcastActions.FAILURE,
+ intent) {
+
+ @Override
+ public void onSuccess(Bundle extras) {
+ Log.i(TAG, "onClick:OnServiceComplete:onSuccess");
+
+ // Toast
+ Toast.makeText(LoginActivity.this,
+ getResources().getString(R.string.loginSuccessMessage, username),
+ Toast.LENGTH_SHORT).show();
+
+ // initiate MainActivity
+ Intent intent = new Intent(LoginActivity.this,
+ MainActivity.class);
+ startActivity(intent);
+
+ }
+
+ }
+ .setFailureMessage(getResources().getString(R.string.couldNotLoginUser, username))
+ .setAlert(new AlertSnackbar(LoginActivity.this));
+
+
+ }
+
+ }
+
+ /**
+ * Return the current state of the permissions needed.
+ */
+ private boolean checkPermissions() {
+ int permissionState = ActivityCompat.checkSelfPermission(this,
+ Manifest.permission.ACCESS_FINE_LOCATION);
+ return permissionState == PackageManager.PERMISSION_GRANTED;
+ }
+
+ /**
+ * Request permission to access fine location
+ */
+ private void requestPermissions() {
+
+ boolean shouldProvideRationale =
+ ActivityCompat.shouldShowRequestPermissionRationale(this,
+ Manifest.permission.ACCESS_FINE_LOCATION);
+
+ // Provide an additional rationale to the user. This would happen if the user denied the
+ // request previously, but didn't check the "Don't ask again" checkbox.
+ if (shouldProvideRationale) {
+ Log.i(TAG, "Displaying permission rationale to provide additional context.");
+
+ new AlertSnackbar(this)
+ .alert(getString(R.string.permission_rationale),
+ new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ // Request permission
+ ActivityCompat.requestPermissions(LoginActivity.this,
+ new String[]{Manifest.permission.ACCESS_FINE_LOCATION},
+ REQUEST_PERMISSIONS_REQUEST_CODE);
+ }
+ });
+
+ } else {
+ Log.i(TAG, "Requesting permission");
+ // Request permission. It's possible this can be auto answered if device policy
+ // sets the permission in a given state or the user denied the permission
+ // previously and checked "Never ask again".
+ ActivityCompat.requestPermissions(LoginActivity.this,
+ new String[]{Manifest.permission.ACCESS_FINE_LOCATION},
+ REQUEST_PERMISSIONS_REQUEST_CODE);
+ }
+ }
+
+ /**
+ * Callback received when a permissions request has been completed.
+ */
+ @Override
+ public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
+ @NonNull int[] grantResults) {
+
+ Log.i(TAG, "onRequestPermissionResult");
+ if (requestCode == REQUEST_PERMISSIONS_REQUEST_CODE) {
+ if (grantResults.length <= 0) {
+
+ // If user interaction was interrupted, the permission request is cancelled and you
+ // receive empty arrays.
+ Log.i(TAG, "User interaction was cancelled.");
+
+ } else if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
+
+ // Permission granted
+ Log.i(TAG, "Permission granted");
+
+ // Will check location settings
+ checkLocationSettings();
+
+ } else {
+ // Permission denied.
+
+ // Notify the user via a SnackBar that they have rejected a core permission for the
+ // app, which makes the Activity useless. In a real app, core permissions would
+ // typically be best requested during a welcome-screen flow.
+
+ // Additionally, it is important to remember that a permission might have been
+ // rejected without asking the user for permission (device policy or "Never ask
+ // again" prompts). Therefore, a user interface affordance is typically implemented
+ // when permissions are denied. Otherwise, your app could appear unresponsive to
+ // touches or interactions which have required permissions.
+ new AlertSnackbar(this)
+ .alert(getString(R.string.permission_denied_explanation),
+ new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ // Build intent that displays the App settings screen.
+ Intent intent = new Intent();
+ intent.setAction(
+ Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
+ Uri uri = Uri.fromParts("package",
+ BuildConfig.APPLICATION_ID, null);
+ intent.setData(uri);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ startActivity(intent);
+ }
+ });
+ }
+ }
+ }
+
+ public void checkLocationSettings() {
+
+ // Build settings client
+ SettingsClient settingsClient = LocationServices.getSettingsClient(this);
+
+ // Check if the device has the necessary location settings.
+ settingsClient.checkLocationSettings(AmbulanceForegroundService.getLocationSettingsRequest())
+ .addOnSuccessListener(this, new OnSuccessListener() {
+ @SuppressLint("MissingPermission")
+ @Override
+ public void onSuccess(LocationSettingsResponse locationSettingsResponse) {
+ Log.i(TAG, "All location settings are satisfied.");
+
+ // enable location updates
+ AmbulanceForegroundService.setCanUpdateLocation(true);
+
+ // enable login
+ enableLogin();
+
+ }
+ })
+ .addOnFailureListener(this, new OnFailureListener() {
+ @Override
+ public void onFailure(@NonNull Exception e) {
+ int statusCode = ((ApiException) e).getStatusCode();
+ switch (statusCode) {
+ case LocationSettingsStatusCodes.RESOLUTION_REQUIRED:
+ Log.i(TAG, "Location settings are not satisfied. Attempting to upgrade " +
+ "location settings ");
+ try {
+ // Show the dialog by calling startResolutionForResult(), and check the
+ // result in onActivityResult().
+ ResolvableApiException rae = (ResolvableApiException) e;
+ rae.startResolutionForResult(LoginActivity.this, REQUEST_CHECK_SETTINGS);
+ } catch (IntentSender.SendIntentException sie) {
+ Log.i(TAG, "PendingIntent unable to execute request.");
+ }
+ break;
+ case LocationSettingsStatusCodes.SETTINGS_CHANGE_UNAVAILABLE:
+ String errorMessage = "Location settings are inadequate, and cannot be " +
+ "fixed here. Fix in Settings.";
+
+ new AlertSnackbar(LoginActivity.this)
+ .alert(errorMessage);
+
+ // disable location updates
+ AmbulanceForegroundService.setCanUpdateLocation(false);
+
+ // enable login
+ enableLogin();
+
+ }
+
+ }
+ });
+
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ switch (requestCode) {
+ // Check for the integer request code originally supplied to startResolutionForResult().
+ case REQUEST_CHECK_SETTINGS:
+ switch (resultCode) {
+ case Activity.RESULT_OK:
+ Log.i(TAG, "User agreed to make required location settings changes.");
+ break;
+ case Activity.RESULT_CANCELED:
+ Log.i(TAG, "User chose not to make required location settings changes.");
+ break;
+ }
+ break;
+ }
+ }
+
+}
diff --git a/app/src/main/java/org/emstrack/ambulance/MainActivity.java b/app/src/main/java/org/emstrack/ambulance/MainActivity.java
new file mode 100644
index 00000000..9470dbf4
--- /dev/null
+++ b/app/src/main/java/org/emstrack/ambulance/MainActivity.java
@@ -0,0 +1,877 @@
+package org.emstrack.ambulance;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.res.Configuration;
+import android.graphics.PorterDuff;
+import android.os.Bundle;
+import android.support.design.widget.NavigationView;
+import android.support.design.widget.TabLayout;
+import android.support.v4.app.Fragment;
+import android.support.v4.content.ContextCompat;
+import android.support.v4.content.LocalBroadcastManager;
+import android.support.v4.view.ViewPager;
+import android.support.v4.widget.DrawerLayout;
+import android.support.v7.app.ActionBarDrawerToggle;
+import android.support.v7.app.AlertDialog;
+import android.support.v7.app.AppCompatActivity;
+import android.support.v7.widget.Toolbar;
+import android.util.Log;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.ImageButton;
+import android.widget.ImageView;
+import android.widget.Toast;
+
+import org.emstrack.ambulance.adapters.FragmentPager;
+import org.emstrack.ambulance.dialogs.AboutDialog;
+import org.emstrack.ambulance.dialogs.AlertSnackbar;
+import org.emstrack.ambulance.dialogs.LogoutDialog;
+import org.emstrack.ambulance.fragments.AmbulanceFragment;
+import org.emstrack.ambulance.fragments.HospitalFragment;
+import org.emstrack.ambulance.fragments.MapFragment;
+import org.emstrack.ambulance.services.AmbulanceForegroundService;
+import org.emstrack.ambulance.services.OnServiceComplete;
+import org.emstrack.ambulance.services.OnServicesComplete;
+import org.emstrack.models.Ambulance;
+import org.emstrack.models.AmbulancePermission;
+import org.emstrack.models.Call;
+import org.emstrack.models.Profile;
+import org.emstrack.mqtt.MqttProfileClient;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This is the main activity -- the default screen
+ */
+public class MainActivity extends AppCompatActivity {
+
+ private static final String TAG = MainActivity.class.getSimpleName();
+
+ private static final float enabledAlpha = 1.0f;
+ private static final float disabledAlpha = 0.25f;
+
+ private ViewPager viewPager;
+ private FragmentPager adapter;
+
+ private Button ambulanceButton;
+ List ambulancePermissions;
+ ArrayAdapter ambulanceListAdapter;
+
+ private DrawerLayout mDrawer;
+ private NavigationView nvDrawer;
+ private ActionBarDrawerToggle drawerToggle;
+ private Toolbar toolbar;
+ private ImageButton panicButton;
+ private ImageView onlineIcon;
+ private ImageView trackingIcon;
+ private MainActivityBroadcastReceiver receiver;
+
+ public class TrackingClickListener implements View.OnClickListener {
+
+ @Override
+ public void onClick(View v) {
+
+ if (AmbulanceForegroundService.isUpdatingLocation()) {
+
+ // stop updating location
+ stopUpdatingLocation();
+
+ } else {
+
+ // start updating location
+ startUpdatingLocation();
+
+ }
+ }
+ }
+
+ public class AmbulanceButtonClickListener implements View.OnClickListener {
+
+ @Override
+ public void onClick(View v) {
+
+ new AlertDialog.Builder(
+ MainActivity.this)
+ .setTitle(R.string.selectAmbulance)
+ .setAdapter(ambulanceListAdapter,
+ new DialogInterface.OnClickListener() {
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+
+ AmbulancePermission selectedAmbulance = ambulancePermissions.get(which);
+ Log.d(TAG, "Selected ambulance " + selectedAmbulance.getAmbulanceIdentifier());
+
+ // Any ambulance currently selected?
+ final Ambulance ambulance = AmbulanceForegroundService.getAmbulance();
+
+ // Warn if current ambulance
+ if (ambulance != null) {
+
+ Log.d(TAG, "Current ambulance " + ambulance.getIdentifier());
+ Log.d(TAG, "Requesting location updates? " +
+ (AmbulanceForegroundService.isUpdatingLocation() ? "TRUE" : "FALSE"));
+
+ // If same ambulance, just return
+ if (ambulance.getId() == selectedAmbulance.getAmbulanceId())
+ return;
+
+ if (AmbulanceForegroundService.isUpdatingLocation()) {
+
+ // confirm first
+ switchAmbulanceDialog(selectedAmbulance);
+ return;
+ }
+
+ }
+
+ // otherwise go ahead!
+ retrieveAmbulance(selectedAmbulance);
+ dialog.dismiss();
+
+ }
+ })
+ .create()
+ .show();
+ }
+ }
+
+ public class MainActivityBroadcastReceiver extends BroadcastReceiver {
+
+ @Override
+ public void onReceive(Context context, Intent intent ) {
+
+ if (intent != null) {
+
+ final String action = intent.getAction();
+ if (action.equals(AmbulanceForegroundService.BroadcastActions.LOCATION_UPDATE_CHANGE)) {
+
+ Log.i(TAG, "LOCATION_UPDATE_CHANGE");
+
+ if (AmbulanceForegroundService.isUpdatingLocation())
+ trackingIcon.setAlpha(enabledAlpha);
+ else
+ trackingIcon.setAlpha(disabledAlpha);
+
+ } else if (action.equals(AmbulanceForegroundService.BroadcastActions.CONNECTIVITY_CHANGE)) {
+
+ Log.i(TAG, "CONNECTIVITY_CHANGE");
+
+ if (AmbulanceForegroundService.isOnline())
+ onlineIcon.setAlpha(enabledAlpha);
+ else
+ onlineIcon.setAlpha(disabledAlpha);
+
+ } else if (action.equals(AmbulanceForegroundService.BroadcastActions.PROMPT_CALL_ACCEPT)) {
+
+ Log.i(TAG, "PROMPT_CALL_ACCEPT");
+
+ int callId = intent.getIntExtra("CALL_ID", -1);
+ acceptCallDialog(callId);
+
+ } else if (action.equals(AmbulanceForegroundService.BroadcastActions.PROMPT_CALL_END)) {
+
+ Log.i(TAG, "PROMPT_CALL_END");
+ endCallDialog();
+
+ }
+ else if (action.equals(AmbulanceForegroundService.BroadcastActions.CALL_ONGOING)) {
+
+ Log.i(TAG, "CALL_ONGOING");
+
+ // change button color to red
+ int myVectorColor = ContextCompat.getColor(MainActivity.this, R.color.colorRed);
+ trackingIcon.setColorFilter(myVectorColor, PorterDuff.Mode.SRC_IN);
+
+ } else if (action.equals(AmbulanceForegroundService.BroadcastActions.CALL_FINISHED)) {
+
+ Log.i(TAG, "CALL_FINISHED");
+
+ // change button color to black
+ int myVectorColor = ContextCompat.getColor(MainActivity.this, R.color.colorBlack);
+ trackingIcon.setColorFilter(myVectorColor, PorterDuff.Mode.SRC_IN);
+
+ }
+ }
+ }
+ };
+
+ /**
+ * @param savedInstanceState
+ */
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_main);
+
+ // Ambulance spinner
+ ambulanceButton = (Button) findViewById(R.id.ambulanceButton);
+
+ // Panic button
+ panicButton = (ImageButton) findViewById(R.id.panicButton);
+ panicButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ panicPopUp();
+ }
+ });
+
+ // Set a Toolbar to replace the ActionBar.
+ toolbar = (Toolbar) findViewById(R.id.toolbar);
+ setSupportActionBar(toolbar);
+ // getSupportActionBar().setDisplayShowTitleEnabled(false);
+
+ // Find our drawer view
+ mDrawer = (DrawerLayout) findViewById(R.id.drawer_layout);
+ drawerToggle = setupDrawerToggle();
+ mDrawer.addDrawerListener(drawerToggle);
+
+ // set hamburger color to be black
+ drawerToggle.getDrawerArrowDrawable().setColor(getResources().getColor(R.color.colorBlack));
+
+ // Find our drawer view
+ nvDrawer = (NavigationView) findViewById(R.id.nvView);
+
+ // Setup drawer view
+ setupDrawerContent(nvDrawer);
+
+ // pager
+ viewPager = (ViewPager) findViewById(R.id.pager);
+
+ // Setup Adapter for tabLayout
+ adapter = new FragmentPager(getSupportFragmentManager(),
+ new Fragment[] {new AmbulanceFragment(), new MapFragment(), new HospitalFragment()},
+ new CharSequence[] {"Ambulance", "Map", "Hospitals"});
+ viewPager.setAdapter(adapter);
+
+ //set up TabLayout Structure
+ TabLayout tabLayout = (TabLayout) findViewById(R.id.tab_layout_home);
+ tabLayout.setTabGravity(TabLayout.GRAVITY_FILL);
+ tabLayout.setupWithViewPager(viewPager);
+ tabLayout.getTabAt(0).setIcon(R.drawable.ic_ambulance);
+ tabLayout.getTabAt(1).setIcon(R.drawable.ic_globe);
+ tabLayout.getTabAt(2).setIcon(R.drawable.ic_hospital);
+
+ // Online icon
+ onlineIcon = (ImageView) findViewById(R.id.onlineIcon);
+ if (AmbulanceForegroundService.isOnline())
+ onlineIcon.setAlpha(enabledAlpha);
+ else
+ onlineIcon.setAlpha(disabledAlpha);
+
+ // Tracking icon
+ trackingIcon = (ImageView) findViewById(R.id.trackingIcon);
+ trackingIcon.setOnClickListener(new TrackingClickListener());
+
+ if (AmbulanceForegroundService.isUpdatingLocation()) {
+ trackingIcon.setAlpha(enabledAlpha);
+ } else {
+ trackingIcon.setAlpha(disabledAlpha);
+ }
+
+ // Set up ambulance spinner
+ try {
+
+ ambulancePermissions = new ArrayList<>();
+ MqttProfileClient profileClient = AmbulanceForegroundService.getProfileClient();
+ Profile profile = profileClient.getProfile();
+ if (profile != null)
+ ambulancePermissions = profile.getAmbulances();
+
+ // Creates string arraylist of ambulance names
+ ArrayList ambulanceList = new ArrayList<>();
+ for (AmbulancePermission ambulancePermission : ambulancePermissions)
+ ambulanceList.add(ambulancePermission.getAmbulanceIdentifier());
+
+ // Create the adapter
+ ambulanceListAdapter = new ArrayAdapter<>(this,
+ android.R.layout.simple_spinner_dropdown_item, ambulanceList);
+ ambulanceListAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+
+ // Set the spinner's adapter
+ ambulanceButton.setOnClickListener(new AmbulanceButtonClickListener());
+
+ // Any ambulance currently selected?
+ Ambulance ambulance = AmbulanceForegroundService.getAmbulance();
+ if (ambulance != null) {
+
+ // Set button
+ setAmbulanceButtonText(ambulance.getIdentifier());
+
+ // Automatically attempting to start streaming
+ Log.i(TAG, "Attempting to start streaming");
+ startUpdatingLocation();
+
+ } else {
+
+ // Invoke ambulance selection
+ ambulanceButton.performClick();
+
+ }
+
+ } catch (AmbulanceForegroundService.ProfileClientException e ){
+ Log.e(TAG, "Could not retrieve list of ambulances.");
+ }
+
+ }
+
+ @Override
+ protected void onPostCreate(Bundle savedInstanceState) {
+ super.onPostCreate(savedInstanceState);
+ // Sync the toggle state after onRestoreInstanceState has occurred.
+ drawerToggle.syncState();
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+
+ // Pass any configuration change to the drawer toggles
+ drawerToggle.onConfigurationChanged(newConfig);
+
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+
+ // Update location icon
+ if (AmbulanceForegroundService.isUpdatingLocation())
+ trackingIcon.setAlpha(enabledAlpha);
+ else
+ trackingIcon.setAlpha(disabledAlpha);
+
+ // Online icon
+ onlineIcon = (ImageView) findViewById(R.id.onlineIcon);
+ if (AmbulanceForegroundService.isOnline())
+ onlineIcon.setAlpha(enabledAlpha);
+ else
+ onlineIcon.setAlpha(disabledAlpha);
+
+ // Register receiver
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(AmbulanceForegroundService.BroadcastActions.LOCATION_UPDATE_CHANGE);
+ filter.addAction(AmbulanceForegroundService.BroadcastActions.CONNECTIVITY_CHANGE);
+ filter.addAction(AmbulanceForegroundService.BroadcastActions.PROMPT_CALL_ACCEPT);
+ filter.addAction(AmbulanceForegroundService.BroadcastActions.PROMPT_CALL_END);
+ filter.addAction(AmbulanceForegroundService.BroadcastActions.CALL_ONGOING);
+ filter.addAction(AmbulanceForegroundService.BroadcastActions.CALL_FINISHED);
+ receiver = new MainActivityBroadcastReceiver();
+ getLocalBroadcastManager().registerReceiver(receiver, filter);
+
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+
+ // Unregister receiver
+ if (receiver != null) {
+ getLocalBroadcastManager().unregisterReceiver(receiver);
+ receiver = null;
+ }
+
+ }
+
+ public void retrieveAmbulance(final AmbulancePermission selectedAmbulance) {
+
+ // Retrieve ambulance
+ Intent ambulanceIntent = new Intent(this, AmbulanceForegroundService.class);
+ ambulanceIntent.setAction(AmbulanceForegroundService.Actions.GET_AMBULANCE);
+ ambulanceIntent.putExtra("AMBULANCE_ID", selectedAmbulance.getAmbulanceId());
+
+ // What to do when GET_AMBULANCE service completes?
+ new OnServiceComplete(this,
+ AmbulanceForegroundService.BroadcastActions.SUCCESS,
+ AmbulanceForegroundService.BroadcastActions.FAILURE,
+ ambulanceIntent) {
+
+ @Override
+ public void onSuccess(Bundle extras) {
+
+ // Start MainActivity
+ setAmbulanceButtonText(selectedAmbulance.getAmbulanceIdentifier());
+
+ // Start updating
+ startUpdatingLocation();
+
+ }
+
+ }
+ .setFailureMessage(getResources().getString(R.string.couldNotRetrieveAmbulance,
+ selectedAmbulance.getAmbulanceIdentifier()))
+ .setAlert(new AlertSnackbar(this));
+
+ }
+
+ public boolean canWrite() {
+
+ Ambulance ambulance = AmbulanceForegroundService.getAmbulance();
+
+ // has ambulance?
+ if (ambulance == null)
+ return false;
+
+ // can write?
+ boolean canWrite = false;
+
+ try {
+
+ final MqttProfileClient profileClient = AmbulanceForegroundService.getProfileClient();
+ for (AmbulancePermission permission : profileClient.getProfile().getAmbulances()) {
+ if (permission.getAmbulanceId() == ambulance.getId()) {
+ if (permission.isCanWrite()) {
+ canWrite = true;
+ }
+ break;
+ }
+ }
+
+ } catch (AmbulanceForegroundService.ProfileClientException e) {
+
+ /* no need to do anything */
+ Log.e(TAG, "Failed to get ambulance read/write permissions.");
+
+ }
+
+ return canWrite;
+
+ }
+
+ /**
+ * Set ambulance text
+ *
+ * @param ambulance the ambulance
+ */
+ public void setAmbulanceButtonText(String ambulance) {
+ // Set ambulance selection button
+ ambulanceButton.setText(ambulance);
+ }
+
+ // Hamburger Menu setup
+ private ActionBarDrawerToggle setupDrawerToggle() {
+ return new ActionBarDrawerToggle(this, mDrawer, toolbar, R.string.drawer_open, R.string.drawer_close);
+ }
+
+ // Hamburger Menu Listener
+ private void setupDrawerContent(NavigationView navigationView) {
+ navigationView.setNavigationItemSelectedListener(
+ new NavigationView.OnNavigationItemSelectedListener() {
+ @Override
+ public boolean onNavigationItemSelected(MenuItem menuItem) {
+ selectDrawerItem(menuItem);
+ return true;
+ }
+ });
+ }
+
+ // Start selected activity in Hamburger
+ public void selectDrawerItem(MenuItem menuItem) {
+
+ // Close drawer
+ mDrawer.closeDrawers();
+
+ // Get menuitem
+ int itemId = menuItem.getItemId();
+
+ // Actions
+ if (itemId == R.id.logout) {
+
+ LogoutDialog.newInstance(this).show();
+
+ } else if (itemId == R.id.about) {
+
+ AboutDialog.newInstance(this).show();
+
+ } else if (itemId == R.id.settings) {
+
+ }
+
+ }
+
+ public void panicPopUp() {
+
+ AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ builder.setCancelable(true);
+ builder.setTitle("PANIC!");
+ builder.setMessage("Message");
+ builder.setPositiveButton("Confirm",
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ }
+ });
+ builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ }
+ });
+
+ AlertDialog dialog = builder.create();
+ dialog.show();
+ }
+
+ /**
+ * Get LocalBroadcastManager
+ *
+ * @return the LocalBroadcastManager
+ */
+ private LocalBroadcastManager getLocalBroadcastManager() {
+ return LocalBroadcastManager.getInstance(this);
+ }
+
+ private void acceptCallDialog(final int callId) {
+
+ Log.i(TAG, "Creating accept dialog");
+
+ // Gather call details
+ Call call = AmbulanceForegroundService.getCall(callId);
+
+ if (call == null) {
+
+ Log.d(TAG, "Invalid call/" + callId);
+ return;
+
+ }
+
+ String callDetails = String.format("Priority: %1$s\n%2$s %3$s, %4$s, %5$s %6$s",
+ call.getPriority(),
+ call.getStreet(), call.getNumber(),
+ call.getCity(), call.getState(), call.getZipcode());
+
+ // Use the Builder class for convenient dialog construction
+ AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ builder.setTitle("Accept Incoming Call?")
+ .setMessage(callDetails)
+ .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_ACCEPT);
+ serviceIntent.putExtra("CALL_ID", 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_DECLINE);
+ serviceIntent.putExtra("CALL_ID", callId);
+ startService(serviceIntent);
+
+ }
+ });
+
+ // Create the AlertDialog object and display it
+ builder.create().show();
+
+ }
+
+ private void endCallDialog() {
+
+ Log.i(TAG, "Creating end call dialog");
+
+ // Use the Builder class for convenient dialog construction
+ AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ builder.setTitle("Currently handling call")
+ .setMessage("What do you want to do?")
+ .setNegativeButton("Continue", new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int id) {
+
+ Toast.makeText(MainActivity.this, "Continuing to handle call", Toast.LENGTH_SHORT).show();
+
+ Log.i(TAG, "Continuing with call");
+
+ }
+ })
+ .setNeutralButton("Suspend", new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int id) {
+
+ Toast.makeText(MainActivity.this, "Suspending call", Toast.LENGTH_SHORT).show();
+
+ Log.i(TAG, "Suspending call");
+
+ }
+ })
+ .setPositiveButton("End", new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int id) {
+
+ Toast.makeText(MainActivity.this, "Ending call", Toast.LENGTH_SHORT).show();
+
+ Log.i(TAG, "Ending call");
+
+ Intent serviceIntent = new Intent(MainActivity.this, AmbulanceForegroundService.class);
+ serviceIntent.setAction(AmbulanceForegroundService.Actions.CALL_FINISH);
+ startService(serviceIntent);
+
+ }
+ });
+ // Create the AlertDialog object and return it
+ builder.create().show();
+ }
+
+ void startUpdatingLocation() {
+
+ // start updating location
+
+ if (canWrite()) {
+
+ // Toast to warn user
+ Toast.makeText(MainActivity.this, R.string.requestToStreamLocation,
+ Toast.LENGTH_SHORT).show();
+
+
+ // start updating location
+ Intent intent = new Intent(MainActivity.this, AmbulanceForegroundService.class);
+ intent.setAction(AmbulanceForegroundService.Actions.START_LOCATION_UPDATES);
+
+ new OnServiceComplete(MainActivity.this,
+ AmbulanceForegroundService.BroadcastActions.SUCCESS,
+ AmbulanceForegroundService.BroadcastActions.FAILURE,
+ intent) {
+
+ @Override
+ public void onSuccess(Bundle extras) {
+
+ // Toast to warn user
+ Toast.makeText(MainActivity.this, R.string.startedStreamingLocation,
+ Toast.LENGTH_SHORT).show();
+
+ }
+
+ @Override
+ public void onFailure(Bundle extras) {
+
+ // Otherwise ask user if wants to force
+ AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(MainActivity.this);
+
+ alertDialogBuilder.setTitle(R.string.alert_warning_title);
+ alertDialogBuilder.setMessage(R.string.forceLocationUpdates);
+
+ // Cancel button
+ alertDialogBuilder.setNegativeButton(
+ R.string.alert_button_negative_text,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ // do nothing
+ }
+ });
+
+ // Create the OK button that logs user out
+ alertDialogBuilder.setPositiveButton(
+ R.string.alert_button_positive_text,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+
+ Log.i(TAG, "ForceLocationUpdatesDialog: OK Button Clicked");
+
+ // Toast to warn user
+ Toast.makeText(MainActivity.this, R.string.forcingLocationUpdates,
+ Toast.LENGTH_LONG).show();
+
+ // Reset location_client
+ String payload = "{\"location_client_id\":\"\"}";
+ Intent intent = new Intent(MainActivity.this, AmbulanceForegroundService.class);
+ intent.setAction(AmbulanceForegroundService.Actions.UPDATE_AMBULANCE);
+ Bundle bundle = new Bundle();
+ bundle.putString("UPDATE", payload);
+ intent.putExtras(bundle);
+
+ // What to do when service completes?
+ new OnServicesComplete(MainActivity.this,
+ new String[]{
+ AmbulanceForegroundService.BroadcastActions.SUCCESS,
+ AmbulanceForegroundService.BroadcastActions.AMBULANCE_UPDATE
+ },
+ new String[]{AmbulanceForegroundService.BroadcastActions.FAILURE},
+ intent) {
+
+ @Override
+ public void onSuccess(Bundle extras) {
+ Log.i(TAG, "onSuccess");
+
+ // Toast to warn user
+ Toast.makeText(MainActivity.this, R.string.succeededForcingLocationUpdates,
+ Toast.LENGTH_LONG).show();
+
+ // Start updating locations
+ startUpdatingLocation();
+
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+
+ // Retrieve action
+ String action = intent.getAction();
+
+ // Intercept success
+ if (action.equals(AmbulanceForegroundService.BroadcastActions.SUCCESS))
+ // prevent propagation, still waiting for AMBULANCE_UPDATE
+ return;
+
+ // Intercept AMBULANCE_UPDATE
+ if (action.equals(AmbulanceForegroundService.BroadcastActions.AMBULANCE_UPDATE))
+ // Inject uuid into AMBULANCE_UPDATE
+ intent.putExtra(OnServicesComplete.UUID, getUuid());
+
+ // Call super
+ super.onReceive(context, intent);
+ }
+
+ }
+ .setFailureMessage(getString(R.string.couldNotForceLocationUpdate))
+ .setAlert(new AlertSnackbar(MainActivity.this));
+
+ }
+
+ });
+
+ alertDialogBuilder.create().show();
+
+ }
+
+ }
+ .setFailureMessage(getResources().getString(R.string.anotherClientIsStreamingLocations))
+ .setAlert(new AlertSnackbar(MainActivity.this));
+
+ } else {
+
+ // Toast to warn user
+ Toast.makeText(MainActivity.this, R.string.cantModifyAmbulance, Toast.LENGTH_LONG).show();
+
+ }
+
+ }
+
+ void stopUpdatingLocation() {
+
+ // is handling call?
+ Call currentCall = AmbulanceForegroundService.getCurrentCall();
+ if (currentCall != null) {
+
+ Log.d(TAG, "In call: prompt user");
+
+ // currently handling call, prompt if want to end call
+ endCallDialog();
+
+ } else {
+
+ Log.d(TAG, "Not in call: prompt user");
+
+ endUpdateDialog();
+
+ }
+
+ }
+
+ void doStopUpdatingLocation() {
+
+ if (canWrite()) {
+
+ // turn off tracking
+ Intent intent = new Intent(MainActivity.this, AmbulanceForegroundService.class);
+ intent.setAction(AmbulanceForegroundService.Actions.STOP_LOCATION_UPDATES);
+ startService(intent);
+
+ // Toast to warn user
+ Toast.makeText(MainActivity.this, R.string.stopedStreamingLocation,
+ Toast.LENGTH_SHORT).show();
+
+ }
+
+ }
+
+ private void endUpdateDialog() {
+
+ Log.i(TAG, "Creating end update dialog");
+
+ // Use the Builder class for convenient dialog construction
+ AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ builder.setTitle("Stop server updates")
+ .setMessage("Stoping updates will prevent you from relaying your GPS position, receiving calls or otherwise updating the ambulance status on the server. Do you want to stop server updates?")
+ .setNegativeButton("No", new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int id) {
+
+ Log.i(TAG, "Continue updating");
+
+ }
+ })
+ .setPositiveButton("Yes", new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int id) {
+
+ Toast.makeText(MainActivity.this, "Stopping updates", Toast.LENGTH_SHORT).show();
+
+ Log.d(TAG, "Stop location updates");
+
+ // stop updating location
+ doStopUpdatingLocation();
+
+ }
+ });
+ // Create the AlertDialog object and return it
+ builder.create().show();
+
+ }
+
+ private void switchAmbulanceDialog(final AmbulancePermission newAmbulance) {
+
+ Log.i(TAG, "Creating switch ambulance dialog");
+
+ // Use the Builder class for convenient dialog construction
+ AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ builder.setTitle("Switch ambulance")
+ .setMessage(String.format("Do you want to swtich to ambulance %1$s?", newAmbulance.getAmbulanceIdentifier()))
+ .setNegativeButton("No", new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int id) {
+
+ Log.i(TAG, "Continue with same ambulance");
+
+ }
+ })
+ .setPositiveButton("Yes", new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int id) {
+
+ Log.d(TAG, String.format("Switching to ambulance %1$s", newAmbulance.getAmbulanceIdentifier()));
+
+ // stop updating first
+ doStopUpdatingLocation();
+
+ // then retrieve new ambulance
+ retrieveAmbulance(newAmbulance);
+
+ }
+ });
+ // Create the AlertDialog object and return it
+ builder.create().show();
+
+ }
+
+ @Override
+ public void onBackPressed() {
+
+ LogoutDialog.newInstance(this).show();
+
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/emstrack/ambulance/adapters/FragmentPager.java b/app/src/main/java/org/emstrack/ambulance/adapters/FragmentPager.java
new file mode 100644
index 00000000..77cfe7a2
--- /dev/null
+++ b/app/src/main/java/org/emstrack/ambulance/adapters/FragmentPager.java
@@ -0,0 +1,46 @@
+package org.emstrack.ambulance.adapters;
+
+import android.content.res.Configuration;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentManager;
+import android.support.v4.app.FragmentStatePagerAdapter;
+import android.view.ViewGroup;
+
+import org.emstrack.ambulance.fragments.AmbulanceFragment;
+import org.emstrack.ambulance.fragments.MapFragment;
+import org.emstrack.ambulance.fragments.HospitalFragment;
+
+/**
+ * Created by mauricio on 2/21/18.
+ */
+
+public class FragmentPager extends FragmentStatePagerAdapter {
+
+ private final CharSequence[] titles;
+ Fragment[] fragments;
+ int numberOfTabs;
+
+ public FragmentPager(FragmentManager fm,
+ Fragment[] fragments,
+ CharSequence[] titles) {
+
+ super(fm);
+
+ this.fragments = fragments;
+ this.numberOfTabs = fragments.length;
+ this.titles = titles;
+
+ }
+
+ @Override
+ public Fragment getItem(int position) { return (position < numberOfTabs ? fragments[position] : null); }
+
+ @Override
+ public CharSequence getPageTitle(int position) { return (position < numberOfTabs ? titles[position] : ""); }
+
+ @Override
+ public int getCount() {
+ return numberOfTabs;
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/emstrack/ambulance/adapters/HospitalExpandableRecyclerAdapter.java b/app/src/main/java/org/emstrack/ambulance/adapters/HospitalExpandableRecyclerAdapter.java
new file mode 100644
index 00000000..259718a6
--- /dev/null
+++ b/app/src/main/java/org/emstrack/ambulance/adapters/HospitalExpandableRecyclerAdapter.java
@@ -0,0 +1,64 @@
+package org.emstrack.ambulance.adapters;
+
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.thoughtbot.expandablerecyclerview.ExpandableRecyclerViewAdapter;
+import com.thoughtbot.expandablerecyclerview.models.ExpandableGroup;
+
+import org.emstrack.ambulance.R;
+import org.emstrack.ambulance.fragments.HospitalFragment;
+import org.emstrack.ambulance.models.HospitalExpandableGroup;
+import org.emstrack.ambulance.views.HospitalEquipmentViewHolder;
+import org.emstrack.ambulance.views.HospitalViewHolder;
+import org.emstrack.models.Hospital;
+import org.emstrack.models.HospitalEquipment;
+
+import java.util.List;
+
+/**
+ * Created by mauricio on 3/11/2018.
+ */
+
+public class HospitalExpandableRecyclerAdapter
+ extends ExpandableRecyclerViewAdapter {
+
+ private static final String TAG = HospitalExpandableRecyclerAdapter.class.getSimpleName();
+
+ public HospitalExpandableRecyclerAdapter(List extends ExpandableGroup> groups) {
+ super(groups);
+ }
+
+ @Override
+ public HospitalViewHolder onCreateGroupViewHolder(ViewGroup parent, int viewType) {
+ View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.hospital_item, parent, false);
+ return new HospitalViewHolder(view);
+ }
+
+ @Override
+ public HospitalEquipmentViewHolder onCreateChildViewHolder(ViewGroup child, int viewType) {
+ View view = LayoutInflater.from(child.getContext()).inflate(R.layout.hospital_equipment_item, child, false);
+ return new HospitalEquipmentViewHolder(view);
+ }
+
+ @Override
+ public void onBindChildViewHolder(HospitalEquipmentViewHolder holder, int flatPosition,
+ ExpandableGroup group, int childIndex) {
+ HospitalEquipment hospitalEquipment = ((HospitalExpandableGroup) group).getItems().get(childIndex);
+ // Log.d(TAG, "Binding equipment '" + hospitalEquipment + "'");
+
+ holder.setHospitalEquipment(hospitalEquipment);
+ }
+
+ @Override
+ public void onBindGroupViewHolder(HospitalViewHolder holder, int flatPosition,
+ ExpandableGroup group) {
+ Hospital hospital = ((HospitalExpandableGroup) group).getHospital();
+ // Log.d(TAG, "Binding hospital '" + hospital.getName() + "'");
+
+ holder.setHospital(hospital);
+ }
+}
+
diff --git a/app/src/main/java/org/emstrack/ambulance/dialogs/AboutDialog.java b/app/src/main/java/org/emstrack/ambulance/dialogs/AboutDialog.java
new file mode 100644
index 00000000..8652b0a3
--- /dev/null
+++ b/app/src/main/java/org/emstrack/ambulance/dialogs/AboutDialog.java
@@ -0,0 +1,55 @@
+package org.emstrack.ambulance.dialogs;
+
+import android.app.Activity;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.os.Build;
+import android.support.v7.app.AlertDialog;
+import android.util.Log;
+import android.view.View;
+import android.widget.TextView;
+
+import org.emstrack.ambulance.BuildConfig;
+import org.emstrack.ambulance.LoginActivity;
+import org.emstrack.ambulance.R;
+import org.emstrack.ambulance.services.AmbulanceForegroundService;
+import org.emstrack.models.Ambulance;
+
+import java.util.Date;
+
+/**
+ * Created by mauricio on 4/26/18.
+ */
+
+public class AboutDialog {
+
+ private static final String TAG = AboutDialog.class.getSimpleName();
+
+ public static AlertDialog newInstance(final Activity activity) {
+
+ // Inflate the about message contents
+ View messageView = activity.getLayoutInflater().inflate(R.layout.about, null, false);
+
+ // Set build date
+ TextView buildDate = messageView.findViewById(R.id.buildDate);
+ buildDate.setText(new Date(BuildConfig.TIMESTAMP).toString());
+
+ // Set build version
+ TextView buildVersion = messageView.findViewById(R.id.buildVersion);
+ buildVersion.setText(R.string.app_version);
+
+ // Logout dialog
+ AlertDialog.Builder builder = new AlertDialog.Builder(activity);
+
+ builder.setIcon(R.drawable.ambulancelogo);
+ builder.setTitle(R.string.app_name);
+ builder.setView(messageView);
+
+ // Create OK button
+ builder.setPositiveButton(R.string.alert_button_positive_text, null);
+
+ return builder.create();
+
+ }
+
+}
diff --git a/app/src/main/java/org/emstrack/ambulance/dialogs/AlertSnackbar.java b/app/src/main/java/org/emstrack/ambulance/dialogs/AlertSnackbar.java
new file mode 100644
index 00000000..2c4820a2
--- /dev/null
+++ b/app/src/main/java/org/emstrack/ambulance/dialogs/AlertSnackbar.java
@@ -0,0 +1,56 @@
+package org.emstrack.ambulance.dialogs;
+
+import android.app.Activity;
+import android.support.design.widget.Snackbar;
+import android.util.Log;
+import android.view.View;
+
+/**
+ * Created by mauricio on 3/22/2018.
+ */
+
+public class AlertSnackbar {
+
+ private static String TAG = AlertSnackbar.class.getSimpleName();
+
+ private final View view;
+
+ public AlertSnackbar(String TAG) {
+ this.TAG = TAG;
+ this.view = null;
+ }
+
+ public AlertSnackbar(View view) {
+ this.view = view.findViewById(android.R.id.content);
+ }
+
+ public AlertSnackbar(Activity activity) {
+ this.view = activity.findViewById(android.R.id.content);
+ }
+
+ public void setTag(String TAG) {
+ this.TAG = TAG;
+ }
+
+ public void alert(String message) {
+ alert(message, new View.OnClickListener() {
+ @Override
+ public void onClick(View view) { /* do nothing */ }
+ });
+ }
+
+ public void alert(String message, View.OnClickListener onOkClickAction) {
+
+ // Log message
+ Log.d(TAG, message);
+
+ // Display alert as snack bar
+ if (view != null)
+ Snackbar.make(view, message, Snackbar.LENGTH_INDEFINITE)
+ .setAction(android.R.string.ok, onOkClickAction)
+ .show();
+
+
+ }
+
+}
diff --git a/app/src/main/java/org/emstrack/ambulance/dialogs/LogoutDialog.java b/app/src/main/java/org/emstrack/ambulance/dialogs/LogoutDialog.java
new file mode 100644
index 00000000..184f0614
--- /dev/null
+++ b/app/src/main/java/org/emstrack/ambulance/dialogs/LogoutDialog.java
@@ -0,0 +1,60 @@
+package org.emstrack.ambulance.dialogs;
+
+import android.app.Activity;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.support.v7.app.AlertDialog;
+import android.util.Log;
+
+import org.emstrack.ambulance.LoginActivity;
+import org.emstrack.ambulance.R;
+
+/**
+ * Created by devinhickey on 5/24/17.
+ */
+
+public class LogoutDialog {
+
+ private static final String TAG = LogoutDialog.class.getSimpleName();
+
+ public static AlertDialog newInstance(final Activity activity) {
+
+ // Logout dialog
+ AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(activity);
+
+ alertDialogBuilder.setTitle(R.string.alert_warning_title);
+ alertDialogBuilder.setMessage(R.string.logout_confirm);
+
+ // Cancel button
+ alertDialogBuilder.setNegativeButton(
+ R.string.alert_button_negative_text,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ /* do nothing */
+ }
+ });
+
+ // Create the OK button that logs user out
+ alertDialogBuilder.setPositiveButton(
+ R.string.alert_button_positive_text,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+
+ Log.i(TAG,"LogoutDialog: OK Button Clicked");
+
+ // Start login activity
+ Intent loginIntent = new Intent(activity, LoginActivity.class);
+ loginIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP|Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ loginIntent.setAction(LoginActivity.LOGOUT);
+ activity.startActivity(loginIntent);
+
+ }
+ });
+
+ return alertDialogBuilder.create();
+
+ }
+
+}
diff --git a/app/src/main/java/org/emstrack/ambulance/fragments/AmbulanceFragment.java b/app/src/main/java/org/emstrack/ambulance/fragments/AmbulanceFragment.java
new file mode 100644
index 00000000..2e796eb4
--- /dev/null
+++ b/app/src/main/java/org/emstrack/ambulance/fragments/AmbulanceFragment.java
@@ -0,0 +1,338 @@
+package org.emstrack.ambulance.fragments;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.support.v4.content.LocalBroadcastManager;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.Spinner;
+import android.widget.Switch;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import org.emstrack.ambulance.services.AmbulanceForegroundService;
+import org.emstrack.ambulance.MainActivity;
+import org.emstrack.ambulance.R;
+import org.emstrack.models.Ambulance;
+import org.emstrack.mqtt.MqttProfileClient;
+
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.TimeZone;
+
+public class AmbulanceFragment extends Fragment implements AdapterView.OnItemSelectedListener {
+
+ private static final String TAG = AmbulanceFragment.class.getSimpleName();;
+
+ private View view;
+
+ private Spinner statusSpinner;
+
+ private TextView latitudeText;
+ private TextView longitudeText;
+ private TextView timestampText;
+ private TextView orientationText;
+
+ private Switch startTrackingSwitch;
+
+ private TextView capabilityText;
+
+ private TextView commentText;
+
+ private TextView updatedOnText;
+
+ private Map ambulanceStatus;
+ private List ambulanceStatusList;
+ private ArrayList ambulanceStatusColorList;
+ private Map ambulanceCapabilities;
+ private List ambulanceCapabilityList;
+
+ AmbulancesUpdateBroadcastReceiver receiver;
+
+ public class AmbulancesUpdateBroadcastReceiver extends BroadcastReceiver {
+
+ @Override
+ public void onReceive(Context context, Intent intent ) {
+ if (intent != null) {
+ final String action = intent.getAction();
+ if (action.equals(AmbulanceForegroundService.BroadcastActions.AMBULANCE_UPDATE)) {
+
+ Log.i(TAG, "AMBULANCE_UPDATE");
+ update(AmbulanceForegroundService.getAmbulance());
+
+ }
+
+ }
+ }
+ };
+
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+
+ // inflate view
+ view = inflater.inflate(R.layout.fragment_ambulance, container, false);
+
+ try {
+
+ // Get settings, status and capabilities
+ final MqttProfileClient profileClient = AmbulanceForegroundService.getProfileClient();
+
+ ambulanceStatus = profileClient.getSettings().getAmbulanceStatus();
+ ambulanceStatusList = new ArrayList(ambulanceStatus.values());
+ Collections.sort(ambulanceStatusList);
+
+ int colors [] = getResources().getIntArray(R.array.statusColors);
+ ambulanceStatusColorList = new ArrayList();
+ for (int i = 0; i < ambulanceStatusList.size(); i++) {
+ ambulanceStatusColorList.add(colors[i]);
+ }
+
+ ambulanceCapabilities = profileClient.getSettings().getAmbulanceCapability();
+ ambulanceCapabilityList = new ArrayList(ambulanceCapabilities.values());
+ Collections.sort(ambulanceCapabilityList);
+
+ } catch (AmbulanceForegroundService.ProfileClientException e) {
+
+ ambulanceStatusList = new ArrayList();
+
+ }
+
+ // Retrieve location
+ latitudeText = (TextView) view.findViewById(R.id.latitudeText);
+ longitudeText = (TextView) view.findViewById(R.id.longitudeText);
+ timestampText = (TextView) view.findViewById(R.id.timestampText);
+ orientationText = (TextView) view.findViewById(R.id.orientationText);
+
+ // Other text
+ capabilityText = (TextView) view.findViewById(R.id.capabilityText);
+ commentText = (TextView) view.findViewById(R.id.commentText);
+ updatedOnText = (TextView) view.findViewById(R.id.updatedOnText);
+
+ // Set status spinner
+ statusSpinner = (Spinner) view.findViewById(R.id.statusSpinner);
+ // Create an ArrayAdapter using the string array and a default spinner layout
+ ArrayAdapter statusSpinnerAdapter =
+ new ArrayAdapter(getContext(),
+ R.layout.status_spinner_item,
+ ambulanceStatusList) {
+
+ @Override
+ public View getView(int pos, View view, ViewGroup parent)
+ {
+ Context context = AmbulanceFragment.this.getContext();
+ LayoutInflater inflater = LayoutInflater.from(context);
+ view = inflater.inflate(R.layout.status_spinner_dropdown_item, null);
+ TextView textView = (TextView) view.findViewById(R.id.statusSpinnerDropdownItemText);
+ textView.setBackgroundColor(ambulanceStatusColorList.get(pos));
+ textView.setTextSize(context.getResources().getDimension(R.dimen.statusTextSize));
+ textView.setText(ambulanceStatusList.get(pos));
+ return view;
+ }
+
+ @Override
+ public View getDropDownView(int pos, View view, ViewGroup parent) {
+ return getView(pos, view, parent);
+ }
+
+ };
+ //statusSpinnerAdapter.setDropDownViewResource(R.layout.status_spinner_dropdown_item);
+ statusSpinner.setAdapter(statusSpinnerAdapter);
+
+ // Update ambulance
+ Ambulance ambulance = AmbulanceForegroundService.getAmbulance();
+ if (ambulance != null) {
+
+ update(ambulance);
+
+ }
+
+ // Process change of status
+ statusSpinner.setOnItemSelectedListener(this);
+
+ return view;
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+
+ // Update ambulance
+ Ambulance ambulance = AmbulanceForegroundService.getAmbulance();
+ if (ambulance != null)
+ update(ambulance);
+
+ // Register receiver
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(AmbulanceForegroundService.BroadcastActions.AMBULANCE_UPDATE);
+ receiver = new AmbulanceFragment.AmbulancesUpdateBroadcastReceiver();
+ getLocalBroadcastManager().registerReceiver(receiver, filter);
+
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+
+ // Unregister receiver
+ if (receiver != null) {
+ getLocalBroadcastManager().unregisterReceiver(receiver);
+ receiver = null;
+ }
+
+ }
+
+ public void update(Ambulance ambulance) {
+
+ // set spinner only if position changed
+ // this helps to prevent a possible server loop
+ int position = ambulanceStatusList.indexOf(ambulanceStatus.get(ambulance.getStatus()));
+ int currentPosition = statusSpinner.getSelectedItemPosition();
+ if (currentPosition != position) {
+
+ Log.i(TAG,"Spinner changed from " + currentPosition + " to " + position);
+
+ // set spinner
+ setSpinner(position);
+
+ } else {
+
+ Log.i(TAG, "Spinner continues to be at position " + position + ". Skipping update");
+
+ }
+
+ // set identifier
+ ((MainActivity) getActivity()).setAmbulanceButtonText(ambulance.getIdentifier());
+
+ // set location
+ latitudeText.setText(String.format("%.6f", ambulance.getLocation().getLatitude()));
+ longitudeText.setText(String.format("%.6f", ambulance.getLocation().getLongitude()));
+ orientationText.setText(String.format("%.1f", ambulance.getOrientation()));
+ timestampText.setText(ambulance.getTimestamp().toString());
+
+ // set status and comment
+ commentText.setText(ambulance.getComment());
+ updatedOnText.setText(ambulance.getUpdatedOn().toString());
+
+ // set capability
+ capabilityText.setText(ambulanceCapabilities.get(ambulance.getCapability()));
+
+ }
+
+ public void setSpinner(int position) {
+
+ // temporarily disconnect listener to prevent loop
+ Log.i(TAG, "Suppressing listener");
+ statusSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
+ @Override
+ public void onItemSelected(AdapterView> parent, View view, int position, long id) {
+ /* do nothing */
+ Log.i(TAG,"Ignoring change in spinner. Position '" + position + "' selected.");
+ }
+
+ @Override
+ public void onNothingSelected(AdapterView> parent) {
+ Log.i(TAG,"Ignoring change in spinner. Nothing selected.");
+ }
+ });
+
+ // update spinner
+ statusSpinner.setSelection(position, false);
+
+ // connect listener
+ // this is tricky, see
+ // https://stackoverflow.com/questions/2562248/how-to-keep-onitemselected-from-firing-off-on-a-newly-instantiated-spinner
+ statusSpinner.post(new Runnable() {
+ public void run() {
+ Log.i(TAG, "Restoring listener");
+ statusSpinner.setOnItemSelectedListener(AmbulanceFragment.this);
+ }
+ });
+
+ }
+
+ @Override
+ public void onItemSelected(AdapterView> parent, View view, int position, long id) {
+
+ Log.i(TAG, "Item '" + position + "' selected.");
+
+ // Should only update on server as a result of user interaction
+ // Otherwise this will create a loop with mqtt updating ambulance
+ // TODO: Debug spinner multiple updates
+ // This may not be easy with the updates being called from a service
+
+ Log.i(TAG, "Processing status spinner update.");
+
+ Ambulance ambulance = AmbulanceForegroundService.getAmbulance();
+ if ((ambulance != null) && !((MainActivity) getActivity()).canWrite()) {
+
+ // Toast to warn user
+ Toast.makeText(getContext(), R.string.cantModifyAmbulance, Toast.LENGTH_LONG).show();
+
+ // set spinner
+ int oldPosition = ambulanceStatusList.indexOf(ambulanceStatus.get(ambulance.getStatus()));
+ setSpinner(oldPosition);
+
+ // Return
+ return;
+
+ }
+
+ // Get status from spinner
+ String status = (String) parent.getItemAtPosition(position);
+
+ // Search for entry in ambulanceStatus map
+ String statusCode = "";
+ for (Map.Entry entry : ambulanceStatus.entrySet()) {
+ if (status.equals(entry.getValue())) {
+ statusCode = entry.getKey();
+ break;
+ }
+ }
+
+ // format timestamp
+ DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
+ df.setTimeZone(TimeZone.getTimeZone("UTC"));
+ String timestamp = df.format(new Date());
+
+ // Set update string
+ String updateString = "{\"status\":\"" + statusCode + "\",\"timestamp\":\"" + timestamp + "\"}";
+
+ // Update on server
+ // TODO: Update along with locations because it will be recorded with
+ // the wrong location on the server
+ Intent intent = new Intent(getContext(), AmbulanceForegroundService.class);
+ intent.setAction(AmbulanceForegroundService.Actions.UPDATE_AMBULANCE);
+ Bundle bundle = new Bundle();
+ bundle.putString("UPDATE", updateString);
+ intent.putExtras(bundle);
+ getActivity().startService(intent);
+
+ }
+
+ @Override
+ public void onNothingSelected(AdapterView> parent) {
+ Log.i(TAG, "Nothing selected: this should never happen.");
+ }
+
+ /**
+ * Get LocalBroadcastManager
+ *
+ * @return the LocalBroadcastManager
+ */
+ private LocalBroadcastManager getLocalBroadcastManager() {
+ return LocalBroadcastManager.getInstance(getContext());
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/emstrack/ambulance/fragments/HospitalFragment.java b/app/src/main/java/org/emstrack/ambulance/fragments/HospitalFragment.java
new file mode 100644
index 00000000..3479ec27
--- /dev/null
+++ b/app/src/main/java/org/emstrack/ambulance/fragments/HospitalFragment.java
@@ -0,0 +1,173 @@
+package org.emstrack.ambulance.fragments;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.support.v4.content.LocalBroadcastManager;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import org.emstrack.ambulance.dialogs.AlertSnackbar;
+import org.emstrack.ambulance.services.AmbulanceForegroundService;
+import org.emstrack.ambulance.R;
+import org.emstrack.ambulance.adapters.HospitalExpandableRecyclerAdapter;
+import org.emstrack.ambulance.models.HospitalExpandableGroup;
+import org.emstrack.ambulance.services.OnServiceComplete;
+import org.emstrack.models.Hospital;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+public class HospitalFragment extends Fragment {
+
+ private static final String TAG = HospitalFragment.class.getSimpleName();
+
+ View rootView;
+ RecyclerView recyclerView;
+ HospitalsUpdateBroadcastReceiver receiver;
+
+ public class HospitalsUpdateBroadcastReceiver extends BroadcastReceiver {
+
+ @Override
+ public void onReceive(Context context, Intent intent ) {
+ if (intent != null) {
+ final String action = intent.getAction();
+ if (action.equals(AmbulanceForegroundService.BroadcastActions.HOSPITALS_UPDATE)) {
+
+ Log.i(TAG, "HOSPITALS_UPDATE");
+ update(AmbulanceForegroundService.getHospitals());
+
+ }
+ }
+ }
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+
+ rootView = inflater.inflate(R.layout.fragment_hospital, container, false);
+ recyclerView = rootView.findViewById(R.id.recycler_view);
+
+ Map hospitals = AmbulanceForegroundService.getHospitals();
+
+ // Retrieve hospitals?
+ if (hospitals == null) {
+
+ // Retrieve hospitals
+ Intent hospitalsIntent = new Intent(getContext(), AmbulanceForegroundService.class);
+ hospitalsIntent.setAction(AmbulanceForegroundService.Actions.GET_HOSPITALS);
+
+ // What to do when GET_HOSPITALS service completes?
+ new OnServiceComplete(getContext(),
+ AmbulanceForegroundService.BroadcastActions.SUCCESS,
+ AmbulanceForegroundService.BroadcastActions.FAILURE,
+ hospitalsIntent) {
+
+ @Override
+ public void onSuccess(Bundle extras) {
+
+ Log.i(TAG,"Got all hospitals.");
+
+ // update hospitals
+ update(AmbulanceForegroundService.getHospitals());
+
+ }
+ }
+ .setFailureMessage(getString(R.string.couldNotRetrieveHospitals))
+ .setAlert(new AlertSnackbar(getActivity()));
+
+ } else {
+
+ // Already have hospitals
+ update(hospitals);
+
+ }
+
+ return rootView;
+
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+
+ // Register receiver
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(AmbulanceForegroundService.BroadcastActions.HOSPITALS_UPDATE);
+ receiver = new HospitalsUpdateBroadcastReceiver();
+ getLocalBroadcastManager().registerReceiver(receiver, filter);
+
+ // update UI
+ update(AmbulanceForegroundService.getHospitals());
+
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+
+ // Unregister receiver
+ if (receiver != null) {
+ getLocalBroadcastManager().unregisterReceiver(receiver);
+ receiver = null;
+ }
+
+ }
+
+ /**
+ * Update hospital list
+ *
+ * @param hospitals list of hospitals
+ */
+ public void update(Map hospitals) {
+
+ // fast return if no hospitals
+ if (hospitals == null)
+ return;
+
+ Log.i(TAG,"Updating hospitals UI.");
+
+ // Loop through hospitals
+ final List hospitalExpandableGroup = new ArrayList();
+
+ // Loop over all hospitals
+ for (Map.Entry entry : hospitals.entrySet()) {
+
+ // Get hospital
+ Hospital hospital = entry.getValue();
+
+ // Add to to expandable group
+ hospitalExpandableGroup.add(
+ new HospitalExpandableGroup(hospital.getName(),
+ hospital.getHospitalequipmentSet(),
+ hospital));
+
+ }
+
+ // Install fragment
+ LinearLayoutManager linearLayoutManager = new LinearLayoutManager(getContext());
+ HospitalExpandableRecyclerAdapter adapter =
+ new HospitalExpandableRecyclerAdapter(hospitalExpandableGroup);
+ recyclerView.setLayoutManager(linearLayoutManager);
+ recyclerView.setAdapter(adapter);
+
+ }
+
+ /**
+ * Get LocalBroadcastManager
+ *
+ * @return the LocalBroadcastManager
+ */
+ private LocalBroadcastManager getLocalBroadcastManager() {
+ return LocalBroadcastManager.getInstance(getContext());
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/emstrack/ambulance/fragments/MapFragment.java b/app/src/main/java/org/emstrack/ambulance/fragments/MapFragment.java
new file mode 100644
index 00000000..f3be5aee
--- /dev/null
+++ b/app/src/main/java/org/emstrack/ambulance/fragments/MapFragment.java
@@ -0,0 +1,752 @@
+package org.emstrack.ambulance.fragments;
+
+import android.app.Activity;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.drawable.Drawable;
+import android.hardware.SensorManager;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.v4.app.Fragment;
+import android.support.v4.content.ContextCompat;
+import android.support.v4.content.LocalBroadcastManager;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.OrientationEventListener;
+import android.view.Surface;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+
+import com.google.android.gms.maps.CameraUpdateFactory;
+import com.google.android.gms.maps.GoogleMap;
+import com.google.android.gms.maps.OnMapReadyCallback;
+import com.google.android.gms.maps.SupportMapFragment;
+import com.google.android.gms.maps.model.BitmapDescriptor;
+import com.google.android.gms.maps.model.BitmapDescriptorFactory;
+import com.google.android.gms.maps.model.CameraPosition;
+import com.google.android.gms.maps.model.LatLng;
+import com.google.android.gms.maps.model.LatLngBounds;
+import com.google.android.gms.maps.model.Marker;
+import com.google.android.gms.maps.model.MarkerOptions;
+
+import org.emstrack.ambulance.dialogs.AlertSnackbar;
+import org.emstrack.ambulance.services.AmbulanceForegroundService;
+import org.emstrack.ambulance.R;
+import org.emstrack.ambulance.services.OnServiceComplete;
+import org.emstrack.models.Ambulance;
+import org.emstrack.models.Hospital;
+import org.emstrack.models.Location;
+import org.emstrack.mqtt.MqttProfileClient;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+import static android.content.ContentValues.TAG;
+
+// TODO: Implement listener to ambulance changes
+
+public class MapFragment extends Fragment implements OnMapReadyCallback, GoogleMap.OnCameraIdleListener {
+
+ View rootView;
+ private Map ambulanceStatus;
+ private Map ambulanceMarkers;
+ private HashMap hospitalMarkers;
+
+ private ImageView showLocationButton;
+ private boolean centerAmbulances = false;
+
+ private ImageView showAmbulancesButton;
+ private boolean showAmbulances = false;
+
+ private ImageView showHospitalsButton;
+ private boolean showHospitals = false;
+
+ private boolean myLocationEnabled;
+ private boolean useMyLocation = false;
+
+ private float defaultZoom = 15;
+ private int defaultPadding = 50;
+
+ private float zoomLevel = defaultZoom;
+
+ private GoogleMap googleMap;
+ private AmbulancesUpdateBroadcastReceiver receiver;
+ private int screenOrientation;
+ private OrientationEventListener orientationListener;
+
+ static float ROTATIONS[] = { 0.f, 90.f, 180.f, 270.f };
+ private Location defaultLocation;
+
+ static int degreesToRotation(int degrees) {
+ if (degrees > 315 || degrees < 45)
+ return Surface.ROTATION_0;
+ else if (degrees > 45 && degrees < 135)
+ return Surface.ROTATION_90;
+ else if (degrees > 135 && degrees < 225)
+ return Surface.ROTATION_180;
+ else // if (degrees > 225 && degrees < 315)
+ return Surface.ROTATION_270;
+ };
+
+ public class AmbulancesUpdateBroadcastReceiver extends BroadcastReceiver {
+
+ @Override
+ public void onReceive(Context context, Intent intent ) {
+ if (intent != null) {
+ final String action = intent.getAction();
+ assert action != null;
+ if (action.equals(AmbulanceForegroundService.BroadcastActions.AMBULANCE_UPDATE)) {
+
+ if (centerAmbulances) {
+
+ Log.i(TAG, "AMBULANCE_UPDATE");
+
+ // center ambulances
+ centerAmbulances();
+
+ }
+
+ } else if (action.equals(AmbulanceForegroundService.BroadcastActions.OTHER_AMBULANCES_UPDATE)) {
+
+ Log.i(TAG, "OTHER_AMBULANCES_UPDATE");
+
+ // update markers without centering
+ updateMarkers();
+
+ }
+ }
+ }
+ }
+
+ public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+
+ rootView = inflater.inflate(R.layout.fragment_map, container, false);
+
+ // Retrieve location button
+ showLocationButton = (ImageView) rootView.findViewById(R.id.showLocationButton);
+ showLocationButton.setOnLongClickListener(new View.OnLongClickListener() {
+ @Override
+ public boolean onLongClick(View v) {
+
+ // toggle show ambulances
+ centerAmbulances = !centerAmbulances;
+
+ // Switch color
+ if (centerAmbulances)
+ showLocationButton.setBackgroundColor(getResources().getColor(R.color.mapButtonOn));
+ else
+ showLocationButton.setBackgroundColor(getResources().getColor(R.color.mapButtonOff));
+
+ Log.i(TAG, "Toggle center ambulances: " + (centerAmbulances ? "ON" : "OFF"));
+
+ return true;
+
+ }
+ });
+ showLocationButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+
+ if (googleMap != null) {
+
+ // center ambulances
+ centerAmbulances();
+
+ }
+
+ }
+ });
+
+ // Retrieve ambulance button
+ showAmbulancesButton = (ImageView) rootView.findViewById(R.id.showAmbulancesButton);
+ showAmbulancesButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+
+ // toggle show ambulances
+ showAmbulances = !showAmbulances;
+
+ // Switch color
+ if (showAmbulances)
+ showAmbulancesButton.setBackgroundColor(getResources().getColor(R.color.mapButtonOn));
+ else
+ showAmbulancesButton.setBackgroundColor(getResources().getColor(R.color.mapButtonOff));
+
+ Log.i(TAG, "Toggle show ambulances: " + (showAmbulances ? "ON" : "OFF"));
+
+ if (googleMap != null) {
+
+ if (showAmbulances)
+
+ // retrieve ambulances
+ retrieveAmbulances();
+
+ else {
+
+ // forget ambulances
+ forgetAmbulances();
+
+ // update markers without centering
+ updateMarkers();
+
+ }
+
+ }
+
+
+ }
+ });
+
+ // Retrieve hospitals button
+ showHospitalsButton = (ImageView) rootView.findViewById(R.id.showHospitalsButton);
+ showHospitalsButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+
+ // toggle show hospitals
+ showHospitals = !showHospitals;
+
+ // Switch color
+ if (showHospitals)
+ showHospitalsButton.setBackgroundColor(getResources().getColor(R.color.mapButtonOn));
+ else
+ showHospitalsButton.setBackgroundColor(getResources().getColor(R.color.mapButtonOff));
+
+ Log.i(TAG, "Toggle show hospitals: " + (showHospitals ? "ON" : "OFF"));
+
+ if (googleMap != null) {
+
+ if (!showHospitals) {
+
+ // Clear markers
+ Iterator> iter = hospitalMarkers.entrySet().iterator();
+ while (iter.hasNext())
+ {
+ // get entry
+ Map.Entry entry = iter.next();
+
+ // remove from map
+ entry.getValue().remove();
+
+ // remove from collection
+ iter.remove();
+
+ }
+
+ }
+
+ // update markers without centering
+ updateMarkers();
+
+ }
+
+ }
+ });
+
+ // Initialize markers maps
+ ambulanceMarkers = new HashMap<>();
+ hospitalMarkers = new HashMap<>();
+
+ // Get settings, status and capabilities
+ try {
+
+ final MqttProfileClient profileClient = AmbulanceForegroundService.getProfileClient();
+ ambulanceStatus = profileClient.getSettings().getAmbulanceStatus();
+ defaultLocation = profileClient.getSettings().getDefaults().getLocation();
+
+ } catch (AmbulanceForegroundService.ProfileClientException e) {
+
+ ambulanceStatus = new HashMap();
+ defaultLocation = new Location(0,0);
+
+ }
+
+ // Initialize screen rotation
+ if (rootView.getDisplay() != null)
+ screenOrientation = rootView.getDisplay().getRotation();
+ else
+ screenOrientation = Surface.ROTATION_0;
+
+ // Initialize map
+ SupportMapFragment mapFragment = (SupportMapFragment) getChildFragmentManager()
+ .findFragmentById(R.id.map);
+ mapFragment.getMapAsync(this);
+
+ // Orientation listener
+ orientationListener = new OrientationEventListener(getContext(), SensorManager.SENSOR_DELAY_NORMAL) {
+
+ @Override
+ public void onOrientationChanged(int rotation) {
+
+ Activity activity = getActivity();
+ if (activity != null) {
+
+ //Log.d(TAG,"onOrientationChanged");
+ if (rotation != ORIENTATION_UNKNOWN) {
+ screenOrientation = degreesToRotation(rotation);
+ //Log.d(TAG, "rotation = " + rotation + ", screenOrientation = " + screenOrientation);
+ }
+ }
+
+ }
+
+ };
+
+ // Enable sensor
+ if (orientationListener.canDetectOrientation())
+ orientationListener.enable();
+ else
+ orientationListener.disable();
+
+ return rootView;
+
+ }
+
+ public void retrieveAmbulances() {
+
+ // Get ambulances
+ Map ambulances = AmbulanceForegroundService.getOtherAmbulances();
+
+ if (ambulances == null) {
+
+ Log.i(TAG,"No ambulances.");
+
+ if (showAmbulances) {
+
+ Log.i(TAG,"Retrieving ambulances...");
+
+ // Disable button
+ showAmbulancesButton.setEnabled(false);
+
+ // Retrieve ambulances first
+ Intent ambulancesIntent = new Intent(getContext(), AmbulanceForegroundService.class);
+ ambulancesIntent.setAction(AmbulanceForegroundService.Actions.GET_AMBULANCES);
+
+ // What to do when GET_AMBULANCES service completes?
+ new OnServiceComplete(getContext(),
+ AmbulanceForegroundService.BroadcastActions.SUCCESS,
+ AmbulanceForegroundService.BroadcastActions.FAILURE,
+ ambulancesIntent) {
+
+ @Override
+ public void onSuccess(Bundle extras) {
+
+ Log.i(TAG,"Got them all. Updating markers.");
+
+ // update markers and center bounds
+ centerMap(updateMarkers());
+
+ // Enable button
+ showAmbulancesButton.setEnabled(true);
+
+ }
+ }
+ .setFailureMessage(getString(R.string.couldNotRetrieveAmbulances))
+ .setAlert(new AlertSnackbar(getActivity()));
+
+ }
+
+ } else if (showAmbulances) {
+
+ Log.i(TAG,"Already have ambulances. Updating markers.");
+
+ // Already have ambulances
+
+ // update markers and center bounds
+ centerMap(updateMarkers());
+
+ }
+
+ }
+
+ public void forgetAmbulances() {
+
+ // disable button
+ showAmbulancesButton.setEnabled(false);
+
+ // Clear markers
+ Iterator> iter = ambulanceMarkers.entrySet().iterator();
+ while (iter.hasNext())
+ {
+ // get entry
+ Map.Entry entry = iter.next();
+
+ // remove from map
+ entry.getValue().remove();
+
+ // remove from collection
+ iter.remove();
+
+ }
+
+ // Unsubscribe to ambulances
+ Intent intent = new Intent(getActivity(), AmbulanceForegroundService.class);
+ intent.setAction(AmbulanceForegroundService.Actions.STOP_AMBULANCES);
+
+ // What to do when service completes?
+ new OnServiceComplete(getContext(),
+ AmbulanceForegroundService.BroadcastActions.SUCCESS,
+ AmbulanceForegroundService.BroadcastActions.FAILURE,
+ intent) {
+
+ @Override
+ public void onSuccess(Bundle extras) {
+ Log.i(TAG, "onSuccess");
+
+ showAmbulancesButton.setEnabled(true);
+
+ }
+
+ }
+ .setFailureMessage(getString(R.string.couldNotUnsubscribeToAmbulances))
+ .setAlert(new AlertSnackbar(getActivity()));
+
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+
+ // Enable orientation listener
+ if (orientationListener.canDetectOrientation())
+ orientationListener.enable();
+ else
+ orientationListener.disable();
+
+ // Register receiver
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(AmbulanceForegroundService.BroadcastActions.OTHER_AMBULANCES_UPDATE);
+ filter.addAction(AmbulanceForegroundService.BroadcastActions.AMBULANCE_UPDATE);
+ receiver = new AmbulancesUpdateBroadcastReceiver();
+ getLocalBroadcastManager().registerReceiver(receiver, filter);
+
+ // Switch colors
+ if (showAmbulances)
+ showAmbulancesButton.setBackgroundColor(getResources().getColor(R.color.mapButtonOn));
+ else
+ showAmbulancesButton.setBackgroundColor(getResources().getColor(R.color.mapButtonOff));
+
+ if (showHospitals)
+ showHospitalsButton.setBackgroundColor(getResources().getColor(R.color.mapButtonOn));
+ else
+ showHospitalsButton.setBackgroundColor(getResources().getColor(R.color.mapButtonOff));
+
+ if (centerAmbulances)
+ showLocationButton.setBackgroundColor(getResources().getColor(R.color.mapButtonOn));
+ else
+ showLocationButton.setBackgroundColor(getResources().getColor(R.color.mapButtonOff));
+
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+
+ // Unregister receiver
+ if (receiver != null) {
+ getLocalBroadcastManager().unregisterReceiver(receiver);
+ receiver = null;
+ }
+
+ // disable orientation listener
+ orientationListener.disable();
+
+ }
+
+ @Override
+ public void onCameraIdle() {
+ zoomLevel = googleMap.getCameraPosition().zoom;
+ }
+
+ @Override
+ public void onMapReady(GoogleMap googleMap) {
+
+ // save map
+ this.googleMap = googleMap;
+
+ myLocationEnabled = false;
+ if (AmbulanceForegroundService.canUpdateLocation()) {
+
+ if (useMyLocation) {
+
+ // Use google map's my location
+
+ try {
+
+ Log.i(TAG, "Enable my location on google map.");
+ googleMap.setMyLocationEnabled(true);
+ myLocationEnabled = true;
+
+ } catch (SecurityException e) {
+ Log.i(TAG, "Could not enable my location on google map.");
+ }
+
+ }
+
+ }
+
+ // Add listener to track zoom
+ googleMap.setOnCameraIdleListener(this);
+
+ if (showAmbulances) {
+
+ // retrieve ambulances
+ retrieveAmbulances();
+
+ } else {
+
+ // Update markers and center map
+ centerMap(updateMarkers());
+
+ }
+
+ }
+
+
+
+ public void centerMap(LatLngBounds bounds) {
+
+ if (ambulanceMarkers.size() > 0) {
+
+ // Move camera
+ if (ambulanceMarkers.size() == 1) {
+
+ Ambulance ambulance = AmbulanceForegroundService.getAmbulance();
+ if (ambulance != null) {
+
+ Location location = ambulance.getLocation();
+ LatLng latLng = new LatLng(location.getLatitude(), location.getLongitude());
+
+ googleMap.animateCamera(CameraUpdateFactory.newLatLngZoom(latLng, zoomLevel));
+
+ return;
+ }
+
+ } else if (bounds != null) {
+
+ // move camera
+ googleMap.animateCamera(CameraUpdateFactory.newLatLngBounds(bounds, defaultPadding));
+
+ return;
+
+ }
+
+ }
+
+ // Otherwise center at default location
+ LatLng latLng = new LatLng(defaultLocation.getLatitude(), defaultLocation.getLongitude());
+
+ googleMap.moveCamera(CameraUpdateFactory.newLatLngZoom(latLng, zoomLevel));
+
+ }
+
+ public LatLngBounds updateMarkers() {
+
+ // fast return
+ if (googleMap == null)
+ return null;
+
+ // Assemble marker bounds
+ LatLngBounds.Builder builder = new LatLngBounds.Builder();
+
+ // Update hospitals
+ if (showHospitals) {
+
+ // Get hospitals
+ Map hospitals = AmbulanceForegroundService.getHospitals();
+
+ if (hospitals != null) {
+
+ // Loop over all hospitals
+ for (Map.Entry entry : hospitals.entrySet()) {
+
+ // Get hospital
+ Hospital hospital = entry.getValue();
+
+ // Add marker for hospital
+ Marker marker = addMarkerForHospital(hospital);
+
+ // Add to bound builder
+ builder.include(marker.getPosition());
+
+ }
+ }
+
+ }
+
+ // Update ambulances
+ if (showAmbulances) {
+
+ // Get ambulances
+ Map ambulances = AmbulanceForegroundService.getOtherAmbulances();
+
+ if (ambulances != null) {
+
+ // Loop over all ambulances
+ for (Map.Entry entry : ambulances.entrySet()) {
+
+ // Get ambulance
+ Ambulance ambulance = entry.getValue();
+
+ // Add marker for ambulance
+ Marker marker = addMarkerForAmbulance(ambulance);
+
+ // Add to bound builder
+ builder.include(marker.getPosition());
+
+ }
+ }
+
+ }
+
+ // Handle my location?
+ if (!useMyLocation) {
+
+ Ambulance ambulance = AmbulanceForegroundService.getAmbulance();
+ if (ambulance != null) {
+
+ // Add marker for ambulance
+ Marker marker = addMarkerForAmbulance(ambulance);
+
+ // Add to bound builder
+ builder.include(marker.getPosition());
+
+ }
+
+ }
+
+ // Calculate bounds and return
+ try {
+ return builder.build();
+ } catch (IllegalStateException e) {
+ return null;
+ }
+
+ }
+
+ public Marker addMarkerForAmbulance(Ambulance ambulance) {
+
+ Log.d(TAG,"Adding marker for ambulance " + ambulance.getIdentifier());
+
+ // Find new location
+ Location location = ambulance.getLocation();
+ LatLng latLng = new LatLng(location.getLatitude(), location.getLongitude());
+
+ // Marker exist?
+ Marker marker;
+ if (ambulanceMarkers.containsKey(ambulance.getId())) {
+
+ // Update marker
+ marker = ambulanceMarkers.get(ambulance.getId());
+ marker.setPosition(latLng);
+ marker.setRotation((float) ambulance.getOrientation());
+ marker.setSnippet(ambulanceStatus.get(ambulance.getStatus()));
+
+ } else {
+
+ // Create marker
+ marker = googleMap.addMarker(new MarkerOptions()
+ .position(latLng)
+ .icon(bitmapDescriptorFromVector(getActivity(), R.drawable.ambulance_blue, 0.1))
+ .anchor(0.5F,0.5F)
+ .rotation((float) ambulance.getOrientation())
+ .flat(true)
+ .title(ambulance.getIdentifier())
+ .snippet(ambulanceStatus.get(ambulance.getStatus())));
+
+ // Save marker
+ ambulanceMarkers.put(ambulance.getId(), marker);
+
+ }
+
+ return marker;
+ }
+
+ public Marker addMarkerForHospital(Hospital hospital) {
+
+ Log.d(TAG,"Adding marker for hospital " + hospital.getName());
+
+ // Find new location
+ Location location = hospital.getLocation();
+ LatLng latLng = new LatLng(location.getLatitude(), location.getLongitude());
+
+ // Marker exist?
+ Marker marker;
+ if (hospitalMarkers.containsKey(hospital.getId())) {
+
+ // Update marker
+ marker = hospitalMarkers.get(hospital.getId());
+ marker.setPosition(latLng);;
+
+ } else {
+
+ // Create marker
+ marker = googleMap.addMarker(new MarkerOptions()
+ .position(latLng)
+ .icon(bitmapDescriptorFromVector(getActivity(), R.drawable.ic_hospital_15, 1))
+ .anchor(0.5F,0.5F)
+ .flat(true)
+ .title(hospital.getName()));
+
+ // Save marker
+ hospitalMarkers.put(hospital.getId(), marker);
+
+ }
+
+ return marker;
+ }
+
+ public void centerAmbulances() {
+
+ Ambulance ambulance = AmbulanceForegroundService.getAmbulance();
+ if (ambulance != null) {
+
+ Location location = ambulance.getLocation();
+ LatLng latLng = new LatLng(location.getLatitude(), location.getLongitude());
+
+ // Add marker for ambulance
+ Marker marker = addMarkerForAmbulance(ambulance);
+
+ // Center and orient map
+ CameraPosition currentPlace = new CameraPosition.Builder()
+ .target(latLng)
+ .bearing((float) ambulance.getOrientation() + ROTATIONS[screenOrientation])
+ .zoom(zoomLevel)
+ .build();
+ googleMap.animateCamera(CameraUpdateFactory.newCameraPosition(currentPlace));
+
+ }
+
+ }
+
+ /*
+ * This is from
+ * https://stackoverflow.com/questions/42365658/custom-marker-in-google-maps-in-android-with-vector-asset-icon
+ */
+ private BitmapDescriptor bitmapDescriptorFromVector(Context context, int vectorResId, double scale) {
+ Drawable vectorDrawable = ContextCompat.getDrawable(context, vectorResId);
+ int width = Math.round((float) Math.ceil(vectorDrawable.getIntrinsicWidth()*scale));
+ int height = Math.round((float) Math.ceil(vectorDrawable.getIntrinsicHeight()*scale));
+ vectorDrawable.setBounds(0, 0, width, height);
+ Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+ Canvas canvas = new Canvas(bitmap);
+ vectorDrawable.draw(canvas);
+ return BitmapDescriptorFactory.fromBitmap(bitmap);
+ }
+
+ /**
+ * Get LocalBroadcastManager
+ *
+ * @return the LocalBroadcastManager
+ */
+ private LocalBroadcastManager getLocalBroadcastManager() {
+ return LocalBroadcastManager.getInstance(getContext());
+ }
+
+}
diff --git a/app/src/main/java/org/emstrack/ambulance/models/HospitalExpandableGroup.java b/app/src/main/java/org/emstrack/ambulance/models/HospitalExpandableGroup.java
new file mode 100644
index 00000000..1e37ee1d
--- /dev/null
+++ b/app/src/main/java/org/emstrack/ambulance/models/HospitalExpandableGroup.java
@@ -0,0 +1,26 @@
+package org.emstrack.ambulance.models;
+
+import com.thoughtbot.expandablerecyclerview.models.ExpandableGroup;
+
+import org.emstrack.models.Hospital;
+import org.emstrack.models.HospitalEquipment;
+
+import java.util.List;
+
+/**
+ * Created by mauricio on 3/11/2018.
+ */
+
+public class HospitalExpandableGroup extends ExpandableGroup {
+
+ private Hospital hospital;
+
+ public HospitalExpandableGroup(String title, List items, Hospital hospital) {
+ super(title, items);
+ this.hospital = hospital;
+ }
+
+ public Hospital getHospital() {
+ return hospital;
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/emstrack/ambulance/services/AmbulanceForegroundService.java b/app/src/main/java/org/emstrack/ambulance/services/AmbulanceForegroundService.java
new file mode 100644
index 00000000..8dd1749b
--- /dev/null
+++ b/app/src/main/java/org/emstrack/ambulance/services/AmbulanceForegroundService.java
@@ -0,0 +1,3263 @@
+package org.emstrack.ambulance.services;
+
+import android.annotation.SuppressLint;
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Color;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.v4.app.NotificationCompat;
+import android.support.v4.app.NotificationManagerCompat;
+import android.support.v4.content.LocalBroadcastManager;
+import android.text.TextUtils;
+import android.util.Log;
+import android.widget.Toast;
+
+import com.google.android.gms.common.api.ApiException;
+import com.google.android.gms.iid.InstanceID;
+import com.google.android.gms.location.FusedLocationProviderClient;
+import com.google.android.gms.location.GeofencingClient;
+import com.google.android.gms.location.GeofencingRequest;
+import com.google.android.gms.location.LocationCallback;
+import com.google.android.gms.location.LocationRequest;
+import com.google.android.gms.location.LocationResult;
+import com.google.android.gms.location.LocationServices;
+import com.google.android.gms.location.LocationSettingsRequest;
+import com.google.android.gms.location.LocationSettingsResponse;
+import com.google.android.gms.location.LocationSettingsStatusCodes;
+import com.google.android.gms.location.SettingsClient;
+import com.google.android.gms.tasks.OnFailureListener;
+import com.google.android.gms.tasks.OnSuccessListener;
+import com.google.gson.FieldNamingPolicy;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+
+import org.eclipse.paho.android.service.MqttAndroidClient;
+import org.eclipse.paho.client.mqttv3.MqttException;
+import org.eclipse.paho.client.mqttv3.MqttMessage;
+import org.emstrack.ambulance.LoginActivity;
+import org.emstrack.ambulance.R;
+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;
+import org.emstrack.mqtt.MqttProfileCallback;
+import org.emstrack.mqtt.MqttProfileClient;
+import org.emstrack.mqtt.MqttProfileMessageCallback;
+
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.TimeZone;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * Created by mauricio on 3/18/2018.
+ */
+
+public class AmbulanceForegroundService extends BroadcastService implements MqttProfileCallback {
+
+ final static String TAG = AmbulanceForegroundService.class.getSimpleName();
+
+ // Rate at which locations should be pulled in
+ // @ 70 mph gives an accuracy of about 30m
+ private static final long UPDATE_INTERVAL = 1 * 1000;
+ private static final long FASTEST_UPDATE_INTERVAL = UPDATE_INTERVAL / 2;
+ // Group all updates to be done every minute
+ private static final long MAX_WAIT_TIME = 60 * 1000;
+
+ // Notification channel
+ public static final int NOTIFICATION_ID = 101;
+ public static final String PRIMARY_CHANNEL = "default";
+ public static final String PRIMARY_CHANNEL_LABEL = "Default channel";
+
+ // Notification id
+ private final static AtomicInteger notificationId = new AtomicInteger(NOTIFICATION_ID + 1);
+
+ // SharedPreferences
+ public static final String PREFERENCES_NAME = "org.emstrack.ambulance";
+ public static final String PREFERENCES_USERNAME = "USERNAME";
+ public static final String PREFERENCES_PASSWORD = "PASSWORD";
+
+ private static final String serverUri = "ssl://cruzroja.ucsd.edu:8883";
+
+ private static MqttProfileClient client;
+ private static Ambulance _ambulance;
+ private static Map _hospitals;
+ private static Map _otherAmbulances;
+ private static LocationUpdate _lastLocation;
+ private static Date _lastServerUpdate;
+ private static boolean _updatingLocation = false;
+ private static boolean _canUpdateLocation = false;
+ private static ArrayList _updateBuffer = new ArrayList<>();
+ private static boolean _reconnecting = false;
+ private static boolean _online = false;
+ private static ReconnectionInformation _reconnectionInformation;
+
+ // hold the callIds of pending calls
+ private static int currentCallId;
+ private static Map pendingCalls;
+
+ // Geofences
+ private final static AtomicInteger geofencesId = new AtomicInteger(1);
+ private static Map _geofences;
+
+ private static LocationSettingsRequest locationSettingsRequest;
+ private static LocationRequest locationRequest;
+
+ private NotificationManager notificationManager;
+
+ private FusedLocationProviderClient fusedLocationClient;
+ private SharedPreferences sharedPreferences;
+
+ public LocationFilter locationFilter = new LocationFilter(null);
+
+ private LocationCallback locationCallback;
+ private GeofencingClient fenceClient;
+ private PendingIntent geofenceIntent;
+
+ 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";
+ public final static String GET_AMBULANCE = "org.emstrack.ambulance.ambulanceforegroundservice.action.GET_AMBULANCE";
+ public final static String GET_AMBULANCES= "org.emstrack.ambulance.ambulanceforegroundservice.action.GET_AMBULANCES";
+ public final static String STOP_AMBULANCES= "org.emstrack.ambulance.ambulanceforegroundservice.action.STOP_AMBULANCES";
+ public final static String GET_HOSPITALS = "org.emstrack.ambulance.ambulanceforegroundservice.action.GET_HOSPITALS";
+ public final static String START_LOCATION_UPDATES = "org.emstrack.ambulance.ambulanceforegroundservice.action.START_LOCATION_UPDATES";
+ public final static String STOP_LOCATION_UPDATES = "org.emstrack.ambulance.ambulanceforegroundservice.action.STOP_LOCATION_UPDATES";
+ public final static String UPDATE_AMBULANCE = "org.emstrack.ambulance.ambulanceforegroundservice.action.UPDATE_AMBULANCE";
+ public final static String UPDATE_NOTIFICATION = "org.emstrack.ambulance.ambulanceforegroundservice.action.UPDATE_NOTIFICATION";
+ 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_ACCEPT = "org.emstrack.ambulance.ambulanceforegroundservice.action.CALL_ACCEPT";
+ public final static String CALL_DECLINE = "org.emstrack.ambulance.ambulanceforegroundservice.action.CALL_DECLINE";
+ public final static String CALL_ONGOING = "org.emstrack.ambulance.ambulanceforegroundservice.action.CALL_ONGOING";
+ public final static String CALL_FINISH = "org.emstrack.ambulance.ambulanceforegroundservice.action.CALL_FINISH";
+ }
+
+ public class BroadcastExtras {
+ public final static String MESSAGE = "org.emstrack.ambulance.ambulanceforegroundservice.broadcastextras.MESSAGE";
+ public final static String GEOFENCE_TRANSITION = "org.emstrack.ambulance.ambulanceforegroundservice.broadcastextras.GEOFENCE_TRANSTION";
+ }
+
+ public class BroadcastActions {
+ public final static String HOSPITALS_UPDATE = "org.emstrack.ambulance.ambulanceforegroundservice.broadcastaction.HOSPITALS_UPDATE";
+ public static final String OTHER_AMBULANCES_UPDATE = "org.emstrack.ambulance.ambulanceforegroundservice.broadcastaction.OTHER_AMBULANCES_UPDATE";
+ public final static String AMBULANCE_UPDATE = "org.emstrack.ambulance.ambulanceforegroundservice.broadcastaction.AMBULANCE_UPDATE";
+ public final static String LOCATION_UPDATE_CHANGE = "org.emstrack.ambulance.ambulanceforegroundservice.broadcastaction.LOCATION_UPDATE_CHANGE";
+ public final static String GEOFENCE_EVENT = "org.emstrack.ambulance.ambulanceforegroundservice.broadcastaction.GEOFENCE_EVENT";
+ 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 final String CALL_ONGOING = "org.emstrack.ambulance.ambulanceforegroundservice.broadcastaction.CALL_ONGOING";
+ public static final String CALL_FINISHED = "org.emstrack.ambulance.ambulanceforegroundservice.broadcastaction.CALL_FINISHED";
+ }
+
+ public static class ProfileClientException extends Exception {
+
+ }
+
+ public class ReconnectionInformation {
+
+ private boolean hasAmbulance;
+ private boolean hasOtherAmbulances;
+ private boolean hasHospitals;
+ private boolean isUpdatingLocation;
+
+ public ReconnectionInformation(boolean hasAmbulance, boolean hasOtherAmbulances,
+ boolean hasHospitals, boolean isUpdatingLocation) {
+
+ this.hasAmbulance = hasAmbulance;
+ this.hasOtherAmbulances = hasOtherAmbulances;
+ this.hasHospitals = hasHospitals;
+ this.isUpdatingLocation = isUpdatingLocation;
+
+ }
+
+ public boolean hasAmbulance() { return hasAmbulance; }
+ public boolean hasOtherAmbulances() { return hasOtherAmbulances; }
+ public boolean hasHospitals() { return hasHospitals; }
+ public boolean isUpdatingLocation() { return isUpdatingLocation; }
+
+ @Override
+ public String toString() {
+ return String.format("hasAmbulance = %1$b, hasOtherAmbulances = %2$b, hasHospitals = %3$b, isUpdatingLocation = %4$b",
+ hasAmbulance, hasOtherAmbulances, hasHospitals, isUpdatingLocation);
+ }
+
+ }
+
+ public static String getUpdateString(LocationUpdate lastLocation) {
+ double latitude = lastLocation.getLocation().getLatitude();
+ double longitude = lastLocation.getLocation().getLongitude();
+ double orientation = lastLocation.getBearing();
+
+ // format timestamp
+ DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
+ df.setTimeZone(TimeZone.getTimeZone("UTC"));
+ String timestamp = df.format(lastLocation.getTimestamp());
+
+ String updateString = "{\"orientation\":" + orientation + ",\"location\":{" +
+ "\"latitude\":"+ latitude + ",\"longitude\":" + longitude +"},\"timestamp\":\"" + timestamp + "\"}";
+
+ return updateString;
+ }
+
+ @Nullable
+ @Override
+ public IBinder onBind(Intent intent) {
+ return null;
+ }
+
+ @Override
+ public void onCreate() {
+
+ // Create notification channel
+ if (Build.VERSION.SDK_INT >= 26) {
+
+ NotificationChannel channel = new NotificationChannel(PRIMARY_CHANNEL,
+ PRIMARY_CHANNEL_LABEL, NotificationManager.IMPORTANCE_DEFAULT);
+ channel.setLightColor(Color.GREEN);
+ channel.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);
+ getNotificationManager().createNotificationChannel(channel);
+
+ }
+
+ // Create profile client
+ getProfileClient(this);
+
+ // Create shared preferences editor
+ sharedPreferences = getSharedPreferences(PREFERENCES_NAME, MODE_PRIVATE);
+
+ // Initialize fused location client
+ fusedLocationClient = LocationServices.getFusedLocationProviderClient(this);
+
+ // Initialize geofence map
+ _geofences = new HashMap();
+
+ // Initialize geofence client
+ fenceClient = LocationServices.getGeofencingClient(this);
+
+ // initialize list of pending calls
+ currentCallId = -1;
+ pendingCalls = new HashMap();
+
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+
+ // quick return
+ if (intent == null) {
+ Log.i(TAG, "null intent, return.");
+ return START_STICKY;
+ }
+
+ // retrieve uuid
+ final String uuid = intent.getStringExtra(OnServiceComplete.UUID);
+
+ if (intent.getAction().equals(Actions.START_SERVICE)) {
+
+ Log.i(TAG, "START_SERVICE Foreground Intent ");
+
+ final boolean addStopAction = intent.getBooleanExtra("ADD_STOP_ACTION", false);
+
+ // Make sure client is bound to service
+ AmbulanceForegroundService.getProfileClient(this);
+
+ // Create notification
+
+ // Login intent
+ Intent notificationIntent = new Intent(AmbulanceForegroundService.this, LoginActivity.class);
+ notificationIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ PendingIntent pendingIntent = PendingIntent.getActivity(AmbulanceForegroundService.this, 0,
+ notificationIntent, 0);
+
+ // Restart intent
+ Intent restartServiceIntent = new Intent(AmbulanceForegroundService.this, LoginActivity.class);
+ restartServiceIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ restartServiceIntent.setAction(LoginActivity.LOGOUT);
+ PendingIntent restartServicePendingIntent = PendingIntent.getActivity(AmbulanceForegroundService.this, 0,
+ restartServiceIntent, 0);
+
+ // Stop intent
+ Intent stopServiceIntent = new Intent(AmbulanceForegroundService.this, AmbulanceForegroundService.class);
+ stopServiceIntent.setAction(Actions.STOP_SERVICE);
+ PendingIntent stopServicePendingIntent = PendingIntent.getService(AmbulanceForegroundService.this, 0,
+ stopServiceIntent, 0);
+
+ // Icon
+ Bitmap icon = BitmapFactory.decodeResource(getResources(),
+ R.mipmap.ic_launcher);
+
+ NotificationCompat.Builder notificationBuilder;
+ if (Build.VERSION.SDK_INT >= 26)
+ notificationBuilder = new NotificationCompat.Builder(AmbulanceForegroundService.this,
+ AmbulanceForegroundService.PRIMARY_CHANNEL);
+ else
+ notificationBuilder = new NotificationCompat.Builder(AmbulanceForegroundService.this);
+
+ notificationBuilder
+ .setContentTitle("EMSTrack")
+ .setTicker(getString(R.string.pleaseLogin))
+ .setContentText(getString(R.string.pleaseLogin))
+ .setSmallIcon(R.mipmap.ic_launcher)
+ .setLargeIcon(Bitmap.createScaledBitmap(icon, 128, 128, false))
+ .setContentIntent(pendingIntent)
+ .setOngoing(true)
+ .addAction(android.R.drawable.ic_menu_close_clear_cancel, getString(R.string.restartText), restartServicePendingIntent);
+
+ if (addStopAction)
+ notificationBuilder.addAction(android.R.drawable.ic_menu_close_clear_cancel, getString(R.string.stopText), stopServicePendingIntent);
+
+ Notification notification = notificationBuilder.build();
+
+ // start service
+ startForeground(NOTIFICATION_ID, notification);
+
+ // Broadcast success
+ Intent localIntent = new Intent(BroadcastActions.SUCCESS);
+ sendBroadcastWithUUID(localIntent, uuid);
+
+ } else if (intent.getAction().equals(Actions.STOP_SERVICE)) {
+
+ Log.i(TAG, "STOP_SERVICE Foreground Intent");
+
+ // What to do when logout completes?
+ new OnServiceComplete(this,
+ AmbulanceForegroundService.BroadcastActions.SUCCESS,
+ AmbulanceForegroundService.BroadcastActions.FAILURE,
+ null) {
+
+ public void run() {
+
+ // logout
+ logout(getUuid());
+
+ }
+
+ @Override
+ public void onSuccess(Bundle extras) {
+
+ Log.d(TAG, "STOP_SERVICE::onSuccess.");
+
+ // Set online false
+ setOnline(false);
+
+ // close client
+ client.close();
+
+ // set client to null
+ client = null;
+
+ // stop service
+ stopForeground(true);
+ stopSelf();
+
+ // Broadcast success
+ Intent localIntent = new Intent(BroadcastActions.SUCCESS);
+ sendBroadcastWithUUID(localIntent, uuid);
+
+ }
+
+ @Override
+ public void onFailure(Bundle extras) {
+
+ Log.d(TAG, "STOP_SERVICE::onFailure.");
+
+ // Broadcast failure
+ Intent localIntent = new Intent(BroadcastActions.FAILURE);
+ if (extras != null)
+ localIntent.putExtras(extras);
+ sendBroadcastWithUUID(localIntent, uuid);
+
+ }
+
+ };
+
+
+ } else if (intent.getAction().equals(Actions.LOGIN)) {
+
+ Log.i(TAG, "LOGIN Foreground Intent ");
+
+ // Retrieve username and password
+ String[] loginInfo = intent.getStringArrayExtra("CREDENTIALS");
+ final String username = loginInfo[0];
+ final String password = loginInfo[1];
+
+ // Notify user
+ Toast.makeText(this, "Logging in '" + username + "'", Toast.LENGTH_SHORT).show();
+
+ // Set online false
+ setOnline(false);
+
+ // What to do when login completes?
+ new OnServiceComplete(this,
+ AmbulanceForegroundService.BroadcastActions.SUCCESS,
+ AmbulanceForegroundService.BroadcastActions.FAILURE,
+ null) {
+
+ public void run() {
+
+ // Login user
+ login(username, password, getUuid());
+
+ }
+
+ @Override
+ public void onSuccess(Bundle extras) {
+
+ // Set online true
+ setOnline(true);
+
+ // Update notification
+ updateNotification(getString(R.string.welcomeUser, username));
+
+ // Broadcast success
+ Intent localIntent = new Intent(BroadcastActions.SUCCESS);
+ if (extras != null)
+ localIntent.putExtras(extras);
+ sendBroadcastWithUUID(localIntent, uuid);
+
+ }
+
+ @Override
+ public void onFailure(Bundle extras) {
+
+ // Set online false
+ setOnline(false);
+
+ // Create notification
+ NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(AmbulanceForegroundService.this, PRIMARY_CHANNEL)
+ .setSmallIcon(R.mipmap.ic_launcher)
+ .setContentTitle("EMSTrack")
+ .setContentText(getString(R.string.couldNotLogin, username))
+ .setPriority(NotificationCompat.PRIORITY_DEFAULT)
+ .setAutoCancel(true);
+
+ NotificationManagerCompat notificationManager = NotificationManagerCompat.from(AmbulanceForegroundService.this);
+ notificationManager.notify(notificationId.getAndIncrement(), mBuilder.build());
+
+ // Broadcast failure
+ Intent localIntent = new Intent(BroadcastActions.FAILURE);
+ if (extras != null)
+ localIntent.putExtras(extras);
+ sendBroadcastWithUUID(localIntent, uuid);
+
+ }
+
+ };
+
+ } else if (intent.getAction().equals(Actions.LOGOUT)) {
+
+ Log.i(TAG, "LOGOUT Foreground Intent");
+
+ // Set online false
+ setOnline(false);
+
+ // logout
+ logout(uuid);
+
+ } else if (intent.getAction().equals(Actions.GET_AMBULANCE)) {
+
+ Log.i(TAG, "GET_AMBULANCE Foreground Intent");
+
+ // Retrieve ambulance
+ int ambulanceId = intent.getIntExtra("AMBULANCE_ID", -1);
+ boolean reconnect = intent.getBooleanExtra("RECONNECT", false);
+ retrieveAmbulance(ambulanceId, uuid, reconnect);
+
+ } else if (intent.getAction().equals(Actions.GET_AMBULANCES)) {
+
+ Log.i(TAG, "GET_AMBULANCES Foreground Intent");
+
+ // Retrieve ambulances
+ boolean reconnect = intent.getBooleanExtra("RECONNECT", false);
+ retrieveOtherAmbulances(uuid, reconnect);
+
+ } else if (intent.getAction().equals(Actions.STOP_AMBULANCES)) {
+
+ Log.i(TAG, "STOP_AMBULANCES Foreground Intent");
+
+ // Stop ambulances
+ stopAmbulances(uuid);
+
+ } else if (intent.getAction().equals(Actions.GET_HOSPITALS)) {
+
+ Log.i(TAG, "GET_HOSPITALS Foreground Intent");
+
+ // Retrieve hospitals
+ boolean reconnect = intent.getBooleanExtra("RECONNECT", false);
+ retrieveHospitals(uuid, reconnect);
+
+ } else if (intent.getAction().equals(Actions.START_LOCATION_UPDATES)) {
+
+ Log.i(TAG, "START_LOCATION_UPDATES Foreground Intent");
+
+ boolean reconnect = intent.getBooleanExtra("RECONNECT", false);
+ startLocationUpdates(uuid, reconnect);
+
+ } else if (intent.getAction().equals(Actions.STOP_LOCATION_UPDATES)) {
+
+ Log.i(TAG, "STOP_LOCATION_UPDATES Foreground Intent");
+
+ // stop call updates
+ stopCallUpdates(uuid);
+
+ // stop requesting location updates
+ stopLocationUpdates(uuid);
+
+ } else if (intent.getAction().equals(Actions.UPDATE_AMBULANCE)) {
+
+ Log.i(TAG, "UPDATE_AMBULANCE Foreground Intent");
+
+ Bundle bundle = intent.getExtras();
+
+ // Retrieve update string
+ String update = bundle.getString("UPDATE");
+ if (update != null) {
+
+ // update mqtt server
+ updateAmbulance(update);
+
+ }
+
+ // Retrieve update string array
+ ArrayList updateArray = bundle.getStringArrayList("UPDATES");
+ if (updateArray != null) {
+
+ // update mqtt server
+ updateAmbulance(updateArray);
+
+ }
+
+ } else if (intent.getAction().equals(Actions.UPDATE_NOTIFICATION)) {
+
+ Log.i(TAG, "UPDATE_NOTIFICATION Foreground Intent");
+
+ // Retrieve update string
+ String message = intent.getStringExtra("MESSAGE");
+ if (message != null)
+ updateNotification(message);
+
+ } else if (intent.getAction().equals(Actions.GEOFENCE_START)) {
+
+ Log.i(TAG, "GEOFENCE_START Foreground Intent");
+
+ // Retrieve latitude and longitude
+ 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", 50.f);
+
+ 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
+ String requestId = intent.getStringExtra("REQUESTID");
+ List requestIds = new ArrayList();
+ requestIds.add(requestId);
+
+ stopGeofence(uuid, requestIds);
+
+ } else if (intent.getAction().equals(Actions.CALL_ACCEPT)) {
+
+ Log.i(TAG, "CALL_ACCEPT Foreground Intent");
+
+ // get the ambulance that accepted the call and the call id
+ int callId = intent.getIntExtra("CALL_ID", -1);
+
+ // next steps to publish information to server (steps 3, 4)
+ setCallStatus(callId, "Accepted", uuid);
+
+ } else if (intent.getAction().equals(Actions.CALL_DECLINE)) {
+
+ Log.i(TAG, "CALL_DECLINE Foreground Intent");
+
+ // get the ambulance that accepted the call and the call id
+ int callId = intent.getIntExtra("CALL_ID", -1);
+
+ // next steps to publish information to server (steps 3, 4)
+ declineCall(callId, uuid);
+
+ } else if (intent.getAction().equals(Actions.CALL_FINISH)) {
+
+ Log.i(TAG, "CALL_FINISH Foreground Intent");
+
+ // finish call
+ finishCall(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(uuid, true, true);
+ } else {
+ replyToGeofenceTransitions(uuid, true, false);
+ }
+ }
+
+ // true == entering geofence
+ //replyToGeofenceTransitions(currentCallId, 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(uuid, false, true);
+ } else {
+ replyToGeofenceTransitions(uuid, false, false);
+ }
+ }
+
+ // false = not entering geofence
+ // replyToGeofenceTransitions(currentCallId, uuid, false);
+
+ } else
+
+ Log.i(TAG, "Unknown Intent");
+
+ return START_STICKY;
+ }
+
+ /**
+ * Get the NotificationManager.
+ *
+ * @return The system service NotificationManager
+ */
+ private NotificationManager getNotificationManager() {
+ // lazy initialization
+ if (notificationManager == null) {
+ notificationManager = (NotificationManager) this.getSystemService(
+ Context.NOTIFICATION_SERVICE);
+ }
+ return notificationManager;
+ }
+
+ /**
+ * Get the MqttProfileClient.
+ *
+ * @param context Context
+ * @return The MqttProfileClient
+ */
+ protected static MqttProfileClient getProfileClient(Context context) {
+
+ // lazy initialization
+ if (client == null) {
+
+ // TODO: This is deprecated. New Firebase service is buggy and poorly documented.
+ String clientId = context.getString(R.string.app_version)
+ + context.getString(R.string.client_name)
+ + InstanceID.getInstance(context).getId();
+ //+ UUID.randomUUID().toString();
+ MqttAndroidClient androidClient = new MqttAndroidClient(context, serverUri, clientId);
+ client = new MqttProfileClient(androidClient);
+
+ }
+
+ return client;
+ }
+
+ public static MqttProfileClient getProfileClient() throws ProfileClientException {
+
+ // lazy initialization
+ if (client != null)
+ return client;
+
+ // otherwise log and throw exception
+ Log.e(TAG,"Failed to get profile client.");
+ throw new ProfileClientException();
+
+ }
+
+ public static boolean hasProfileClient() { return client != null; }
+
+ /**
+ * Get current ambulance
+ *
+ * @return the ambulance
+ */
+ public static Ambulance getAmbulance() {
+ return _ambulance;
+ }
+
+ /**
+ * Convenience method to get current ambulance id.
+ * Returns -1 if one does not exist.
+ *
+ * @return
+ */
+ public static int getAmbulanceId() {
+ if (_ambulance == null)
+ return -1;
+ else
+ return getAmbulance().getId();
+ }
+
+ /**
+ * @return current call or nul
+ */
+ public static Call getCurrentCall() {
+ return getCall(currentCallId);
+ }
+
+ /**
+ * @param callId
+ * @return call with id callId or null
+ */
+ public static Call getCall(int callId) {
+ return pendingCalls.get(callId);
+ }
+
+ /**
+ * Get current hospitals
+ *
+ * @return the list of hospitals
+ */
+ public static Map getHospitals() {
+ return _hospitals;
+ }
+
+ /**
+ * Get current ambulances
+ *
+ * @return the list of ambulances
+ */
+ public static Map getOtherAmbulances() { return _otherAmbulances; }
+
+ /**
+ * Return true if online
+ *
+ * @return true if online
+ */
+ public static boolean isOnline() { return _online; }
+
+ /**
+ * Set online status
+ *
+ * @return true if online
+ */
+ public void setOnline(boolean online) { setOnline(online, this); }
+
+ /**
+ * Set online status
+ *
+ * @return true if online
+ */
+ public static void setOnline(boolean online, Context context) {
+
+ if (context != null && online != _online) {
+
+ // broadcast change of connectivity status
+ Intent localIntent = new Intent(BroadcastActions.CONNECTIVITY_CHANGE);
+ localIntent.putExtra("ONLINE", online);
+ getLocalBroadcastManager(context).sendBroadcast(localIntent);
+
+ }
+
+ _online = online;
+
+ }
+
+ /**
+ * Return true if reconnecting
+ *
+ * @return true if reconnecting
+ */
+ public static boolean isReconnecting() { return _reconnecting; }
+
+ /**
+ * Return true if requesting location updates
+ *
+ * @return the location updates status
+ */
+ public static boolean isUpdatingLocation() { return _updatingLocation; }
+
+ /**
+ * Return can update location
+ *
+ * @return the location update status
+ */
+ public static boolean canUpdateLocation() { return _canUpdateLocation; }
+
+ /**
+ * Set can update location status
+ *
+ * @param canUpdateLocation the location update status
+ */
+ public static void setCanUpdateLocation(boolean canUpdateLocation) { AmbulanceForegroundService._canUpdateLocation = canUpdateLocation; }
+
+ /**
+ * Return the LocationRequest
+ * @return the location request
+ */
+ public static LocationRequest getLocationRequest() {
+
+ if (locationRequest == null) {
+
+ // Create request for location updates
+ locationRequest = LocationRequest.create()
+ .setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY)
+ .setInterval(UPDATE_INTERVAL)
+ .setFastestInterval(FASTEST_UPDATE_INTERVAL)
+ .setMaxWaitTime(MAX_WAIT_TIME);
+
+ }
+
+ return locationRequest;
+
+ }
+
+ /**
+ * Return the LocationSettingsRequest
+ *
+ * @return the location settings request
+ */
+ public static LocationSettingsRequest getLocationSettingsRequest() {
+
+ if (locationSettingsRequest == null) {
+
+ // Build location setting request
+ LocationSettingsRequest.Builder builder = new LocationSettingsRequest.Builder();
+ builder.addLocationRequest(getLocationRequest());
+ locationSettingsRequest = builder.build();
+
+ }
+
+ return locationSettingsRequest;
+
+ }
+
+ /**
+ * Add update to buffer for later processing
+ *
+ * @param update
+ */
+ public void addToBuffer(String update) {
+
+ // buffer updates and return
+ // TODO: limit size of buffer or write to disk
+ _updateBuffer.add(update);
+
+ // Log
+ Log.d(TAG, "MQTT Client is not online. Buffering update.");
+
+ }
+
+ public boolean consumeBuffer() {
+
+ // fast return
+ if (_updateBuffer.size() == 0)
+ return true;
+
+ // Get client
+ final MqttProfileClient profileClient = getProfileClient(AmbulanceForegroundService.this);
+
+ // Log and add notification
+ Log.d(TAG, String.format("Attempting to consume update buffer with %1$d entries.", _updateBuffer.size()));
+
+ // Loop through buffer unless it failed
+ Iterator iterator = _updateBuffer.iterator();
+ boolean success = true;
+ while (success && iterator.hasNext()) {
+
+ // Retrieve update and remove from buffer
+ String update = iterator.next();
+ iterator.remove();
+
+ // update ambulance
+ success = updateAmbulance(update);
+
+ }
+
+ return success;
+
+ }
+
+ /**
+ * Send bulk updates to current ambulance
+ * Allowing arbitrary updates might be too broad and a security concern
+ *
+ * @param updates string array
+ */
+ public boolean updateAmbulance(List updates) {
+
+ ArrayList updateString = new ArrayList<>();
+ for (LocationUpdate update : updates) {
+
+ // Set last location
+ _lastLocation = update;
+
+ // update ambulance string
+ updateString.add(getUpdateString(update));
+
+ }
+
+ boolean success = updateAmbulance(updateString);
+ if (!success) {
+
+ // update locally as well
+ Ambulance ambulance = getAmbulance();
+ if (ambulance != null) {
+
+ // Update ambulance
+ android.location.Location location = _lastLocation.getLocation();
+ ambulance.setLocation(new Location(location.getLatitude(), location.getLongitude()));
+ ambulance.setOrientation(_lastLocation.getBearing());
+
+ // Broadcast ambulance update
+ Intent localIntent = new Intent(BroadcastActions.AMBULANCE_UPDATE);
+ sendBroadcastWithUUID(localIntent);
+
+ }
+
+ }
+
+ return success;
+ }
+
+ /**
+ * Send bulk updates to current ambulance
+ * Allowing arbitrary updates might be too broad and a security concern
+ *
+ * @param updates string array
+ */
+ public boolean updateAmbulance(ArrayList updates) {
+
+ // Join updates in array
+ String updateArray = "[" + TextUtils.join(",", updates) + "]";
+
+ // send to server
+ return updateAmbulance(updateArray);
+ }
+
+ /**
+ * Send update to current ambulance
+ * Allowing arbitrary updates might be too broad and a security concern
+ *
+ * @param update string
+ */
+ public boolean updateAmbulance(String update) {
+
+ // Error message
+ String message = getString(R.string.couldNotUpdateAmbulanceOnServer);
+
+ try {
+
+ // is online or connected?
+ final MqttProfileClient profileClient = getProfileClient(AmbulanceForegroundService.this);
+ if (!isOnline() || !profileClient.isConnected()) {
+
+ // add to buffer and return
+ addToBuffer(update);
+ return false;
+
+ }
+
+ // Has ambulance?
+ Ambulance ambulance = getAmbulance();
+ if (ambulance != null ) {
+
+ // Publish current update to MQTT
+ profileClient.publish(String.format("user/%1$s/client/%2$s/ambulance/%3$d/data",
+ profileClient.getUsername(), profileClient.getClientId(), ambulance.getId()),
+ update, 1, false);
+
+ // Set update time
+ _lastServerUpdate = new Date();
+
+ return true;
+
+ } else {
+
+ message += "\n" + "Could not find ambulance";
+
+ }
+
+ }
+ catch (MqttException e) { message += "\n" + e.toString(); }
+ catch (Exception e) { message += "\n" + e.toString(); }
+
+ // Log and build a notification in case of error
+ Log.i(TAG,message);
+
+ // Create notification
+ NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(this, PRIMARY_CHANNEL)
+ .setSmallIcon(R.mipmap.ic_launcher)
+ .setContentTitle("EMSTrack")
+ .setContentText(message)
+ .setPriority(NotificationCompat.PRIORITY_DEFAULT)
+ .setAutoCancel(true);
+
+ NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this);
+ notificationManager.notify(notificationId.getAndIncrement(), mBuilder.build());
+
+ // and return false
+ return false;
+
+ }
+
+ /**
+ * Update current notification message
+ *
+ * @param message the message
+ */
+ public void updateNotification(String message) {
+
+ // Create notification
+
+ // Login intent
+ Intent notificationIntent = new Intent(AmbulanceForegroundService.this, LoginActivity.class);
+ notificationIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ PendingIntent pendingIntent = PendingIntent.getActivity(AmbulanceForegroundService.this, 0,
+ notificationIntent, 0);
+
+ // Stop intent
+ Intent stopServiceIntent = new Intent(AmbulanceForegroundService.this, LoginActivity.class);
+ stopServiceIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ stopServiceIntent.setAction(LoginActivity.LOGOUT);
+ PendingIntent stopServicePendingIntent = PendingIntent.getActivity(AmbulanceForegroundService.this, 0,
+ stopServiceIntent, 0);
+
+ Notification notification = new NotificationCompat.Builder(this, PRIMARY_CHANNEL)
+ .setContentTitle("EMSTrack")
+ .setTicker(message)
+ .setContentText(message)
+ .setStyle(new NotificationCompat.BigTextStyle()
+ .bigText(message))
+ .setSmallIcon(R.mipmap.ic_launcher)
+ .setContentIntent(pendingIntent)
+ .setOngoing(true)
+ .addAction(android.R.drawable.ic_menu_close_clear_cancel, getString(R.string.restartText), stopServicePendingIntent)
+ .build();
+
+ NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this);
+ notificationManager.notify(NOTIFICATION_ID, notification);
+
+ }
+
+ /**
+ * Logout
+ */
+ public void logout(final String uuid) {
+
+ // buffer to be consumed?
+ if (!consumeBuffer()) {
+
+ // could not consume entire buffer, log and empty anyway
+ Log.e(TAG, "Could not empty buffer before logging out.");
+ _updateBuffer = new ArrayList<>();
+
+ }
+
+ // remove ambulance
+ removeAmbulance();
+
+ // remove hospital map
+ removeHospitals();
+
+ // remove ambulance map
+ removeOtherAmbulances();
+
+ // stop geofences
+ // TODO: Should we wait for completion?
+ removeGeofences(null);
+
+ // disconnect mqttclient
+ MqttProfileClient profileClient = getProfileClient(this);
+ try {
+
+ profileClient.disconnect(new MqttProfileCallback() {
+
+ @Override
+ public void onReconnect() {
+
+ Log.d(TAG, "onReconnect during disconnect. Should never happen.");
+
+ }
+
+ @Override
+ public void onSuccess() {
+
+ Log.d(TAG, "Successfully disconnected from broker.");
+
+ // Broadcast success
+ Intent localIntent = new Intent(BroadcastActions.SUCCESS);
+ sendBroadcastWithUUID(localIntent, uuid);
+
+ }
+
+ @Override
+ public void onFailure(Throwable exception) {
+
+ Log.d(TAG, "Failed to disconnect from brocker.");
+
+ String message = getString(R.string.failedToDisconnectFromBroker) + "\n";
+ if (exception instanceof MqttException) {
+ int reason = ((MqttException) exception).getReasonCode();
+ message += getResources().getString(R.string.error_connection_failed, exception.toString());
+ } else {
+ message += getString(R.string.Exception) + exception.toString();
+ }
+
+ Log.d(TAG, "message = " + message);
+
+ // Broadcast failure
+ Intent localIntent = new Intent(BroadcastActions.FAILURE);
+ localIntent.putExtra(BroadcastExtras.MESSAGE, message);
+ sendBroadcastWithUUID(localIntent, uuid);
+
+ }
+
+ });
+
+ } catch (MqttException e) {
+
+ Log.d(TAG, "Failed to disconnect.");
+
+ // Broadcast failure
+ Intent localIntent = new Intent(BroadcastActions.FAILURE);
+ localIntent.putExtra(BroadcastExtras.MESSAGE, "Could not disconnect: " + e.toString());
+ sendBroadcastWithUUID(localIntent, uuid);
+
+ } catch (Exception e) {
+
+ Log.d(TAG, "Failed to disconnect.");
+
+ // Broadcast failure
+ Intent localIntent = new Intent(BroadcastActions.FAILURE);
+ localIntent.putExtra(BroadcastExtras.MESSAGE, "Could not disconnect: " + e.toString());
+ sendBroadcastWithUUID(localIntent, uuid);
+
+ }
+
+ }
+
+ @Override
+ public void onReconnect() {
+
+ Log.d(TAG, "onReconnect.");
+
+ // Suppress changes in updating location until reconnect is complete
+ _reconnecting = true;
+
+ // Store reconnection information
+ _reconnectionInformation = new ReconnectionInformation(
+ _ambulance != null,
+ _otherAmbulances != null,
+ _hospitals != null,
+ isUpdatingLocation());
+
+ // Set online false
+ setOnline(false);
+
+ // Create notification
+ NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(this, PRIMARY_CHANNEL)
+ .setSmallIcon(R.mipmap.ic_launcher)
+ .setContentTitle("EMSTrack")
+ .setContentText(getString(R.string.attemptingToReconnect))
+ .setPriority(NotificationCompat.PRIORITY_DEFAULT)
+ .setAutoCancel(true);
+
+ NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this);
+ notificationManager.notify(notificationId.getAndIncrement(), mBuilder.build());
+
+ }
+
+ /**
+ * Callback after handling successful connection
+ */
+ @Override
+ public void onSuccess() {
+
+ Log.d(TAG, "onSuccess after reconnect. Restoring subscriptions.");
+
+ // Set online true
+ setOnline(true);
+
+ if (_reconnectionInformation == null) {
+
+ Log.e(TAG, "Null reconnection information. Aborting...");
+ return;
+
+ }
+
+ Log.d(TAG, "Reconnection information = " + _reconnectionInformation.toString());
+
+ // Retrieve credentials
+ String username = sharedPreferences.getString(PREFERENCES_USERNAME, null);
+
+ // Subscribe to error topic
+ if (username != null)
+ subscribeToError(username);
+
+ if (_reconnectionInformation.hasAmbulance()) {
+
+ final int ambulanceId = _ambulance.getId();
+ final String ambulanceIdentifier = _ambulance.getIdentifier();
+
+ // Remove current ambulance
+ // TODO: Does it need to be asynchrounous?
+ removeAmbulance(true);
+
+ // Retrieve ambulance
+ Intent ambulanceIntent = new Intent(this, AmbulanceForegroundService.class);
+ ambulanceIntent.setAction(Actions.GET_AMBULANCE);
+ ambulanceIntent.putExtra("AMBULANCE_ID", ambulanceId);
+ ambulanceIntent.putExtra("RECONNECT", true);
+
+ // What to do when GET_AMBULANCE service completes?
+ new OnServiceComplete(this,
+ BroadcastActions.AMBULANCE_UPDATE,
+ BroadcastActions.FAILURE,
+ ambulanceIntent) {
+
+ @Override
+ public void onSuccess(Bundle extras) {
+
+ Log.i(TAG, String.format("Subscribed to ambulance %1$s", ambulanceIdentifier));
+
+ // clear reconnecting flag
+ _reconnecting = false;
+
+ // Create notification
+ NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(AmbulanceForegroundService.this, PRIMARY_CHANNEL)
+ .setSmallIcon(R.mipmap.ic_launcher)
+ .setContentTitle("EMSTrack")
+ .setContentText(getString(R.string.serverIsBackOnline))
+ .setPriority(NotificationCompat.PRIORITY_DEFAULT)
+ .setAutoCancel(true);
+
+ NotificationManagerCompat notificationManager = NotificationManagerCompat.from(AmbulanceForegroundService.this);
+ notificationManager.notify(notificationId.getAndIncrement(), mBuilder.build());
+
+ if (_reconnectionInformation.isUpdatingLocation()) {
+
+ // can stream location?
+ MqttProfileClient profileClient = getProfileClient(AmbulanceForegroundService.this);
+ Ambulance ambulance = getAmbulance();
+ String ambulanceLocationClientId = ambulance.getLocationClientId();
+ final String clientId = profileClient.getClientId();
+ if (clientId.equals(ambulanceLocationClientId)) {
+
+ // ambulance is available, start updates
+ Log.d(TAG, "Ambulance is available: starting updates.");
+
+ // Start location updates
+ Intent locationUpdatesIntent = new Intent(AmbulanceForegroundService.this,
+ AmbulanceForegroundService.class);
+ locationUpdatesIntent.setAction(Actions.START_LOCATION_UPDATES);
+ startService(locationUpdatesIntent);
+
+ } else if (ambulanceLocationClientId == null) {
+
+ // ambulance is available, request update
+ Log.d(TAG, "Ambulance is available: requesting updates.");
+
+ // Update location_client on server
+ new OnServiceComplete(AmbulanceForegroundService.this,
+ BroadcastActions.AMBULANCE_UPDATE,
+ BroadcastActions.FAILURE,
+ null) {
+
+ public void run() {
+
+ // update ambulance
+ String payload = String.format("{\"location_client_id\":\"%1$s\"}", clientId);
+ updateAmbulance(payload);
+
+ }
+
+ @Override
+ public void onSuccess(Bundle extras) {
+
+ // TODO: Can fail
+
+ // ambulance is available, request update
+ Log.d(TAG, "Ambulance is available: starting updates.");
+
+ // Start location updates
+ Intent locationUpdatesIntent = new Intent(AmbulanceForegroundService.this,
+ AmbulanceForegroundService.class);
+ locationUpdatesIntent.setAction(Actions.START_LOCATION_UPDATES);
+ startService(locationUpdatesIntent);
+
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+
+ // Retrieve action
+ String action = intent.getAction();
+
+ // Intercept AMBULANCE_UPDATE
+ if (action.equals(BroadcastActions.AMBULANCE_UPDATE))
+ // Inject uuid into AMBULANCE_UPDATE
+ intent.putExtra(OnServicesComplete.UUID, getUuid());
+
+ // Call super
+ super.onReceive(context, intent);
+ }
+
+ };
+
+ } else {
+
+ // ambulance is no longer available
+ // TODO: What to do?
+ Log.d(TAG, "Ambulance is not longer available for updates.");
+
+ }
+
+ } else {
+
+ // This may not be necessary
+ // Stop location updates
+ Intent locationUpdatesIntent = new Intent(AmbulanceForegroundService.this,
+ AmbulanceForegroundService.class);
+ locationUpdatesIntent.setAction(Actions.STOP_LOCATION_UPDATES);
+ startService(locationUpdatesIntent);
+
+ }
+
+ if (_reconnectionInformation.hasOtherAmbulances()) {
+
+ Log.i(TAG, "Subscribing to ambulances.");
+
+ // Remove ambulances
+ // TODO: Does it need to be asynchrounous?
+ removeOtherAmbulances(true);
+
+ // Retrieve ambulance
+ Intent ambulanceIntent = new Intent(AmbulanceForegroundService.this,
+ AmbulanceForegroundService.class);
+ ambulanceIntent.setAction(Actions.GET_AMBULANCES);
+ ambulanceIntent.putExtra("RECONNECT", true);
+
+ }
+
+ if (_reconnectionInformation.hasHospitals()) {
+
+ Log.i(TAG, "Subscribing to hospitals.");
+
+ // Remove hospitals
+ // TODO: Does it need to be asynchrounous?
+ removeHospitals(true);
+
+ // Retrieve hospital
+ Intent hospitalIntent = new Intent(AmbulanceForegroundService.this,
+ AmbulanceForegroundService.class);
+ hospitalIntent.setAction(Actions.GET_HOSPITALS);
+ hospitalIntent.putExtra("RECONNECT", true);
+ startService(hospitalIntent);
+
+ }
+
+ }
+
+ @Override
+ public void onFailure(Bundle extras) {
+
+ // clear reconnecting flag
+ _reconnecting = false;
+
+ // Create notification
+ NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(AmbulanceForegroundService.this, PRIMARY_CHANNEL)
+ .setSmallIcon(R.mipmap.ic_launcher)
+ .setContentTitle("EMSTrack")
+ .setContentText(getString(R.string.failedToReconnect))
+ .setPriority(NotificationCompat.PRIORITY_DEFAULT)
+ .setAutoCancel(true);
+
+ NotificationManagerCompat notificationManager = NotificationManagerCompat.from(AmbulanceForegroundService.this);
+ notificationManager.notify(notificationId.getAndIncrement(), mBuilder.build());
+
+
+ super.onFailure(extras);
+
+ }
+
+ }
+ .setFailureMessage(getResources().getString(R.string.couldNotRetrieveAmbulance,
+ ambulanceIdentifier));
+
+ } else {
+
+ // clear reconnecting flag
+ _reconnecting = false;
+
+ }
+
+ }
+
+ /**
+ * Callback after handling successful connection
+ *
+ * @param exception
+ */
+ @Override
+ public void onFailure(Throwable exception) {
+
+ Log.d(TAG, "onFailure: " + exception);
+
+ if (exception instanceof MqttException) {
+
+ int reason = ((MqttException) exception).getReasonCode();
+
+ if (reason == MqttException.REASON_CODE_CLIENT_CONNECTED) {
+
+ // Not an error, already connected, just log
+ Log.d(TAG, "Tried to connect, but already connected.");
+
+ // Set online true
+ setOnline(true);
+
+ return;
+
+ }
+
+ }
+
+ // Set online false
+ setOnline(false);
+
+ // Notify user and return
+ NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(this, PRIMARY_CHANNEL)
+ .setSmallIcon(R.mipmap.ic_launcher)
+ .setContentTitle(getString(R.string.EMSTrack))
+ .setContentText(getString(R.string.serverIsOffline))
+ .setPriority(NotificationCompat.PRIORITY_DEFAULT)
+ .setAutoCancel(true);
+
+ NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this);
+ notificationManager.notify(notificationId.getAndIncrement(), mBuilder.build());
+
+ return;
+
+ }
+
+
+ public void subscribeToError(final String username) {
+
+ MqttProfileClient profileClient = getProfileClient(AmbulanceForegroundService.this);
+ try {
+
+ profileClient.subscribe(String.format("user/%1$s/client/%2$s/error",
+ username, profileClient.getClientId()),
+ 1, new MqttProfileMessageCallback() {
+
+ @Override
+ public void messageArrived(String topic, MqttMessage message) {
+
+ Log.d(TAG, "MQTT error message.");
+
+ // Create notification
+ NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(AmbulanceForegroundService.this, PRIMARY_CHANNEL)
+ .setSmallIcon(R.mipmap.ic_launcher)
+ .setContentTitle("EMSTrack")
+ .setContentText(getString(R.string.serverError, String.valueOf(message.getPayload())))
+ .setPriority(NotificationCompat.PRIORITY_DEFAULT)
+ .setAutoCancel(true);
+
+ NotificationManagerCompat notificationManager = NotificationManagerCompat.from(AmbulanceForegroundService.this);
+ notificationManager.notify(notificationId.getAndIncrement(), mBuilder.build());
+
+ }
+
+ });
+
+ } catch (MqttException e1) {
+
+ Log.d(TAG, "Could not subscribe to error topic.");
+
+ }
+
+ }
+
+
+
+ /**
+ * Login user
+ *
+ * @param username Username
+ * @param password Password
+ */
+ public void login(final String username, final String password, final String uuid) {
+
+ // What to do when logout completes?
+ new OnServiceComplete(this,
+ AmbulanceForegroundService.BroadcastActions.SUCCESS,
+ AmbulanceForegroundService.BroadcastActions.FAILURE,
+ null) {
+
+ public void run() {
+
+ // logout
+ logout(getUuid());
+
+ }
+
+ @Override
+ public void onSuccess(Bundle extras) {
+
+ // Retrieve client
+ final MqttProfileClient profileClient = getProfileClient(AmbulanceForegroundService.this);
+
+ // Set callback to be called after profile is retrieved
+ profileClient.setCallback(new MqttProfileCallback() {
+
+ @Override
+ public void onReconnect() {
+
+ Log.d(TAG, "onReconnect after connection. Could happen.");
+ // TODO: but I am not sure how to handle it yet.
+
+ }
+
+ @Override
+ public void onSuccess() {
+
+ // Get preferences editor
+ SharedPreferences.Editor editor = sharedPreferences.edit();
+
+ // Save credentials
+ Log.d(TAG, "Storing credentials");
+ editor.putString(PREFERENCES_USERNAME, username);
+ editor.putString(PREFERENCES_PASSWORD, password);
+ editor.apply();
+
+ // Broadcast success
+ Intent localIntent = new Intent(BroadcastActions.SUCCESS);
+ sendBroadcastWithUUID(localIntent, uuid);
+
+ // Subscribe to error topic
+ subscribeToError(username);
+
+ // set callback for handling loss of connection/reconnection
+ getProfileClient(AmbulanceForegroundService.this).setCallback(AmbulanceForegroundService.this);
+
+ }
+
+ @Override
+ public void onFailure(Throwable exception) {
+
+ Log.d(TAG, "Failed to retrieve profile.");
+
+ // Build error message
+ String message = exception.toString();
+
+ // Broadcast failure
+ Intent localIntent = new Intent(BroadcastActions.FAILURE);
+ localIntent.putExtra(BroadcastExtras.MESSAGE, message);
+ sendBroadcastWithUUID(localIntent, uuid);
+
+ }
+
+ });
+
+ try {
+
+ // Attempt to connect
+ profileClient.connect(username, password, new MqttProfileCallback() {
+
+ @Override
+ public void onReconnect() {
+
+ Log.d(TAG, "onReconnect during connection. Should not happen.");
+
+ }
+
+ @Override
+ public void onSuccess() {
+
+ Log.d(TAG, "Successfully connected to broker.");
+
+ // Do nothing. All work is done on the callback
+
+ }
+
+ @Override
+ public void onFailure(Throwable exception) {
+
+ String message = getString(R.string.failedToConnectToBrocker) + "\n";
+
+ if (exception instanceof MqttException) {
+
+ int reason = ((MqttException) exception).getReasonCode();
+
+ if (reason == MqttException.REASON_CODE_FAILED_AUTHENTICATION ||
+ reason == MqttException.REASON_CODE_NOT_AUTHORIZED ||
+ reason == MqttException.REASON_CODE_INVALID_CLIENT_ID)
+
+ message += getResources().getString(R.string.error_invalid_credentials);
+
+ else
+
+ message += getResources().getString(R.string.error_connection_failed, exception.toString());
+
+ } else
+ message += getString(R.string.Exception) + exception.toString();
+
+ Log.d(TAG, "message = " + message);
+
+ // Broadcast failure
+ Intent localIntent = new Intent(BroadcastActions.FAILURE);
+ localIntent.putExtra(BroadcastExtras.MESSAGE, message);
+ sendBroadcastWithUUID(localIntent, uuid);
+
+ }
+
+ });
+
+ } catch (MqttException exception) {
+
+ // Build error message
+ String message = getResources().getString(R.string.error_connection_failed, exception.toString());
+
+ // Broadcast failure
+ Intent localIntent = new Intent(BroadcastActions.FAILURE);
+ localIntent.putExtra(BroadcastExtras.MESSAGE, message);
+ sendBroadcastWithUUID(localIntent, uuid);
+
+ }
+
+ }
+
+ @Override
+ public void onFailure(Bundle extras) {
+
+ // Broadcast failure
+ Intent localIntent = new Intent(BroadcastActions.FAILURE);
+ if (extras != null)
+ localIntent.putExtras(extras);
+ sendBroadcastWithUUID(localIntent, uuid);
+
+ }
+
+ };
+
+ }
+
+ /**
+ * Retrieve ambulance
+ *
+ * @param ambulanceId the ambulance id
+ */
+ public void retrieveAmbulance(final int ambulanceId, final String uuid, final boolean reconnect) {
+
+ // Is ambulance id valid?
+ if (ambulanceId < 0) {
+
+ // Broadcast failure
+ Intent localIntent = new Intent(BroadcastActions.FAILURE);
+ localIntent.putExtra(BroadcastExtras.MESSAGE, getString(R.string.invalidAmbulanceId));
+ sendBroadcastWithUUID(localIntent, uuid);
+
+ return;
+ }
+
+ // Is ambulance new and not reconnect?
+ Ambulance ambulance = getAmbulance();
+ if (!reconnect && ambulance != null && ambulance.getId() == ambulanceId) {
+ return;
+ }
+
+ // Remove current ambulance
+ // TODO: Does it need to be asynchrounous?
+ removeAmbulance(reconnect);
+
+ // Remove current ambulances
+ // TODO: Does it need to be asynchrounous?
+ removeOtherAmbulances(reconnect);
+
+ // Retrieve client
+ MqttProfileClient profileClient = getProfileClient(this);
+
+ try {
+
+ // Publish ambulance login
+ String topic = String.format("user/%1$s/client/%2$s/ambulance/%3$s/status",
+ profileClient.getUsername(), profileClient.getClientId(), ambulanceId);
+ profileClient.publish(topic, "ambulance login",2,true);
+
+ } catch (MqttException e) {
+
+ Log.d(TAG, "Could not login to ambulance");
+
+ // Broadcast failure
+ Intent localIntent = new Intent(BroadcastActions.FAILURE);
+ localIntent.putExtra(BroadcastExtras.MESSAGE, getString(R.string.couldNotSubscribeToAmbulance));
+ sendBroadcastWithUUID(localIntent, uuid);
+
+ }
+
+ try {
+
+ // Start retrieving data
+ profileClient.subscribe(String.format("ambulance/%1$d/data", ambulanceId),
+ 1, new MqttProfileMessageCallback() {
+
+ @Override
+ public void messageArrived(String topic, MqttMessage message) {
+
+ // Keep subscription to ambulance to make sure we receive
+ // the latest updates.
+
+ Log.d(TAG, "Retrieving ambulance.");
+
+ // First time?
+ boolean firstTime = (_ambulance == null);
+
+ // parse ambulance data
+ GsonBuilder gsonBuilder = new GsonBuilder();
+ gsonBuilder.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES);
+ Gson gson = gsonBuilder.create();
+
+ try {
+
+ // Parse and set ambulance
+ // TODO: Check for potential errors
+ Ambulance ambulance = gson
+ .fromJson(new String(message.getPayload()),
+ Ambulance.class);
+
+ // Has location been updated?
+ if (_lastLocation == null || ambulance.getTimestamp().after(_lastLocation.getTimestamp())) {
+
+ // Update last location
+ _lastLocation = new LocationUpdate();
+ android.location.Location location = new android.location.Location("FusedLocationClient");
+ location.setLatitude(ambulance.getLocation().getLatitude());
+ location.setLongitude(ambulance.getLocation().getLongitude());
+ _lastLocation.setLocation(location);
+ _lastLocation.setBearing(ambulance.getOrientation());
+ _lastLocation.setTimestamp(ambulance.getTimestamp());
+
+ }
+
+ // Set current ambulance
+ _ambulance = ambulance;
+
+ // stop updates?
+ MqttProfileClient profileClient = getProfileClient(AmbulanceForegroundService.this);
+ String clientId = profileClient.getClientId();
+ if (isUpdatingLocation() &&
+ (ambulance.getLocationClientId() == null ||
+ !clientId.equals(ambulance.getLocationClientId()))) {
+
+ // turn off tracking
+ Intent localIntent = new Intent(AmbulanceForegroundService.this, AmbulanceForegroundService.class);
+ localIntent.setAction(AmbulanceForegroundService.Actions.STOP_LOCATION_UPDATES);
+ startService(localIntent);
+
+ }
+
+ if (firstTime) {
+
+ // Broadcast success
+ Intent localIntent = new Intent(BroadcastActions.SUCCESS);
+ sendBroadcastWithUUID(localIntent, uuid);
+
+ }
+
+ // Broadcast ambulance update
+ Intent localIntent = new Intent(BroadcastActions.AMBULANCE_UPDATE);
+ sendBroadcastWithUUID(localIntent, uuid);
+
+ } catch (Exception e) {
+
+ Log.i(TAG, "Could not parse ambulance update.");
+
+ // Broadcast failure
+ Intent localIntent = new Intent(BroadcastActions.FAILURE);
+ localIntent.putExtra(BroadcastExtras.MESSAGE, getString(R.string.couldNotParseAmbulance));
+ sendBroadcastWithUUID(localIntent, uuid);
+
+ }
+
+ }
+
+ });
+
+ } catch (MqttException e) {
+
+ Log.d(TAG, "Could not subscribe to ambulance data");
+
+ // Broadcast failure
+ Intent localIntent = new Intent(BroadcastActions.FAILURE);
+ localIntent.putExtra(BroadcastExtras.MESSAGE, getString(R.string.couldNotSubscribeToAmbulance));
+ sendBroadcastWithUUID(localIntent, uuid);
+
+ }
+
+ }
+
+ /**
+ * Remove current ambulance
+ */
+ public void removeAmbulance() { removeAmbulance(false); }
+
+ /**
+ * Remove current ambulance
+ */
+ public void removeAmbulance(boolean reconnect) {
+
+ Ambulance ambulance = getAmbulance();
+ if (ambulance == null ) {
+ Log.i(TAG,"No ambulance to remove.");
+ return;
+ }
+
+ // Logout and unsubscribe if not a reconnect
+ if (!reconnect) {
+
+ // remove call updates
+ stopCallUpdates(null);
+
+ // remove location updates
+ stopLocationUpdates(null);
+
+ // get ambulance id
+ int ambulanceId = ambulance.getId();
+
+ // Retrieve client
+ final MqttProfileClient profileClient = getProfileClient(this);
+
+ try {
+
+ // Publish ambulance logout
+ String topic = String.format("user/%1$s/client/%2$s/ambulance/%3$s/status",
+ profileClient.getUsername(), profileClient.getClientId(), ambulanceId);
+ profileClient.publish(topic, "ambulance logout", 2, true);
+
+ } catch (MqttException e) {
+ Log.d(TAG, "Could not logout from ambulance");
+ }
+
+ try {
+
+ // 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'");
+ }
+
+
+ // Remove ambulance
+ _ambulance = null;
+
+ }
+
+ }
+
+ /**
+ * Retrieve hospitals
+ */
+ public void retrieveHospitals(final String uuid, boolean reconnect) {
+
+ // Remove current hospital map
+ // TODO: Does it need to be asynchrounous?
+ removeHospitals(reconnect);
+
+ // Retrieve hospital data
+ final MqttProfileClient profileClient = getProfileClient(this);
+
+ // Get list of hospitals
+ final List hospitalPermissions = profileClient.getProfile().getHospitals();
+
+ // Initialize hospitals
+ final Map hospitals = new HashMap<>();
+
+ // Loop over all hospitals
+ for (HospitalPermission hospitalPermission : hospitalPermissions) {
+
+ final int hospitalId = hospitalPermission.getHospitalId();
+
+ try {
+
+ // Start retrieving data
+ profileClient.subscribe("hospital/" + hospitalId + "/data",
+ 1, new MqttProfileMessageCallback() {
+
+ @Override
+ public void messageArrived(String topic, MqttMessage message) {
+
+ // first time?
+ boolean firstTime = (_hospitals == null);
+
+ // Parse hospital
+ GsonBuilder gsonBuilder = new GsonBuilder();
+ gsonBuilder.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES);
+ Gson gson = gsonBuilder.create();
+
+ // Found hospital
+ final Hospital hospital = gson.fromJson(message.toString(), Hospital.class);
+
+ if (firstTime) {
+
+ // Add to hospital map
+ hospitals.put(hospitalId, hospital);
+
+ // Done yet?
+ if (hospitals.size() == hospitalPermissions.size()) {
+
+ Log.d(TAG, "Done retrieving all hospitals");
+
+ // set _hospitals
+ _hospitals = hospitals;
+
+ // Broadcast hospitals update
+ Intent localIntent = new Intent(BroadcastActions.SUCCESS);
+ sendBroadcastWithUUID(localIntent, uuid);
+
+ }
+
+ } else {
+
+ // Modify hospital map
+ hospitals.put(hospitalId, hospital);
+
+ // Broadcast hospitals update
+ Intent localIntent = new Intent(BroadcastActions.HOSPITALS_UPDATE);
+ sendBroadcastWithUUID(localIntent);
+
+ }
+ }
+ });
+
+ } catch (MqttException e) {
+ Log.d(TAG, "Could not subscribe to hospital data");
+
+ // Broadcast failure
+ Intent localIntent = new Intent(BroadcastActions.FAILURE);
+ localIntent.putExtra(BroadcastExtras.MESSAGE, getString(R.string.couldNotSubscribeToHospital));
+ sendBroadcastWithUUID(localIntent, uuid);
+
+ }
+
+ }
+
+ }
+
+ /**
+ * Remove current hospitals
+ */
+ public void removeHospitals() { removeHospitals(false); }
+
+ /**
+ * Remove current hospitals
+ */
+ public void removeHospitals(boolean reconnect) {
+
+ Map hospitals = getHospitals();
+ if (hospitals == null || hospitals.size() == 0) {
+ Log.i(TAG, "No hospital to remove.");
+ return;
+ }
+
+ // Unsubscribe only if not reconnect
+ if (!reconnect) {
+
+ // Retrieve client
+ final MqttProfileClient profileClient = getProfileClient(this);
+
+ // Loop over all hospitals
+ for (Map.Entry entry : hospitals.entrySet()) {
+
+ // Get hospital
+ Hospital hospital = entry.getValue();
+
+ try {
+
+ // Unsubscribe to hospital data
+ profileClient.unsubscribe("hospital/" + hospital.getId() + "/data");
+
+ } catch (MqttException exception) {
+ Log.d(TAG, "Could not unsubscribe to 'hospital/" + hospital.getId() + "/data'");
+ }
+
+ }
+
+ // Remove hospitals
+ _hospitals = null;
+
+ }
+
+ }
+
+ /**
+ * Retrieve ambulances
+ */
+ public void retrieveOtherAmbulances(final String uuid, boolean reconnect) {
+
+ // Remove current ambulance map
+ // TODO: Does it need to be asynchrounous?
+ removeOtherAmbulances(reconnect);
+
+ // Retrieve ambulance data
+ final MqttProfileClient profileClient = getProfileClient(this);
+
+ // Get list of ambulances
+ final List ambulancePermissions = profileClient.getProfile().getAmbulances();
+
+ // Initialize ambulances
+ final Map ambulances = new HashMap<>();
+
+ // Current ambulance id
+ int currentAmbulanceId = getAmbulanceId();
+
+ // Loop over all ambulances
+ for (AmbulancePermission ambulancePermission : ambulancePermissions) {
+
+ final int ambulanceId = ambulancePermission.getAmbulanceId();
+
+ // Skip if current ambulance
+ if (ambulanceId == currentAmbulanceId)
+ continue;
+
+ try {
+
+ // Start retrieving data
+ profileClient.subscribe("ambulance/" + ambulanceId + "/data",
+ 1, new MqttProfileMessageCallback() {
+
+ @Override
+ public void messageArrived(String topic, MqttMessage message) {
+
+ // first time?
+ boolean firstTime = (_otherAmbulances == null);
+
+ // Parse ambulance
+ GsonBuilder gsonBuilder = new GsonBuilder();
+ gsonBuilder.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES);
+ Gson gson = gsonBuilder.create();
+
+ // Found ambulance
+ final Ambulance ambulance = gson.fromJson(message.toString(), Ambulance.class);
+
+ if (firstTime) {
+
+ // Add to ambulance map
+ ambulances.put(ambulanceId, ambulance);
+
+ // Done yet?
+ if (ambulances.size() ==
+ (ambulancePermissions.size() + (getAmbulanceId() == -1 ? 0 : - 1))) {
+
+ Log.d(TAG, "Done retrieving all ambulances");
+
+ // set _otherAmbulances
+ _otherAmbulances = ambulances;
+
+ // Broadcast success
+ Intent localIntent = new Intent(BroadcastActions.SUCCESS);
+ sendBroadcastWithUUID(localIntent, uuid);
+
+ }
+
+ } else {
+
+ // Update ambulance map
+ ambulances.put(ambulanceId, ambulance);
+
+ // Broadcast ambulances update
+ Intent localIntent = new Intent(BroadcastActions.OTHER_AMBULANCES_UPDATE);
+ sendBroadcastWithUUID(localIntent);
+
+ }
+ }
+ });
+
+ } catch (MqttException e) {
+ Log.d(TAG, "Could not subscribe to ambulance data");
+
+ // Broadcast failure
+ Intent localIntent = new Intent(BroadcastActions.FAILURE);
+ localIntent.putExtra(BroadcastExtras.MESSAGE, getString(R.string.couldNotSubscribeToAmbulance));
+ sendBroadcastWithUUID(localIntent, uuid);
+
+ }
+
+ }
+
+ }
+
+ /**
+ * Remove current ambulances
+ */
+ public void removeOtherAmbulances() { removeOtherAmbulances(false); }
+
+ /**
+ * Remove current ambulances
+ */
+ public void removeOtherAmbulances(boolean reconnect) {
+
+ Map ambulances = getOtherAmbulances();
+ if (ambulances == null || ambulances.size() == 0) {
+ Log.i(TAG, "No ambulances to remove.");
+ return;
+ }
+
+ // Remove subscriptions if not reconnect
+ if (!reconnect) {
+
+ // Retrieve client
+ final MqttProfileClient profileClient = getProfileClient(this);
+
+ // Loop over all ambulances
+ for (Map.Entry entry : ambulances.entrySet()) {
+
+ // Get ambulance
+ Ambulance ambulance = entry.getValue();
+
+ try {
+
+ // Unsubscribe to ambulance data
+ profileClient.unsubscribe("ambulance/" + ambulance.getId() + "/data");
+
+ } catch (MqttException exception) {
+ Log.d(TAG, "Could not unsubscribe to 'ambulance/" + ambulance.getId() + "/data'");
+ }
+
+ }
+
+ // Remove ambulances
+ _otherAmbulances = null;
+
+ }
+
+ }
+
+ /**
+ * Stop subscribing to ambulances
+ */
+ public void stopAmbulances(final String uuid) {
+
+ // Remove current ambulance map
+ // TODO: Does it need to be asynchrounous?
+ removeOtherAmbulances();
+
+ // Broadcast success
+ Intent localIntent = new Intent(BroadcastActions.SUCCESS);
+ sendBroadcastWithUUID(localIntent, uuid);
+
+ }
+
+ private void startLocationUpdates(final String uuid, final boolean reconnect) {
+
+ // Already started?
+ if (_updatingLocation) {
+ Log.i(TAG, "Already requesting location updates. Skipping.");
+
+ Log.i(TAG, "Consume buffer.");
+ consumeBuffer();
+
+ // Broadcast success and return
+ Intent localIntent = new Intent(BroadcastActions.SUCCESS);
+ sendBroadcastWithUUID(localIntent, uuid);
+ return;
+
+ }
+
+ if (!canUpdateLocation()) {
+
+ // Broadcast failure and return
+ Intent localIntent = new Intent(BroadcastActions.FAILURE);
+ localIntent.putExtra(BroadcastExtras.MESSAGE, getString(R.string.cannotUseLocationServices));
+ sendBroadcastWithUUID(localIntent, uuid);
+ return;
+
+ }
+
+ // Logged in?
+ Ambulance ambulance = AmbulanceForegroundService.getAmbulance();
+ if (ambulance == null) {
+
+ // Broadcast failure and return
+ Intent localIntent = new Intent(BroadcastActions.FAILURE);
+ localIntent.putExtra(BroadcastExtras.MESSAGE, getString(R.string.noAmbulanceSelected));
+ sendBroadcastWithUUID(localIntent, uuid);
+ return;
+
+ }
+
+ // is location_client available?
+ final MqttProfileClient profileClient = AmbulanceForegroundService.getProfileClient(this);
+
+ // can stream location?
+ String ambulanceLocationClientId = ambulance.getLocationClientId();
+ String clientId = profileClient.getClientId();
+ if (clientId.equals(ambulanceLocationClientId)) {
+
+ // ambulance is available, start updates
+
+ // Create settings client
+ SettingsClient settingsClient = LocationServices.getSettingsClient(this);
+
+ // Check if the device has the necessary location settings.
+ settingsClient.checkLocationSettings(getLocationSettingsRequest())
+ .addOnSuccessListener(new OnSuccessListener() {
+ @SuppressLint("MissingPermission")
+ @Override
+ public void onSuccess(LocationSettingsResponse locationSettingsResponse) {
+
+ Log.i(TAG, "All location settings are satisfied.");
+
+ Log.i(TAG, "Consume buffer.");
+ consumeBuffer();
+
+ Log.i(TAG, "Starting location updates.");
+ beginLocationUpdates(uuid);
+
+ Log.i(TAG, "Starting call updates.");
+ beginCallUpdates(uuid);
+
+ }
+ })
+ .addOnFailureListener(new OnFailureListener() {
+ @Override
+ public void onFailure(@NonNull Exception e) {
+ int statusCode = ((ApiException) e).getStatusCode();
+ String message = getString(R.string.inadequateLocationSettings);
+ switch (statusCode) {
+ case LocationSettingsStatusCodes.RESOLUTION_REQUIRED:
+ message += "Try restarting app.";
+ break;
+ case LocationSettingsStatusCodes.SETTINGS_CHANGE_UNAVAILABLE:
+ message += "Please fix in Settings.";
+ }
+ Log.e(TAG, message);
+
+ // Broadcast failure and return
+ Intent localIntent = new Intent(BroadcastActions.FAILURE);
+ localIntent.putExtra(BroadcastExtras.MESSAGE, message);
+ sendBroadcastWithUUID(localIntent, uuid);
+ return;
+
+ }
+ });
+
+
+ } else if (ambulanceLocationClientId == null) {
+
+ // ambulance is available, request update
+
+ // Update location_client on server, listening to updates already
+ String payload = String.format("{\"location_client_id\":\"%1$s\"}", clientId);
+ Intent intent = new Intent(this, AmbulanceForegroundService.class);
+ intent.setAction(Actions.UPDATE_AMBULANCE);
+ Bundle bundle = new Bundle();
+ bundle.putString("UPDATE", payload);
+ intent.putExtras(bundle);
+
+ // What to do when service completes?
+ new OnServicesComplete(this,
+ new String[] {
+ BroadcastActions.SUCCESS,
+ BroadcastActions.AMBULANCE_UPDATE
+ },
+ new String[] {BroadcastActions.FAILURE},
+ intent) {
+
+ @Override
+ public void onSuccess(Bundle extras) {
+
+ Log.i(TAG, "onSuccess");
+
+ // Try again with right permissions
+ // TODO: This could potentially lead to a loop, add counter to prevent infinite recursion
+ startLocationUpdates(uuid, reconnect);
+
+ }
+
+ @Override
+ public void onFailure(Bundle extras) {
+
+ // Broadcast failure
+ String message = extras.getString(BroadcastExtras.MESSAGE);
+ Intent localIntent = new Intent(BroadcastActions.FAILURE);
+ localIntent.putExtra(BroadcastExtras.MESSAGE, message);
+ sendBroadcastWithUUID(localIntent, uuid);
+
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+
+ // Retrieve action
+ String action = intent.getAction();
+
+ // Intercept success
+ if (action.equals(BroadcastActions.SUCCESS))
+ // prevent propagation, still waiting for AMBULANCE_UPDATE
+ return;
+
+ // Intercept AMBULANCE_UPDATE
+ if (action.equals(BroadcastActions.AMBULANCE_UPDATE))
+ // Inject uuid into AMBULANCE_UPDATE
+ intent.putExtra(OnServicesComplete.UUID, getUuid());
+
+ // Call super
+ super.onReceive(context, intent);
+ }
+
+ };
+
+ } else {
+
+ // ambulance is not available, report failure
+ Intent localIntent = new Intent(BroadcastActions.FAILURE);
+ localIntent.putExtra(BroadcastExtras.MESSAGE, getString(R.string.anotherClientReporting));
+ sendBroadcastWithUUID(localIntent, uuid);
+ return;
+
+ }
+
+ }
+
+ /**
+ * Handles the Request Updates button and requests start of location updates.
+ */
+ public void beginLocationUpdates(final String uuid) {
+
+ // Create location callback
+ locationCallback = new LocationCallback() {
+
+ @Override
+ public void onLocationResult(LocationResult result) {
+
+ // get profile client
+ final MqttProfileClient profileClient = AmbulanceForegroundService.getProfileClient(AmbulanceForegroundService.this);
+
+ // Retrieve results
+ if (result != null) {
+
+ List locations = result.getLocations();
+ Log.i(TAG, "Received " + locations.size() + " location updates");
+
+ // Initialize locationFilter
+ if (_lastLocation != null)
+ locationFilter.setLocation(_lastLocation);
+
+ // Filter location
+ List filteredLocations = locationFilter.update(locations);
+
+ // Publish update
+ if (filteredLocations.size() > 0)
+ updateAmbulance(filteredLocations);
+
+ // Notification message
+ // TODO: These need to be internationalized but cannot be retrieved without a context
+ String message = "Last location update at "
+ + new SimpleDateFormat("d MMM HH:mm:ss z", Locale.getDefault()).format(new Date());
+
+ if (_lastServerUpdate != null)
+ message += "\nLast server update at "
+ + new SimpleDateFormat("d MMM HH:mm:ss z", Locale.getDefault()).format(_lastServerUpdate);
+
+ if (isOnline())
+ message += "\nServer is online";
+ else
+ message += "\nServer is offline";
+
+ if (_updateBuffer.size() > 1)
+ message += ", " + String.format("%1$d messages on buffer", _updateBuffer.size());
+ else if (_updateBuffer.size() > 0)
+ message += ", " + String.format("1 message on buffer");
+
+ // modify foreground service notification
+ Intent notificationIntent = new Intent(AmbulanceForegroundService.this, AmbulanceForegroundService.class);
+ notificationIntent.setAction(AmbulanceForegroundService.Actions.UPDATE_NOTIFICATION);
+ notificationIntent.putExtra("MESSAGE", message);
+ startService(notificationIntent);
+
+ }
+
+ }
+
+ };
+
+ try {
+
+ fusedLocationClient.requestLocationUpdates(getLocationRequest(), locationCallback, null)
+ .addOnSuccessListener(new OnSuccessListener() {
+ @Override
+ public void onSuccess(Void aVoid) {
+
+ Log.i(TAG, "Starting location updates");
+ _updatingLocation = true;
+
+ // Broadcast success
+ Intent localIntent = new Intent(BroadcastActions.SUCCESS);
+ sendBroadcastWithUUID(localIntent, uuid);
+
+ // Broadcast location change
+ Intent changeIntent = new Intent(BroadcastActions.LOCATION_UPDATE_CHANGE);
+ sendBroadcastWithUUID(changeIntent, uuid);
+
+ }
+ })
+ .addOnFailureListener(new OnFailureListener() {
+ @Override
+ public void onFailure(@NonNull Exception e) {
+ Log.i(TAG, "Failed to start location updates");
+ e.printStackTrace();
+
+ // Broadcast failure
+ Intent localIntent = new Intent(BroadcastActions.FAILURE);
+ localIntent.putExtra(BroadcastExtras.MESSAGE, getString(R.string.failedToStartLocationUpdates));
+ sendBroadcastWithUUID(localIntent, uuid);
+
+ }
+ });
+
+ } catch (SecurityException e) {
+ Log.i(TAG, "Failed to start location updates");
+ e.printStackTrace();
+
+ // Broadcast failure
+ Intent localIntent = new Intent(BroadcastActions.FAILURE);
+ localIntent.putExtra(BroadcastExtras.MESSAGE, getString(R.string.failedToStartLocationUpdates));
+ sendBroadcastWithUUID(localIntent, uuid);
+
+ }
+ }
+
+ /**
+ * Handles the Remove Updates button, and requests removal of location updates.
+ */
+ public void stopLocationUpdates(final String uuid) {
+
+ _lastLocation = null;
+
+ // Already started?
+ if (!_updatingLocation) {
+ Log.i(TAG, "Not requesting location updates. Skipping.");
+ return;
+ }
+
+ // get profile client
+ MqttProfileClient profileClient = AmbulanceForegroundService.getProfileClient(this);
+
+ // clear location_client on server?
+ Ambulance ambulance = getAmbulance();
+ if (ambulance != null && profileClient != null &&
+ profileClient.getClientId().equals(ambulance.getLocationClientId())) {
+
+ Log.i(TAG, "Will clear location client on server");
+
+ // clear location_client on server
+ String payload = "{\"location_client_id\":\"\"}";
+
+ // Update location_client on server, listening to updates already
+ Intent intent = new Intent(this, AmbulanceForegroundService.class);
+ intent.setAction(AmbulanceForegroundService.Actions.UPDATE_AMBULANCE);
+ Bundle bundle = new Bundle();
+ bundle.putString("UPDATE", payload);
+ intent.putExtras(bundle);
+ startService(intent);
+
+ } else {
+
+ Log.i(TAG, "No need to clear location client on server");
+
+ }
+
+ // remove on fusedLocationclient
+ fusedLocationClient.removeLocationUpdates(locationCallback)
+ .addOnSuccessListener(new OnSuccessListener() {
+ @Override
+ public void onSuccess(Void aVoid) {
+
+ Log.i(TAG, "Stopping location updates");
+ _updatingLocation = false;
+
+ // Broadcast location change
+ Intent changeIntent = new Intent(BroadcastActions.LOCATION_UPDATE_CHANGE);
+ sendBroadcastWithUUID(changeIntent, uuid);
+
+ }
+ })
+ .addOnFailureListener(new OnFailureListener() {
+ @Override
+ public void onFailure(@NonNull Exception e) {
+ Log.i(TAG, "Failed to stop location updates");
+ e.printStackTrace();
+ }
+ });
+
+ }
+
+ /**
+ * Begin call updates
+ *
+ * @param uuid
+ */
+ public void beginCallUpdates(final String uuid) {
+
+ Log.i(TAG, "Subscribing to call status");
+
+ // Logged in?
+ Ambulance ambulance = AmbulanceForegroundService.getAmbulance();
+ if (ambulance == null) {
+
+ // Broadcast failure and return
+ Intent localIntent = new Intent(BroadcastActions.FAILURE);
+ localIntent.putExtra(BroadcastExtras.MESSAGE, getString(R.string.noAmbulanceSelected));
+ sendBroadcastWithUUID(localIntent, uuid);
+ return;
+
+ }
+
+ // get profile client
+ MqttProfileClient profileClient = AmbulanceForegroundService.getProfileClient(this);
+
+ // subscribe to call status
+ try {
+
+ final String clientId = profileClient.getClientId();
+
+ profileClient.subscribe(String.format("ambulance/%1$d/call/+/status", ambulance.getId()),
+ 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 statuses, currentCallId = " + currentCallId);
+
+ try {
+
+ // parse the status
+ String status = new String(message.getPayload());
+
+ // the call id is the 3rd value
+ int callId = Integer.valueOf(topic.split("/")[3]);
+
+ Log.i(TAG, "Received call/" + callId + "/status='" + status + "'");
+
+ // check if message is "requested"
+ // literally has to be "requested" in quotes
+ if (status.equalsIgnoreCase("\"requested\"")) {
+
+ // subscribe to call data then prompt user to accept
+ subscribeToCall(callId, uuid);
+
+ } else if (status.equalsIgnoreCase("\"ongoing\"")) {
+
+ if (currentCallId <= 0) {
+
+ // reply to ongoing
+ setCallOngoing(callId, uuid);
+
+ } else {
+
+ // TODO: Resume call instead of accept fresh
+
+ }
+
+ } else {
+ Log.i(TAG, "Unknown status '" + 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 int callId, final String uuid) {
+
+ MqttProfileClient profileClient = getProfileClient(this);
+
+ try {
+
+ Log.i(TAG, "Subscribing to call/" + callId);
+
+ 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 call data
+ Call call = gson.fromJson(new String(message.getPayload()),
+ Call.class);
+
+ // Add or update pending calls
+ pendingCalls.put(call.getId(), call);
+
+ // if no current call prompt user
+ if (currentCallId < 0) {
+
+ processNextCall(uuid);
+
+ } else {
+
+ // prompt update call details
+
+ }
+
+ } catch (Exception e) {
+
+ Log.i(TAG, "Could not parse call 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 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 stopCallUpdates(final String uuid) {
+
+ // get profile client
+ MqttProfileClient profileClient = AmbulanceForegroundService.getProfileClient(this);
+ Ambulance ambulance = getAmbulance();
+ if (ambulance != null && profileClient != null) {
+
+ // terminate current call, does not process next
+ finishCall(uuid, false);
+
+ Iterator> iterator = pendingCalls.entrySet().iterator();
+ while (iterator.hasNext()) {
+
+ Map.Entry pair = iterator.next();
+ int callId = pair.getKey();
+
+ // unsubscribe from call
+ Log.i(TAG, "Unsubscribe from call/" + callId);
+ try {
+ profileClient.unsubscribe("call/" + callId + "/data");
+ } catch (MqttException e) {
+ Log.d(TAG, "Could not unsubscribe to 'call/" + callId + "/data'");
+ }
+
+ // remove from pending calls
+ iterator.remove();
+
+ }
+
+ Log.i(TAG, "Unsubscribe from call updates");
+ try {
+ profileClient.unsubscribe(String.format("ambulance/%1$d/call/+/status", ambulance.getId()));
+ } catch (MqttException e) {
+ Log.d(TAG, String.format("ambulance/%1$d/call/+/status", ambulance.getId()));
+ }
+
+ } else {
+
+ Log.i(TAG, "No need to stop call updates");
+
+ }
+
+ }
+
+ public void finishCall(String uuid) {
+ finishCall(uuid, true);
+ }
+
+ public void finishCall(String uuid, boolean processNext) {
+
+ // if currently not serving call
+ if (currentCallId <= 0) {
+
+ Log.d(TAG, "Can't finish call: not serving any call.");
+
+ return;
+
+ }
+
+ // publish finished status to server
+ setCallStatus(currentCallId, "finished", uuid);
+
+ // unsubscribe from call
+ Log.i(TAG, "Unsubscribe from call/" + currentCallId);
+ try {
+ // get profile client
+ MqttProfileClient profileClient = AmbulanceForegroundService.getProfileClient(this);
+ profileClient.unsubscribe("call/" + currentCallId + "/data");
+ } catch (MqttException e) {
+ Log.d(TAG, "Could not unsubscribe to 'call/" + currentCallId + "/data'");
+ }
+
+ // publish available to server
+ String payload = String.format("{\"status\":\"%1$s\"}", "AV");
+ Intent intent = new Intent(this, AmbulanceForegroundService.class);
+ intent.setAction(Actions.UPDATE_AMBULANCE);
+ Bundle bundle = new Bundle();
+ bundle.putString("UPDATE", payload);
+ intent.putExtras(bundle);
+ startService(intent);
+
+ // remove call from the queu
+ pendingCalls.remove(currentCallId);
+ currentCallId = -1;
+
+ // broadcast CALL_FINISHED
+ Intent callFinishedIntent = new Intent(BroadcastActions.CALL_FINISHED);
+ sendBroadcastWithUUID(callFinishedIntent, uuid);
+
+ if (processNext) {
+
+ // process next call
+ processNextCall(uuid);
+
+ }
+
+ }
+
+ public void declineCall(int callId, String uuid) {
+
+ // if currently serving call, can't decline
+ if (currentCallId > 0) {
+
+ Log.d(TAG, "Can't decline call: currently serving call/" + currentCallId);
+
+ Intent localIntent = new Intent(BroadcastActions.FAILURE);
+ localIntent.putExtra(BroadcastExtras.MESSAGE, getString(R.string.couldNotDeclineCall));
+ sendBroadcastWithUUID(localIntent, uuid);
+
+ return;
+
+ }
+
+ // remove call from the queu
+ pendingCalls.remove(callId);
+ currentCallId = -1;
+
+ // process next call
+ processNextCall(uuid);
+
+ }
+
+ public void processNextCall(String uuid) {
+
+ // if current call, bark
+ if (currentCallId > 0) {
+
+ Log.d(TAG, "Will not process next call: currently serving call/" + currentCallId);
+
+ Intent localIntent = new Intent(BroadcastActions.FAILURE);
+ localIntent.putExtra(BroadcastExtras.MESSAGE, getString(R.string.willNotProcessNextCall));
+ sendBroadcastWithUUID(localIntent, uuid);
+
+ return;
+
+ }
+
+ // if prompting user, ignore
+ if (currentCallId == 0) {
+
+ Log.d(TAG, "Will not process next call: currently prompting user to accept call.");
+
+ return;
+
+ }
+
+ // Are there more calls in the queu?
+ Iterator> iterator = pendingCalls.entrySet().iterator();
+ if (iterator.hasNext()) {
+
+ Log.i(TAG, "Will prompt user to accept call");
+
+ Map.Entry pair = iterator.next();
+
+ // set curentCallId to zero
+ currentCallId = 0;
+
+ // create intent to prompt user
+ Intent callPromptIntent = new Intent(BroadcastActions.PROMPT_CALL_ACCEPT);
+ callPromptIntent.putExtra("CALL_ID", pair.getKey());
+ sendBroadcastWithUUID(callPromptIntent, uuid);
+
+ } else {
+
+ Log.i(TAG, "No more pending calls");
+
+ }
+
+ }
+
+ // handles steps 3 and 4 of Accepting Calls
+ public void setCallStatus(int callId, String status, String uuid) {
+
+ Log.i(TAG, "Setting call/" + callId + "/status to '" + status + "'");
+
+ MqttProfileClient profileClient = getProfileClient(AmbulanceForegroundService.this);
+ Ambulance ambulance = getAmbulance();
+ if (ambulance != null) {
+
+ // step 4: publish accepted to server
+ String path = String.format("user/%1$s/client/%2$s/ambulance/%3$s/call/%4$s/status",
+ profileClient.getUsername(), profileClient.getClientId(), ambulance.getId(), callId);
+
+ // publish status to server
+ publishToPath(status, path, uuid);
+
+ } else {
+
+ Log.d(TAG, "Ambulance not found while in acceptCall()");
+
+ Intent localIntent = new Intent(BroadcastActions.FAILURE);
+ localIntent.putExtra(BroadcastExtras.MESSAGE, getString(R.string.couldNotFindAmbulance));
+ sendBroadcastWithUUID(localIntent, uuid);
+ }
+
+ }
+
+ // handles steps 6 to 7
+ public void setCallOngoing(final int callId, final String uuid) {
+
+ if (currentCallId > 0) {
+
+ Log.d(TAG, "Can't set call as ongoing: already servicing call " + currentCallId);
+
+ Intent localIntent = new Intent(BroadcastActions.FAILURE);
+ localIntent.putExtra(BroadcastExtras.MESSAGE, getString(R.string.cannotSetCallOngoing));
+ sendBroadcastWithUUID(localIntent, uuid);
+
+ return;
+ }
+
+ if (!pendingCalls.containsKey(callId)) {
+
+ Log.d(TAG, "Could not retrieve call/" + currentCallId);
+
+ Intent localIntent = new Intent(BroadcastActions.FAILURE);
+ localIntent.putExtra(BroadcastExtras.MESSAGE, "Could not retrieve call/" + currentCallId);
+ sendBroadcastWithUUID(localIntent, uuid);
+
+ return;
+ }
+
+ MqttProfileClient profileClient = getProfileClient(AmbulanceForegroundService.this);
+ Ambulance ambulance = getAmbulance();
+
+ if (ambulance == null) {
+
+ 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);
+
+ return;
+ }
+
+ // step 6 & 7
+ Log.i(TAG, "Replying to ongoing call/" + callId);
+
+ // set current call
+ currentCallId = callId;
+
+ // retrieve call
+ Call call = pendingCalls.get(callId);
+
+ // step 6
+ // 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", (float) call.getLocation().getLatitude());
+ serviceIntent.putExtra("LONGITUDE", (float) call.getLocation().getLongitude());
+ serviceIntent.putExtra("RADIUS", 50.f);
+ startService(serviceIntent);
+
+ // step 7: publish patient bound to server
+ String payload = String.format("{\"status\":\"%1$s\"}", "PB");
+ Intent intent = new Intent(this, AmbulanceForegroundService.class);
+ intent.setAction(Actions.UPDATE_AMBULANCE);
+ Bundle bundle = new Bundle();
+ bundle.putString("UPDATE", payload);
+ intent.putExtras(bundle);
+ startService(intent);
+
+ // broadcast CALL_ONGOING
+ Intent callOngoingIntent = new Intent(BroadcastActions.CALL_ONGOING);
+ sendBroadcastWithUUID(callOngoingIntent, 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);
+ }
+ }
+
+ /*
+ **
+ */
+ public void replyToGeofenceTransitions(String uuid, boolean enter, boolean isHospital) {
+
+ // if currently not serving call
+ if (currentCallId < 0) {
+
+ Log.d(TAG, "Ignoring geofence transition: not serving any call.");
+
+ return;
+
+ }
+
+ MqttProfileClient profileClient = getProfileClient(AmbulanceForegroundService.this);
+ Ambulance ambulance = getAmbulance();
+
+ // step 7
+ if (ambulance == null) {
+
+ 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);
+
+ return;
+
+ }
+
+ if (!isHospital) {
+
+ // step 7: publish status to server
+
+ String status = enter ? "AP" : "HB";
+
+ String payload = String.format("{\"status\":\"%1$s\"}", status);
+ Intent intent = new Intent(this, AmbulanceForegroundService.class);
+ intent.setAction(Actions.UPDATE_AMBULANCE);
+ Bundle bundle = new Bundle();
+ bundle.putString("UPDATE", payload);
+ intent.putExtras(bundle);
+ startService(intent);
+
+ } else {
+
+ if (enter) {
+
+ Log.i(TAG, "User has entered hospital");
+
+ String status = "AH";
+
+ String payload = String.format("{\"status\":\"%1$s\"}", status);
+ Intent intent = new Intent(this, AmbulanceForegroundService.class);
+ intent.setAction(Actions.UPDATE_AMBULANCE);
+ Bundle bundle = new Bundle();
+ bundle.putString("UPDATE", payload);
+ intent.putExtras(bundle);
+ startService(intent);
+
+ } else {
+
+ // user is leaving the hospital
+
+ Log.i(TAG, "User is leaving hospital");
+
+ // create intent to prompt user to end call
+ Intent callPromptIntent = new Intent(BroadcastActions.PROMPT_CALL_END);
+ callPromptIntent.putExtra("CALL_ID", currentCallId);
+ sendBroadcastWithUUID(callPromptIntent, uuid);
+
+ }
+
+ }
+
+ }
+
+ private GeofencingRequest getGeofencingRequest(com.google.android.gms.location.Geofence geofence) {
+
+ Log.i(TAG, "GEOFENCE_REQUEST: Built Geofencing Request");
+
+ GeofencingRequest.Builder builder = new GeofencingRequest.Builder();
+ builder.setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER);
+ builder.addGeofence(geofence);
+
+ return builder.build();
+
+ }
+
+ private PendingIntent getGeofencePendingIntent() {
+
+ // Reuse the PendingIntent if we already have it.
+ if (geofenceIntent != null) {
+ return geofenceIntent;
+ }
+ Intent intent = new Intent(this, GeofenceBroadcastReceiver.class);
+
+ // We use FLAG_UPDATE_CURRENT so that we get the same pending intent back when
+ // calling addGeofences() and removeGeofences().
+ geofenceIntent = PendingIntent.getBroadcast(this, 0,
+ intent, PendingIntent.FLAG_UPDATE_CURRENT);
+
+ 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()));
+
+ // Set unique id
+ final String id = "GEOFENCE_" + geofencesId.getAndIncrement();
+
+ // Create settings client
+ SettingsClient settingsClient = LocationServices.getSettingsClient(this);
+
+ // Check if the device has the necessary location settings.
+ settingsClient.checkLocationSettings(getLocationSettingsRequest())
+ .addOnSuccessListener(new OnSuccessListener() {
+ @SuppressLint("MissingPermission")
+ @Override
+ public void onSuccess(LocationSettingsResponse locationSettingsResponse) {
+
+ Log.i(TAG, "All location settings are satisfied.");
+
+ Log.i(TAG, "Adding GEOFENCE.");
+
+ fenceClient.addGeofences(getGeofencingRequest(geofence.build(id)),
+ getGeofencePendingIntent())
+ .addOnSuccessListener(new OnSuccessListener() {
+
+ @Override
+ public void onSuccess(Void aVoid) {
+ // Geofences added
+ Log.i(TAG, "GEOFENCES ADDED.");
+
+ // Add to map
+ _geofences.put(id, geofence);
+
+ // Broadcast success
+ Intent localIntent = new Intent(BroadcastActions.SUCCESS);
+ sendBroadcastWithUUID(localIntent, uuid);
+
+ }
+ })
+ .addOnFailureListener(new OnFailureListener() {
+ @Override
+ public void onFailure(@NonNull Exception e) {
+
+ // Failed to add geofences
+ Log.e(TAG, "FAILED TO ADD GEOFENCES: " + e.toString());
+
+ // Broadcast failure and return
+ Intent localIntent = new Intent(BroadcastActions.FAILURE);
+ localIntent.putExtra(BroadcastExtras.MESSAGE, getString(R.string.failedToAddGeofence));
+ sendBroadcastWithUUID(localIntent, uuid);
+ return;
+
+ }
+ });
+
+ }
+ })
+ .addOnFailureListener(new OnFailureListener() {
+ @Override
+ public void onFailure(@NonNull Exception e) {
+ int statusCode = ((ApiException) e).getStatusCode();
+ String message = getString(R.string.inadequateLocationSettings);
+ switch (statusCode) {
+ case LocationSettingsStatusCodes.RESOLUTION_REQUIRED:
+ message += "Try restarting app.";
+ break;
+ case LocationSettingsStatusCodes.SETTINGS_CHANGE_UNAVAILABLE:
+ message += "Please fix in Settings.";
+ }
+ Log.e(TAG, message);
+
+ // Broadcast failure and return
+ Intent localIntent = new Intent(BroadcastActions.FAILURE);
+ localIntent.putExtra(BroadcastExtras.MESSAGE, message);
+ sendBroadcastWithUUID(localIntent, uuid);
+ return;
+
+ }
+ });
+ }
+
+
+ private void removeGeofences(final String uuid) {
+
+ fenceClient.removeGeofences(getGeofencePendingIntent())
+ .addOnSuccessListener(new OnSuccessListener() {
+ @Override
+ public void onSuccess(Void aVoid) {
+ // Geofences removed
+ Log.i(TAG, "GEOFENCES REMOVED.");
+
+ // Clear all geofence ids
+ _geofences.clear();
+
+ // Broadcast success
+ Intent localIntent = new Intent(BroadcastActions.SUCCESS);
+ sendBroadcastWithUUID(localIntent, uuid);
+
+ }
+ })
+ .addOnFailureListener(new OnFailureListener() {
+ @Override
+ public void onFailure(@NonNull Exception e) {
+
+ // Failed to remove geofences
+ Log.e(TAG, "FAILED TO REMOVE GEOFENCES: " + e.toString());
+
+ // Broadcast failure and return
+ Intent localIntent = new Intent(BroadcastActions.FAILURE);
+ localIntent.putExtra(BroadcastExtras.MESSAGE, getString(R.string.failedToRemoveGeofence));
+ sendBroadcastWithUUID(localIntent, uuid);
+ return;
+
+ }
+ });
+
+ }
+
+ private void stopGeofence(final String uuid, final List requestIds) {
+
+ fenceClient.removeGeofences(requestIds)
+ .addOnSuccessListener(new OnSuccessListener() {
+ @Override
+ public void onSuccess(Void aVoid) {
+
+ // Geofences removed
+ Log.i(TAG, "GEOFENCES REMOVED.");
+
+ // Loop through list of ids and remove them
+ for (String id : requestIds) {
+ _geofences.remove(id);
+ }
+
+ }
+ })
+ .addOnFailureListener(new OnFailureListener() {
+ @Override
+ public void onFailure(@NonNull Exception e) {
+ // Failed to remove geofences
+ Log.e(TAG, "FAILED TO REMOVE GEOFENCES: " + e.toString());
+ }
+ });
+
+ }
+
+ /**
+ * Get LocalBroadcastManager
+ *
+ * @return the LocalBroadcastManager
+ */
+ private LocalBroadcastManager getLocalBroadcastManager() {
+ return LocalBroadcastManager.getInstance(this);
+ }
+
+ /**
+ * Get LocalBroadcastManager
+ *
+ * @return the LocalBroadcastManager
+ */
+ private static LocalBroadcastManager getLocalBroadcastManager(Context context) {
+ return LocalBroadcastManager.getInstance(context);
+ }
+}
diff --git a/app/src/main/java/org/emstrack/ambulance/services/BroadcastService.java b/app/src/main/java/org/emstrack/ambulance/services/BroadcastService.java
new file mode 100644
index 00000000..fa471081
--- /dev/null
+++ b/app/src/main/java/org/emstrack/ambulance/services/BroadcastService.java
@@ -0,0 +1,38 @@
+package org.emstrack.ambulance.services;
+
+import android.app.Service;
+import android.content.Intent;
+import android.support.v4.content.LocalBroadcastManager;
+
+/**
+ * Created by mauricio on 3/24/2018.
+ */
+
+public abstract class BroadcastService extends Service {
+
+ public void sendBroadcastWithUUID(Intent intent) {
+ sendBroadcastWithUUID(intent, null);
+ }
+
+ public void sendBroadcastWithUUID(Intent intent, String uuid) {
+
+ if (uuid != null)
+ // inject uuid
+ intent.putExtra(OnServiceComplete.UUID, uuid);
+
+ // broadcast
+ getLocalBroadcastManager().sendBroadcast(intent);
+
+ }
+
+
+ /**
+ * Get the LocalBroadcastManager
+ *
+ * @return The system LocalBroadcastManager
+ */
+ private LocalBroadcastManager getLocalBroadcastManager() {
+ return LocalBroadcastManager.getInstance(this);
+ }
+
+}
diff --git a/app/src/main/java/org/emstrack/ambulance/services/GeofenceBroadcastReceiver.java b/app/src/main/java/org/emstrack/ambulance/services/GeofenceBroadcastReceiver.java
new file mode 100644
index 00000000..6bd3d715
--- /dev/null
+++ b/app/src/main/java/org/emstrack/ambulance/services/GeofenceBroadcastReceiver.java
@@ -0,0 +1,107 @@
+package org.emstrack.ambulance.services;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.support.v4.content.LocalBroadcastManager;
+import android.util.Log;
+
+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 {
+
+ final String TAG = GeofenceBroadcastReceiver.class.getSimpleName();
+
+ /**
+ * Receives incoming intents.
+ *
+ * @param context the application context.
+ * @param intent sent by Location Services. This Intent is provided to Location
+ * Services (inside a PendingIntent) when addGeofences() is called.
+ */
+ @Override
+ public void onReceive(Context context, Intent intent) {
+
+ // Enqueues a JobIntentService passing the context and intent as parameters
+ Log.d(TAG, "Got broadcast");
+
+ GeofencingEvent geofencingEvent = GeofencingEvent.fromIntent(intent);
+ if (geofencingEvent.hasError()) {
+ //String errorMessage = GeofenceErrorMessages.getErrorString(this,
+ // geofencingEvent.getErrorCode());
+ //Log.e(TAG, errorMessage);
+ Log.d(TAG, "Geofencing Error");
+ return;
+ }
+
+ // 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");
+ }
+
+ // Broadcast event
+ Intent localIntent = new Intent(AmbulanceForegroundService.BroadcastActions.GEOFENCE_EVENT);
+ localIntent.putExtra(AmbulanceForegroundService.BroadcastExtras.GEOFENCE_TRANSITION,
+ geofenceTransition);
+ getLocalBroadcastManager(context).sendBroadcast(localIntent);
+
+ } else {
+ Log.i(TAG, "GEOFENCE_TRIGGERED: UNKNOWN EVENT " + String.valueOf(geofenceTransition));
+ }
+
+ }
+
+ /**
+ * Get the LocalBroadcastManager
+ *
+ * @return The system LocalBroadcastManager
+ */
+ private LocalBroadcastManager getLocalBroadcastManager(Context context) {
+ return LocalBroadcastManager.getInstance(context);
+ }
+
+}
diff --git a/app/src/main/java/org/emstrack/ambulance/services/OnServiceComplete.java b/app/src/main/java/org/emstrack/ambulance/services/OnServiceComplete.java
new file mode 100644
index 00000000..b20e6474
--- /dev/null
+++ b/app/src/main/java/org/emstrack/ambulance/services/OnServiceComplete.java
@@ -0,0 +1,199 @@
+package org.emstrack.ambulance.services;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.os.Handler;
+import android.support.v4.content.LocalBroadcastManager;
+import android.util.Log;
+
+import org.emstrack.ambulance.dialogs.AlertSnackbar;
+
+/**
+ * Created by mauricio on 3/21/2018.
+ */
+
+public abstract class OnServiceComplete extends BroadcastReceiver {
+
+ public static final String UUID = "_UUID_";
+
+ private final String TAG = OnServiceComplete.class.getSimpleName();
+
+ private final String successAction;
+ private final String failureAction;
+ private final String uuid;
+ private String failureMessage;
+ private AlertSnackbar alert;
+
+ private boolean successFlag;
+ private boolean completeFlag;
+
+ public OnServiceComplete(final Context context,
+ final String successAction,
+ final String failureAction,
+ Intent intent,
+ int timeout) {
+
+ // uuid
+ this.uuid = java.util.UUID.randomUUID().toString();
+
+ // actions
+ this.successAction = successAction;
+ this.failureAction = failureAction;
+
+ // success and complete flags
+ this.successFlag = false;
+ this.completeFlag = false;
+
+ // Register actions for broadcasting
+ IntentFilter successIntentFilter = new IntentFilter(successAction);
+ getLocalBroadcastManager(context).registerReceiver(this, successIntentFilter);
+
+ IntentFilter failureIntentFilter = new IntentFilter(failureAction);
+ getLocalBroadcastManager(context).registerReceiver(this, failureIntentFilter);
+
+ // Default alert is AlertLog
+ this.alert = new AlertSnackbar(TAG);
+
+ // Defaiult failure message
+ this.failureMessage = "Failed to complete service request";
+
+ // Run
+ this.run();
+
+ // Start service
+ if (intent != null) {
+
+ // Start service
+ intent.putExtra(UUID, this.uuid);
+ context.startService(intent);
+
+ }
+
+ // Start timeout timer
+ new Handler().postDelayed(new Runnable() {
+ @Override
+ public void run() {
+
+ if (!isComplete()) {
+
+ // Broadcast failure
+ Intent localIntent = new Intent(failureAction);
+ localIntent.putExtra(OnServiceComplete.UUID, uuid);
+ localIntent.putExtra(AmbulanceForegroundService.BroadcastExtras.MESSAGE,
+ "Timed out without completing service.");
+ context.sendBroadcast(localIntent);
+
+ }
+
+ // otherwise die graciously
+
+ }
+ }, timeout);
+
+ }
+
+ public OnServiceComplete(final Context context,
+ final String successAction,
+ final String failureAction,
+ Intent intent) {
+
+ this(context, successAction, failureAction, intent, 10000);
+
+ }
+
+ public String getUuid() { return uuid; }
+
+ public boolean isSuccess() {
+ return successFlag;
+ }
+
+ public boolean isComplete() {
+ return completeFlag;
+ }
+
+ public OnServiceComplete setAlert(AlertSnackbar alert) {
+ this.alert = alert;
+ return this;
+ }
+
+ public OnServiceComplete setFailureMessage(String failureMessage) {
+ this.failureMessage = failureMessage;
+ return this;
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+
+ // quick return if no intent
+ if (intent == null)
+ return;
+
+ // get uuid
+ String uuid = intent.getStringExtra(UUID);
+
+ // quick return if not same uuid
+ if (!this.uuid.equals(uuid))
+ return;
+
+ // unregister first
+ unregister(context);
+
+ // Process actions
+ final String action = intent.getAction();
+ if (action.equals(successAction)) {
+
+ Log.i(TAG, "SUCCESS");
+ this.successFlag = true;
+ onSuccess(intent.getExtras());
+
+ } else if (action.equals(failureAction)) {
+
+ Log.i(TAG, "FAILURE");
+ this.successFlag = false;
+ onFailure(intent.getExtras());
+
+ } else
+ Log.i(TAG, "Unknown action '" + action + "'");
+
+ // complete
+ this.completeFlag = true;
+
+ }
+
+ public void run() { }
+
+ public abstract void onSuccess(Bundle extras);
+
+ public void onFailure(Bundle extras) {
+
+ // Alert user
+ String message = failureMessage;
+ if (extras != null) {
+ String msg = extras.getString(AmbulanceForegroundService.BroadcastExtras.MESSAGE);
+ if (msg != null)
+ message += '\n' + msg;
+
+ }
+
+ // Alert user
+ alert.alert(message);
+
+ }
+
+ public void unregister(Context context) {
+ getLocalBroadcastManager(context).unregisterReceiver(this);
+ }
+
+ /**
+ * Get the LocalBroadcastManager
+ *
+ * @return The system LocalBroadcastManager
+ */
+ private LocalBroadcastManager getLocalBroadcastManager(Context context) {
+ return LocalBroadcastManager.getInstance(context);
+ }
+
+}
diff --git a/app/src/main/java/org/emstrack/ambulance/services/OnServicesComplete.java b/app/src/main/java/org/emstrack/ambulance/services/OnServicesComplete.java
new file mode 100644
index 00000000..dc2a895b
--- /dev/null
+++ b/app/src/main/java/org/emstrack/ambulance/services/OnServicesComplete.java
@@ -0,0 +1,229 @@
+package org.emstrack.ambulance.services;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.os.Handler;
+import android.support.v4.content.LocalBroadcastManager;
+import android.util.Log;
+
+import org.emstrack.ambulance.dialogs.AlertSnackbar;
+
+/**
+ * Created by mauricio on 3/21/2018.
+ */
+
+public abstract class OnServicesComplete extends BroadcastReceiver {
+
+ public static final String UUID = "_UUID_";
+
+ private final String TAG = OnServicesComplete.class.getSimpleName();
+
+ private final String[] successActions;
+ private final String[] failureActions;
+ private final String uuid;
+ private String failureMessage;
+ private AlertSnackbar alert;
+
+ private boolean successFlag;
+ private boolean completeFlag;
+
+ public OnServicesComplete(final Context context,
+ final String[] successActions,
+ final String[] failureActions,
+ Intent intent,
+ int timeout) {
+
+ // uuid
+ this.uuid = java.util.UUID.randomUUID().toString();
+
+ // actions
+ this.successActions = successActions;
+ this.failureActions = failureActions;
+
+ // success and complete flags
+ this.successFlag = false;
+ this.completeFlag = false;
+
+ // Register actions for broadcasting
+ IntentFilter intentFilter = new IntentFilter();
+ for (String action : successActions)
+ intentFilter.addAction(action);
+ for (String action: failureActions)
+ intentFilter.addAction(action);
+ getLocalBroadcastManager(context).registerReceiver(this, intentFilter);
+
+ // Default alert is AlertLog
+ this.alert = new AlertSnackbar(TAG);
+
+ // Defaiult failure message
+ this.failureMessage = "Failed to complete service request";
+
+ // Run
+ this.run();
+
+ // Start service
+ if (intent != null) {
+
+ // Start service
+ intent.putExtra(UUID, this.uuid);
+ context.startService(intent);
+
+ }
+
+ // Start timeout timer
+ new Handler().postDelayed(new Runnable() {
+ @Override
+ public void run() {
+
+ if (!isComplete()) {
+
+ for (String action: failureActions) {
+
+ // Broadcast failures
+ Intent localIntent = new Intent(action);
+ localIntent.putExtra(OnServicesComplete.UUID, uuid);
+ localIntent.putExtra(AmbulanceForegroundService.BroadcastExtras.MESSAGE,
+ "Timed out without completing service.");
+ context.sendBroadcast(localIntent);
+
+ }
+
+ }
+
+ // otherwise die graciously
+
+ }
+ }, timeout);
+
+ }
+
+ public OnServicesComplete(final Context context,
+ final String[] successActions,
+ final String[] failureActions,
+ Intent intent) {
+
+ this(context, successActions, failureActions, intent, 10000);
+
+ }
+
+ public String getUuid() { return uuid; }
+
+ public boolean isSuccess() {
+ return successFlag;
+ }
+
+ public boolean isComplete() {
+ return completeFlag;
+ }
+
+ public OnServicesComplete setAlert(AlertSnackbar alert) {
+ this.alert = alert;
+ return this;
+ }
+
+ public OnServicesComplete setFailureMessage(String failureMessage) {
+ this.failureMessage = failureMessage;
+ return this;
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+
+ // quick return if no intent
+ if (intent == null)
+ return;
+
+ // get uuid
+ String uuid = intent.getStringExtra(UUID);
+
+ // quick return if not same uuid
+ if (!this.uuid.equals(uuid))
+ return;
+
+ // unregister first
+ unregister(context);
+
+ // Retrieve action
+ final String action = intent.getAction();
+
+ // Process success actions
+ boolean match = false;
+ for (String _action: successActions) {
+
+ if (action.equals(_action)) {
+
+ Log.i(TAG, "SUCCESS");
+ this.successFlag = true;
+ onSuccess(intent.getExtras());
+
+ match = true;
+ break;
+ }
+
+ }
+
+ // Process failure actions
+ if (!match)
+ for (String _action: failureActions) {
+
+ if (action.equals(_action)) {
+
+ Log.i(TAG, "FAILURE");
+ this.successFlag = false;
+ onFailure(intent.getExtras());
+
+ match = true;
+ break;
+
+ }
+
+ }
+
+ if (match)
+
+ // complete
+ this.completeFlag = true;
+
+ else
+
+ Log.i(TAG, "Unknown action '" + action + "'");
+
+ }
+
+ public void run() { }
+
+ public abstract void onSuccess(Bundle extras);
+
+ public void onFailure(Bundle extras) {
+
+ // Alert user
+ String message = failureMessage;
+ if (extras != null) {
+ String msg = extras.getString(AmbulanceForegroundService.BroadcastExtras.MESSAGE);
+ if (msg != null)
+ message += '\n' + msg;
+
+ }
+
+ // Alert user
+ alert.alert(message);
+
+ }
+
+ public void unregister(Context context) {
+ getLocalBroadcastManager(context).unregisterReceiver(this);
+ }
+
+ /**
+ * Get the LocalBroadcastManager
+ *
+ * @return The system LocalBroadcastManager
+ */
+ private LocalBroadcastManager getLocalBroadcastManager(Context context) {
+ return LocalBroadcastManager.getInstance(context);
+ }
+
+}
diff --git a/app/src/main/java/org/emstrack/ambulance/util/Geofence.java b/app/src/main/java/org/emstrack/ambulance/util/Geofence.java
new file mode 100644
index 00000000..26e422a3
--- /dev/null
+++ b/app/src/main/java/org/emstrack/ambulance/util/Geofence.java
@@ -0,0 +1,47 @@
+package org.emstrack.ambulance.util;
+
+import org.emstrack.models.Location;
+
+import static com.google.android.gms.location.Geofence.GEOFENCE_TRANSITION_ENTER;
+import static com.google.android.gms.location.Geofence.GEOFENCE_TRANSITION_EXIT;
+import static com.google.android.gms.location.Geofence.NEVER_EXPIRE;
+
+/**
+ * Created by mauricio on 4/26/18.
+ */
+
+public class Geofence {
+
+ Location location;
+ float radius;
+ boolean isHospital;
+
+ 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);
+ }
+
+ private com.google.android.gms.location.Geofence build(String id, int transitionTypes) {
+
+ // Create geofence object
+ com.google.android.gms.location.Geofence.Builder builder = new com.google.android.gms.location.Geofence.Builder();
+ builder.setRequestId(id);
+ builder.setCircularRegion((float) location.getLatitude(), (float) location.getLongitude(), radius);
+ builder.setExpirationDuration(NEVER_EXPIRE);
+ builder.setTransitionTypes(transitionTypes);
+ return builder.build();
+
+ }
+
+}
diff --git a/app/src/main/java/org/emstrack/ambulance/util/LatLon.java b/app/src/main/java/org/emstrack/ambulance/util/LatLon.java
new file mode 100644
index 00000000..cb1ea7ed
--- /dev/null
+++ b/app/src/main/java/org/emstrack/ambulance/util/LatLon.java
@@ -0,0 +1,85 @@
+package org.emstrack.ambulance.util;
+
+import android.location.Location;
+
+/**
+ * Created by mauricio on 3/14/2018.
+ */
+
+public class LatLon {
+
+ public final static double earthRadius = 6371e3; // in meters
+ public static double stationaryRadius = 10.; // in meters
+ public static double stationaryVelocity= .1; // in meters/s
+
+ public static double calculateDistanceHaversine(Location location1, Location location2) {
+
+ // convert latitude and longitude to radians first
+ double lat1 = Math.PI * location1.getLatitude() / 180;
+ double lat2 = Math.PI * location2.getLatitude() / 180;
+ double d_phi = lat2 - lat1;
+ double d_lambda = Math.PI * (location2.getLongitude() - location1.getLongitude()) / 180;
+
+ double a = Math.sin(d_phi / 2) * Math.sin(d_phi / 2) + Math.cos(lat1) * Math.cos(lat2) * Math.sin(d_lambda / 2) * Math.sin(d_lambda / 2);
+ double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
+
+ return LatLon.earthRadius * c;
+
+ }
+
+ public static double calculateBearing(Location location1, Location location2) {
+
+ // convert latitude and longitude to radians first
+ double lat1 = Math.PI * location1.getLatitude() / 180;
+ double lat2 = Math.PI * location2.getLatitude() / 180;
+ double d_lambda = Math.PI * (location2.getLongitude() - location1.getLongitude()) / 180;
+
+ // calculate bearing and convert to degrees
+ double bearing = (180 / Math.PI) * Math.atan2(Math.sin(d_lambda) * Math.cos(lat2),
+ Math.cos(lat1) * Math.sin(lat2) - Math.sin(lat1) * Math.cos(lat2) * Math.cos(d_lambda));
+
+ return (bearing < 0 ? bearing + 360 : bearing);
+
+ }
+
+ public static double[] calculateDistanceAndBearing(Location location1, Location location2) {
+
+ // convert latitude and longitude to radians first
+ double lat1 = Math.PI * location1.getLatitude() / 180;
+ double lat2 = Math.PI * location2.getLatitude() / 180;
+ double d_phi = lat2 - lat1;
+ double d_lambda = Math.PI * (location2.getLongitude() - location1.getLongitude()) / 180;
+
+ // calculate distance
+ double a = Math.sin(d_phi / 2) * Math.sin(d_phi / 2) + Math.cos(lat1) * Math.cos(lat2) * Math.sin(d_lambda / 2) * Math.sin(d_lambda / 2);
+ double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
+ double distance = LatLon.earthRadius * c;
+
+ // calculate bearing and convert to degrees
+ double bearing = (180 / Math.PI) * Math.atan2(Math.sin(d_lambda) * Math.cos(lat2),
+ Math.cos(lat1) * Math.sin(lat2) - Math.sin(lat1) * Math.cos(lat2) * Math.cos(d_lambda));
+
+ return new double[] {distance, (bearing < 0 ? bearing + 360 : bearing)};
+
+ }
+
+ public static Location updateLocation(Location start, double bearing, double distance) {
+
+ // convert latitude, longitude, and bearing to radians first
+ double lat1 = Math.PI * start.getLatitude() / 180;
+ double lon1 = Math.PI * start.getLatitude() / 180;
+ double brng = Math.PI * bearing / 180;
+
+ double lat2 = Math.asin(Math.sin(lat1) * Math.cos(distance / earthRadius) +
+ Math.cos(lat1) * Math.sin(distance / earthRadius) * Math.cos(brng));
+ double lon2 = lon1 + Math.atan2(Math.sin(brng) * Math.sin(distance / earthRadius) * Math.cos(lat1),
+ Math.cos(distance / earthRadius) - Math.sin(lat1) * Math.sin(lat2));
+
+ Location location = new Location(start);
+ location.setLatitude(lat2);
+ location.setLongitude(lon2);
+
+ return location;
+
+ }
+}
diff --git a/app/src/main/java/org/emstrack/ambulance/util/LocationFilter.java b/app/src/main/java/org/emstrack/ambulance/util/LocationFilter.java
new file mode 100644
index 00000000..b6459a41
--- /dev/null
+++ b/app/src/main/java/org/emstrack/ambulance/util/LocationFilter.java
@@ -0,0 +1,184 @@
+package org.emstrack.ambulance.util;
+
+import android.location.Location;
+import android.util.Log;
+
+import org.ejml.data.DMatrix5x5;
+import org.ejml.simple.SimpleMatrix;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+import static org.emstrack.ambulance.util.LatLon.calculateBearing;
+import static org.emstrack.ambulance.util.LatLon.calculateDistanceAndBearing;
+import static org.emstrack.ambulance.util.LatLon.calculateDistanceHaversine;
+import static org.emstrack.ambulance.util.LatLon.stationaryRadius;
+import static org.emstrack.ambulance.util.LatLon.stationaryVelocity;
+import static org.emstrack.ambulance.util.LatLon.updateLocation;
+
+/**
+ * Created by mauricio on 3/22/2018.
+ */
+
+public class LocationFilter {
+
+ private static final String TAG = LocationFilter.class.getSimpleName();
+
+ private LocationUpdate location;
+
+ public LocationFilter(LocationUpdate location) {
+ this.location = location;
+ }
+
+ public void setLocation(LocationUpdate location) {
+ this.location = location;
+ }
+
+ /**
+ * Update current position based on a new measurement
+ *
+ * @param update the update
+ */
+ public void update(Location update, List filteredLocations) {
+
+ // elapsed time
+ double dt = update.getTime() - location.getTimestamp().getTime();
+
+ // Predict next location
+ // Location prediction = updateLocation(location, bearing, velocity * dt);
+
+ // measure velocity and bearing
+ double[] dandb = calculateDistanceAndBearing(location.getLocation(), update);
+ double distance = dandb[0];
+ double brn = dandb[1];
+ double vel = location.getVelocity();
+ if (dt > 0)
+ vel = distance / dt;
+
+ // locationFilter velocity
+ double Kv = 0.9;
+ double velocity = location.getVelocity();
+ velocity += Kv * (vel - velocity);
+ location.setVelocity(velocity);
+
+ // locationFilter bearing
+ double Kb = 0.9;
+ double bearing = location.getBearing();
+ bearing += Kb * (brn - bearing);
+ location.setBearing(bearing);
+
+ if ((velocity > stationaryVelocity && distance > stationaryRadius) ||
+ (velocity <= stationaryVelocity && distance > 3 * stationaryRadius)) {
+
+ // update location
+ location.setLocation(update);
+ location.setTimestamp(new Date(update.getTime()));
+
+ // add location to filtered locations
+ filteredLocations.add(new LocationUpdate(location));
+
+ }
+
+ Log.i(TAG, "velocity = " + velocity + ", distance = " + distance + ", bearing = " + bearing + "(" + update.getBearing() + ")");
+
+}
+
+ public List update(List locations) {
+
+ // Fast return if no updates
+ List filteredLocations = new ArrayList<>();
+ if (locations == null || locations.size() == 0)
+ return filteredLocations;
+
+ // initialize
+ if (location == null)
+ // use first record
+ location = new LocationUpdate(locations.get(0));
+
+ // loop through records
+ for (Location location : locations)
+ update(location, filteredLocations);
+
+ return filteredLocations;
+ }
+
+ /**
+ * Model is that of a constant forward velocity and constant angular velocity
+ *
+ * xDot(t) = v(t) cos(theta(t))
+ * yDot(t) = v(t) cos(theta(t))
+ * thetaDot(t) = thetaDot(t)
+ * thetaDotDot(t) = 0
+ * vDot(t) = 0
+ *
+ * Discretizing at t+ = tk + dt, t = tk, dt = t+ - t, we obtain the model:
+ *
+ * x(tk+), y(tk+) = f(x(tk), y(tk), theta(tk-), v(tk) dt)
+ * theta(tk+) = theta(tk) + thetaDot(tk) dt
+ * thetaDot(tk+) = thetaDot(tk)
+ * v(tk+) = v(tk)
+ *
+ * or
+ *
+ * X = (x, y, theta, thetaDot, v)
+ * X(tk+) = F(X(tk),tk),
+ * Z(tk) = G(X(tk),tk)
+ *
+ * Partials:
+ *
+ * f1: x(tk+) = x(tk) + dt v(tk) cos(theta(tk))
+ * f2: y(tk+) = y(tk) + dt v(tk) sin(theta(tk))
+ * f3: theta(tk+) = theta(tk) + thetaDot(tk) dt
+ * f4: thetaDot(tk+) = thetaDot(tk)
+ * f5: v(tk+) = v(tk)
+ *
+ * Fk = dF/dx
+ * = [1, 0, -dt*v(tk)*sin(theta(tk)), 0, dt*cos(theta(tk));
+ * 0, 1, dt*v(tk)*cos(theta(tk)), 0, dt*sin(theta(tk));
+ * 0, 0, 1, dt, 0;
+ * 0, 0, 0, 1, 0;
+ * 0, 0, 0, 0, 1]
+ *
+ * g1: z1(tk) = x(tk)
+ * g2: z2(tk) = y(tk)
+ *
+ * Hk = dG/dx
+ * = [1, 0, 0, 0;
+ * 0, 1, 0, 0];
+ *
+ *
+ * Extended Kalman locationFilter
+ *
+ * Prediction:
+ *
+ * xHat(tk+|tk), yHat(tk+|tk) = f(xHat(tk), yHat(tk), thetaHat(tk), vHat(tk) tk)
+ * thetaHat(tk+|tk) = thetaHat(tk) + thetaDotHat(tk) dt
+ * thetaDotHat(tk+|tk) = thetaDotHat(tk)
+ * vHat(tk+|tk) = vHat(tk)
+ *
+ * or
+ *
+ * XHat(tk+|tk) = F(XHat(tk),tk)
+ *
+ * Covariance prediction:
+ *
+ * P(tk+|tk) = Fk P(tk) Fk' + Qk
+ *
+ * At time tk we obtain a measurement ot z(tk) = (x(tk), y(tk))
+ *
+ * Update:
+ *
+ * Sk = Hk P(tk|tk) Hk' + Rk
+ * Kk = P(tk+|tk) Hk' inv(Sk)
+ *
+ * XHat(tk+) = XHat(tk+|tk) + K (z(tk) - zHat(tk))
+ *
+ * Covariance update:
+ *
+ * P(tk+ = P(tk+|tk+) = (I - Kk Hk) P(tk+|tk)
+ *
+ */
+
+
+}
diff --git a/app/src/main/java/org/emstrack/ambulance/util/LocationUpdate.java b/app/src/main/java/org/emstrack/ambulance/util/LocationUpdate.java
new file mode 100644
index 00000000..ac81ef40
--- /dev/null
+++ b/app/src/main/java/org/emstrack/ambulance/util/LocationUpdate.java
@@ -0,0 +1,80 @@
+package org.emstrack.ambulance.util;
+
+import android.location.Location;
+
+import java.util.Date;
+
+/**
+ * Created by mauricio on 3/22/2018.
+ */
+
+public class LocationUpdate {
+
+ private Location location;
+ private float bearing;
+ private float velocity;
+ private Date timestamp;
+
+ public LocationUpdate() {
+ this.location = null;
+ this.bearing = (float) 0.0;
+ this.velocity = (float) 0.0;
+ this.timestamp = new Date();
+ }
+
+ public LocationUpdate(Location location) {
+ this.location = new Location(location);
+ this.bearing = location.getBearing();
+ this.velocity = location.getSpeed();
+ this.timestamp = new Date(location.getTime());
+ }
+
+ public LocationUpdate(LocationUpdate update) {
+ this.location = new Location(update.location);
+ this.bearing = update.bearing;
+ this.velocity = update.velocity;
+ this.timestamp = new Date(update.timestamp.getTime());
+ }
+
+ public void setLocation(Location location) {
+ this.location = location;
+ }
+
+ public void setBearing(float bearing) {
+ this.bearing = bearing;
+ }
+
+ public void setVelocity(float velocity) {
+ this.velocity = velocity;
+ }
+
+ public void setBearing(double bearing) {
+ this.bearing = (float) bearing;
+ }
+
+ public void setVelocity(double velocity) {
+ this.velocity = (float) velocity;
+ }
+
+ public void setTimestamp(Date timestamp) {
+ this.timestamp = timestamp;
+ }
+
+ public Location getLocation() {
+ return location;
+ }
+
+ public float getBearing() {
+ return bearing;
+ }
+
+ public float getVelocity() {
+ return velocity;
+ }
+
+ public Date getTimestamp() {
+ return timestamp;
+ }
+};
+
+
diff --git a/app/src/main/java/org/emstrack/ambulance/views/HospitalEquipmentViewHolder.java b/app/src/main/java/org/emstrack/ambulance/views/HospitalEquipmentViewHolder.java
new file mode 100644
index 00000000..c06efb02
--- /dev/null
+++ b/app/src/main/java/org/emstrack/ambulance/views/HospitalEquipmentViewHolder.java
@@ -0,0 +1,33 @@
+package org.emstrack.ambulance.views;
+
+import android.view.View;
+import android.widget.CheckBox;
+import android.widget.TextView;
+
+import com.thoughtbot.expandablerecyclerview.viewholders.ChildViewHolder;
+
+import org.emstrack.ambulance.R;
+import org.emstrack.models.HospitalEquipment;
+
+/**
+ * Created by mauricio on 3/11/2018.
+ */
+
+public class HospitalEquipmentViewHolder extends ChildViewHolder {
+
+ TextView hospitalEquipmentNameTextView;
+ TextView hospitalEquipmentValueTextView;
+
+ public HospitalEquipmentViewHolder(View itemView) {
+ super(itemView);
+
+ hospitalEquipmentNameTextView = itemView.findViewById(R.id.hospital_equipment_name);
+ hospitalEquipmentValueTextView = itemView.findViewById(R.id.hospital_equipment_value);
+ }
+
+ public void setHospitalEquipment(HospitalEquipment hospitalEquipment) {
+ hospitalEquipmentNameTextView.setText(hospitalEquipment.getEquipmentName());
+ hospitalEquipmentValueTextView.setText(hospitalEquipment.getValue());
+ }
+
+}
diff --git a/app/src/main/java/org/emstrack/ambulance/views/HospitalViewHolder.java b/app/src/main/java/org/emstrack/ambulance/views/HospitalViewHolder.java
new file mode 100644
index 00000000..53c0e6d1
--- /dev/null
+++ b/app/src/main/java/org/emstrack/ambulance/views/HospitalViewHolder.java
@@ -0,0 +1,39 @@
+package org.emstrack.ambulance.views;
+
+import android.view.View;
+import android.widget.FrameLayout;
+import android.widget.ImageButton;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.thoughtbot.expandablerecyclerview.viewholders.GroupViewHolder;
+
+import org.emstrack.ambulance.R;
+import org.emstrack.ambulance.models.HospitalExpandableGroup;
+import org.emstrack.models.Hospital;
+
+/**
+ * Created by mauricio on 3/11/2018.
+ */
+
+// TODO: Modify equipment display depending on type
+// TODO: Icon has white instead of transparency
+
+public class HospitalViewHolder extends GroupViewHolder {
+
+ ImageView hospitalThumbnailImageView;
+ TextView hospitalNameTextView;
+ FrameLayout frameLayout;
+
+ public HospitalViewHolder(View itemView) {
+ super(itemView);
+
+ hospitalNameTextView = (TextView) itemView.findViewById(R.id.hospital_name);
+ hospitalThumbnailImageView = (ImageView) itemView.findViewById(R.id.hospital_thumbnail);
+ }
+
+ public void setHospital(Hospital hospital) {
+ hospitalNameTextView.setText(hospital.getName());
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/emstrack/hospital/HospitalApp.java b/app/src/main/java/org/emstrack/hospital/HospitalApp.java
deleted file mode 100644
index 4b25bb1e..00000000
--- a/app/src/main/java/org/emstrack/hospital/HospitalApp.java
+++ /dev/null
@@ -1,33 +0,0 @@
-package org.emstrack.hospital;
-
-import android.app.Application;
-
-import org.eclipse.paho.android.service.MqttAndroidClient;
-import org.emstrack.mqtt.MqttProfileClient;
-
-import java.util.UUID;
-
-/**
- * Created by mauri on 2/10/2018.
- */
-
-public class HospitalApp extends Application {
-
- final String serverUri = "ssl://cruzroja.ucsd.edu:8883";
- final String clientId = "HospitalAppClient_" + UUID.randomUUID().toString();
-
- MqttAndroidClient androidClient;
- MqttProfileClient client;
-
- public MqttProfileClient getProfileClient() {
- // lazy initialization
- if (client == null) {
-
- androidClient = new MqttAndroidClient(getApplicationContext(), serverUri, clientId);
- client = new MqttProfileClient(androidClient);
-
- }
- return client;
- }
-
-}
diff --git a/app/src/main/java/org/emstrack/hospital/HospitalEquipmentActivity.java b/app/src/main/java/org/emstrack/hospital/HospitalEquipmentActivity.java
deleted file mode 100644
index b4cee813..00000000
--- a/app/src/main/java/org/emstrack/hospital/HospitalEquipmentActivity.java
+++ /dev/null
@@ -1,200 +0,0 @@
-package org.emstrack.hospital;
-
-import java.util.ArrayList;
-
-import android.os.Bundle;
-import android.support.v7.app.ActionBar;
-import android.support.v7.app.AppCompatActivity;
-import android.util.Log;
-import android.view.View;
-import android.view.Window;
-import android.widget.ImageView;
-import android.widget.ListView;
-
-import com.google.gson.FieldNamingPolicy;
-import com.google.gson.Gson;
-import com.google.gson.GsonBuilder;
-
-import org.eclipse.paho.client.mqttv3.MqttException;
-import org.eclipse.paho.client.mqttv3.MqttMessage;
-
-import org.emstrack.hospital.adapters.ListAdapter;
-import org.emstrack.hospital.dialogs.LogoutDialog;
-import org.emstrack.hospital.interfaces.DataListener;
-
-import org.emstrack.mqtt.MqttProfileClient;
-import org.emstrack.mqtt.MqttProfileMessageCallback;
-
-import org.emstrack.models.HospitalEquipment;
-
-/**
- * Created by devinhickey on 4/20/17.
- * The Dashboard
- */
-public class HospitalEquipmentActivity extends AppCompatActivity {
-
- private static final String TAG = HospitalEquipmentActivity.class.getSimpleName();
- private ListAdapter adapter;
- int hospitalId = -1;
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
-
- super.onCreate(savedInstanceState);
-
- this.requestWindowFeature(Window.FEATURE_NO_TITLE);
- setContentView(R.layout.activity_dashboard);
-
- // Action bar
- getSupportActionBar().setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM);
- getSupportActionBar().setDisplayShowCustomEnabled(true);
- getSupportActionBar().setCustomView(R.layout.maintitlebar);
-
- // Connect logout dialog
- View view = getSupportActionBar().getCustomView();
- ImageView imageButton= view.findViewById(R.id.LogoutBtn);
- imageButton.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- // Create the logout dialog for the user
- LogoutDialog ld = LogoutDialog.newInstance();
- ld.show(getFragmentManager(), "logout_dialog");
- }
- });
-
- // Get data from Login and place it into the hospital
- hospitalId = Integer
- .parseInt(getIntent().getStringExtra("SELECTED_HOSPITAL_ID"));
-
- // Retrieve client
- final MqttProfileClient profileClient = ((HospitalApp) getApplication()).getProfileClient();
-
- // Set list adapter
- ListView lv = findViewById(R.id.dashboardListView);
- adapter = new ListAdapter(this,
- new ArrayList(),
- getSupportFragmentManager());
- adapter.setOnDataChangedListener(new DataListener() {
-
- @Override
- public void onDataChanged(String equipmentName, String data) {
-
- Log.d(TAG, "onDataChanged: " + equipmentName + "@" + data);
- String format = "{\"hospital_id\":%1$s,\"equipment_name\":\"%2$s\",\"value\":%3$s}";
-
- try {
- profileClient.publish("user/" + profileClient.getUsername() +
- "/hospital/" + hospitalId +
- "/equipment/" + equipmentName + "/data",
- String.format(format, hospitalId, equipmentName, data),
- 2, false);
- } catch (MqttException e) {
- Log.d(TAG, "Failed to publish updated equipment");
- }
- }
-
- });
- lv.setAdapter(adapter);
-
- try {
-
- // Start retrieving data
- profileClient.subscribe("hospital/" + hospitalId + "/metadata",
- 1, new MqttProfileMessageCallback() {
-
- @Override
- public void messageArrived(String topic, MqttMessage message) {
-
- try {
-
- // Unsubscribe to metadata
- profileClient.unsubscribe("hospital/" + hospitalId + "/metadata");
-
- } catch ( MqttException exception ) {
-
- Log.d(TAG,"Could not unsubscribe to 'hospital/" + hospitalId + "/metadata'");
- return;
- }
-
- // Parse to hospital metadata
- GsonBuilder gsonBuilder = new GsonBuilder();
- gsonBuilder.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES);
- Gson gson = gsonBuilder.create();
-
- try {
- // Subscribe to all hospital equipment topics
- profileClient.subscribe(
- "hospital/" + hospitalId + "/equipment/+/data",
- 1, new MqttProfileMessageCallback() {
- @Override
- public void messageArrived(String topic, MqttMessage message) {
-
- // Parse to hospital equipment
- GsonBuilder gsonBuilder = new GsonBuilder();
- gsonBuilder.setFieldNamingPolicy(
- FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES);
- Gson gson = gsonBuilder.create();
-
- // Found item in the hospital equipments object
- HospitalEquipment equipment = gson
- .fromJson(new String(message.getPayload()),
- HospitalEquipment.class);
-
- // add equipment to list
- adapter.add(equipment);
-
- }
- });
-
- /*
-
- // NOT NECESSARY: Already subscribed to all equipment topics
-
- HospitalEquipmentMetadata[] equipmentMetadata = gson
- .fromJson(new String(message.getPayload()),
- HospitalEquipmentMetadata[].class);
- for (HospitalEquipmentMetadata equipment : equipmentMetadata) {
- // Subscribe without a callback
- profileClient.subscribe(
- "hospital/" + hospitalId +
- "/equipment/" + equipment.getName() + "/data",
- 1, null);
- }
-
- */
-
- } catch (MqttException e) {
- Log.d(TAG, "Could not subscribe to hospital equipment topics");
- }
- }
-
- });
-
- } catch (MqttException e) {
- Log.d(TAG, "Could not subscribe to hospital metadata");
- }
-
- }
-
- @Override
- public void onBackPressed() {
-
- try {
-
- // Retrieve client
- final MqttProfileClient profileClient = ((HospitalApp) getApplication()).getProfileClient();
-
- // unsubscribe to current hospital equipment data
- profileClient.unsubscribe("hospital/" + hospitalId + "/equipment/+/data");
-
- } catch ( MqttException exception ) {
-
- Log.d(TAG,"Could not unsubscribe to 'hospital/" + hospitalId + "/equipment/+/data'");
-
- }
-
- // go back
- super.onBackPressed();
- }
-
-}
diff --git a/app/src/main/java/org/emstrack/hospital/HospitalListActivity.java b/app/src/main/java/org/emstrack/hospital/HospitalListActivity.java
deleted file mode 100644
index 1d6e5a39..00000000
--- a/app/src/main/java/org/emstrack/hospital/HospitalListActivity.java
+++ /dev/null
@@ -1,125 +0,0 @@
-package org.emstrack.hospital;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import android.content.Intent;
-import android.os.Bundle;
-import android.support.v7.app.AppCompatActivity;
-import android.support.v7.app.ActionBar;
-import android.view.View;
-import android.widget.AdapterView;
-import android.widget.ArrayAdapter;
-import android.widget.Button;
-import android.widget.ImageView;
-import android.widget.Spinner;
-import android.widget.Toast;
-import android.util.Log;
-
-import org.emstrack.hospital.dialogs.LogoutDialog;
-import org.emstrack.models.HospitalPermission;
-import org.emstrack.mqtt.MqttProfileClient;
-
-/**
- * Created by devinhickey on 5/24/17.
- */
-
-public class HospitalListActivity extends AppCompatActivity {
-
- private static final String TAG = "HospitalListActivity";
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
-
- Log.d(TAG, "onCreate");
-
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_hospital_list);
-
- // Action bar
- getSupportActionBar().setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM);
- getSupportActionBar().setDisplayShowCustomEnabled(true);
- getSupportActionBar().setCustomView(R.layout.maintitlebar);
- View view = getSupportActionBar().getCustomView();
-
- // Connect logout dialog
- ImageView imageButton = view.findViewById(R.id.LogoutBtn);
- imageButton.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- LogoutDialog ld = LogoutDialog.newInstance();
- ld.show(getFragmentManager(), "logout_dialog");
- }
- });
-
- // Retrieve hospitals
- final MqttProfileClient profileClient = ((HospitalApp) getApplication()).getProfileClient();
- final List hospitals = profileClient.getProfile().getHospitals();
-
- // No hospitals associated with this account
- if (hospitals.size() < 1) {
- Toast toast = new Toast(this);
- toast.setText("This account has no hospitals associated with it!");
- toast.setDuration(Toast.LENGTH_LONG);
- toast.show();
- return;
- }
-
- // Creates string arraylist of hospital names
- Log.d(TAG, "Creating hospital list...");
- ArrayList listObjects = new ArrayList<>();
- for (HospitalPermission hospital : hospitals) {
- Log.d(TAG, "Adding hospital " + hospital.getHospitalName());
- listObjects.add(hospital.getHospitalName());
- }
-
- // Create the Spinner connection
- final Spinner hospitalSpinner = findViewById(R.id.hospitalSpinner);
- hospitalSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
- @Override
- public void onItemSelected(AdapterView> parent, View view, int position, long id) {
- Log.d(TAG, "OnItemSelected Called");
- }
-
- @Override
- public void onNothingSelected(AdapterView> parent) {
- Log.d(TAG, "OnNothingSelected Called");
- }
- });
-
- // Create the basic adapter
- ArrayAdapter hospitalListAdapter = new ArrayAdapter<>(this,
- android.R.layout.simple_spinner_item, listObjects);
- hospitalListAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
-
- // Set the spinner's adapter
- hospitalSpinner.setAdapter(hospitalListAdapter);
-
- // Create the hospital button
- Button submitHospitalButton = findViewById(R.id.submitHospitalButton);
- submitHospitalButton.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- Log.d(TAG, "HospitalEquipmentMetadata Submit Button Clicked");
-
- int position = hospitalSpinner.getSelectedItemPosition();
- Log.d(TAG, "Position Selected: " + position);
- HospitalPermission selectedHospital = hospitals.get(position);
- Log.d(TAG, "Selected HospitalEquipmentMetadata: " + selectedHospital.getHospitalName());
-
- // Set the static list of HospitalEquipment in Dashboard
- Intent intent = new Intent(HospitalListActivity.this, HospitalEquipmentActivity.class);
- intent.putExtra("SELECTED_HOSPITAL_ID", Integer.toString(selectedHospital.getHospitalId()));
- startActivity(intent);
- }
- });
-
- } // end onCreate
-
- @Override
- public void onBackPressed() {
- LogoutDialog ld = LogoutDialog.newInstance();
- ld.show(getFragmentManager(), "logout_dialog");
- }
-
-}
diff --git a/app/src/main/java/org/emstrack/hospital/LoginActivity.java b/app/src/main/java/org/emstrack/hospital/LoginActivity.java
deleted file mode 100644
index fec2a68d..00000000
--- a/app/src/main/java/org/emstrack/hospital/LoginActivity.java
+++ /dev/null
@@ -1,250 +0,0 @@
-package org.emstrack.hospital;
-
-import android.app.Activity;
-import android.app.ProgressDialog;
-import android.content.DialogInterface;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.support.v7.app.ActionBar;
-import android.support.v7.app.AlertDialog;
-import android.support.v7.app.AppCompatActivity;
-import android.os.Bundle;
-import android.util.Log;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.inputmethod.InputMethodManager;
-import android.widget.Button;
-import android.widget.CheckBox;
-import android.widget.EditText;
-import android.widget.ImageView;
-
-import org.eclipse.paho.client.mqttv3.MqttException;
-
-import org.emstrack.mqtt.MqttProfileCallback;
-import org.emstrack.mqtt.MqttProfileClient;
-
-public class LoginActivity extends AppCompatActivity {
-
- private final String PREFERENCES_USERNAME = "username";
- private final String PREFERENCES_PASSWORD = "password";
- private final String PREFERENCES_REMEMBER_ME = "remember_me";
-
- private static final String TAG = "LoginActvity";
-
- private SharedPreferences creds_prefs;
- private ProgressDialog progressDialog;
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
-
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_login);
-
- // Action bar
- getSupportActionBar().setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM);
- getSupportActionBar().setDisplayShowCustomEnabled(true);
- getSupportActionBar().setCustomView(R.layout.maintitlebar);
- View view = getSupportActionBar().getCustomView();
- ImageView imageButton= view.findViewById(R.id.LogoutBtn);
- imageButton.setVisibility(View.GONE);
-
- // Create progress dialog
- progressDialog = new ProgressDialog(LoginActivity.this);
-
- // Find username and password from layout
- final EditText usernameField = findViewById(R.id.username);
- final EditText passwordField = findViewById(R.id.password);
- final CheckBox savedUsernameCheck = findViewById(R.id.checkBox);
-
- // Get credentials
- creds_prefs = getSharedPreferences("org.emstrack.hospital", MODE_PRIVATE);
-
- // Check if credentials are cached
- if (creds_prefs.getBoolean(PREFERENCES_REMEMBER_ME, false)) {
-
- Log.d(TAG, "Remember user enabled, using credentials");
- usernameField.setText(creds_prefs.getString(PREFERENCES_USERNAME, null));
- savedUsernameCheck.setChecked(true);
-
- } else{
- Log.d(TAG, "Remember user not enabled, asking for credentials");
- }
-
- // Submit button's click listener
- Button login_submit = findViewById(R.id.submit_login);
- login_submit.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View view) {
-
- // Get user info & remove whitspace
- String username = usernameField.getText().toString().trim();
- String password = passwordField.getText().toString().trim();
-
- if (username.isEmpty()) {
- alertDialog(LoginActivity.this,
- getResources().getString(R.string.alert_error_title),
- getResources().getString(R.string.error_empty_username));
- } else if (password.isEmpty()) {
- alertDialog(LoginActivity.this,
- getResources().getString(R.string.alert_error_title),
- getResources().getString(R.string.error_empty_password));
- } else {
-
- // Show progress dialog
- progressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
- progressDialog.setMessage(getResources().getString(R.string.message_please_wait));
- progressDialog.setIndeterminate(true);
- progressDialog.setCanceledOnTouchOutside(false);
- progressDialog.show();
-
- // while attempting to login
- loginHospital(username, password);
- }
-
- }
- });
-
- // Code to hide keyboard if user clicks out of window
- findViewById(R.id.relativeLayout).setOnTouchListener(new View.OnTouchListener() {
- @Override
- public boolean onTouch(View v, MotionEvent event) {
- InputMethodManager imm = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
- imm.hideSoftInputFromWindow(getCurrentFocus().getWindowToken(), 0);
- return true;
- }
- });
- }
-
- public void loginHospital(final String username, final String password) {
-
- // Retrieve client
- final MqttProfileClient profileClient = ((HospitalApp) getApplication()).getProfileClient();
-
- // Set callback to be called after profile is retrieved
- profileClient.setCallback(new MqttProfileCallback() {
-
- @Override
- public void onSuccess() {
-
- // Get preferences editor
- SharedPreferences.Editor editor = creds_prefs.edit();
-
- // Check if remember me enabled
- CheckBox remember_box = findViewById(R.id.checkBox);
- if (remember_box.isChecked()) {
-
- Log.d(TAG, "Checkbox checked, storing credentials");
- editor.putString(PREFERENCES_USERNAME, username);
- editor.putString(PREFERENCES_PASSWORD, password);
- editor.putBoolean(PREFERENCES_REMEMBER_ME, true);
-
- } else {
-
- editor.clear();
-
- }
-
- editor.apply();
-
- // Initiate new activity
- Intent hospitalIntent = new Intent(getApplicationContext(), HospitalListActivity.class);
- startActivity(hospitalIntent);
-
- // Clear the loading screen
- progressDialog.dismiss();
-
- // Clear the password field
- EditText passwordField = findViewById(R.id.password);
- passwordField.setText("");
- passwordField.clearFocus();
-
- // Clear the username field
- EditText usernameField = findViewById(R.id.username);
- usernameField.clearFocus();
-
- Log.d(TAG, "Done with LoginActivity.");
-
- }
-
- @Override
- public void onFailure(Throwable exception) {
-
- // Dismiss dialog
- progressDialog.dismiss();
-
- Log.d(TAG, "Failed to retrieve profile.");
- alertDialog(LoginActivity.this,
- getResources().getString(R.string.alert_error_title),
- exception.toString());
-
- }
-
- });
-
- try {
-
- // Attempt to connect
- profileClient.connect(username, password, new MqttProfileCallback() {
-
- @Override
- public void onSuccess() {
- Log.d(TAG, "Successfully connected to broker.");
- }
-
- @Override
- public void onFailure(Throwable exception) {
-
- // Dismiss dialog
- progressDialog.dismiss();
-
- Log.d(TAG, "Failed to connected to broker.");
- String message;
- if (exception instanceof MqttException) {
- int reason = ((MqttException) exception).getReasonCode();
- if (reason == MqttException.REASON_CODE_FAILED_AUTHENTICATION ||
- reason == MqttException.REASON_CODE_NOT_AUTHORIZED ||
- reason == MqttException.REASON_CODE_INVALID_CLIENT_ID)
- message = getResources().getString(R.string.error_invalid_credentials);
- else
- message = String.format(getResources().getString(R.string.error_connection_failed),
- exception.toString());
- } else {
- message = exception.toString();
- }
-
- // Alert user
- alertDialog(LoginActivity.this,
- getResources().getString(R.string.alert_error_title),
- message);
- }
-
- });
-
- } catch (MqttException exception) {
-
- // Alert user
- alertDialog(LoginActivity.this,
- getResources().getString(R.string.alert_error_title),
- String.format(getResources().getString(R.string.error_connection_failed),
- exception.toString()));
-
- }
-
- }
-
- public void alertDialog(Activity activity, String title, String message) {
-
- AlertDialog alertDialog = new AlertDialog.Builder(activity).create();
- alertDialog.setTitle(title);
- alertDialog.setMessage(message);
- alertDialog.setButton(AlertDialog.BUTTON_POSITIVE,
- getResources().getString(R.string.alert_button_positive_text),
- new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog, int which) {
- dialog.dismiss();
- }
- });
- alertDialog.show();
- }
-
-}
diff --git a/app/src/main/java/org/emstrack/hospital/adapters/ListAdapter.java b/app/src/main/java/org/emstrack/hospital/adapters/ListAdapter.java
deleted file mode 100644
index 64082393..00000000
--- a/app/src/main/java/org/emstrack/hospital/adapters/ListAdapter.java
+++ /dev/null
@@ -1,156 +0,0 @@
-package org.emstrack.hospital.adapters;
-
-import android.content.Context;
-import android.support.annotation.NonNull;
-import android.support.v4.app.FragmentManager;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ArrayAdapter;
-import android.widget.ImageView;
-import android.widget.TextView;
-
-//import org.emstrack.hospital.CustomDialog;
-import org.emstrack.hospital.interfaces.DataListener;
-import org.emstrack.hospital.R;
-import org.emstrack.hospital.dialogs.EquipmentBooleanDialog;
-import org.emstrack.hospital.dialogs.EquipmentValueDialog;
-import org.emstrack.models.HospitalEquipment;
-
-import java.util.ArrayList;
-
-/**
- * Created by Fabian Choi on 5/16/2017.
- */
-public class ListAdapter extends ArrayAdapter {
-
- private final Context context;
- private final ArrayList objects;
- private final FragmentManager fragmentManager;
- private DataListener dr;
-
- public ListAdapter(Context context, ArrayList objects, FragmentManager fm) {
- super(context, -1, objects);
- this.context = context;
- this.objects = objects;
- this.fragmentManager = fm;
- }
-
- public void setOnDataChangedListener(DataListener dr) {
- this.dr = dr;
- }
-
- @Override
- @NonNull
- public View getView(int position, View convertView, ViewGroup parent) {
-
- // Get the object, and the type
- // Inflate the correct view based on the type and update the parts of the layout -> set onClicks
-
- // TODO: This needs a revamp; the different equipment types should know how to convert to view
-
- System.out.println("GETVIEW - Position: " + position);
- final HospitalEquipment equipmentItem = objects.get(position);
-
- LayoutInflater inflater = LayoutInflater.from(context);
-
- final View row;
-
- final Character equipmentEtype = equipmentItem.getEquipmentEtype();
-
- try {
-
- // Integer type
- if (equipmentEtype == 'I' || equipmentEtype == 'S') {
-
- System.out.println("Adding Value to List");
- row = inflater.inflate(R.layout.list_item_value, parent, false);
-
- // Grab the elements of the Value ListItem
- System.out.println("Grabbing Elements from Row");
- TextView text = row.findViewById(R.id.valueTextView);
- TextView value = row.findViewById(R.id.valueData);
-
- // Set the elements of the ListItem
- System.out.println("Setting Elements in Row");
- text.setText(equipmentItem.getEquipmentName());
- if (equipmentEtype == 'I')
- value.setText(equipmentItem.getValue());
- else
- value.setText("...");
- // Store value in a tag
- row.setTag(equipmentItem.getValue());
-
- System.out.println("Setting row onClick Listener");
- row.setOnClickListener(new View.OnClickListener() {
-
- @Override
- public void onClick(View v) {
-
- String title = ((TextView) row.findViewById(R.id.valueTextView)).getText().toString();
- String value = v.getTag().toString();
- Character equipmentType =
- ((TextView) row.findViewById(R.id.valueData)).getText()
- .equals("...") ? 'S' : 'I';
- String message;
- if (equipmentEtype == 'I')
- message = context.getResources().getString(R.string.equipment_integer_message);
- else
- message = context.getResources().getString(R.string.equipment_string_message);
-
- EquipmentValueDialog vd = EquipmentValueDialog.newInstance(title, message, value);
- vd.setOnDataChangedListener(dr);
- vd.show(fragmentManager,
- "integer_dialog");
- }
-
- });
-
- } else if (equipmentEtype == 'B') {
-
- System.out.println("Adding Toggle to List");
- row = inflater.inflate(R.layout.list_item_boolean, parent, false);
-
- // Grab the elements of the Toggle ListItem
- TextView text = row.findViewById(R.id.toggleTextView);
- ImageView image = row.findViewById(R.id.toggleImage);
-
- // Set the elements of the ListItem
- text.setText(equipmentItem.getEquipmentName());
- row.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
-
- String title = ((TextView) row.findViewById(R.id.toggleTextView)).getText().toString();
- String message = context.getResources().getString(R.string.equipment_boolean_message);
- String value = equipmentItem.getValue();
- boolean toggled = (value.equals("True"));
-
- EquipmentBooleanDialog td = EquipmentBooleanDialog.newInstance(title, message, toggled, value);
- td.setOnDataChangedListener(dr);
-
- td.show(fragmentManager,
- "boolean_dialog");
- }
- });
-
- // Check which image to set
- if (equipmentItem.getValue().equals("True")) {
- image.setImageResource(R.drawable.green_check);
- } else {
- image.setImageResource(R.drawable.redx);
- }
-
- } else {
- return new View(context);
- }
-
- } catch (Exception e) {
- System.out.println("Caught an Exception");
- return new View(context);
- }
-
- return row;
- } // end getView
-
-}
diff --git a/app/src/main/java/org/emstrack/hospital/dialogs/EquipmentBooleanDialog.java b/app/src/main/java/org/emstrack/hospital/dialogs/EquipmentBooleanDialog.java
deleted file mode 100644
index 4a5dc4c7..00000000
--- a/app/src/main/java/org/emstrack/hospital/dialogs/EquipmentBooleanDialog.java
+++ /dev/null
@@ -1,181 +0,0 @@
-package org.emstrack.hospital.dialogs;
-
-import android.app.Dialog;
-import android.graphics.Typeface;
-import android.support.annotation.NonNull;
-import android.support.v4.app.DialogFragment;
-import android.content.DialogInterface;
-import android.os.Bundle;
-import android.support.v7.app.AlertDialog;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.widget.Button;
-import android.widget.CompoundButton;
-import android.widget.Switch;
-import android.widget.TextView;
-
-import org.emstrack.hospital.interfaces.DataListener;
-import org.emstrack.hospital.R;
-
-/**
- * Created by devinhickey on 6/5/17.
- */
-
-public class EquipmentBooleanDialog extends DialogFragment {
-
- private String title;
- private String message;
- private boolean isToggled;
- private String updatedData = "";
- private String oldData = "";
- private DataListener dr;
-
- /**
- * Empty Constructor
- */
- public EquipmentBooleanDialog() {
-
- }
-
- /**
- *
- * @param title
- * @param message
- * @return
- */
- public static EquipmentBooleanDialog newInstance(String title, String message, boolean isToggled, String data) {
-
- EquipmentBooleanDialog td = new EquipmentBooleanDialog();
-
- Bundle args = new Bundle();
- args.putString("Title", title);
- args.putString("Message", message);
- args.putBoolean("Toggle", isToggled);
- args.putString("Data", data);
-
- td.setArguments(args);
-
- return td;
-
- }
-
- @Override
- @NonNull
- public Dialog onCreateDialog(Bundle savedInstanceState) {
-
- title = getArguments().getString("Title");
- message = getArguments().getString("Message");
- isToggled = getArguments().getBoolean("Toggle");
- oldData = getArguments().getString("Data");
-
- System.out.println("Title: " + title);
- System.out.println("Message: " + message);
- System.out.println("Toggle: " + isToggled);
- System.out.println("Data: " + oldData);
-
- final AlertDialog.Builder alertBuilder = new AlertDialog.Builder(getActivity());
-// final LinearLayout ll = new LinearLayout(getActivity().getApplicationContext());
- final Button yesButton = new Button(getActivity().getApplicationContext());
- final Button noButton = new Button(getActivity().getApplicationContext());
-
- // Set the data elements
- // Check if the resource is available and set the title to account for it
- System.out.println("Data = " + oldData);
- if (oldData.equals("True")) {
- alertBuilder.setTitle((title + " - " + getResources().getString(R.string.equipment_boolean_true)));
- } else {
- alertBuilder.setTitle((title + " - " + getResources().getString(R.string.equipment_boolean_false)));
- }
- alertBuilder.setMessage(message);
-
- LayoutInflater inflater = LayoutInflater.from(getContext());
-
- View v = inflater.inflate(R.layout.toggle_dialog, null, false);
- alertBuilder.setView(v.findViewById(R.id.toggleDialogLayout));
-
- final Switch toggleSwitch = v.findViewById(R.id.availableSwitch);
- final TextView availableText = v.findViewById(R.id.availableText);
- final TextView unavailableText = v.findViewById(R.id.unavailableText);
-
- toggleSwitch.setChecked(!isToggled);
- toggleText(!isToggled, availableText, unavailableText);
-
- /**
- * OnClicks for the elements of the dialog
- */
-
- availableText.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- toggleSwitch.setChecked(false);
- toggleText(false, availableText, unavailableText);
- }
- });
-
- unavailableText.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- toggleSwitch.setChecked(true);
- toggleText(true, availableText, unavailableText);
- }
- });
-
- toggleSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
- @Override
- public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
- toggleText(isChecked, availableText, unavailableText);
- }
- });
-
- alertBuilder.setNeutralButton(getResources().getString(R.string.equipment_bolean_button_positive_text),
- new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- if (!toggleSwitch.isChecked()) {
- updatedData = "True";
- } else {
- updatedData = "False";
- }
-
- // Update the data
- onDataChanged(title, updatedData);
- dialog.dismiss();
- }
- });
-
- alertBuilder.setNegativeButton(getResources().getString(R.string.equipment_bolean_button_negative_text),
- new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- updatedData = "";
- dialog.dismiss();
- }
- });
-
- return alertBuilder.create();
-
- }
-
- private void toggleText (boolean toggle, final TextView availableText, final TextView unavailableText) {
- if (!toggle) {
- availableText.setTypeface(null, Typeface.BOLD);
- availableText.setTextColor(getResources().getColor(R.color.colorGreen));
- unavailableText.setTextColor(getResources().getColor(R.color.colorGrey));
- unavailableText.setTypeface(null, Typeface.NORMAL);
- } else {
- availableText.setTypeface(null, Typeface.NORMAL);
- unavailableText.setTextColor(getResources().getColor(R.color.colorPrimary));
- availableText.setTextColor(getResources().getColor(R.color.colorGrey));
- unavailableText.setTypeface(null, Typeface.BOLD);
- }
- }
-
- public void setOnDataChangedListener(DataListener dr) {
- this.dr = dr;
- }
-
- public void onDataChanged(String name, String data) {
- dr.onDataChanged(name, data);
- }
-
-}
diff --git a/app/src/main/java/org/emstrack/hospital/dialogs/EquipmentValueDialog.java b/app/src/main/java/org/emstrack/hospital/dialogs/EquipmentValueDialog.java
deleted file mode 100644
index b61b1092..00000000
--- a/app/src/main/java/org/emstrack/hospital/dialogs/EquipmentValueDialog.java
+++ /dev/null
@@ -1,129 +0,0 @@
-package org.emstrack.hospital.dialogs;
-
-import android.app.Dialog;
-import android.support.annotation.NonNull;
-import android.support.v4.app.DialogFragment;
-import android.content.DialogInterface;
-import android.os.Bundle;
-import android.support.v7.app.AlertDialog;
-import android.text.InputType;
-import android.view.Gravity;
-import android.widget.EditText;
-import android.widget.LinearLayout;
-
-import org.emstrack.hospital.interfaces.DataListener;
-import org.emstrack.hospital.R;
-
-/**
- * Created by devinhickey on 6/5/17.
- */
-
-public class EquipmentValueDialog extends DialogFragment {
-
- private String title;
- private String message;
- private String updatedData = "";
- private String oldData = "";
- private DataListener dr;
-
- /**
- * Empty Constructor
- */
- public EquipmentValueDialog() {
-
- }
-
- /**
- *
- * @param title
- * @param message
- * @return
- */
- public static EquipmentValueDialog newInstance(String title, String message, String data) {
- EquipmentValueDialog vd = new EquipmentValueDialog();
-
- Bundle args = new Bundle();
- args.putString("Title", title);
- args.putString("Message", message);
- args.putString("Data", data);
-
- vd.setArguments(args);
-
- return vd;
-
- }
-
- @Override
- @NonNull
- public Dialog onCreateDialog(Bundle savedInstanceState) {
-
- title = getArguments().getString("Title");
- message = getArguments().getString("Message");
- oldData = getArguments().getString("Data");
-
- System.out.println("Title: " + title);
- System.out.println("Message: " + message);
- System.out.println("Data: " + oldData);
-
-
- final AlertDialog.Builder alertBuilder = new AlertDialog.Builder(getActivity());
- final EditText valueText = new EditText(getActivity().getApplicationContext());
-
- System.out.println("Value Type, adding box");
-
- // Set the data elements
- alertBuilder.setTitle(title);
- alertBuilder.setMessage(message);
-
- // Initialize the Value Text
- valueText.setGravity(Gravity.CENTER_HORIZONTAL);
- valueText.setRawInputType(InputType.TYPE_CLASS_NUMBER);
- valueText.setTextColor(getResources().getColor(R.color.colorPrimaryDark));
-
- // Set the current data and move the cursor to the end
- valueText.setText(oldData);
- valueText.setSelection(valueText.getText().length());
-
- // Create the EditText LayoutParams
- LinearLayout.LayoutParams params = new LinearLayout.LayoutParams
- (LinearLayout.LayoutParams.MATCH_PARENT,
- LinearLayout.LayoutParams.MATCH_PARENT);
- valueText.setLayoutParams(params);
-
- alertBuilder.setView(valueText);
-
- alertBuilder.setNeutralButton(getResources().getString(R.string.equipment_integer_button_positive_text), new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- System.out.println("Update Button Clicked");
- // Update the value
- // Grab the new value
- updatedData = valueText.getText().toString();
-
- // Update the data
- onDataChanged(title, updatedData);
- dialog.dismiss();
- }
- });
-
- alertBuilder.setNegativeButton(getResources().getString(R.string.equipment_integer_button_negative_text), new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- updatedData = "";
- dialog.dismiss();
- }
- });
-
- return alertBuilder.create();
-
- }
-
- public void setOnDataChangedListener(DataListener dr) {
- this.dr = dr;
- }
-
- public void onDataChanged(String name, String data) {
- dr.onDataChanged(name, data);
- }
-
-}
diff --git a/app/src/main/java/org/emstrack/hospital/dialogs/LogoutDialog.java b/app/src/main/java/org/emstrack/hospital/dialogs/LogoutDialog.java
deleted file mode 100644
index 322c85d8..00000000
--- a/app/src/main/java/org/emstrack/hospital/dialogs/LogoutDialog.java
+++ /dev/null
@@ -1,76 +0,0 @@
-package org.emstrack.hospital.dialogs;
-
-import android.app.Dialog;
-import android.app.DialogFragment;
-import android.content.DialogInterface;
-import android.content.Intent;
-import android.os.Bundle;
-import android.support.annotation.NonNull;
-import android.support.v7.app.AlertDialog;
-import android.util.Log;
-
-import org.eclipse.paho.client.mqttv3.MqttException;
-import org.emstrack.hospital.HospitalApp;
-import org.emstrack.hospital.LoginActivity;
-import org.emstrack.hospital.R;
-import org.emstrack.mqtt.MqttProfileClient;
-
-/**
- * Created by devinhickey on 5/24/17.
- */
-
-public class LogoutDialog extends DialogFragment {
-
- final String TAG = "LogoutDialog";
-
- public LogoutDialog() {}
-
- public static LogoutDialog newInstance() {
- return new LogoutDialog();
- }
-
- @Override
- @NonNull
- public Dialog onCreateDialog(Bundle savedInstanceState) {
- System.out.println("Logout Dialog onCreateDialog");
-
- AlertDialog.Builder alertBuilder = new AlertDialog.Builder(getActivity());
-
- alertBuilder.setTitle(getResources().getString(R.string.alert_warning));
- alertBuilder.setMessage(getResources().getString(R.string.logout_confirm));
-
- // Create the OK button that logs user out
- alertBuilder.setNeutralButton(getResources().getString(R.string.alert_button_positive_text), new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- System.out.println("OK Button Clicked");
-
- // Retrieve client
- final MqttProfileClient profileClient = ((HospitalApp) getActivity().getApplication()).getProfileClient();
- try {
- profileClient.disconnect();
- } catch (MqttException e) {
- Log.d(TAG,"Failed to disconnect.");
- }
-
- Intent rootIntent = new Intent(getActivity(), LoginActivity.class);
- rootIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
- startActivity(rootIntent);
-
- }
- });
-
- // Create the Cancel Button
- alertBuilder.setNegativeButton(getResources().getString(R.string.alert_button_negative_text), new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- System.out.println("Cancel Button Clicked");
- dialog.dismiss();
- }
- });
-
- return alertBuilder.create();
-
- }
-
-}
diff --git a/app/src/main/java/org/emstrack/hospital/interfaces/DataListener.java b/app/src/main/java/org/emstrack/hospital/interfaces/DataListener.java
deleted file mode 100644
index 518b6974..00000000
--- a/app/src/main/java/org/emstrack/hospital/interfaces/DataListener.java
+++ /dev/null
@@ -1,9 +0,0 @@
-package org.emstrack.hospital.interfaces;
-
-/**
- * Created by Fabian Choi on 5/30/2017.
- */
-
-public interface DataListener {
- void onDataChanged(String name, String data);
-}
diff --git a/app/src/main/res/drawable-hdpi/add_bttn.png b/app/src/main/res/drawable-hdpi/add_bttn.png
deleted file mode 100644
index 3f3227a2..00000000
Binary files a/app/src/main/res/drawable-hdpi/add_bttn.png and /dev/null differ
diff --git a/app/src/main/res/drawable-hdpi/apple.png b/app/src/main/res/drawable-hdpi/apple.png
deleted file mode 100644
index 8d67e7ea..00000000
Binary files a/app/src/main/res/drawable-hdpi/apple.png and /dev/null differ
diff --git a/app/src/main/res/drawable-hdpi/checkmark.png b/app/src/main/res/drawable-hdpi/checkmark.png
deleted file mode 100644
index 1d9e902b..00000000
Binary files a/app/src/main/res/drawable-hdpi/checkmark.png and /dev/null differ
diff --git a/app/src/main/res/drawable-hdpi/green_check.png b/app/src/main/res/drawable-hdpi/green_check.png
deleted file mode 100644
index db164c76..00000000
Binary files a/app/src/main/res/drawable-hdpi/green_check.png and /dev/null differ
diff --git a/app/src/main/res/drawable-hdpi/log_out.png b/app/src/main/res/drawable-hdpi/log_out.png
deleted file mode 100644
index 8989e48f..00000000
Binary files a/app/src/main/res/drawable-hdpi/log_out.png and /dev/null differ
diff --git a/app/src/main/res/drawable-hdpi/redx.png b/app/src/main/res/drawable-hdpi/redx.png
deleted file mode 100644
index ee992211..00000000
Binary files a/app/src/main/res/drawable-hdpi/redx.png and /dev/null differ
diff --git a/app/src/main/res/drawable-ldpi/add_bttn.png b/app/src/main/res/drawable-ldpi/add_bttn.png
deleted file mode 100644
index d4f1d148..00000000
Binary files a/app/src/main/res/drawable-ldpi/add_bttn.png and /dev/null differ
diff --git a/app/src/main/res/drawable-ldpi/apple.png b/app/src/main/res/drawable-ldpi/apple.png
deleted file mode 100644
index cc07978a..00000000
Binary files a/app/src/main/res/drawable-ldpi/apple.png and /dev/null differ
diff --git a/app/src/main/res/drawable-ldpi/checkmark.png b/app/src/main/res/drawable-ldpi/checkmark.png
deleted file mode 100644
index abff56f7..00000000
Binary files a/app/src/main/res/drawable-ldpi/checkmark.png and /dev/null differ
diff --git a/app/src/main/res/drawable-ldpi/green_check.png b/app/src/main/res/drawable-ldpi/green_check.png
deleted file mode 100644
index 0e2d189d..00000000
Binary files a/app/src/main/res/drawable-ldpi/green_check.png and /dev/null differ
diff --git a/app/src/main/res/drawable-ldpi/log_out.png b/app/src/main/res/drawable-ldpi/log_out.png
deleted file mode 100644
index 6e893442..00000000
Binary files a/app/src/main/res/drawable-ldpi/log_out.png and /dev/null differ
diff --git a/app/src/main/res/drawable-ldpi/redx.png b/app/src/main/res/drawable-ldpi/redx.png
deleted file mode 100644
index 1e795c74..00000000
Binary files a/app/src/main/res/drawable-ldpi/redx.png and /dev/null differ
diff --git a/app/src/main/res/drawable-mdpi/add_bttn.png b/app/src/main/res/drawable-mdpi/add_bttn.png
deleted file mode 100644
index a2d19c38..00000000
Binary files a/app/src/main/res/drawable-mdpi/add_bttn.png and /dev/null differ
diff --git a/app/src/main/res/drawable-mdpi/apple.png b/app/src/main/res/drawable-mdpi/apple.png
deleted file mode 100644
index 0e2eccdf..00000000
Binary files a/app/src/main/res/drawable-mdpi/apple.png and /dev/null differ
diff --git a/app/src/main/res/drawable-mdpi/checkmark.png b/app/src/main/res/drawable-mdpi/checkmark.png
deleted file mode 100644
index 474d5456..00000000
Binary files a/app/src/main/res/drawable-mdpi/checkmark.png and /dev/null differ
diff --git a/app/src/main/res/drawable-mdpi/green_check.png b/app/src/main/res/drawable-mdpi/green_check.png
deleted file mode 100644
index ba6b7dbe..00000000
Binary files a/app/src/main/res/drawable-mdpi/green_check.png and /dev/null differ
diff --git a/app/src/main/res/drawable-mdpi/log_out.png b/app/src/main/res/drawable-mdpi/log_out.png
deleted file mode 100644
index f19e0a61..00000000
Binary files a/app/src/main/res/drawable-mdpi/log_out.png and /dev/null differ
diff --git a/app/src/main/res/drawable-mdpi/redx.png b/app/src/main/res/drawable-mdpi/redx.png
deleted file mode 100644
index d9d09448..00000000
Binary files a/app/src/main/res/drawable-mdpi/redx.png and /dev/null differ
diff --git a/app/src/main/res/drawable-xhdpi/add_bttn.png b/app/src/main/res/drawable-xhdpi/add_bttn.png
deleted file mode 100644
index 85783f6c..00000000
Binary files a/app/src/main/res/drawable-xhdpi/add_bttn.png and /dev/null differ
diff --git a/app/src/main/res/drawable-xhdpi/apple.png b/app/src/main/res/drawable-xhdpi/apple.png
deleted file mode 100644
index 2dd2769b..00000000
Binary files a/app/src/main/res/drawable-xhdpi/apple.png and /dev/null differ
diff --git a/app/src/main/res/drawable-xhdpi/button_states.xml b/app/src/main/res/drawable-xhdpi/button_states.xml
new file mode 100644
index 00000000..a6c42136
--- /dev/null
+++ b/app/src/main/res/drawable-xhdpi/button_states.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable-xhdpi/checkmark.png b/app/src/main/res/drawable-xhdpi/checkmark.png
deleted file mode 100644
index 64c75de2..00000000
Binary files a/app/src/main/res/drawable-xhdpi/checkmark.png and /dev/null differ
diff --git a/app/src/main/res/drawable-xhdpi/green_check.png b/app/src/main/res/drawable-xhdpi/green_check.png
deleted file mode 100644
index 9baf9972..00000000
Binary files a/app/src/main/res/drawable-xhdpi/green_check.png and /dev/null differ
diff --git a/app/src/main/res/drawable-xhdpi/imagebuttons.xml b/app/src/main/res/drawable-xhdpi/imagebuttons.xml
new file mode 100644
index 00000000..716793c6
--- /dev/null
+++ b/app/src/main/res/drawable-xhdpi/imagebuttons.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable-xhdpi/log_out.png b/app/src/main/res/drawable-xhdpi/log_out.png
deleted file mode 100644
index d6463cd9..00000000
Binary files a/app/src/main/res/drawable-xhdpi/log_out.png and /dev/null differ
diff --git a/app/src/main/res/drawable-xhdpi/nav_img.png b/app/src/main/res/drawable-xhdpi/nav_img.png
new file mode 100644
index 00000000..5ae312a7
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/nav_img.png differ
diff --git a/app/src/main/res/drawable-xhdpi/panicbutton.png b/app/src/main/res/drawable-xhdpi/panicbutton.png
new file mode 100644
index 00000000..36934732
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/panicbutton.png differ
diff --git a/app/src/main/res/drawable-xhdpi/redx.png b/app/src/main/res/drawable-xhdpi/redx.png
deleted file mode 100644
index b35db24f..00000000
Binary files a/app/src/main/res/drawable-xhdpi/redx.png and /dev/null differ
diff --git a/app/src/main/res/drawable/alert.png b/app/src/main/res/drawable/alert.png
new file mode 100644
index 00000000..9ea2150f
Binary files /dev/null and b/app/src/main/res/drawable/alert.png differ
diff --git a/app/src/main/res/drawable/ambulance_blue.xml b/app/src/main/res/drawable/ambulance_blue.xml
new file mode 100644
index 00000000..aeac88e8
--- /dev/null
+++ b/app/src/main/res/drawable/ambulance_blue.xml
@@ -0,0 +1,115 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ambulance_gray.xml b/app/src/main/res/drawable/ambulance_gray.xml
new file mode 100644
index 00000000..e5e8a457
--- /dev/null
+++ b/app/src/main/res/drawable/ambulance_gray.xml
@@ -0,0 +1,115 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ambulance_green.xml b/app/src/main/res/drawable/ambulance_green.xml
new file mode 100644
index 00000000..56f73ccc
--- /dev/null
+++ b/app/src/main/res/drawable/ambulance_green.xml
@@ -0,0 +1,115 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ambulance_orange.xml b/app/src/main/res/drawable/ambulance_orange.xml
new file mode 100644
index 00000000..15b4ca4c
--- /dev/null
+++ b/app/src/main/res/drawable/ambulance_orange.xml
@@ -0,0 +1,115 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ambulance_purple.xml b/app/src/main/res/drawable/ambulance_purple.xml
new file mode 100644
index 00000000..bc529ace
--- /dev/null
+++ b/app/src/main/res/drawable/ambulance_purple.xml
@@ -0,0 +1,115 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ambulance_red.xml b/app/src/main/res/drawable/ambulance_red.xml
new file mode 100644
index 00000000..246f0a99
--- /dev/null
+++ b/app/src/main/res/drawable/ambulance_red.xml
@@ -0,0 +1,115 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ambulance_yellow.xml b/app/src/main/res/drawable/ambulance_yellow.xml
new file mode 100644
index 00000000..069c0729
--- /dev/null
+++ b/app/src/main/res/drawable/ambulance_yellow.xml
@@ -0,0 +1,115 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ambulancelogo.png b/app/src/main/res/drawable/ambulancelogo.png
new file mode 100644
index 00000000..5c613c7f
Binary files /dev/null and b/app/src/main/res/drawable/ambulancelogo.png differ
diff --git a/app/src/main/res/drawable/button_states.xml b/app/src/main/res/drawable/button_states.xml
new file mode 100644
index 00000000..a6c42136
--- /dev/null
+++ b/app/src/main/res/drawable/button_states.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/hospital_symbol.png b/app/src/main/res/drawable/hospital_symbol.png
new file mode 100644
index 00000000..1bdb646a
Binary files /dev/null and b/app/src/main/res/drawable/hospital_symbol.png differ
diff --git a/app/src/main/res/drawable/ic_ambulance.xml b/app/src/main/res/drawable/ic_ambulance.xml
new file mode 100644
index 00000000..4ea4bbc7
--- /dev/null
+++ b/app/src/main/res/drawable/ic_ambulance.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_cogs.xml b/app/src/main/res/drawable/ic_cogs.xml
new file mode 100644
index 00000000..bfcb9991
--- /dev/null
+++ b/app/src/main/res/drawable/ic_cogs.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_defibrillator_15.xml b/app/src/main/res/drawable/ic_defibrillator_15.xml
new file mode 100644
index 00000000..714be58c
--- /dev/null
+++ b/app/src/main/res/drawable/ic_defibrillator_15.xml
@@ -0,0 +1,4 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_exclamation_triangle.xml b/app/src/main/res/drawable/ic_exclamation_triangle.xml
new file mode 100644
index 00000000..78b76172
--- /dev/null
+++ b/app/src/main/res/drawable/ic_exclamation_triangle.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_expand_more.xml b/app/src/main/res/drawable/ic_expand_more.xml
new file mode 100644
index 00000000..e8558d07
--- /dev/null
+++ b/app/src/main/res/drawable/ic_expand_more.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_globe.xml b/app/src/main/res/drawable/ic_globe.xml
new file mode 100644
index 00000000..3afcf1c5
--- /dev/null
+++ b/app/src/main/res/drawable/ic_globe.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_home_15.xml b/app/src/main/res/drawable/ic_home_15.xml
new file mode 100644
index 00000000..d78f8ce6
--- /dev/null
+++ b/app/src/main/res/drawable/ic_home_15.xml
@@ -0,0 +1,4 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_hospital.xml b/app/src/main/res/drawable/ic_hospital.xml
new file mode 100644
index 00000000..ee43d828
--- /dev/null
+++ b/app/src/main/res/drawable/ic_hospital.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_hospital_15.xml b/app/src/main/res/drawable/ic_hospital_15.xml
new file mode 100644
index 00000000..3b1d4b09
--- /dev/null
+++ b/app/src/main/res/drawable/ic_hospital_15.xml
@@ -0,0 +1,4 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_info_circle.xml b/app/src/main/res/drawable/ic_info_circle.xml
new file mode 100644
index 00000000..7ce4c237
--- /dev/null
+++ b/app/src/main/res/drawable/ic_info_circle.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_location_arrow.xml b/app/src/main/res/drawable/ic_location_arrow.xml
new file mode 100644
index 00000000..246a4282
--- /dev/null
+++ b/app/src/main/res/drawable/ic_location_arrow.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_marker_15.xml b/app/src/main/res/drawable/ic_marker_15.xml
new file mode 100644
index 00000000..5fa7acc8
--- /dev/null
+++ b/app/src/main/res/drawable/ic_marker_15.xml
@@ -0,0 +1,4 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_shipping_fast.xml b/app/src/main/res/drawable/ic_shipping_fast.xml
new file mode 100644
index 00000000..a676d1c1
--- /dev/null
+++ b/app/src/main/res/drawable/ic_shipping_fast.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_sign_out_alt.xml b/app/src/main/res/drawable/ic_sign_out_alt.xml
new file mode 100644
index 00000000..2c32a95a
--- /dev/null
+++ b/app/src/main/res/drawable/ic_sign_out_alt.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_wifi.xml b/app/src/main/res/drawable/ic_wifi.xml
new file mode 100644
index 00000000..249f0bb1
--- /dev/null
+++ b/app/src/main/res/drawable/ic_wifi.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_wrench.xml b/app/src/main/res/drawable/ic_wrench.xml
new file mode 100644
index 00000000..ab88fe41
--- /dev/null
+++ b/app/src/main/res/drawable/ic_wrench.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/imagebuttons.xml b/app/src/main/res/drawable/imagebuttons.xml
new file mode 100644
index 00000000..716793c6
--- /dev/null
+++ b/app/src/main/res/drawable/imagebuttons.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/logout.png b/app/src/main/res/drawable/logout.png
new file mode 100644
index 00000000..82947771
Binary files /dev/null and b/app/src/main/res/drawable/logout.png differ
diff --git a/app/src/main/res/drawable/message.png b/app/src/main/res/drawable/message.png
new file mode 100644
index 00000000..18b30955
Binary files /dev/null and b/app/src/main/res/drawable/message.png differ
diff --git a/app/src/main/res/drawable/nav_img.png b/app/src/main/res/drawable/nav_img.png
new file mode 100644
index 00000000..5ae312a7
Binary files /dev/null and b/app/src/main/res/drawable/nav_img.png differ
diff --git a/app/src/main/res/drawable/panicbutton.png b/app/src/main/res/drawable/panicbutton.png
new file mode 100644
index 00000000..36934732
Binary files /dev/null and b/app/src/main/res/drawable/panicbutton.png differ
diff --git a/app/src/main/res/drawable/spinner_background.9.png b/app/src/main/res/drawable/spinner_background.9.png
new file mode 100644
index 00000000..d7933eac
Binary files /dev/null and b/app/src/main/res/drawable/spinner_background.9.png differ
diff --git a/app/src/main/res/layout/about.xml b/app/src/main/res/layout/about.xml
new file mode 100644
index 00000000..2fd36057
--- /dev/null
+++ b/app/src/main/res/layout/about.xml
@@ -0,0 +1,71 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_dashboard.xml b/app/src/main/res/layout/activity_dashboard.xml
deleted file mode 100644
index a89394cd..00000000
--- a/app/src/main/res/layout/activity_dashboard.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
-
-
-
-
diff --git a/app/src/main/res/layout/activity_hospital_list.xml b/app/src/main/res/layout/activity_hospital_list.xml
deleted file mode 100644
index 4a00588d..00000000
--- a/app/src/main/res/layout/activity_hospital_list.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_login.xml b/app/src/main/res/layout/activity_login.xml
index ef88110a..14d61580 100644
--- a/app/src/main/res/layout/activity_login.xml
+++ b/app/src/main/res/layout/activity_login.xml
@@ -1,167 +1,58 @@
-
- tools:context="org.emstrack.hospital.LoginActivity">
-
-
-
-
-
-
-
-
-
+ android:layout_height="220dp"
+ android:layout_marginTop="20dp"
+ app:srcCompat="@drawable/ambulancelogo" />
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ android:layout_marginBottom="20dp"
+ android:layout_marginLeft="30dp"
+ android:layout_marginRight="30dp"
+ android:fontFamily="sans-serif"
+ android:hint="Enter password"
+ android:inputType="textPassword" />
-
-
+ android:layout_height="0dip"
+ android:layout_weight="1"
+ android:gravity="center|bottom"
+ android:orientation="vertical" >
-
-
+ android:layout_marginLeft="30dp"
+ android:layout_marginRight="30dp"
+ android:background="@color/colorAccent"
+ android:text="Sign in"
+ android:textColor="@color/common_google_signin_btn_text_dark_default" />
-
-
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
new file mode 100644
index 00000000..ff0a78af
--- /dev/null
+++ b/app/src/main/res/layout/activity_main.xml
@@ -0,0 +1,123 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/fragment_ambulance.xml b/app/src/main/res/layout/fragment_ambulance.xml
new file mode 100644
index 00000000..f5287b4d
--- /dev/null
+++ b/app/src/main/res/layout/fragment_ambulance.xml
@@ -0,0 +1,173 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/fragment_hospital.xml b/app/src/main/res/layout/fragment_hospital.xml
new file mode 100644
index 00000000..0e4f3fc3
--- /dev/null
+++ b/app/src/main/res/layout/fragment_hospital.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/fragment_map.xml b/app/src/main/res/layout/fragment_map.xml
new file mode 100644
index 00000000..514e8976
--- /dev/null
+++ b/app/src/main/res/layout/fragment_map.xml
@@ -0,0 +1,49 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/hospital_equipment_item.xml b/app/src/main/res/layout/hospital_equipment_item.xml
new file mode 100644
index 00000000..d65fa73f
--- /dev/null
+++ b/app/src/main/res/layout/hospital_equipment_item.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/hospital_item.xml b/app/src/main/res/layout/hospital_item.xml
new file mode 100644
index 00000000..c0cbc829
--- /dev/null
+++ b/app/src/main/res/layout/hospital_item.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/list_item_boolean.xml b/app/src/main/res/layout/list_item_boolean.xml
deleted file mode 100644
index 2de52c76..00000000
--- a/app/src/main/res/layout/list_item_boolean.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/layout/list_item_value.xml b/app/src/main/res/layout/list_item_value.xml
deleted file mode 100644
index da78d0e8..00000000
--- a/app/src/main/res/layout/list_item_value.xml
+++ /dev/null
@@ -1,38 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/layout/maintitlebar.xml b/app/src/main/res/layout/maintitlebar.xml
deleted file mode 100644
index a24b1fb8..00000000
--- a/app/src/main/res/layout/maintitlebar.xml
+++ /dev/null
@@ -1,36 +0,0 @@
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/layout/nav_header.xml b/app/src/main/res/layout/nav_header.xml
new file mode 100644
index 00000000..9730592f
--- /dev/null
+++ b/app/src/main/res/layout/nav_header.xml
@@ -0,0 +1,7 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/status_spinner_dropdown_item.xml b/app/src/main/res/layout/status_spinner_dropdown_item.xml
new file mode 100644
index 00000000..6dba594b
--- /dev/null
+++ b/app/src/main/res/layout/status_spinner_dropdown_item.xml
@@ -0,0 +1,11 @@
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/status_spinner_item.xml b/app/src/main/res/layout/status_spinner_item.xml
new file mode 100644
index 00000000..6ea2ea23
--- /dev/null
+++ b/app/src/main/res/layout/status_spinner_item.xml
@@ -0,0 +1,10 @@
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/toggle_dialog.xml b/app/src/main/res/layout/toggle_dialog.xml
deleted file mode 100644
index 7f503776..00000000
--- a/app/src/main/res/layout/toggle_dialog.xml
+++ /dev/null
@@ -1,40 +0,0 @@
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/menu/drawer_view.xml b/app/src/main/res/menu/drawer_view.xml
new file mode 100644
index 00000000..0cd76111
--- /dev/null
+++ b/app/src/main/res/menu/drawer_view.xml
@@ -0,0 +1,21 @@
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png
index cde69bcc..c73300fa 100644
Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher.png and b/app/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher2.png b/app/src/main/res/mipmap-hdpi/ic_launcher2.png
deleted file mode 100644
index d99b1e8f..00000000
Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher2.png and /dev/null differ
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
deleted file mode 100644
index 9a078e3e..00000000
Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png and /dev/null differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png
index c133a0cb..cc442237 100644
Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher.png and b/app/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher2.png b/app/src/main/res/mipmap-mdpi/ic_launcher2.png
deleted file mode 100644
index b5309484..00000000
Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher2.png and /dev/null differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
deleted file mode 100644
index efc028a6..00000000
Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png and /dev/null differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png
index bfa42f0e..a51db5ab 100644
Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher.png and b/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher2.png b/app/src/main/res/mipmap-xhdpi/ic_launcher2.png
deleted file mode 100644
index cbd3d7e0..00000000
Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher2.png and /dev/null differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
deleted file mode 100644
index 3af2608a..00000000
Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png and /dev/null differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
index 324e72cd..9e9c8b03 100644
Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher2.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher2.png
deleted file mode 100644
index 0d771ca6..00000000
Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher2.png and /dev/null differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
deleted file mode 100644
index 9bec2e62..00000000
Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png and /dev/null differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
index aee44e13..a88f3265 100644
Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher2.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher2.png
deleted file mode 100644
index e64cce2b..00000000
Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher2.png and /dev/null differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
deleted file mode 100644
index 34947cd6..00000000
Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png and /dev/null differ
diff --git a/app/src/main/res/values-b+es/strings.xml b/app/src/main/res/values-es/strings.xml
similarity index 54%
rename from app/src/main/res/values-b+es/strings.xml
rename to app/src/main/res/values-es/strings.xml
index fd08c7e2..2b042357 100644
--- a/app/src/main/res/values-b+es/strings.xml
+++ b/app/src/main/res/values-es/strings.xml
@@ -1,15 +1,17 @@
- Hospital
+ EMSTrack
+
+ Cajón de navegación abierto
+ Cerrar cajón de navegación
Error
- ¡Advertencia!
+ Aviso
OK
Cancelar
- Por favor espere…
-
+ Por favor espere...
¿Seguro que desea cerrar sesión?
Usuário
@@ -23,7 +25,7 @@
No se pudo conectar. Razón: \'%1$s\'
Ningún hospital esta asociado con esta cuenta.
- MANEJAR HOSPITAL
+ OK
Caja
Actualizar
@@ -42,4 +44,33 @@
Cancelar
Valor
+ Configuraciones
+
+ The location settings on the device are not
+ adequate to run this sample. Fix in Settings.
+
+ Location permission is needed for core functionality
+ Permission was denied, but is needed for core
+ functionality.
+
+ Configuraciones
+ Despacho
+ Hospital
+ GPS
+
+ unknown
+
+ Comenzar a seguir:
+ Ubicación:
+ Latitud:
+ Longitud:
+ Hora:
+ Orientación:
+ °
+ Estado:
+ Identificador:
+ Capacidad:
+ Commentarios:
+ Ultima actualizacion:
+
\ No newline at end of file
diff --git a/app/src/main/res/values-w820dp/dimens.xml b/app/src/main/res/values-w820dp/dimens.xml
new file mode 100644
index 00000000..9309de98
--- /dev/null
+++ b/app/src/main/res/values-w820dp/dimens.xml
@@ -0,0 +1,6 @@
+
+
+ 64dp
+
\ No newline at end of file
diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml
new file mode 100644
index 00000000..c075806f
--- /dev/null
+++ b/app/src/main/res/values/arrays.xml
@@ -0,0 +1,14 @@
+
+
+
+
+ - @color/statusBlue
+ - @color/statusBlue
+ - @color/statusGreen
+ - @color/statusOrange
+ - @color/statusRed
+ - @color/statusOrange
+ - @color/statusPurple
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
index bdc22570..a518e387 100644
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -1,8 +1,26 @@
- #d32f2f
- #9a0007
- #536DFE
- #00ff00
- #D3D3D3
-
+ #ffffff
+ #a6acb0
+ #f65050
+ #ffffffff
+ #000000
+ #000000
+ #8FFF
+ #8888
+ #FFFF0000
+ #4CAF50
+
+ #FF33B5E5
+ #FFAA66CC
+ #FF99CC00
+ #FFFFBB33
+ #FFFF4444
+ #FF0099CC
+ #FF9933CC
+ #FF669900
+ #FFFF8800
+ #FFCC0000
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml
index 59a0b0c4..6b431238 100644
--- a/app/src/main/res/values/dimens.xml
+++ b/app/src/main/res/values/dimens.xml
@@ -1,3 +1,10 @@
-
- 16dp
+
+
+ 16dp
+ 16dp
+ 16dp
+ 180dp
+ 56dp
+ 16dp
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 51c59f7e..e658a69f 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -1,13 +1,20 @@
- Hospital
+ EMSTrack
+ v0_3_2
+ _AndroidClient_
+
+ Open navigation drawer
+ Close navigation drawer
Error
- Warning
+ Warning
OK
Cancel
+ Stop \'%1$s\' from updating locations first.
+
Please wait…
Are you sure that you want to logout?
@@ -19,7 +26,7 @@
Please provide a valid user name.
Please provide a valid password.
- Please provide valide credentials.
+ Please provide valid credentials.
Connection failed. Reason: \'%1$s\'
No hospitals are associated with this account.
@@ -42,4 +49,111 @@
Cancel
Value
+ Settings
+
+ The location settings on the device are not
+ adequate to run this sample. Fix in Settings.
+
+ Location permission is needed for core functionality
+ Permission was denied, but is needed for core
+ functionality.
+
+ Settings
+ Dispatch
+ Hospital
+ GPS
+
+ unknown
+
+ Start tracking:
+ Location:
+ Latitude:
+ Longitude:
+ Timestamp:
+ Orientation:
+ deg
+ Status:
+ Identifier:
+ Capability:
+ Comments:
+ Last updated on:
+ pk.eyJ1IjoieWFuZ2Y5NiIsImEiOiJjaXltYTNmbTcwMDJzMzNwZnpzM3Z6ZW9kIn0.gjEwLiCIbYhVFUGud9B56w
+ Failed to connected to broker.
+ Exception:
+ User \'%1$s\' successfully logged in.
+ Could not login user \'%1$s\'
+ Invalid ambulance id
+ Could not parse ambulance
+ Could not subscribe to ambulance
+ Could not subscribe to hospital data
+ Could not retrieve ambulance \'%1$s\'.
+ Could not retrieve hospitals
+ Could not update ambulance on server.
+ Failed to disconnect from brocker.
+ Could not logout.
+ Could not retrieve ambulances
+ Could not unsubscribe to ambulances
+ You do not have permission to modify this ambulance.
+ Another client is currently streaming locations.
+ Started streaming updates.
+ Could not start sending updates to server. This probably means another user is logged in on the same ambulance. Would you like to force sending updates? This will disconnect the other user from the current ambulance.
+ Forcing updates…
+ Another client requested to stream locations.
+ Requesting to stream location.
+ EMSTrack
+ Could not process entire buffer, %1$d messages left.
+ Server is online, local buffer is empty.
+ Server is offline. Buffering updates.
+ Application cannot use location services. Please modify permissions on Settings.
+ No ambulance is currently selected.
+ Location settings are inadequate, and cannot be fixed here.
+ Failed to start location updates.
+ Another client is currently reporting locations.
+ Success! Will attempt to start sending updates.
+ Could not force updating location.
+ Server is not online, %1$d message(s) in the buffer.
+ Attempting to reconnect to server.
+ Server is back online.
+ Failed to reconnect to server.
+ Could not login user \'%1$s\'
+ Please login.
+ Welcome %1$s
+ Restart
+ Could not start foreground service.
+ Server error: %1$s
+ Stop
+ Stopped streaming location.
+ Use this app to track emergency resources using the EMSTrack system.
+ The following open source projects were used to build this app:
+ Credits:
+ Build date:
+ Build version:
+ 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
+ Cannot decline call. Currently serving another call.
+ Will not process next call. Currently serving call.
+ Can\'t set call as ongoing: already servicing call
+ Requesting to stream updates...
+ Select ambulance:
+ Select ambulance
+
+
+ - @string/dispatch
+ - @string/hospital
+
+
+
+ - @string/dispatch
+ - @string/hospital
+ - @string/gps
+
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
index 76b4d01a..c4f95c74 100644
--- a/app/src/main/res/values/styles.xml
+++ b/app/src/main/res/values/styles.xml
@@ -6,28 +6,25 @@
- @color/colorPrimary
- @color/colorPrimaryDark
- @color/colorAccent
- - @style/spinnerItemStyle
- -
- @style/spinnerDropDownItemStyle
-
-
- #d32f2f
-
-
-
-
+
\ No newline at end of file
diff --git a/app/src/test/java/org/emstrack/hospital/ExampleUnitTest.java b/app/src/test/java/org/emstrack/ambulance/ExampleUnitTest.java
similarity index 92%
rename from app/src/test/java/org/emstrack/hospital/ExampleUnitTest.java
rename to app/src/test/java/org/emstrack/ambulance/ExampleUnitTest.java
index eaca0975..095e3ece 100644
--- a/app/src/test/java/org/emstrack/hospital/ExampleUnitTest.java
+++ b/app/src/test/java/org/emstrack/ambulance/ExampleUnitTest.java
@@ -1,4 +1,4 @@
-package org.emstrack.hospital;
+package org.emstrack.ambulance;
import org.junit.Test;
diff --git a/build.gradle b/build.gradle
index 7e01a0aa..55db43fb 100644
--- a/build.gradle
+++ b/build.gradle
@@ -6,7 +6,7 @@ buildscript {
google()
}
dependencies {
- classpath 'com.android.tools.build:gradle:3.0.1'
+ classpath 'com.android.tools.build:gradle:3.1.4'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
diff --git a/gradle.properties b/gradle.properties
index aac7c9b4..315c2e9c 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -15,3 +15,4 @@ org.gradle.jvmargs=-Xmx1536m
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
+GOOGLE_MAPS_API_KEY=AIzaSyAKxTi7mAzn7XgpXVlkd4k5n2G8tYkaoW0
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 2b292586..baf403d3 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
-#Mon Feb 05 09:34:50 PST 2018
+#Tue Mar 27 21:17:30 PDT 2018
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip
diff --git a/models/build.gradle b/models/build.gradle
index 7383eff7..cd127fee 100644
--- a/models/build.gradle
+++ b/models/build.gradle
@@ -1,13 +1,11 @@
apply plugin: 'com.android.library'
android {
- compileSdkVersion 26
-
-
+ compileSdkVersion 27
defaultConfig {
- minSdkVersion 15
- targetSdkVersion 26
+ minSdkVersion 16
+ targetSdkVersion 27
versionCode 1
versionName "1.0"
@@ -25,12 +23,16 @@ android {
}
dependencies {
- implementation fileTree(dir: 'libs', include: ['*.jar'])
+ implementation fileTree(include: ['*.jar'], dir: 'libs')
+ implementation 'com.android.support:appcompat-v7:27.1.0'
- implementation 'com.android.support:appcompat-v7:26.1.0'
testImplementation 'junit:junit:4.12'
+
androidTestImplementation 'com.android.support.test:runner:1.0.1'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
api 'com.google.code.gson:gson:2.8.0'
+
+ compile group: 'org.ejml', name: 'ejml-all', version: '0.33'
+
}
diff --git a/models/src/main/java/org/emstrack/models/Address.java b/models/src/main/java/org/emstrack/models/Address.java
new file mode 100644
index 00000000..30cfe1d8
--- /dev/null
+++ b/models/src/main/java/org/emstrack/models/Address.java
@@ -0,0 +1,114 @@
+package org.emstrack.models;
+
+import java.util.Date;
+
+/**
+ * Created by mauricio on 3/11/2018.
+ * This model is not used yet since all database models are flat
+ * Could be used in the future but requires a custom serializer
+ */
+
+public class Address {
+
+ 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;
+
+ public Address(String number,
+ String street,
+ String unit,
+ String neighborhood,
+ String city,
+ String state,
+ String zipcode,
+ String country) {
+
+ this.number = number;
+ this.street= street;
+ this.unit = unit;
+ this.neighborhood= neighborhood;
+ this.city = city;
+ this.state = state;
+ this.zipcode = zipcode;
+ this.country = country;
+ }
+
+ 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 String getUnit() {
+ return unit;
+ }
+
+ public void setUnit(String unit) {
+ this.unit = unit;
+ }
+
+ public String 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;
+ }
+
+}
diff --git a/models/src/main/java/org/emstrack/models/Ambulance.java b/models/src/main/java/org/emstrack/models/Ambulance.java
new file mode 100644
index 00000000..f05f8a4d
--- /dev/null
+++ b/models/src/main/java/org/emstrack/models/Ambulance.java
@@ -0,0 +1,148 @@
+package org.emstrack.models;
+
+import com.google.gson.annotations.Expose;
+
+import java.util.Date;
+
+public class Ambulance {
+
+ private int id;
+ private String identifier;
+ private String capability;
+ @Expose
+ private String status;
+ @Expose
+ private double orientation;
+ @Expose
+ private Location location;
+ @Expose
+ private Date timestamp;
+ private String locationClientId;
+ private String comment;
+ private int updatedBy;
+ private Date updatedOn;
+
+ public Ambulance(int id, String identifier, String capability, String status,
+ double orientation, Location location, Date timestamp, String comment,
+ int updatedBy, Date updatedOn) {
+ this.id = id;
+ this.identifier = identifier;
+ this.capability = capability;
+ this.status = status;
+ this.orientation = orientation;
+ this.location = location;
+ this.timestamp = timestamp;
+ this.comment = comment;
+ this.locationClientId = null;
+ this.updatedBy = updatedBy;
+ this.updatedOn = updatedOn;
+ }
+
+ public Ambulance(int id, String identifier, String capability) {
+ this.id = id;
+ this.identifier = identifier;
+ this.capability = capability;
+ this.status = "UK";
+ this.orientation = 0.0;
+ this.location = null;
+ this.timestamp = null;
+ this.locationClientId = null;
+ this.comment = "";
+ this.updatedBy = -1;
+ this.updatedOn = null;
+ }
+
+ public void updateLocation(android.location.Location lastLocation) {
+
+ // Update ambulance
+ location = new Location(lastLocation.getLatitude(),lastLocation.getLongitude());
+ orientation = lastLocation.getBearing();
+ timestamp = new Date(lastLocation.getTime());
+
+ }
+
+ public int getId() {
+ return id;
+ }
+
+ public void setId(int id) {
+ this.id = id;
+ }
+
+ public String getIdentifier() {
+ return identifier;
+ }
+
+ public void setIdentifier(String identifier) {
+ this.identifier = identifier;
+ }
+
+ public String getCapability() {
+ return capability;
+ }
+
+ public void setCapability(String capability) {
+ this.capability = capability;
+ }
+
+ public String getStatus() {
+ return status;
+ }
+
+ public void setStatus(String status) {
+ this.status = status;
+ }
+
+ public double getOrientation() {
+ return orientation;
+ }
+
+ public void setOrientation(double orientation) {
+ this.orientation = orientation;
+ }
+
+ public Location getLocation() {
+ return location;
+ }
+
+ public void setLocation(Location location) {
+ this.location = location;
+ }
+
+ public Date getTimestamp() {
+ return timestamp;
+ }
+
+ public void setTimestamp(Date timestamp) {
+ this.timestamp = timestamp;
+ }
+
+ public String getLocationClientId() { return locationClientId; }
+
+ public void setLocationClientId(String locationClientId) { this.locationClientId = locationClientId; }
+
+ public String getComment() {
+ return comment;
+ }
+
+ public void setComment(String comment) {
+ this.comment = comment;
+ }
+
+ public int getUpdatedBy() {
+ return updatedBy;
+ }
+
+ public void setUpdatedBy(int updatedBy) {
+ this.updatedBy = updatedBy;
+ }
+
+ public Date getUpdatedOn() {
+ return updatedOn;
+ }
+
+ public void setUpdatedOn(Date updatedOn) {
+ this.updatedOn = updatedOn;
+ }
+
+}
\ No newline at end of file
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