com.barkside.travellocblog.LocationUpdates.java Source code

Java tutorial

Introduction

Here is the source code for com.barkside.travellocblog.LocationUpdates.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 com.barkside.travellocblog;

import java.io.IOException;
import java.util.List;
import java.util.Locale;
import java.util.regex.PatternSyntaxException;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentSender;
import android.location.Address;
import android.location.Geocoder;
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.os.SystemClock;
import android.support.v4.app.DialogFragment;
import android.support.v7.app.ActionBarActivity;
import android.util.Log;
import android.view.View;
import android.widget.ProgressBar;
import android.widget.Toast;

import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GooglePlayServicesClient;
import com.google.android.gms.common.GooglePlayServicesUtil;
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.model.LatLng;

/**
 * http://developer.android.com/training/location/receive-location-updates.html
 * 
 * The code from MainActivity has been wrapped into a abstract LocationUpdatesActivity
 * class which may be used by multiple activities that need to turn on/off location updates
 * and receive the current position. Like the original class this also provides address
 * information.
 * The LocationUtils class fields have been merged into this one since only this class needs it. 
 * 
 * Since we are using this as a superclass activity for the TravelLocationBlog app, there may
 * be other common functionality that is executed here. Other than error dialogs, there may be
 * no UI elements used by this base activity, it merely exists to return information to
 * subclasses which can display it as needed.
 * This class is declared abstract so there is no need to add to the Android Manifest.
 *
 * Subclasses should implement:
 * {@link #getLayoutResourceId} this layout id will be passed to setContentView in onCreate.
 * {@link #getProgressBar} if used, to indicate progress.
 *
 * Subclasses can call:
 * {@link #enableLocationUpdates} to indicate interest in periodic update requests.
 *  Accepts a duration value after which updates are turned off.
 * {@link #disableLocationUpdates} to indicate that periodic update requests are no longer needed
 * {@link #stopLocationUpdates} cancels previous periodic update requests.
 * {@link #getLastLocation} gets the current location using the library API getLastLocation()
 * function.
 * {@link #getAddress} calls geocoding to get a street address for the current location.
 * {@link #startLocationUpdates} sends a request to Location Services to send periodic
 * location updates to the Activity. [Not usually needed, since enableLocationUpdates is more
 * useful.]
 *
 * The update interval is hard-coded to be 5 seconds, and so on - to try to get accurate readings,
 * but not use much battery.
 */
public abstract class LocationUpdates extends ActionBarActivity implements LocationListener,
        GooglePlayServicesClient.ConnectionCallbacks, GooglePlayServicesClient.OnConnectionFailedListener {

    // Constants
    private static final String TAG = "LocationUpdates";

    /*
     * Define a request code to send to Google Play services
     * This code is returned in Activity.onActivityResult
     */
    public final static int CONNECTION_FAILURE_RESOLUTION_REQUEST = 9000;

    /*
     * Constants for location update parameters
     */
    // Milliseconds per second
    public static final int MILLISECONDS_PER_SECOND = 1000;

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

    // 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;

    // Create an empty string for initializing strings
    public static final String EMPTY_STRING = new String();

    // A request to connect to Location Services
    private LocationRequest mLocationRequest;

    // Stores the current instantiation of the location client in this object
    private LocationClient mLocationClient = null;

    // Handles to UI widgets - usually created by the derived concrete class
    private ProgressBar mActivityIndicator;

    /*
     * Note if updates have been turned on. Starts out as "false".
     * Subclass have to enable/disable this flag.
     *
     */
    private boolean mUpdatesRequested = false;

    public final static int mNoAutoOff = -9999; // should match @location_duration_values array
    private int mUpdatesDurationSecs = mNoAutoOff;
    private long mExpirationTime = 0; // setExpirationTime for LocationRequest

    private final Handler timerHandler = new Handler();
    private final Runnable timerEvent = new Runnable() {
        @Override
        public void run() {
            Log.d(TAG, "timer triggered to stop location updates");
            timerCancelUpdates();
        }
    };

    // If no location services are turned on, and we need location updates, we display a warning.
    private boolean mDisplayedServiceOffMessage = false;

    /*
     * Returns the R.layout.id for the activity content.
     * To be implemented by the concrete class.
     */
    protected abstract int getLayoutResourceId();

    /*
     * Some activities may turn on a indefinite progress bar.
     * If the following function returns a non-null ProgressBar it will be used to
     * indicate progress.
     */
    protected abstract ProgressBar getProgressBar();

    /*
     * Initialize the Activity
     */
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(getLayoutResourceId());

        // Get handles to the UI view objects - for now there are none in this abstract class.
        // Parent class may define some common UI controls, and those can be accessed here.

        mActivityIndicator = getProgressBar();

        // Create a new global location parameters object
        mLocationRequest = LocationRequest.create();

        /*
         * Set the update interval
         */
        mLocationRequest.setInterval(UPDATE_INTERVAL_IN_MILLISECONDS);

        // Use high accuracy
        mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);

        // Set the interval ceiling to one minute
        mLocationRequest.setFastestInterval(FAST_INTERVAL_CEILING_IN_MILLISECONDS);

        // Note that location updates are off until the concrete class turns them on
        mUpdatesRequested = false;

        mExpirationTime = 0;
        if (savedInstanceState != null) {
            mExpirationTime = savedInstanceState.getLong("mExpirationTime");
        }

        /*
         * Create a new location client, using the enclosing class to
         * handle callbacks.
         */
        mLocationClient = new LocationClient(this, this, this);

    }

    /**
     * The concrete subclass needs to turn on/off the location updates.
     * Call this in an onCreate or onStart to enable the receipt of location updates.
     * By the time an onConnected event is called, this must be set correctly to attach
     * the LocationRequest object to the LocationClient.
     * Note that to actually stop updates, call stopLocationUpdates().
     * 
     * @param durationSecs how long to look for location updates
     */
    protected void enableLocationUpdates(int durationSecs) {
        mUpdatesRequested = true;
        mUpdatesDurationSecs = durationSecs;

        // Compute the expiration time as now + durationSecs.
        // We don't use durationSecs if mExpirationTime is already known - which happens
        // when this activity is recreated on a screen orientation change, etc.
        if (mUpdatesDurationSecs != mNoAutoOff && mExpirationTime == 0) {
            long durationMillis = Math.abs(durationSecs) * MILLISECONDS_PER_SECOND;
            mExpirationTime = SystemClock.uptimeMillis() + durationMillis;
        }

        if (durationSecs > 0 && mLocationClient != null) {
            Log.d(TAG, "Starting location timer for seconds: " + durationSecs);
            Log.d(TAG, "Starting location timer to time: " + mExpirationTime);

            // Location Updates are to expire at given time
            // Since this is exact time, this is not affected by screen orientation changes,
            // it will not restart timer. This will be affected if phone goes to sleep, since
            // uptimeMillis does not advance in that case. If mExpirationTime is less than
            // current uptimeMillis, it means no location updates will be received.
            //
            // Since we may be called multiple times in case caller has to turn on/off location
            // updates multiple times, for each enable call we set the time again, and
            // we make sure to remove old callbacks before we add a new one.
            timerHandler.removeCallbacks(timerEvent);
            timerHandler.postAtTime(timerEvent, mExpirationTime);

            // mLocationRequest.setExpirationDuration(durationSecs * MILLISECONDS_PER_SECOND); 
            // Not using setExpirationDuration since we may be called multiple times, and
            // need to keep resetting the duration, which can't be done with setExpirationDuration
            // More importantly, we need to know when the timer has stopped, so we can update
            // the location, or print a message, as is done in the run() method.

            if (mLocationClient.isConnected()) {
                // Most likely we have not received a onConnected event but if we have,
                // we need to start up the updates again, in case they are no longer active.
                // This can be called multiple times - if called again, will just replace
                // the old listener with "this" object as per the API doc.
                mLocationClient.requestLocationUpdates(mLocationRequest, this);
            }
        }
    }

    /**
     * Turn off the request for location updates. We don't actually stop the listener,
     * that will happen in normal onStop or other such events.
     * Probably not used? stopPeriodicUpdates is sufficient for all current use cases.
     */
    protected void disableLocationUpdates() {
        mUpdatesRequested = false;
    }

    /*
     * Called when the Activity is no longer visible at all.
     * Stop updates and disconnect.
     * Based on the examples seen, this class uses onStart and onStop to connect and
     * disconnect to the location client. onResume does not do any location client or request
     * updates objects setup.
     */
    @Override
    public void onStop() {

        stopPeriodicUpdates();

        // After disconnect() is called, the client is considered "dead".
        if (mLocationClient != null) {
            mLocationClient.disconnect();
        }

        // In case any callbacks are still around, remove all.
        timerHandler.removeCallbacksAndMessages(null);

        super.onStop();
    }

    /*
     * Called when the Activity is going into the background.
     * Parts of the UI may be visible, but the Activity is inactive.
     */
    @Override
    public void onPause() {
        super.onPause();
    }

    /*
     * Called when the Activity is restarted, even before it becomes visible.
     */
    @Override
    public void onStart() {

        super.onStart();

        Log.d(TAG, "onStart calling .connect");
        /*
         * Connect the client. Don't re-start any requests here;
         * instead, wait for onResume()
         * Issue report#8 reported a NullPointerException
         * at com.barkside.travellocblog.LocationUpdates.onStart
         * Cannot reproduce it, web search seems to suggest may happen when Google Play
         * is not installed? In any case, we check for it here.
         */
        if (mLocationClient != null) {
            mLocationClient.connect();
        }
    }

    /*
     * Called when the system detects that this Activity is now visible.
     */
    @Override
    public void onResume() {
        super.onResume();
        Log.d(TAG, "onResume");
        // If user has turned off location services, there will never be location
        // event - neither onConnected nor onLocationChanged. Detect that and report to user.
        if (mUpdatesRequested && !isLocationServiceOn()) {
            if (mDisplayedServiceOffMessage) {
                // We display the dialog only once, after that, just a Toast is enough
                Toast.makeText(this, R.string.no_location_services_message, Toast.LENGTH_SHORT).show();
            } else {
                // First time in, display a warning dialog about turning on location services.
                mDisplayedServiceOffMessage = true;
                AlertDialog.Builder builder = new AlertDialog.Builder(this);
                builder.setMessage(this.getString(R.string.no_location_services_message));
                builder.setTitle(this.getString(R.string.no_location_services_title));
                builder.setPositiveButton(R.string.OK, new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int id) {
                        dialog.cancel();
                    }
                });
                builder.setCancelable(true);
                builder.create().show();
            }
        }
    }

    /*
     * Handle results returned to this Activity by other Activities started with
     * startActivityForResult(). In particular, the method onConnectionFailed() in
     * LocationUpdateRemover and LocationUpdateRequester may call startResolutionForResult() to
     * start an Activity that handles Google Play services problems. The result of this
     * call returns here, to onActivityResult.
     */
    @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 CONNECTION_FAILURE_RESOLUTION_REQUEST:

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

                // Log the result
                Log.d(TAG, getString(R.string.resolved));

                // Display the result (if needed)
                // mConnectionState.setText(R.string.connected);
                // mConnectionStatus.setText(R.string.resolved);
                break;

            // If any other result was returned by Google Play services
            default:
                // Log the result
                Log.d(TAG, getString(R.string.no_resolution));

                // Display the result (if needed)
                // mConnectionState.setText(R.string.disconnected);
                // mConnectionStatus.setText(R.string.no_resolution);

                break;
            }

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

            break;
        }
    }

    /**
     * Calls getLastLocation() to get the current location
     */
    public Location getLastLocation() {

        Location location = null;
        // If Google Play Services is available
        if (Utils.playServicesAvailable(this) && mLocationClient != null) {

            // Get the current location
            location = mLocationClient.getLastLocation();
        }
        return location;
    }

    /**
     * Stop periodic updates as a result of timer firing.
     * This can be also defined in the subclass to display appropriate messages if location
     * has still not been found.
     */
    protected void timerCancelUpdates() {
        stopPeriodicUpdates();
    }

    /**
     * We need to survive a device orientation change. Android will completely destroy
     * and recreate this activity.
     * If we don't remember mExpirationTime for example, we may restart this activity and
     * forget that the timer had been already set previously.
     */

    @Override
    public void onSaveInstanceState(Bundle savedInstanceState) {
        super.onSaveInstanceState(savedInstanceState);
        Log.d(TAG, "save instance state");
        // Save UI state changes to the savedInstanceState.
        // This bundle will be passed to onCreate if the process is
        // killed and restarted.
        savedInstanceState.putLong("mExpirationTime", mExpirationTime);
    }

    /**
     * Get the address of the current location, using reverse geocoding. This only works if
     * a geocoding service is available.
     * NOT USED YET
     *
     */
    // For Eclipse with ADT, suppress warnings about Geocoder.isPresent()
    @SuppressLint("NewApi")
    public void getAddress() {

        // In Gingerbread and later, use Geocoder.isPresent() to see if a geocoder is available.
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD && !Geocoder.isPresent()) {
            // No geocoder is present. Issue an error message
            Toast.makeText(this, getString(R.string.no_geocoder_available), Toast.LENGTH_LONG).show();
            return;
        }

        if (Utils.playServicesAvailable(this) && mLocationClient != null) {

            // Get the current location
            Location currentLocation = mLocationClient.getLastLocation();

            // Turn the indefinite activity indicator on
            if (mActivityIndicator != null) {
                mActivityIndicator.setVisibility(View.VISIBLE);
            }

            // Start the background task
            (new LocationUpdates.GetAddressTask(this)).execute(currentLocation);
        }
    }

    /*
     * Called by Location Services when the request to connect the
     * client finishes successfully. At this point, you can
     * request the current location or start periodic updates
     */
    @Override
    public void onConnected(Bundle bundle) {
        Log.d(TAG, "onConnected");

        if (mUpdatesRequested) {
            startPeriodicUpdates();
        }
    }

    /*
     * Called by Location Services if the connection to the
     * location client drops because of an error.
     */
    @Override
    public void onDisconnected() {
        Log.d(TAG, getString(R.string.disconnected));
    }

    /*
     * Called by Location Services if the attempt to
     * Location Services fails.
     */
    @Override
    public void onConnectionFailed(ConnectionResult connectionResult) {

        /*
         * Google Play services can resolve some errors it detects.
         * If the error has a resolution, try sending an Intent to
         * start a Google Play services activity that can resolve
         * error.
         */
        if (connectionResult.hasResolution()) {
            try {

                // Start an Activity that tries to resolve the error
                connectionResult.startResolutionForResult(this, CONNECTION_FAILURE_RESOLUTION_REQUEST);

                /*
                * Thrown if Google Play services canceled the original
                * PendingIntent
                */

            } catch (IntentSender.SendIntentException e) {

                // Log the error
                e.printStackTrace();
            }
        } else {

            // If no resolution is available, display a dialog to the user with the error.
            showErrorDialog(connectionResult.getErrorCode());
        }
    }

    /**
     * Handle location updates.
     * This base class does nothing, since we don't need the location, and the
     * concrete derived class is the one that will use the updates.
     *
     * @param location The updated location.
     */
    @Override
    public void onLocationChanged(Location location) {
        // We received a location fix.
        // If needed: the text of the location is == getLatLng(this, location);

        // If this is the first location fix received, we may need to start timer
        int durationSecs = mUpdatesDurationSecs;
        if (durationSecs < 0 && durationSecs != mNoAutoOff) {
            timerHandler.removeCallbacks(timerEvent);
            timerHandler.postAtTime(timerEvent, mExpirationTime);
            mUpdatesDurationSecs = 0; // reset it since we only need to set timer once
        }
    }

    /**
     * Sends a request to start location updates.
     *
     * This should only be called after a onConnected event, after the location client has been
     * connected.
     */
    protected void startPeriodicUpdates() {
        if (!mUpdatesRequested) {
            // If updates are not enabled, we return. We have to check for this since we may
            // receive multiple disconnected and connected to Google Play Services events,
            // and on each onConnected, we need to restart the updates when necessary.
            // That will happen if some subclass calls us in a onConnected handler.
            return;
        }

        Log.d(TAG, "starting periodic updates");

        // This can be called multiple times - if called again, will just replace
        // the old listener with "this" object.
        if (mLocationClient != null) {
            mLocationClient.requestLocationUpdates(mLocationRequest, this);
        }
    }

    protected boolean updatesRequested() {
        return mUpdatesRequested;
    }

    /**
     * Sends a request to remove location updates and stop updates.
     * 
     * This may be called multiple times in the same activity, and it is safe to do so.
     */
    protected void stopPeriodicUpdates() {
        // Unlike startPeriodicUpdates, we don't look at existing value of mUpdatesRequested
        // but always force it to be off. Once this function is called, location updates
        // will never be received.
        mUpdatesRequested = false;

        Log.d(TAG, "stopping periodic updates");
        // Remove all location updates, ok to call multiple times, just removes
        // whatever listener has been attached by requestLocationUpdates.
        if (mLocationClient != null && mLocationClient.isConnected()) {
            mLocationClient.removeLocationUpdates(this);
        }
    }

    /**
     * An AsyncTask that calls getFromLocation() in the background.
     * The class uses the following generic types:
     * Location - A {@link android.location.Location} object containing the current location,
     *            passed as the input parameter to doInBackground()
     * Void     - indicates that progress units are not used by this subclass
     * String   - An address passed to onPostExecute()
     */
    protected class GetAddressTask extends AsyncTask<Location, Void, String> {

        // Store the context passed to the AsyncTask when the system instantiates it.
        Context localContext;

        // Constructor called by the system to instantiate the task
        public GetAddressTask(Context context) {

            // Required by the semantics of AsyncTask
            super();

            // Set a Context for the background task
            localContext = context;
        }

        /**
         * Get a geocoding service instance, pass latitude and longitude to it, format the returned
         * address, and return the address to the UI thread.
         */
        @Override
        protected String doInBackground(Location... params) {
            /*
             * Get a new geocoding service instance, set for localized addresses. This example uses
             * android.location.Geocoder, but other geocoders that conform to address standards
             * can also be used.
             */
            Geocoder geocoder = new Geocoder(localContext, Locale.getDefault());

            // Get the current location from the input parameter list
            Location location = params[0];

            // Create a list to contain the result address
            List<Address> addresses = null;

            // Try to get an address for the current location. Catch IO or network problems.
            try {

                /*
                 * Call the synchronous getFromLocation() method with the latitude and
                 * longitude of the current location. Return at most 1 address.
                 */
                addresses = geocoder.getFromLocation(location.getLatitude(), location.getLongitude(), 1);

                // Catch network or other I/O problems.
            } catch (IOException exception1) {

                // Log an error and return an error message
                Log.e(TAG, getString(R.string.IO_Exception_getFromLocation));

                // print the stack trace
                exception1.printStackTrace();

                // Return an error message
                return (getString(R.string.IO_Exception_getFromLocation));

                // Catch incorrect latitude or longitude values
            } catch (IllegalArgumentException exception2) {

                // Construct a message containing the invalid arguments
                String errorString = getString(R.string.illegal_argument_exception, location.getLatitude(),
                        location.getLongitude());
                // Log the error and print the stack trace
                Log.e(TAG, errorString);
                exception2.printStackTrace();

                //
                return errorString;
            }
            // If the reverse geocode returned an address
            if (addresses != null && addresses.size() > 0) {

                // Get the first address
                Address address = addresses.get(0);

                // Format the first line of address
                String addressText = getString(R.string.address_output_string,

                        // If there's a street address, add it
                        address.getMaxAddressLineIndex() > 0 ? address.getAddressLine(0) : "",

                        // Locality is usually a city
                        address.getLocality(),

                        // The country of the address
                        address.getCountryName());

                // Return the text
                return addressText;

                // If there aren't any addresses, post a message
            } else {
                return getString(R.string.no_address_found);
            }
        }

        /**
         * A method that's called once doInBackground() completes. Set the text of the
         * UI element that displays the address. This method runs on the UI thread.
         */
        @Override
        protected void onPostExecute(String address) {

            // Turn off the progress bar
            mActivityIndicator.setVisibility(View.GONE);

            // Set the address in the UI
            // when geoCoding is needed, figure out how to send this data to the subclass.
            // mAddress.setText(address);
        }
    }

    /**
     * Show a dialog returned by Google Play services for the
     * connection error code
     *
     * @param errorCode An error code returned from onConnectionFailed
     */
    private void showErrorDialog(int errorCode) {

        // Get the error dialog from Google Play services
        Dialog errorDialog = GooglePlayServicesUtil.getErrorDialog(errorCode, this,
                CONNECTION_FAILURE_RESOLUTION_REQUEST);

        // If Google Play services can provide an error dialog
        if (errorDialog != null) {

            // Create a new DialogFragment in which to show the error dialog
            ErrorDialogFragment errorFragment = new ErrorDialogFragment();

            // Set the dialog in the DialogFragment
            errorFragment.setDialog(errorDialog);

            // Show the error dialog in the DialogFragment
            errorFragment.show(getSupportFragmentManager(), TAG);
        }
    }

    /**
     * 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;
        }
    }

    // Utility functions, maybe better someplace else?
    /**
     * Extract the latitude and longitude from the Location object returned by
     * Location Services.
     *
     * @param currentLocation A Location object containing the current location
     * @return The string latitude and longitude of the current location, or "" if no
     * location is available.
     */
    public static String getLatLng(Context context, Location currentLocation) {
        // If the location is valid
        if (currentLocation != null) {
            // Return the latitude and longitude as strings
            return context.getString(R.string.latitude_longitude_format, currentLocation.getLatitude(),
                    currentLocation.getLongitude());
        } else {
            // Otherwise, return the empty string
            return EMPTY_STRING;
        }
    }

    /**
      * Extract the latitude and longitude from the Location object returned by
      * Location Services.
      * Uses Longitude,Latitude format to match KML file coordinates element.
      * 
      * @param currentLocation
      *           A Location object containing the current location
      * @return The string longitude and latitude of the current location, or "" if no
      *         location is available.
      */
    public static String getLngLat(Context context, Location currentLocation) {
        // If the location is valid
        if (currentLocation != null) {
            // Return the longitude and latitude as strings
            return context.getString(R.string.longitude_latitude_format, currentLocation.getLongitude(),
                    currentLocation.getLatitude());
        } else {
            // Otherwise, return the empty string
            return EMPTY_STRING;
        }
    }

    public static String getLngLat(Context context, LatLng latlng) {
        // If the location is valid
        if (latlng != null) {
            // Return the longitude and latitude as strings
            return context.getString(R.string.longitude_latitude_format, latlng.longitude, latlng.latitude);
        } else {
            // Otherwise, return the empty string
            return EMPTY_STRING;
        }
    }

    public static String getAccuracy(Context context, Location currentLocation) {
        // If the location is valid
        if (currentLocation != null) {
            // Return the latitude and longitude as strings
            return context.getString(R.string.accuracy_format, currentLocation.getAccuracy());
        } else {
            // Otherwise, return the empty string
            return EMPTY_STRING;
        }
    }

    // Parse the stored blog.location string into Location object
    public static Location stringToLocation(String locstr) {
        if (locstr == null) {
            return null;
        }
        Location locobj = null;
        try {
            String temp[] = locstr.split(",");
            if (temp.length >= 2) {
                float lon = Float.parseFloat(temp[0]);
                float lat = Float.parseFloat(temp[1]);
                locobj = new Location("INTERNAL");
                locobj.setLatitude(lat);
                locobj.setLongitude(lon);
            }
        } catch (PatternSyntaxException e) {
            Log.e(TAG, "Internal program error incorrect split argument");
        }
        return locobj;
    }

    // Parse the stored blog.location string into LatLng object.
    // null or "" locstr returns null, as does an invalid locstr.
    public static LatLng stringToLatLng(String locstr) {
        if (locstr == null) {
            return null;
        }
        LatLng latlng = null;
        try {
            String temp[] = locstr.split(",");
            if (temp.length >= 2) {
                float lon = Float.parseFloat(temp[0]);
                float lat = Float.parseFloat(temp[1]);
                latlng = new LatLng(lat, lon);
            }
        } catch (PatternSyntaxException e) {
            Log.e(TAG, "Internal program error incorrect split argument");
        }
        return latlng;
    }

    /** Determines whether one Location reading is better than the current Location fix
     *
     * From http://developer.android.com/guide/topics/location/strategies.html
     * 
     * http://developer.android.com/guide/topics/location/strategies.html
     * @param location  The new Location that you want to evaluate
     * @param currentBestLocation  The current Location fix, to which you want to compare the new one
     */
    public static boolean isBetterLocation(Location location, Location currentBestLocation) {
        if (currentBestLocation == null) {
            // A new location is always better than no location
            return true;
        }

        // Check whether the new location fix is newer or older
        long timeDelta = location.getTime() - currentBestLocation.getTime();
        boolean isSignificantlyNewer = timeDelta > TWO_MINUTES;
        boolean isSignificantlyOlder = timeDelta < -TWO_MINUTES;
        boolean isNewer = timeDelta > 0;

        // If it's been more than two minutes since the current location, use the new location
        // because the user has likely moved
        if (isSignificantlyNewer) {
            return true;
            // If the new location is more than two minutes older, it must be worse
        } else if (isSignificantlyOlder) {
            return false;
        }

        // Check whether the new location fix is more or less accurate
        int accuracyDelta = (int) (location.getAccuracy() - currentBestLocation.getAccuracy());
        boolean isLessAccurate = accuracyDelta > 0;
        boolean isMoreAccurate = accuracyDelta < 0;
        boolean isSignificantlyLessAccurate = accuracyDelta > 200;

        // Check if the old and new location are from the same provider
        boolean isFromSameProvider = isSameProvider(location.getProvider(), currentBestLocation.getProvider());

        // Determine location quality using a combination of timeliness and accuracy
        if (isMoreAccurate) {
            return true;
        } else if (isNewer && !isLessAccurate) {
            return true;
        } else if (isNewer && !isSignificantlyLessAccurate && isFromSameProvider) {
            return true;
        }
        return false;
    }

    private static final int TWO_MINUTES = 1000 * 60 * 2;

    /** Checks whether two providers are the same.
     * Not really necessary for the single Fused provider using new Google Maps API v2.
     */
    private static boolean isSameProvider(String provider1, String provider2) {
        if (provider1 == null) {
            return provider2 == null;
        }
        return provider1.equals(provider2);
    }

    /**
     * Check if user has turned on location services. If not, then we'll never get
     * onConnected or onLocationChanged events.
     * Google Play services does not catch this condition, so have to manually check it.
    http://stackoverflow.com/questions/16862987/android-check-location-services-enabled-with-play-services-location-api
     */
    public boolean isLocationServiceOn() {
        LocationManager locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);

        // Was checking for non-empty list locationManager.getProviders(true) but
        // that also contains the PASSIVE provider, which may not return
        // any location in some cases.
        // For best performance, need both GPS and NETWORK, but for this call,
        // will accept either one being on.
        return (locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)
                || locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER));
    }
}