au.org.ala.fielddata.mobile.CollectSurveyData.java Source code

Java tutorial

Introduction

Here is the source code for au.org.ala.fielddata.mobile.CollectSurveyData.java

Source

/*******************************************************************************
 * Copyright (C) 2010 Atlas of Living Australia
 * All Rights Reserved.
 *  
 * The contents of this file are subject to the Mozilla Public
 * License Version 1.1 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.mozilla.org/MPL/
 *  
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 ******************************************************************************/
package au.org.ala.fielddata.mobile;

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;

import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.Intent;
import android.graphics.Rect;
import android.graphics.Typeface;
import android.location.Location;
import android.location.LocationListener;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.provider.MediaStore;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter;
import android.support.v4.view.ViewPager.OnPageChangeListener;
import android.text.SpannableString;
import android.text.style.StyleSpan;
import android.util.Log;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.widget.ScrollView;
import android.widget.Toast;
import au.org.ala.fielddata.mobile.dao.DraftRecordDAO;
import au.org.ala.fielddata.mobile.dao.RecordDAO;
import au.org.ala.fielddata.mobile.dao.SpeciesDAO;
import au.org.ala.fielddata.mobile.map.PointLocationSelectionActivity;
import au.org.ala.fielddata.mobile.map.WayPointActivity;
import au.org.ala.fielddata.mobile.model.Attribute;
import au.org.ala.fielddata.mobile.model.MapDefaults;
import au.org.ala.fielddata.mobile.model.Record;
import au.org.ala.fielddata.mobile.model.Species;
import au.org.ala.fielddata.mobile.model.SurveyViewModel;
import au.org.ala.fielddata.mobile.model.SurveyViewModel.TempValue;
import au.org.ala.fielddata.mobile.model.WayPoints;
import au.org.ala.fielddata.mobile.nrmplus.R;
import au.org.ala.fielddata.mobile.pref.Preferences;
import au.org.ala.fielddata.mobile.service.LocationServiceHelper;
import au.org.ala.fielddata.mobile.service.LocationServiceHelper.LocationServiceConnection;
import au.org.ala.fielddata.mobile.service.StorageManager;
import au.org.ala.fielddata.mobile.service.UploadService;
import au.org.ala.fielddata.mobile.ui.SpeciesSelectionListener;
import au.org.ala.fielddata.mobile.ui.ValidatingViewPager;
import au.org.ala.fielddata.mobile.validation.Binder;
import au.org.ala.fielddata.mobile.validation.RecordValidator.RecordValidationResult;

import com.actionbarsherlock.app.ActionBar.LayoutParams;
import com.actionbarsherlock.app.SherlockFragmentActivity;
import com.actionbarsherlock.view.MenuItem;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GooglePlayServicesUtil;
import com.viewpagerindicator.TitlePageIndicator;

/**
 * The CollectSurveyData activity presents a survey form to the user to fill
 * out.
 */
public class CollectSurveyData extends SherlockFragmentActivity
        implements SpeciesSelectionListener, OnPageChangeListener, LocationListener {

    private static final String GPS_TRACKING_BUNDLE_KEY = "GpsTracking";
    private static final String GPS_TIMEOUT_BUNDLE_KEY = "GpsTimeout";

    private static final int GPS_TIMEOUT = 60; // seconds

    public static final String SURVEY_BUNDLE_KEY = "SurveyIdKey";
    public static final String RECORD_BUNDLE_KEY = "RecordIdKey";
    public static final String SPECIES = "species";

    /** The accuracy required to auto-populate the location from GPS */
    private static final float ACCURACY_THESHOLD = 21f;

    private static final float FALLBACK_ACCURACY_THRESHOLD = 200f;

    /**
     * Used to identify a request to the LocationSelectionActivity when a result
     * is returned
     */
    public static final int SELECT_LOCATION_REQUEST = 1;

    public static final int SELECT_POLYGON_LOCATION_REQUEST = 2;

    /** Used to identify a request to the Camera when a result is returned */
    public static final int TAKE_PHOTO_REQUEST = 10000;

    /**
     * Used to identify a request to the Image Gallery when a result is returned
     */
    public static final int SELECT_FROM_GALLERY_REQUEST = 3;

    private SurveyViewModel surveyViewModel;

    private SurveyPagerAdapter pagerAdapter;
    private ValidatingViewPager pager;
    private Species selectedSpecies;
    private View leftArrow;
    private View rightArrow;
    private Attribute autoScrollAttribute;

    // GPS stuff.
    private LocationServiceConnection locationServiceConnection;
    private ScheduledFuture<?> timer;
    private ScheduledExecutorService scheduler;

    // GPS acquisition state
    private boolean gpsTrackingOn;
    private int gpsTimeoutCount;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_collect_survey_data);

        buildCustomActionBar();

        if (savedInstanceState == null) {
            // create and attach our model fragment.
            getSupportFragmentManager().beginTransaction().add(new SurveyModelHolder(), "model").commit();
        } else {
            gpsTrackingOn = savedInstanceState.getBoolean(GPS_TRACKING_BUNDLE_KEY);
            gpsTimeoutCount = savedInstanceState.getInt(GPS_TIMEOUT_BUNDLE_KEY);
        }

        pagerAdapter = new SurveyPagerAdapter(getSupportFragmentManager());
        pager = (ValidatingViewPager) findViewById(R.id.surveyPager);
        leftArrow = findViewById(R.id.leftArrow);
        rightArrow = findViewById(R.id.rightArrow);
        Intent i = getIntent();
        int speciesId = i.getIntExtra(CollectSurveyData.SPECIES, 0);
        if (speciesId > 0) {
            SpeciesDAO speciesDao = new SpeciesDAO(this);
            selectedSpecies = speciesDao.load(Species.class, speciesId);
        }

    }

    public void startLocationUpdates() {
        if (locationServiceConnection == null) {
            locationServiceConnection = new LocationServiceConnection(this, getRequiredLocationAccuracy());
            Intent intent = new Intent(this, LocationServiceHelper.class);
            bindService(intent, locationServiceConnection, Context.BIND_AUTO_CREATE);
        }
        if (scheduler == null) {
            scheduler = Executors.newScheduledThreadPool(1);
        }

        timer = scheduler.schedule(new Runnable() {
            public void run() {
                cancelLocationUpdates();
            }
        }, GPS_TIMEOUT, TimeUnit.SECONDS);
        gpsTrackingOn = true;
    }

    public float getRequiredLocationAccuracy() {
        return gpsTimeoutCount == 0 ? ACCURACY_THESHOLD : FALLBACK_ACCURACY_THRESHOLD;
    }

    public void stopLocationUpdates() {
        stopLocationUpdates(false);
    }

    private void stopLocationUpdates(boolean systemChange) {

        if (locationServiceConnection != null) {
            unbindService(locationServiceConnection);
            locationServiceConnection = null;
        }
        if (timer != null) {
            timer.cancel(false);
            timer = null;
        }
        if (!systemChange) {
            gpsTrackingOn = false;
        }
    }

    public void cancelLocationUpdates() {
        if (Utils.DEBUG) {
            Log.i("LocationBinder", "Cancelling location updates due to timeout!");
        }
        new Handler(getMainLooper()).post(new Runnable() {
            public void run() {
                gpsTimeoutCount++;
                stopLocationUpdates();
                // We do this to force the LocationBinder to update the display.
                surveyViewModel.setLocation(surveyViewModel.getLocation());

                AlertDialog.Builder builder = new AlertDialog.Builder(CollectSurveyData.this);
                if (gpsTimeoutCount == 1) {
                    builder.setTitle("GPS timeout")
                            .setMessage("Unable to acquire a location via GPS.  Please try again.")
                            .setPositiveButton("Ok", null).show();
                } else {
                    builder.setTitle("GPS timeout").setMessage(
                            "Unable to acquire a location via GPS.  Please edit the location using the web site after saving this record.")
                            .setPositiveButton("Ok", null).show();
                    surveyViewModel.disableLocationValidation();
                }
            }
        });
    }

    public void onLocationChanged(Location location) {
        Toast.makeText(this, R.string.locationSelectedByGPS, Toast.LENGTH_SHORT).show();
        surveyViewModel.setLocation(location);
    }

    // State management for the GPS acquisition functionality.
    public boolean isGpsTrackingEnabled() {
        return gpsTrackingOn;
    }

    public int getGpsTimeoutCount() {
        return gpsTimeoutCount;
    }

    public void setGpsTimeoutCount(int gpsTimeoutCount) {
        this.gpsTimeoutCount = gpsTimeoutCount;
    }

    public void onStatusChanged(String provider, int status, Bundle extras) {
    }

    public void onProviderEnabled(String provider) {
    }

    public void onProviderDisabled(String provider) {
    }

    @Override
    public void onResume() {
        super.onResume();

        if (gpsTrackingOn) {
            startLocationUpdates();
        }
    }

    @Override
    public void onPause() {
        super.onPause();
        if (gpsTrackingOn) {
            stopLocationUpdates(true);
        }
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);

        outState.putBoolean(GPS_TRACKING_BUNDLE_KEY, gpsTrackingOn);
        outState.putInt(GPS_TIMEOUT_BUNDLE_KEY, gpsTimeoutCount);
    }

    private void buildCustomActionBar() {
        getSupportActionBar().setDisplayShowTitleEnabled(false);
        getSupportActionBar().setDisplayShowHomeEnabled(false);

        View customNav = LayoutInflater.from(this).inflate(R.layout.cancel_done, null);

        customNav.findViewById(R.id.action_done).setOnClickListener(customActionBarListener);
        customNav.findViewById(R.id.action_cancel).setOnClickListener(customActionBarListener);

        LayoutParams params = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT,
                Gravity.FILL_HORIZONTAL);
        getSupportActionBar().setCustomView(customNav, params);
        getSupportActionBar().setDisplayShowCustomEnabled(true);
    }

    public void setViewModel(SurveyViewModel model) {
        this.surveyViewModel = model;
        getSupportActionBar().setTitle(Utils.bold(model.getSurvey().name));
        getSupportActionBar().setSubtitle(model.getSurvey().description);

        if (surveyViewModel.getPageCount() > 1) {
            TitlePageIndicator titleIndicator = (TitlePageIndicator) findViewById(R.id.titles);
            titleIndicator.setViewPager(pager);
            titleIndicator.setOnPageChangeListener(this);
            titleIndicator.setVisibility(View.VISIBLE);
            rightArrow.setVisibility(View.VISIBLE);
        }
        if (selectedSpecies != null) {
            onSpeciesSelected(selectedSpecies);
        }
        pager.setAdapter(pagerAdapter);

    }

    public void onPageSelected(int page) {
        int leftVisiblity = View.VISIBLE;
        int rightVisibility = View.VISIBLE;
        if (page == 0) {
            leftVisiblity = View.GONE;
        }
        int count = pagerAdapter.getCount();
        if (page == count - 1) {
            rightVisibility = View.GONE;
        }

        leftArrow.setVisibility(leftVisiblity);
        rightArrow.setVisibility(rightVisibility);

        if (autoScrollAttribute != null) {
            final Attribute invalid = autoScrollAttribute;
            scrollTo(invalid);
            autoScrollAttribute = null;
        }

    }

    public void onPageScrolled(int arg0, float arg1, int arg2) {
    }

    public void onPageScrollStateChanged(int arg0) {
    }

    public SurveyViewModel getViewModel() {
        return surveyViewModel;
    }

    public void scrollTo(final Attribute attribute) {
        pager.post(new Runnable() {
            public void run() {

                Binder binder = (Binder) surveyViewModel.getAttributeListener(attribute);
                View boundView = binder.getView();
                ViewParent parent = boundView.getParent();
                while (parent != null && !(parent instanceof ScrollView)) {
                    parent = parent.getParent();
                }

                if (parent != null) {
                    final ScrollView view = (ScrollView) parent;
                    final Rect r = new Rect();

                    view.offsetDescendantRectToMyCoords((View) boundView.getParent(), r);
                    view.post(new Runnable() {
                        public void run() {
                            view.scrollTo(0, r.bottom);
                        }
                    });

                }
            }
        });
    }

    public void onSpeciesSelected(Species selectedSpecies) {

        surveyViewModel.speciesSelected(selectedSpecies);
        pager.setCurrentItem(1);
        SpannableString title = new SpannableString(selectedSpecies.scientificName);
        title.setSpan(new StyleSpan(Typeface.ITALIC), 0, title.length(), 0);
        getSupportActionBar().setTitle(title);
        getSupportActionBar().setSubtitle(selectedSpecies.commonName);

    }

    @Override
    public boolean onMenuItemSelected(int featureId, MenuItem item) {
        onActionBarItemSelected(item.getItemId());
        return true;
    }

    public void onActionBarItemSelected(int itemId) {
        if (itemId == R.id.action_done) {

            RecordValidationResult result = surveyViewModel.validate();

            if (result.valid()) {
                Preferences prefs = new Preferences(this);
                boolean attemptUpload = prefs.getUploadAutomatically();

                new SaveRecordTask(this, attemptUpload).execute(surveyViewModel.getRecord());
                if (!attemptUpload) {
                    Toast.makeText(this, "Record saved.", Toast.LENGTH_LONG).show();
                }

                finish();
            } else {
                new SaveRecordTask(this, false).execute(surveyViewModel.getRecord());

                Toast.makeText(this, R.string.validationMessage, Toast.LENGTH_LONG).show();
                Attribute firstInvalid = result.invalidAttributes().get(0).getAttribute();
                int firstInvalidPage = surveyViewModel.pageOf(firstInvalid);
                if (pager.getCurrentItem() != firstInvalidPage) {
                    pager.setCurrentItem(firstInvalidPage);
                } else {
                    autoScrollAttribute = firstInvalid;
                    scrollTo(autoScrollAttribute);
                }

            }
        } else if (itemId == R.id.action_cancel) {
            finish();
        }
    }

    @Override
    public void finish() {
        // Delete our draft.
        DraftRecordDAO recordDAO = new DraftRecordDAO(getApplicationContext());
        recordDAO.deleteAll(Record.class);

        super.finish();
    }

    class SurveyPagerAdapter extends FragmentPagerAdapter {

        public SurveyPagerAdapter(FragmentManager manager) {
            super(manager);
        }

        @Override
        public Fragment getItem(int page) {

            SurveyPage surveyPage = new SurveyPage();
            Bundle args = new Bundle();
            args.putInt("pageNum", page);
            surveyPage.setArguments(args);

            return surveyPage;
        }

        @Override
        public int getCount() {
            return surveyViewModel.getPageCount();
        }

        @Override
        public String getPageTitle(int page) {
            return "PAGE " + (page + 1);
        }

    }

    /**
     * Launches the default camera application to take a photo and store the
     * result for the supplied attribute.
     * 
     * @param attribute
     *            the attribute the photo relates to.
     */
    public void takePhoto(Attribute attribute) {
        if (StorageManager.canWriteToExternalStorage()) {
            Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
            Uri fileUri = StorageManager.getOutputMediaFileUri(StorageManager.MEDIA_TYPE_IMAGE);
            intent.putExtra(MediaStore.EXTRA_OUTPUT, fileUri);
            // Unfortunately, this URI isn't being returned in the
            // result as expected so we have to save it somewhere it can
            // survive an activity restart.
            surveyViewModel.setTempValue(attribute, fileUri.toString());
            startActivityForResult(intent, CollectSurveyData.TAKE_PHOTO_REQUEST);
        } else {
            AlertDialog.Builder builder = new AlertDialog.Builder(this);
            builder.setTitle("Cannot take photo")
                    .setMessage("Please ensure you have mounted your SD card and it is writable")
                    .setPositiveButton("OK", null).show();
        }
    }

    /**
     * Launches the default gallery application to allow the user to select an
     * image to be attached to the supplied attribute.
     * 
     * @param attribute
     *            the attribute the image is being selected for.
     */
    public void selectFromGallery(Attribute attribute) {
        Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
        intent.setType("image/*");
        // Just saving the attribute we are working with.
        surveyViewModel.setTempValue(attribute, "");
        startActivityForResult(Intent.createChooser(intent, "Select Photo"),
                CollectSurveyData.SELECT_FROM_GALLERY_REQUEST);
    }

    public void selectLocation() {

        int checkGooglePlayServices = GooglePlayServicesUtil.isGooglePlayServicesAvailable(this);
        if (checkGooglePlayServices != ConnectionResult.SUCCESS) {

            GooglePlayServicesUtil.getErrorDialog(checkGooglePlayServices, this, 1122).show();
        } else {

            if (surveyViewModel.getSurvey().locationPolygon) {
                selectPolygonLocation();
            } else {
                selectPointLocation();
            }
        }
    }

    public void selectPolygonLocation() {

        Intent intent = new Intent(this, WayPointActivity.class);
        Location location = surveyViewModel.getLocation();
        MapDefaults defaults = surveyViewModel.getSurvey().map;

        Attribute photoPointAttribute = getViewModel().getSurvey().getPhotoPointAttribute();
        if (photoPointAttribute != null) {
            intent.putExtra(WayPointActivity.ATTRIBUTE_ID_KEY, photoPointAttribute.getServerId());
        }

        intent.putExtra(PointLocationSelectionActivity.MAP_DEFAULTS_BUNDLE_KEY, defaults);
        if (location != null) {
            intent.putExtra(PointLocationSelectionActivity.LOCATION_BUNDLE_KEY, location);
        }

        intent.putExtra(WayPointActivity.WAY_POINTS_KEY, surveyViewModel.getWayPoints());

        startActivityForResult(intent, CollectSurveyData.SELECT_POLYGON_LOCATION_REQUEST);
    }

    public void selectPointLocation() {
        Intent intent = new Intent(this, PointLocationSelectionActivity.class);
        Location location = surveyViewModel.getLocation();
        MapDefaults defaults = surveyViewModel.getSurvey().map;

        intent.putExtra(PointLocationSelectionActivity.MAP_DEFAULTS_BUNDLE_KEY, defaults);
        if (location != null) {
            intent.putExtra(PointLocationSelectionActivity.LOCATION_BUNDLE_KEY, location);

        }
        startActivityForResult(intent, CollectSurveyData.SELECT_LOCATION_REQUEST);
    }

    /**
     * Callback made to this activity after the camera, gallery or map activity
     * has finished.
     */
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {

        if (requestCode == SELECT_LOCATION_REQUEST) {
            if (resultCode == RESULT_OK) {
                Location location = (Location) data.getExtras().get(LocationSelectionActivity.LOCATION_BUNDLE_KEY);
                surveyViewModel.setLocation(location);
            }
        } else if (requestCode == SELECT_POLYGON_LOCATION_REQUEST) {
            if (resultCode == RESULT_OK) {

                WayPoints result = (WayPoints) data.getExtras().get(WayPointActivity.WAY_POINTS_KEY);
                result.setPhotoPointAttribute(data.getIntExtra(WayPointActivity.ATTRIBUTE_ID_KEY, -1));
                surveyViewModel.setWayPoints(result);
            }
        } else if (requestCode == TAKE_PHOTO_REQUEST) {
            if (resultCode == RESULT_OK) {
                surveyViewModel.persistTempValue();
            } else {
                surveyViewModel.clearTempValue();
            }
        } else if (requestCode == SELECT_FROM_GALLERY_REQUEST) {
            TempValue value = surveyViewModel.clearTempValue();
            if (resultCode == RESULT_OK) {
                Uri selected = data.getData();
                if (selected != null) {
                    surveyViewModel.setValue(value.getAttribute(), selected.toString());
                } else {
                    Log.e("CollectSurveyData", "Null data returned from gallery intent!" + data);
                }
            }
        }
    }

    public static class SurveyPage extends Fragment {

        private int pageNum;
        private SurveyViewModel viewModel;
        private BinderManager binder;
        private CollectSurveyData ctx;
        private ScrollView scroller;

        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            pageNum = getArguments() != null ? getArguments().getInt("pageNum") : 0;

        }

        @Override
        public void onAttach(Activity activity) {

            super.onAttach(activity);
            ctx = (CollectSurveyData) activity;
        }

        @Override
        public void onActivityCreated(Bundle bundle) {
            super.onActivityCreated(bundle);

        }

        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {

            viewModel = ctx.getViewModel();
            binder = new BinderManager(ctx);
            View view = inflater.inflate(R.layout.survey_data_page, container, false);
            view.setTag(binder);
            scroller = (ScrollView) view.findViewById(R.id.tableScroller);
            scroller.setTag(binder);

            SurveyBuilder builder = new SurveyBuilder(getActivity(), viewModel, binder);

            builder.buildSurveyForm(view, pageNum);

            return view;
        }

        @Override
        public void onDestroyView() {
            super.onDestroyView();
            binder.clearBindings();
        }
    }

    static class SaveRecordTask extends AsyncTask<Record, Void, Boolean> {

        private CollectSurveyData ctx;
        private boolean upload;

        public SaveRecordTask(CollectSurveyData ctx, boolean upload) {
            this.ctx = ctx;
            this.upload = upload;
        }

        @Override
        protected Boolean doInBackground(Record... params) {
            boolean success = true;
            try {

                RecordDAO recordDao = new RecordDAO(ctx.getApplicationContext());
                recordDao.save(ctx.getViewModel().getRecord());

                if (upload) {
                    Intent upload = new Intent(ctx, UploadService.class);
                    ctx.startService(upload);
                }

            } catch (Exception e) {
                success = false;
                Log.e("SurveyUpload", "Upload failed", e);
            }
            return success;
        }

    }

    private final View.OnClickListener customActionBarListener = new View.OnClickListener() {
        public void onClick(View v) {
            onActionBarItemSelected(v.getId());
        }
    };
}