com.application.treasurehunt.ScanQRCodeActivity.java Source code

Java tutorial

Introduction

Here is the source code for com.application.treasurehunt.ScanQRCodeActivity.java

Source

/*
 * Emma Davidson - Treasure Hunt 2013-3014 Final Year Project
 */
package com.application.treasurehunt;

import android.location.Location;
import android.location.LocationManager;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.app.ActionBar;
import android.app.Activity;
import android.app.ProgressDialog;
import android.app.AlertDialog.Builder;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;

import org.apache.http.NameValuePair;
import org.apache.http.message.BasicNameValuePair;

import org.json.JSONException;
import org.json.JSONObject;

import sqlLiteDatabase.MapDataDAO;

import com.dm.zbar.android.scanner.ZBarConstants;
import com.dm.zbar.android.scanner.ZBarScannerActivity;

import Mapping.LocationReceiver;
import Mapping.MapManager;

import Utilities.JSONParser;
import Utilities.PHPHelper;

import java.util.ArrayList;
import java.util.List;

//https://github.com/DushyanthMaguluru/ZBarScanner for ZBAR - 
//This class references and uses the source forge project. 

/* The purpose of this Activity is to allow a participant to scan a QR Code and view on screen the question 
 * that the code represents in order to complete the treasure hunt.
 * [See Dissertation Section 2.4.2.6]*/

//For the use of ZBar Scanner - MIT Licence - http://opensource.org/licenses/MIT
//is released under the MIT Open Source Initiative license (MIT) which is unrestricted usage rights providing
//the copyright notice and permission notice at the link below should be included in the source code as comments.
//This copyright notice is found within the Licences Activity and the referenced project itself.

public class ScanQRCodeActivity extends Activity {

    /*
     * Global variables used within ScanQRCodeActivity.
     */
    private static final String SCAN_RESULT_URL = "http://lowryhosting.com/emmad/updateScanResults.php";

    private ProgressDialog mScanDialog;

    public JSONParser jsonParser = new JSONParser();
    private SaveScanResultTask mScanResultTask;

    private Button mScanButton;
    private TextView mQuestionReturned;

    private Builder mAlertForNoLocationServices;

    private SharedPreferences mSettings;
    private SharedPreferences.Editor mEditor;

    private MapDataDAO mMapDataSource;
    private MapManager mMapManager;

    private Location mLastLocation;
    private LocationManager mLocationManager;

    private long mStartTime;
    private int mCurrentHuntId;
    private int mCurrentParticipantId;

    private boolean mLocationServicesChecked = false;

    private String mConnectionTimeout = "Connection timeout. Please try again.";

    /*
     * Method called when the Activity is created (as part of the android life cycle) which sets up this Activity's variables.
     * It also checks to see if the location services of the participant's device has been turned on in order for 
     * pins to be dropped on the participants map for the given treasure hunt.
     * */
    @Override
    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_scan_qrcode);

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
            ActionBar actionBar = getActionBar();
            actionBar.setTitle("Treasure Hunt");
            actionBar.setSubtitle("Scan a QR code");
        }

        mScanButton = (Button) findViewById(R.id.scan_qr_code_button);
        mQuestionReturned = (TextView) findViewById(R.id.scan_content_received);

        mMapManager = MapManager.get(this);
        mLocationManager = (LocationManager) this.getSystemService(Context.LOCATION_SERVICE);

        mMapDataSource = MapDataDAO.getInstance(this);
        mMapDataSource.open();

        mSettings = getSharedPreferences("UserPreferencesFile", 0);
        mEditor = mSettings.edit();

        //http://developer.android.com/guide/topics/data/data-storage.html#pref
        mCurrentHuntId = mSettings.getInt("currentHuntId", 0);
        mCurrentParticipantId = mSettings.getInt("huntParticipantId", 0);

        Log.i("ScanQRCode", "The hunt retrieved from the editor is: " + mCurrentHuntId);

        if (savedInstanceState == null) {
            checkLocationServices();
        }

        ScanQRCodeActivity.this.registerReceiver(mLocationReceiver, new IntentFilter(MapManager.ACTION_LOCATION));

        mScanButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                mQuestionReturned.setText("");
                Intent intent = new Intent(ScanQRCodeActivity.this, ZBarScannerActivity.class);
                startActivityForResult(intent, 1);
            }
        });
    }

    /* Methods to set up the on screen menu. */
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {

        //http://mobileorchard.com/android-app-development-menus-part-1-options-menu/
        super.onCreateOptionsMenu(menu);
        getMenuInflater().inflate(R.menu.login, menu);
        menu.add(Menu.NONE, 1, Menu.NONE, "Home");
        menu.add(Menu.NONE, 2, Menu.NONE, "Log out");
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {

        switch (item.getItemId()) {
        case 1: {

            Intent homepageActivityIntent = new Intent(ScanQRCodeActivity.this, HomepageActivity.class);
            startActivity(homepageActivityIntent);

            return true;
        }
        case 2: {

            mEditor.clear();
            mEditor.commit();

            mMapManager.stopLocationUpdates();

            Intent loginActivityIntent = new Intent(ScanQRCodeActivity.this, LoginActivity.class);
            startActivity(loginActivityIntent);
        }
        }

        return super.onOptionsItemSelected(item);
    }

    /*
     * Method saves the current HuntId and the last location that has been saved by the device when the Activity is paused.
     * */
    @Override
    public void onSaveInstanceState(Bundle savedInstanceState) {

        savedInstanceState.putParcelable("lastLocation", mLastLocation);
        savedInstanceState.putBoolean("mLocationServicesChecked", mLocationServicesChecked);
        super.onSaveInstanceState(savedInstanceState);
    }

    /*
     * Method restoring the current HuntId and last know location when the Activity is restored.
     * */
    @Override
    public void onRestoreInstanceState(Bundle savedInstanceState) {

        super.onRestoreInstanceState(savedInstanceState);
        mLastLocation = savedInstanceState.getParcelable("lastLocation");
        mLocationServicesChecked = savedInstanceState.getBoolean("mLocationServicesChecked");
    }

    /* Method to deal with the result of a scan. If the scan was valid for the given treasure hunt (i.e. the participant
     * did not scan a QR code unrelated to this particular treasure hunt) their scan is saved. Else, a dialog appears 
     * to inform the participant of an error.*/
    //https://github.com/dm77/ZBarScanner
    public void onActivityResult(int requestCode, int resultCode, Intent intent) {

        if (resultCode == RESULT_OK) {
            if (intent.getStringExtra(ZBarConstants.SCAN_RESULT).contains(mCurrentHuntId + "")) {
                String questionReturned = intent.getStringExtra(ZBarConstants.SCAN_RESULT);
                //http://stackoverflow.com/questions/8694984/remove-part-of-string
                String questionReturnedWithoutHuntId = questionReturned.replace(mCurrentHuntId + "", "");
                mQuestionReturned.setText(questionReturnedWithoutHuntId);
                saveScanResult();
            } else {
                showFailedScanMessage();
            }

        } else if (resultCode == RESULT_CANCELED) {
            Toast.makeText(this, "Camera unavailable", Toast.LENGTH_SHORT).show();
        }
    }

    /* Method to call the asynchronous class 'ScanResultTask'. If call to the database takes too long then a timeout should occur.*/
    private void saveScanResult() {

        if (mScanResultTask != null) {
            return;
        }

        mScanResultTask = new SaveScanResultTask();
        mScanResultTask.execute((String) null);

        Handler handlerForUserTask = new Handler();
        handlerForUserTask.postDelayed(new Runnable() {
            @Override
            public void run() {
                if (mScanResultTask != null) {
                    if (mScanResultTask.getStatus() == AsyncTask.Status.RUNNING) {
                        mScanDialog.cancel();
                        mScanResultTask.cancel(true);
                        Toast.makeText(ScanQRCodeActivity.this, mConnectionTimeout, Toast.LENGTH_LONG).show();
                    }
                }
            }
        }, 20000);
    }

    /* Method to check whether location services are turned on for a device. If turned off then
     * the current participant is unable to drop a pin on the map associated with this treasure hunt for a given scan.*/
    private void checkLocationServices() {

        //http://stackoverflow.com/questions/10311834/android-dev-how-to-check-if-location-services-are-enabled
        if (!mLocationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {

            mAlertForNoLocationServices = new Builder(ScanQRCodeActivity.this);
            mAlertForNoLocationServices.setTitle("Location Services");
            mAlertForNoLocationServices
                    .setMessage("Location services are not turned on. Turn on to track your treasure hunt.");
            mAlertForNoLocationServices.setCancelable(false);
            mAlertForNoLocationServices.setNegativeButton("OK", new DialogInterface.OnClickListener() {

                @Override
                public void onClick(DialogInterface dialog, int which) {
                    dialog.cancel();
                }
            });

            mAlertForNoLocationServices.create();
            mAlertForNoLocationServices.show();
        }

        mLocationServicesChecked = true;
    }

    /* Method to show a dialog if the current scan has failed e.g. if the participant has scanned a QR Code that
     * is not associated with the given treasure hunt.*/
    private void showFailedScanMessage() {

        Builder alertForNoData = new Builder(ScanQRCodeActivity.this);
        alertForNoData.setTitle("Invalid Code");
        alertForNoData.setMessage("This was an invalid scan for this particular treasure hunt. Please try again.");
        alertForNoData.setCancelable(false);
        alertForNoData.setNegativeButton("OK", new DialogInterface.OnClickListener() {

            @Override
            public void onClick(DialogInterface dialog, int which) {
                dialog.cancel();
            }
        });

        alertForNoData.create();
        alertForNoData.show();

        Log.w("ScanQRCode", "Invalid scan for hunt: " + mCurrentHuntId);
    }

    /* This BroadcastRecevier will listen out for location services updates. If a new location has been noted, the last 
     * known location is recorded. This is to be used in conjunction with saving a pin to the associated map for the 
     * given participant. [See SaveScanResultTask OnPostExecute() method.] */
    private BroadcastReceiver mLocationReceiver = new LocationReceiver() {

        @Override
        protected void onLocationReceived(Context context, Location loc) {
            mLastLocation = loc;
        }

        @Override
        protected void onProviderEnabledChanged(boolean enabled) {
            String toastText = enabled ? "enabled" : "disabled";
            Toast.makeText(ScanQRCodeActivity.this, toastText, Toast.LENGTH_LONG).show();
        }
    };

    /* This internal class attempts to save in the database a participant's elapsed time for each valid scan made i.e. it takes
     * the participant's start time and compares it with the current time (of the scan) and date to work out the elapsed time. */
    public class SaveScanResultTask extends AsyncTask<String, String, String> {

        /* A dialog will appear on screen to show the participant that a save is being made.*/
        @Override
        protected void onPreExecute() {

            super.onPreExecute();
            mScanDialog = new ProgressDialog(ScanQRCodeActivity.this);
            mScanDialog.setMessage("Attempting to retrieve scan result");
            mScanDialog.setIndeterminate(false);
            mScanDialog.setCancelable(false);
            mScanDialog.show();
        }

        /* Method calling the database a participant's elapsed time for each valid scan made.*/
        @Override
        public String doInBackground(String... args) {
            //http://www.mybringback.com/tutorial-series/13193/android-mysql-php-json-part-5-developing-the-android-application/

            int success;
            List<NameValuePair> parameters = new ArrayList<NameValuePair>();

            //NEED TO FIND REFERENCES
            //http://stackoverflow.com/questions/10862845/how-to-set-android-chronometer-base-time-from-date-object
            mStartTime = mSettings.getLong(mCurrentHuntId + " startTime", 0);
            Log.i("ScanQRCode", "The start time retrieved from the editor is: " + mStartTime);
            Log.i("ScanQRCode", "Current millis time: " + System.currentTimeMillis());
            long elapsedTime = System.currentTimeMillis() - mStartTime;
            Log.i("ScanQRCode", "elapsed time from last scan: " + elapsedTime);

            //http://stackoverflow.com/questions/625433/how-to-convert-milliseconds-to-x-mins-x-seconds-in-java
            //http://stackoverflow.com/questions/10593834/displaying-seconds-in-3-decimal-places-java
            long timeElapsedSeconds = (elapsedTime / 1000);
            Log.i("ScanQRCode", "elapsedTime in seconds: " + timeElapsedSeconds);

            double timeElapsedHours = (elapsedTime / (1000.0f * 60.0f * 60.0f));
            Log.i("ScanQRCode", "elapsedTime in hours with calc " + timeElapsedHours);

            //http://stackoverflow.com/questions/8603583/sending-integer-to-http-server-using-namevaluepair
            //Chose elapsed hours because it may take some time to complete the hunt
            parameters.add(new BasicNameValuePair("huntParticipantId", Integer.toString(mCurrentParticipantId)));
            parameters.add(new BasicNameValuePair("timeElapsed", "" + timeElapsedHours));
            //Don't need to save the tally as it will update in the php call
            //http://stackoverflow.com/questions/3866524/mysql-update-column-1

            try {
                Log.i("ScanQRCode", "starting");
                JSONObject json = jsonParser.makeHttpRequest(SCAN_RESULT_URL, "POST", parameters);
                Log.i("ScanQRCode", json.toString());
                success = json.getInt(PHPHelper.SUCCESS);

                if (success == 1) {
                    Log.i("ScanQRCode", "SCAN: huntParticipantId is: " + mCurrentParticipantId + ", timeElapsed= "
                            + timeElapsedHours);
                    return json.getString(PHPHelper.MESSAGE);
                } else {
                    Log.w("ScanQRCode", json.getString(PHPHelper.MESSAGE));
                    return json.getString(PHPHelper.MESSAGE);
                }

            } catch (JSONException e) {
                try {
                    throw new JSONException(e.toString());
                } catch (JSONException e1) {
                    e1.printStackTrace();
                }
            }

            return null;
        }

        /* Method called after the database call has been made. If location services have picked up the location of the device during 
         * the scan, the location is recorded against this participant for a pin to be dropped on the associated map. Else, they 
         * are notified of an error on screen. */
        @Override
        protected void onPostExecute(final String fileUrl) {
            mScanResultTask = null;
            mScanDialog.dismiss();

            if (mLastLocation != null) {
                mMapDataSource.insertMarker(mCurrentParticipantId, mLastLocation);
            } else {
                Toast.makeText(ScanQRCodeActivity.this, "Pin not saved.", Toast.LENGTH_LONG).show();
                //http://www.mkyong.com/android/android-alert-dialog-example/
            }

            if (fileUrl != null) {
                Log.i("ScanQRCode", fileUrl);
            } else {
                Log.w("ScanQRCode", "Nothing returned");
            }
        }

        /* Method to cancel the current task.*/
        @Override
        protected void onCancelled() {
            mScanResultTask = null;
        }
    }
}