me.hoen.geofencing.MainActivity.java Source code

Java tutorial

Introduction

Here is the source code for me.hoen.geofencing.MainActivity.java

Source

/*
 * Copyright (C) 2013 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package me.hoen.geofencing;

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

import me.hoen.geofencing.GeofenceUtils.REMOVE_TYPE;
import me.hoen.geofencing.GeofenceUtils.REQUEST_TYPE;
import me.hoen.geofencing.display.EventListActivity;
import android.app.Activity;
import android.app.Dialog;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.graphics.Color;
import android.location.Location;
import android.os.Bundle;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.FragmentActivity;
import android.support.v4.content.LocalBroadcastManager;
import android.text.TextUtils;
import android.text.format.DateUtils;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.widget.Toast;

import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GooglePlayServicesClient.ConnectionCallbacks;
import com.google.android.gms.common.GooglePlayServicesClient.OnConnectionFailedListener;
import com.google.android.gms.common.GooglePlayServicesUtil;
import com.google.android.gms.location.Geofence;
import com.google.android.gms.location.LocationClient;
import com.google.android.gms.location.LocationListener;
import com.google.android.gms.location.LocationRequest;
import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.MapFragment;
import com.google.android.gms.maps.model.CircleOptions;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.Marker;
import com.google.android.gms.maps.model.MarkerOptions;

public class MainActivity extends FragmentActivity
        implements LocationListener, ConnectionCallbacks, OnConnectionFailedListener {

    protected LocationRequest mLocationRequest;
    protected LocationClient mLocationClient;

    // Milliseconds per second
    public static final int MILLISECONDS_PER_SECOND = 1000;

    // The update interval
    public static final int UPDATE_INTERVAL_IN_SECONDS = 60;

    // A fast interval ceiling
    public static final int FAST_CEILING_IN_SECONDS = 1;

    // Update interval in milliseconds
    public static final long UPDATE_INTERVAL_IN_MILLISECONDS = MILLISECONDS_PER_SECOND * UPDATE_INTERVAL_IN_SECONDS;

    // A fast ceiling of update intervals, used when the app is visible
    public static final long FAST_INTERVAL_CEILING_IN_MILLISECONDS = MILLISECONDS_PER_SECOND
            * FAST_CEILING_IN_SECONDS;

    /*
     * Use to set an expiration time for a geofence. After this amount of time
     * Location Services will stop tracking the geofence. Remember to unregister
     * a geofence when you're finished with it. Otherwise, your app will use up
     * battery. To continue monitoring a geofence indefinitely, set the
     * expiration time to Geofence#NEVER_EXPIRE.
     */
    private static final long GEOFENCE_EXPIRATION_IN_HOURS = 12;
    public static final long GEOFENCE_EXPIRATION_IN_MILLISECONDS = GEOFENCE_EXPIRATION_IN_HOURS
            * DateUtils.HOUR_IN_MILLIS;

    // Store the current request
    private REQUEST_TYPE mRequestType;

    // Store the current type of removal
    private REMOVE_TYPE mRemoveType;

    // Persistent storage for geofences
    private SimpleGeofenceStore mPrefs;

    // Store a list of geofences to add
    List<Geofence> mCurrentGeofences;

    // Add geofences handler
    private GeofenceRequester mGeofenceRequester;
    // Remove geofences handler
    private GeofenceRemover mGeofenceRemover;

    /*
     * An instance of an inner class that receives broadcasts from listeners and
     * from the IntentService that receives geofence transition events
     */
    private GeofenceSampleReceiver mBroadcastReceiver;

    // An intent filter for the broadcast receiver
    private IntentFilter mIntentFilter;

    // Store the list of geofences to remove
    private List<String> mGeofenceIdsToRemove;

    protected GoogleMap map;
    protected Marker marker;

    protected ArrayList<SimpleGeofence> simpleGeofences;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        simpleGeofences = SimpleGeofenceDataStore.getInstance().getSimpleGeofences();

        // Create a new broadcast receiver to receive updates from the listeners
        // and service
        mBroadcastReceiver = new GeofenceSampleReceiver();

        // Create an intent filter for the broadcast receiver
        mIntentFilter = new IntentFilter();

        // Action for broadcast Intents that report successful addition of
        // geofences
        mIntentFilter.addAction(GeofenceUtils.ACTION_GEOFENCES_ADDED);

        // Action for broadcast Intents that report successful removal of
        // geofences
        mIntentFilter.addAction(GeofenceUtils.ACTION_GEOFENCES_REMOVED);

        // Action for broadcast Intents containing various types of geofencing
        // errors
        mIntentFilter.addAction(GeofenceUtils.ACTION_GEOFENCE_ERROR);

        // All Location Services sample apps use this category
        mIntentFilter.addCategory(GeofenceUtils.CATEGORY_LOCATION_SERVICES);

        // Instantiate a new geofence storage area
        mPrefs = new SimpleGeofenceStore(this);

        // Instantiate the current List of geofences
        mCurrentGeofences = new ArrayList<Geofence>();

        // Instantiate a Geofence requester
        mGeofenceRequester = new GeofenceRequester(this);

        // Instantiate a Geofence remover
        mGeofenceRemover = new GeofenceRemover(this);

        // Attach to the main UI
        setContentView(R.layout.activity_main);

        map = ((MapFragment) getFragmentManager().findFragmentById(R.id.map)).getMap();
        map.setMyLocationEnabled(true);

        registerGeofences();

        mLocationRequest = LocationRequest.create();
        mLocationRequest.setInterval(UPDATE_INTERVAL_IN_MILLISECONDS);
        mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
        mLocationRequest.setFastestInterval(FAST_INTERVAL_CEILING_IN_MILLISECONDS);
        mLocationClient = new LocationClient(this, this, this);

        mLocationClient.connect();

    }

    /*
     * Handle results returned to this Activity by other Activities started with
     * startActivityForResult(). In particular, the method onConnectionFailed()
     * in GeofenceRemover and GeofenceRequester may call
     * startResolutionForResult() to start an Activity that handles Google Play
     * services problems. The result of this call returns here, to
     * onActivityResult. calls
     */
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
        // Choose what to do based on the request code
        switch (requestCode) {

        // If the request code matches the code sent in onConnectionFailed
        case GeofenceUtils.CONNECTION_FAILURE_RESOLUTION_REQUEST:

            switch (resultCode) {
            // If Google Play services resolved the problem
            case Activity.RESULT_OK:

                // If the request was to add geofences
                if (GeofenceUtils.REQUEST_TYPE.ADD == mRequestType) {

                    // Toggle the request flag and send a new request
                    mGeofenceRequester.setInProgressFlag(false);

                    // Restart the process of adding the current geofences
                    mGeofenceRequester.addGeofences(mCurrentGeofences);

                    // If the request was to remove geofences
                } else if (GeofenceUtils.REQUEST_TYPE.REMOVE == mRequestType) {

                    // Toggle the removal flag and send a new removal request
                    mGeofenceRemover.setInProgressFlag(false);

                    // If the removal was by Intent
                    if (GeofenceUtils.REMOVE_TYPE.INTENT == mRemoveType) {

                        // Restart the removal of all geofences for the
                        // PendingIntent
                        mGeofenceRemover.removeGeofencesByIntent(mGeofenceRequester.getRequestPendingIntent());

                        // If the removal was by a List of geofence IDs
                    } else {

                        // Restart the removal of the geofence list
                        mGeofenceRemover.removeGeofencesById(mGeofenceIdsToRemove);
                    }
                }
                break;

            // If any other result was returned by Google Play services
            default:

                // Report that Google Play services was unable to resolve the
                // problem.
                Log.d(GeofenceUtils.APPTAG, getString(R.string.no_resolution));
            }

            // If any other request code was received
        default:
            // Report that this Activity received an unknown requestCode
            Log.d(GeofenceUtils.APPTAG, getString(R.string.unknown_activity_request_code, requestCode));

            break;
        }
    }

    /*
     * Whenever the Activity resumes, reconnect the client to Location Services
     * and reload the last geofences that were set
     */
    @Override
    protected void onResume() {
        super.onResume();
        // Register the broadcast receiver to receive status updates
        LocalBroadcastManager.getInstance(this).registerReceiver(mBroadcastReceiver, mIntentFilter);
        /*
         * Get existing geofences from the latitude, longitude, and radius
         * values stored in SharedPreferences. If no values exist, null is
         * returned.
         */

        for (SimpleGeofence sg : this.simpleGeofences) {
            SimpleGeofence mUIGeofence = mPrefs.getGeofence(sg.getId());
            if (mUIGeofence != null) {
                CircleOptions circleOptions1 = new CircleOptions()
                        .center(new LatLng(mUIGeofence.getLatitude(), mUIGeofence.getLongitude()))
                        .radius(mUIGeofence.getRadius()).strokeColor(Color.GRAY).strokeWidth(2)
                        .fillColor(Color.RED);
                map.addCircle(circleOptions1);
            }
        }

    }

    /*
     * Save the current geofence settings in SharedPreferences.
     */
    @Override
    protected void onPause() {
        super.onPause();
        for (SimpleGeofence sg : this.simpleGeofences) {
            mPrefs.setGeofence(sg.getId(), sg);
        }
    }

    /**
     * Verify that Google Play services is available before making a request.
     * 
     * @return true if Google Play services is available, otherwise false
     */
    private boolean servicesConnected() {

        // Check that Google Play services is available
        int resultCode = GooglePlayServicesUtil.isGooglePlayServicesAvailable(this);

        // If Google Play services is available
        if (ConnectionResult.SUCCESS == resultCode) {

            // In debug mode, log the status
            Log.d(GeofenceUtils.APPTAG, getString(R.string.play_services_available));

            // Continue
            return true;

            // Google Play services was not available for some reason
        } else {

            // Display an error dialog
            Dialog dialog = GooglePlayServicesUtil.getErrorDialog(resultCode, this, 0);
            if (dialog != null) {
                ErrorDialogFragment errorFragment = new ErrorDialogFragment();
                errorFragment.setDialog(dialog);
                errorFragment.show(getSupportFragmentManager(), GeofenceUtils.APPTAG);
            }
            return false;
        }
    }

    /**
     * Called when the user clicks the "Register geofences" button. Get the
     * geofence parameters for each geofence and add them to a List. Create the
     * PendingIntent containing an Intent that Location Services sends to this
     * app's broadcast receiver when Location Services detects a geofence
     * transition. Send the List and the PendingIntent to Location Services.
     */
    public void registerGeofences() {

        /*
         * Record the request as an ADD. If a connection error occurs, the app
         * can automatically restart the add request if Google Play services can
         * fix the error
         */
        mRequestType = GeofenceUtils.REQUEST_TYPE.ADD;

        /*
         * Check for Google Play services. Do this after setting the request
         * type. If connecting to Google Play services fails, onActivityResult
         * is eventually called, and it needs to know what type of request was
         * in progress.
         */
        if (!servicesConnected()) {

            return;
        }

        for (SimpleGeofence sg : this.simpleGeofences) {
            mPrefs.setGeofence(sg.getId(), sg);
        }

        for (SimpleGeofence sg : this.simpleGeofences) {
            /*
             * Add Geofence objects to a List. toGeofence() creates a Location
             * Services Geofence object from a flat object
             */
            mCurrentGeofences.add(sg.toGeofence());
        }

        // Start the request. Fail if there's already a request in progress
        try {
            // Try to add geofences
            mGeofenceRequester.addGeofences(mCurrentGeofences);

        } catch (UnsupportedOperationException e) {
            // Notify user that previous request hasn't finished.
            Toast.makeText(this, R.string.add_geofences_already_requested_error, Toast.LENGTH_LONG).show();
        }
    }

    @Override
    public void onDestroy() {
        mLocationClient.disconnect();
        super.onDestroy();
    }

    @Override
    public void onConnectionFailed(ConnectionResult arg0) {
    }

    @Override
    public void onConnected(Bundle connectionHint) {
        startPeriodicUpdates();
    }

    @Override
    public void onDisconnected() {
        stopPeriodicUpdates();
    }

    @Override
    public void onLocationChanged(Location location) {
        if (location.hasAccuracy() && location.getAccuracy() < 100) {
            LatLng itemPosition = new LatLng(location.getLatitude(), location.getLongitude());

            if (marker == null) {
                MarkerOptions markerOptions = new MarkerOptions().position(itemPosition).title("My coordinates");

                marker = map.addMarker(markerOptions);
                map.moveCamera(CameraUpdateFactory.newLatLngZoom(itemPosition, 17));
            } else {
                marker.setPosition(itemPosition);
            }
        }
    }

    protected void startPeriodicUpdates() {
        mLocationClient.requestLocationUpdates(mLocationRequest, this);
    }

    protected void stopPeriodicUpdates() {
        mLocationClient.removeLocationUpdates(this);
    }

    /**
     * Define a Broadcast receiver that receives updates from connection
     * listeners and the geofence transition service.
     */
    public class GeofenceSampleReceiver extends BroadcastReceiver {
        /*
         * Define the required method for broadcast receivers This method is
         * invoked when a broadcast Intent triggers the receiver
         */
        @Override
        public void onReceive(Context context, Intent intent) {

            // Check the action code and determine what to do
            String action = intent.getAction();

            // Intent contains information about errors in adding or removing
            // geofences
            if (TextUtils.equals(action, GeofenceUtils.ACTION_GEOFENCE_ERROR)) {

                handleGeofenceError(context, intent);

                // Intent contains information about successful addition or
                // removal of geofences
            } else if (TextUtils.equals(action, GeofenceUtils.ACTION_GEOFENCES_ADDED)
                    || TextUtils.equals(action, GeofenceUtils.ACTION_GEOFENCES_REMOVED)) {

                handleGeofenceStatus(context, intent);

                // Intent contains information about a geofence transition
            } else if (TextUtils.equals(action, GeofenceUtils.ACTION_GEOFENCE_TRANSITION)) {

                handleGeofenceTransition(context, intent);

                // The Intent contained an invalid action
            } else {
                Log.e(GeofenceUtils.APPTAG, getString(R.string.invalid_action_detail, action));
                Toast.makeText(context, R.string.invalid_action, Toast.LENGTH_LONG).show();
            }
        }

        /**
         * If you want to display a UI message about adding or removing
         * geofences, put it here.
         * 
         * @param context
         *            A Context for this component
         * @param intent
         *            The received broadcast Intent
         */
        private void handleGeofenceStatus(Context context, Intent intent) {

        }

        /**
         * Report geofence transitions to the UI
         * 
         * @param context
         *            A Context for this component
         * @param intent
         *            The Intent containing the transition
         */
        private void handleGeofenceTransition(Context context, Intent intent) {
            /*
             * If you want to change the UI when a transition occurs, put the
             * code here. The current design of the app uses a notification to
             * inform the user that a transition has occurred.
             */

        }

        /**
         * Report addition or removal errors to the UI, using a Toast
         * 
         * @param intent
         *            A broadcast Intent sent by ReceiveTransitionsIntentService
         */
        private void handleGeofenceError(Context context, Intent intent) {
            String msg = intent.getStringExtra(GeofenceUtils.EXTRA_GEOFENCE_STATUS);
            Log.e(GeofenceUtils.APPTAG, msg);
            Toast.makeText(context, msg, Toast.LENGTH_LONG).show();
        }
    }

    /**
     * Define a DialogFragment to display the error dialog generated in
     * showErrorDialog.
     */
    public static class ErrorDialogFragment extends DialogFragment {

        // Global field to contain the error dialog
        private Dialog mDialog;

        /**
         * Default constructor. Sets the dialog field to null
         */
        public ErrorDialogFragment() {
            super();
            mDialog = null;
        }

        /**
         * Set the dialog to display
         * 
         * @param dialog
         *            An error dialog
         */
        public void setDialog(Dialog dialog) {
            mDialog = dialog;
        }

        /*
         * This method must return a Dialog to the DialogFragment.
         */
        @Override
        public Dialog onCreateDialog(Bundle savedInstanceState) {
            return mDialog;
        }
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        MenuInflater inflater = getMenuInflater();
        inflater.inflate(R.menu.main_menu, menu);
        return true;

    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
        case R.id.list:
            Intent i = new Intent(getApplicationContext(), EventListActivity.class);
            startActivity(i);
            return true;
        default:
            return super.onOptionsItemSelected(item);
        }

    }
}