/** * ORcycle, Copyright 2014, 2015, PSU Transportation, Technology, and People Lab. * * @author Robin Murray <robin5@pdx.edu> (code) * @author Miguel Figliozzi <figliozzi@pdx.edu> and ORcycle team (general app * design and features, report questionnaires and new ORcycle features) * * For more information on the project, go to * http://www.pdx.edu/transportation-lab/orcycle and http://www.pdx.edu/transportation-lab/app-development * * Updated/modified for Oregon pilot study and app deployment. * * ORcycle is free software: you can redistribute it and/or modify it under the * terms of the GNU General Public License as published by the Free Software * Foundation, either version 3 of the License, or any later version. * ORcycle is distributed in the hope that it will be useful, but WITHOUT ANY * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR * A PARTICULAR PURPOSE. See the GNU General Public License for more details. * You should have received a copy of the GNU General Public License along with * ORcycle. If not, see <http://www.gnu.org/licenses/>. * */ package edu.pdx.cecs.orcycle; import java.text.SimpleDateFormat; import java.util.Locale; import android.app.Activity; import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.location.Location; import android.os.Bundle; import android.util.Log; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.widget.AdapterView; import android.widget.AdapterView.OnItemSelectedListener; import android.widget.Button; import android.widget.RelativeLayout; import android.widget.Spinner; import android.widget.Toast; public class ReportSafetyIssuesActivity extends Activity { private static final String MODULE_TAG = "ReportSafetyIssuesActivity"; public static final String EXTRA_NOTE_ID = "noteId"; public static final String EXTRA_NOTE_TYPE = "noteType"; public static final String EXTRA_NOTE_SEVERITY = "noteSeverity"; public static final String EXTRA_NOTE_SOURCE = "noteSource"; public static final int EXTRA_NOTE_ID_UNDEFINED = -1; public static final int EXTRA_NOTE_TYPE_UNDEFINED = -1; public static final int EXTRA_NOTE_SOURCE_UNDEFINED = -1; public static final int EXTRA_NOTE_SOURCE_MAIN_INPUT = 0; public static final int EXTRA_NOTE_SOURCE_TRIP_MAP = 1; public static final String EXTRA_TRIP_ID = "EXTRA_TRIP_ID"; private static final int EXTRA_TRIP_ID_UNDEFINED = -1; public static final String EXTRA_TRIP_SOURCE = "tripSource"; private static final int EXTRA_TRIP_SOURCE_UNDEFINED = -1; public static final int EXTRA_TRIP_SOURCE_MAIN_INPUT = 0; public static final int EXTRA_TRIP_SOURCE_SAVED_TRIPS = 1; public static final String EXTRA_IS_BACK = "isBack"; private long tripId; private long noteId; private int noteSource = EXTRA_NOTE_SOURCE_UNDEFINED; private int tripSource = EXTRA_TRIP_SOURCE_UNDEFINED; private boolean isBack; private static final String PREFS_SAFETY_ISSUE_QUESTIONS = "PREFS_SAFETY_ISSUE_QUESTIONS"; private static final int PREF_SAFETY_ISSUES = 1; private static final int PREF_URGENCY = 2; private static final int PREF_LOCATION = 3; private static final int PREF_REPORT_DATE = 4; private static final int PREF_REPORT_DATE_TAG = 5; private static final int PREF_SAFETY_ISSUES_OTHER = 1001; private Button btnSave; private MultiSelectionSpinner spnSafetyIssues; private Spinner spnUrgency; private Spinner spnLocation; private Button btnReportDate; private final SimpleDateFormat reportDateFormatter = new SimpleDateFormat("EEEE, MM/dd/yyyy", Locale.US); private final CustomLocation_OnClickListener customLocation_OnClickListener = new CustomLocation_OnClickListener(); private static final int USE_GPS_LOCATION_POS = 1; private static final int USE_CUSTOM_LOCATION_POS = 2; @Override protected void onCreate(Bundle savedInstanceState) { try { super.onCreate(savedInstanceState); setContentView(R.layout.activity_report_safety_issues); loadVars(savedInstanceState); spnSafetyIssues = (MultiSelectionSpinner) findViewById(R.id.spn_arsi_problem); spnSafetyIssues.setItems(getResources().getStringArray(R.array.arsi_a_safety_issues)); spnSafetyIssues.setTitle(getResources().getString(R.string.arsi_q_problem_type_title)); spnSafetyIssues.setOtherIndex(DbAnswers.findIndex(DbAnswers.safetyIssue, DbAnswers.safetyIssueOther)); spnUrgency = (Spinner) findViewById(R.id.spn_arsi_urgency); spnLocation = (Spinner) findViewById(R.id.spn_arsi_location); spnLocation.setOnItemSelectedListener(customLocation_OnClickListener); if (noteSource == EXTRA_NOTE_SOURCE_TRIP_MAP) { RelativeLayout rl = (RelativeLayout) findViewById(R.id.rl_arsi_location); rl.setVisibility(View.GONE); } btnSave = (Button) findViewById(R.id.btn_arsi_save_report); btnSave.setOnClickListener(new ButtonSave_OnClickListener()); btnReportDate = (Button) findViewById(R.id.btn_issue_date); btnReportDate.setTag(0L); btnReportDate.setOnClickListener(new ReportDate_OnClickListener()); } catch(Exception ex) { Log.e(MODULE_TAG, ex.getMessage()); } } private void loadVars(Bundle savedInstanceState) { if (null == savedInstanceState) { Intent myIntent = getIntent(); noteId = myIntent.getLongExtra(EXTRA_NOTE_ID, EXTRA_NOTE_ID_UNDEFINED); noteSource = myIntent.getIntExtra(EXTRA_NOTE_SOURCE, EXTRA_NOTE_SOURCE_UNDEFINED); tripId = myIntent.getLongExtra(EXTRA_TRIP_ID, EXTRA_TRIP_ID_UNDEFINED); tripSource = myIntent.getIntExtra(EXTRA_TRIP_SOURCE, EXTRA_TRIP_SOURCE_UNDEFINED); isBack = myIntent.getBooleanExtra(EXTRA_IS_BACK, false); } else { noteId = savedInstanceState.getLong(EXTRA_NOTE_ID, EXTRA_NOTE_ID_UNDEFINED); noteSource = savedInstanceState.getInt(EXTRA_NOTE_SOURCE, EXTRA_NOTE_SOURCE_UNDEFINED); tripId = savedInstanceState.getLong(EXTRA_TRIP_ID, EXTRA_TRIP_ID_UNDEFINED); tripSource = savedInstanceState.getInt(EXTRA_TRIP_SOURCE, EXTRA_TRIP_SOURCE_UNDEFINED); } if (EXTRA_NOTE_ID_UNDEFINED == noteId) { throw new IllegalArgumentException(MODULE_TAG + ": EXTRA_NOTE_ID undefined."); } if (!((noteSource == EXTRA_NOTE_SOURCE_MAIN_INPUT) ||(noteSource == EXTRA_NOTE_SOURCE_TRIP_MAP))) { throw new IllegalArgumentException(MODULE_TAG + ": EXTRA_NOTE_SOURCE invalid argument."); } // Note: these extras are used for transitioning back to the TripMapActivity if done if (EXTRA_TRIP_ID_UNDEFINED == tripId) { throw new IllegalArgumentException(MODULE_TAG + ": invalid extra - EXTRA_TRIP_ID"); } if (EXTRA_TRIP_SOURCE_UNDEFINED == tripSource) { throw new IllegalArgumentException(MODULE_TAG + ": invalid extra - EXTRA_TRIP_SOURCE"); } } @Override public void onStart() { super.onStart(); try { if (isBack) { recallUiSettings(); } } catch(Exception ex) { Log.e(MODULE_TAG, ex.getMessage()); } } @Override public void onRestoreInstanceState(Bundle savedInstanceState) { try { super.onRestoreInstanceState(savedInstanceState); Log.v(MODULE_TAG, "Cycle: onRestoreInstanceState()"); recallUiSettings(); } catch(Exception ex) { Log.e(MODULE_TAG, ex.getMessage()); } } @Override public void onSaveInstanceState(Bundle savedInstanceState) { try { Log.v(MODULE_TAG, "Cycle: onSaveInstanceState()"); saveUiSettings(); } catch(Exception ex) { Log.e(MODULE_TAG, ex.getMessage()); } finally { super.onSaveInstanceState(savedInstanceState); } } /* Creates the menu items */ @Override public boolean onCreateOptionsMenu(Menu menu) { try { // Inflate the menu items for use in the action bar MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.report_safety_issues, menu); } catch(Exception ex) { Log.e(MODULE_TAG, ex.getMessage()); } return super.onCreateOptionsMenu(menu); } /* Handles item selections */ @Override public boolean onOptionsItemSelected(MenuItem item) { try { switch (item.getItemId()) { case R.id.rsia_menu_item_save_answers: finishReport(); return true; } } catch(Exception ex) { Log.e(MODULE_TAG, ex.getMessage()); } return super.onOptionsItemSelected(item); } private void finishReport() { if (MandatoryQuestionsAnswered()) { submitAnswers(); saveUiSettings(); if (noteSource == EXTRA_NOTE_SOURCE_TRIP_MAP) { transitionToNoteDetailActivity(); } else if (useCustomLocation()) { transitionToCustomLocationActivity(); } else if (setNoteLocation()) { transitionToNoteDetailActivity(); } else { dialogNoGPS(); } } else { AlertUserMandatoryAnswers(); } } private boolean setNoteLocation() { Location location; if (null != (location = MyApplication.getInstance().getLastKnownLocation())) { NoteData note = NoteData.fetchNote(this, noteId); note.setLocation(location); return true; } return false; } private boolean useCustomLocation() { return spnLocation.getSelectedItemPosition() == USE_CUSTOM_LOCATION_POS; } private boolean useGPSLocation() { return spnLocation.getSelectedItemPosition() == USE_GPS_LOCATION_POS; } /** * Build dialog telling user that the GPS is not available */ private void dialogNoGPS() { final AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle(R.string.ara_no_gps_title); builder.setMessage(R.string.ara_no_gps_message); builder.setCancelable(false); builder.setPositiveButton(getResources().getString(R.string.ara_ok), new DialogNoGPS_OkListener()); final AlertDialog alert = builder.create(); alert.show(); } private final class DialogNoGPS_OkListener implements DialogInterface.OnClickListener { public void onClick(final DialogInterface dialog, final int id) { dialog.cancel(); } } private boolean MandatoryQuestionsAnswered() { if (noteSource == EXTRA_NOTE_SOURCE_TRIP_MAP) { return ((spnSafetyIssues.getSelectedIndicies().size() > 0) && (spnUrgency.getSelectedItemPosition() > 0) && (btnReportDate.getText().length() > 0)); } else { return ((spnSafetyIssues.getSelectedIndicies().size() > 0) && (spnUrgency.getSelectedItemPosition() > 0) && (btnReportDate.getText().length() > 0) && (spnLocation.getSelectedItemPosition() > 0)); } } private void AlertUserMandatoryAnswers() { AlertUserMandatoryAnswersDialog dialog = new AlertUserMandatoryAnswersDialog(this); dialog.show(); } @Override public void onBackPressed() { try { transitionToPreviousActivity(); } catch(Exception ex) { Log.e(MODULE_TAG, ex.getMessage()); } } // ********************************************************************************* // * Saving & Recalling UI Settings // ********************************************************************************* /** * Saves UI settings to preferences file */ private void saveUiSettings() { try { SpinnerPreferences prefs = new SpinnerPreferences(getSharedPreferences(PREFS_SAFETY_ISSUE_QUESTIONS, Context.MODE_PRIVATE)); prefs.save(spnSafetyIssues, PREF_SAFETY_ISSUES, DbAnswers.safetyIssue, PREF_SAFETY_ISSUES_OTHER, DbAnswers.safetyIssueOther); prefs.save(spnUrgency, PREF_URGENCY); prefs.save(spnLocation, PREF_LOCATION); prefs.save(btnReportDate, PREF_REPORT_DATE); prefs.saveTag(btnReportDate, PREF_REPORT_DATE_TAG); prefs.commit(); } catch(Exception ex) { Log.e(MODULE_TAG, ex.getMessage()); } } /** * Recalls UI settings from preferences file */ private void recallUiSettings() { try { SpinnerPreferences prefs = new SpinnerPreferences(getSharedPreferences(PREFS_SAFETY_ISSUE_QUESTIONS, Context.MODE_PRIVATE) ); prefs.recall(spnSafetyIssues, PREF_SAFETY_ISSUES); prefs.recall(spnUrgency, PREF_URGENCY); prefs.recall(spnLocation, PREF_LOCATION); prefs.recall(btnReportDate, PREF_REPORT_DATE); prefs.recallTag(btnReportDate, PREF_REPORT_DATE_TAG); } catch(Exception ex) { Log.e(MODULE_TAG, ex.getMessage()); } } // ********************************************************************************* // * Saving Spinner Settings to Database // ********************************************************************************* /** * Saves UI settings to database */ private void submitAnswers() { DbAdapter dbAdapter = new DbAdapter(this); dbAdapter.open(); // Update report date long reportDate = (Long)btnReportDate.getTag(); dbAdapter.updateNoteReportDate(noteId, reportDate); // This activity can be entered into a number of times with // the same noteId so we always delete any previous submissions dbAdapter.deleteNoteAnswers(noteId); SpinnerAdapter spAdapter = new SpinnerAdapter(dbAdapter, noteId); // Enter the user selections into the local database try { spAdapter.put(spnSafetyIssues, DbQuestions.SAFETY_ISSUE, DbAnswers.safetyIssue, DbAnswers.safetyIssueOther); spAdapter.put(spnUrgency , DbQuestions.SAFETY_URGENCY, DbAnswers.safetyUrgency); // This is utterly bad programming!!! We are setting the severity // column in the database with urgency values. Later we key off this value // to determine if the report is a safety or accident report setSeverity(spnUrgency, DbAnswers.safetyUrgency); } catch(Exception ex) { Log.e(MODULE_TAG, ex.getMessage()); } finally { dbAdapter.close(); } } private void setSeverity(Spinner spinner, int[] answerIds) { int answerIndex = spinner.getSelectedItemPosition() - 1; if (answerIndex >= 0) { int noteSeverity = answerIds[answerIndex]; DbAdapter mDb = null; try { mDb = new DbAdapter(this); mDb.open(); mDb.updateNoteSeverity(noteId, noteSeverity); } catch(Exception ex) { Log.e(MODULE_TAG, ex.getMessage()); } finally { if (null != mDb) mDb.close(); } } } // ********************************************************************************* // * Button Handlers // ********************************************************************************* /** * Class: ButtonSave_OnClickListener * * Description: Callback to be invoked when btnSave button is clicked */ private final class ButtonSave_OnClickListener implements View.OnClickListener { /** * Description: Handles onClick for view */ public void onClick(View v) { try { finishReport(); } catch(Exception ex) { Log.e(MODULE_TAG, ex.getMessage()); } } } /** * Class: ButtonStart_OnClickListener * * Description: Callback to be invoked when startButton button is clicked */ private final class CustomLocation_OnClickListener implements OnItemSelectedListener { @Override public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { try { if (position == 2) { ReportSafetyIssuesActivity.this.alertCustomLocation(); } } catch(Exception ex) { Log.e(MODULE_TAG, ex.getMessage()); } } @Override public void onNothingSelected(AdapterView<?> parent) { try { } catch(Exception ex) { Log.e(MODULE_TAG, ex.getMessage()); } } } private void alertCustomLocation() { Toast.makeText(ReportSafetyIssuesActivity.this, getResources().getString(R.string.fyi_custom_location), Toast.LENGTH_LONG).show(); } /** * Class: ReportDate_OnClickListener * * Description: Callback to be invoked when btnReportDate button is clicked */ private final class ReportDate_OnClickListener implements View.OnClickListener { /** * Description: Handles onClick for view */ public void onClick(View v) { try { showDatePicker(); } catch(Exception ex) { Log.e(MODULE_TAG, ex.getMessage()); } } } /** * Builds a custom dialog based on the defined layout * 'res/layout/datepicker_layout.xml' and shows it */ public void showDatePicker() { DatePickerDialog datePickerDialog = new DatePickerDialog(this, btnReportDate, reportDateFormatter); datePickerDialog.show(); // Finish } // ********************************************************************************* // * Transitioning to other activities // ********************************************************************************* private void transitionToPreviousActivity() { // Cancel if (noteSource == EXTRA_NOTE_SOURCE_MAIN_INPUT) { transitionToTabsConfigActivity(); } else if (noteSource == EXTRA_NOTE_SOURCE_TRIP_MAP) { transitionToTripMapActivity(); } } private void transitionToTripMapActivity() { Intent intent = new Intent(this, TripMapActivity.class); intent.putExtra(TripMapActivity.EXTRA_TRIP_ID, tripId); intent.putExtra(TripMapActivity.EXTRA_IS_NEW_TRIP, true); intent.putExtra(TripMapActivity.EXTRA_TRIP_SOURCE, TripMapActivity.EXTRA_TRIP_SOURCE_TRIP_QUESTIONS); startActivity(intent); overridePendingTransition(android.R.anim.slide_in_left, android.R.anim.slide_out_right); finish(); } private void transitionToTabsConfigActivity() { Intent intent = new Intent(this, TabsConfig.class); intent.putExtra(TabsConfig.EXTRA_SHOW_FRAGMENT, TabsConfig.FRAG_INDEX_MAIN_INPUT); startActivity(intent); overridePendingTransition(android.R.anim.slide_in_left, android.R.anim.slide_out_right); finish(); } private void transitionToNoteDetailActivity() { // Create intent to go to the NoteDetailActivity Intent intent = new Intent(this, NoteDetailActivity.class); intent.putExtra(NoteDetailActivity.EXTRA_NOTE_ID, noteId); intent.putExtra(NoteDetailActivity.EXTRA_NOTE_SOURCE, noteSource); // the NoteType activity needs these when the back button // is pressed and we have to restart this activity intent.putExtra(NoteDetailActivity.EXTRA_TRIP_ID, tripId); intent.putExtra(NoteDetailActivity.EXTRA_TRIP_SOURCE, tripSource); intent.putExtra(ReportTypeActivity.EXTRA_REPORT_TYPE, ReportTypeActivity.EXTRA_REPORT_TYPE_SAFETY_ISSUE_REPORT); // Exit this activity startActivity(intent); overridePendingTransition(R.anim.slide_in_right, R.anim.slide_out_left); finish(); } private void transitionToCustomLocationActivity() { // Create intent to go to the NoteDetailActivity Intent intent = new Intent(this, CustomLocationActivity.class); intent.putExtra(NoteDetailActivity.EXTRA_NOTE_ID, noteId); intent.putExtra(NoteDetailActivity.EXTRA_NOTE_SOURCE, noteSource); // the NoteType activity needs these when the back button // is pressed and we have to restart this activity intent.putExtra(NoteDetailActivity.EXTRA_TRIP_ID, tripId); intent.putExtra(NoteDetailActivity.EXTRA_TRIP_SOURCE, tripSource); intent.putExtra(ReportTypeActivity.EXTRA_REPORT_TYPE, ReportTypeActivity.EXTRA_REPORT_TYPE_SAFETY_ISSUE_REPORT); // Exit this activity startActivity(intent); overridePendingTransition(R.anim.slide_in_right, R.anim.slide_out_left); finish(); } }