ca.uwaterloo.magic.goodhikes.MapsActivity.java Source code

Java tutorial

Introduction

Here is the source code for ca.uwaterloo.magic.goodhikes.MapsActivity.java

Source

/*------------------------------------------------------------------------------
 *   Authors: Slavik, George, Thao, Chelsea
 *   Copyright: (c) 2016 Team Magic
 *
 *   This file is part of GoodHikes.
 *
 *   GoodHikes is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   GoodHikes is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with GoodHikes.  If not, see <http://www.gnu.org/licenses/>.
 */
package ca.uwaterloo.magic.goodhikes;

import android.annotation.SuppressLint;
import android.app.Dialog;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.content.SharedPreferences;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.drawable.BitmapDrawable;
import android.location.Location;
import android.net.Uri;
import android.os.Bundle;
import android.os.IBinder;
import android.preference.PreferenceManager;
import android.provider.MediaStore;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.content.LocalBroadcastManager;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;

import com.google.android.gms.maps.CameraUpdate;
import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.OnMapReadyCallback;
import com.google.android.gms.maps.SupportMapFragment;
import com.google.android.gms.maps.UiSettings;
import com.google.android.gms.maps.model.BitmapDescriptorFactory;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.Marker;
import com.google.android.gms.maps.model.MarkerOptions;
import com.google.android.gms.maps.model.Polyline;
import com.google.android.gms.maps.model.PolylineOptions;

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

import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

import ca.uwaterloo.magic.goodhikes.data.LocationPoint;
import ca.uwaterloo.magic.goodhikes.data.Milestone;
import ca.uwaterloo.magic.goodhikes.data.Route;
import ca.uwaterloo.magic.goodhikes.data.RoutesContract.RouteEntry;
import ca.uwaterloo.magic.goodhikes.data.RoutesDatabaseManager;
import ca.uwaterloo.magic.goodhikes.data.UserManager;

public class MapsActivity extends AppCompatActivity implements OnMapReadyCallback,
        GoogleMap.OnMyLocationButtonClickListener, GoogleMap.OnInfoWindowClickListener {

    protected GoogleMap mMap;
    private RoutesDatabaseManager database;
    private UserManager userManager;
    protected static final String LOG_TAG = "MapsActivity";
    private GPSUpdatesReceiver mGPSUpdatesReceiver;
    private IntentFilter mFilter;
    private ServiceConnection mConnection;
    private GPSLoggingService mLoggingService;
    private ImageButton mGPSTrackingButton, mMilestoneButton, mSettingsButton, mHistoryButton, mStatisticsButton,
            mShareButton;
    private Route selectedRoute;
    private Polyline visualRouteTrace, previousVisualRouteTrace;
    private Marker initRoutePointMarker, lastRoutePointMarker, previousRoutePointMarkerStart,
            previousRoutePointMarkerEnd;
    private boolean followingExistingRoute;
    private ArrayList<Marker> milestonePointMarkers;
    private ImageView previewImage;
    private Map<String, Bitmap> markerImageMap;

    //Identifiers for opening other activities and returning results to MapsActivity
    private final int PICK_ROUTE_REQUEST = 1;
    private final int RESULT_LOAD_IMG = 2;
    private final int RESULT_CAPTURE_IMG = 3;

    private final BroadcastReceiver logoutReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            mLoggingService.stopLocationTracking();

            finish();
        }
    };

    /**
     * onCreate() is called only once for Activity, whereas onStart() - each time
     * appActivity is hidden from screen (user uses other apps), and then App is activated again.
     * <p/>
     * Using startService() overrides the default service lifetime that is managed by
     * bindService(Intent, ServiceConnection, int): it requires the service to remain running
     * until stopService(Intent) is called, regardless of whether any clients are connected to it.
     */

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_maps);
        database = RoutesDatabaseManager.getInstance(this);
        userManager = new UserManager(getApplicationContext());
        milestonePointMarkers = new ArrayList<Marker>();
        markerImageMap = new HashMap<String, Bitmap>();

        //Obtain the SupportMapFragment and get notified when the map is ready to be used.
        SupportMapFragment mapFragment = (SupportMapFragment) getSupportFragmentManager()
                .findFragmentById(R.id.map);
        mapFragment.getMapAsync(this);

        //Create and attach listeners to buttons
        mSettingsButton = (ImageButton) findViewById(R.id.settings_button);
        mShareButton = (ImageButton) findViewById(R.id.share_button);
        mStatisticsButton = (ImageButton) findViewById(R.id.statistics_button);
        mHistoryButton = (ImageButton) findViewById(R.id.history_button);
        mGPSTrackingButton = (ImageButton) findViewById(R.id.gps_tracking_control_button);
        mMilestoneButton = (ImageButton) findViewById(R.id.milestone_button);
        attachUICallbacks();

        if (savedInstanceState != null) {
            long routeId = savedInstanceState.getLong(RouteEntry._ID);
            selectedRoute = database.getRoute(routeId);
            followingExistingRoute = savedInstanceState.getBoolean("followingExistingRoute");
        }
    }

    //updateMapType changes the map type when the setting is changed.

    public void updateMapType() {
        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
        int map_type = Integer
                .parseInt(prefs.getString(getString(R.string.map_pref), getString(R.string.map_type_default)));
        mMap.setMapType(map_type);
    }

    @Override
    protected void onStart() {
        super.onStart();
        mFilter = new IntentFilter(GPSLoggingService.locationUpdateCommand);
        mGPSUpdatesReceiver = new GPSUpdatesReceiver();
        mConnection = new GPSLoggingServiceConnection();
        if (selectedRoute == null)
            selectedRoute = database.getLatestRoute(userManager.getUser());
        //        clearMap();
        LocalBroadcastManager.getInstance(this).registerReceiver(mGPSUpdatesReceiver, mFilter);
        registerReceiver(logoutReceiver, new IntentFilter("logout"));
        startService(new Intent(this, GPSLoggingService.class));
        Log.d(LOG_TAG, "Thread: " + Thread.currentThread().getId() + "; MapActivity started");
    }

    /**
     * Back key destroys the current Activity, home key doesn't. In the Activity lyfecycle,
     * pressing back calls all the way to current activity's onDestroy() method. On the other hand,
     * pressing home pauses the Activity, which stays alive in background.
     * http://stackoverflow.com/questions/6031052/difference-between-android-home-key-and-back-key-and-their-behaviour
     */
    @Override
    protected void onStop() {
        super.onStop();
        unregisterReceiver(logoutReceiver);
        LocalBroadcastManager.getInstance(this).unregisterReceiver(mGPSUpdatesReceiver);
    }

    @Override
    public void onResume() {
        super.onResume();
        Intent intent = new Intent(this, GPSLoggingService.class);
        bindService(intent, mConnection, BIND_AUTO_CREATE);
        Log.d(LOG_TAG, "Thread: " + Thread.currentThread().getId() + "; Binding to service");
        if (mMap != null) {
            updateMapType();
        }
    }

    /*
     * Unbinding GPS service in onPause, so that there are always matching pairs of bind/unbind calls.
     * */
    @Override
    protected void onPause() {
        super.onPause();
        unbindService(mConnection);
        Log.d(LOG_TAG, "Thread: " + Thread.currentThread().getId() + "; Unbinding from GPS service");
    }

    /*
     * When display is rotated, the currently selected route needs to be saved.
     * */
    @Override
    public void onSaveInstanceState(Bundle outState) {
        if (selectedRoute != null)
            outState.putLong(RouteEntry._ID, selectedRoute.getId());
        if (followingExistingRoute != false)
            outState.putBoolean("followingExistingRoute", followingExistingRoute);
        super.onSaveInstanceState(outState);
    }

    /*
    * When GPSLoggingService starts it has it's own lifecycle independent of the Activity that started it.
    * This means that GPSLoggingService will be running even if user switches to another app for a bit,
    * or even simply rotates the screen.
    * GPS tracking logging runs in a separate LoopingThread within GPSLoggingService,
    * and tracking should continue running in the background, even if the app is put in the background.
    * */

    public void stopGPSLoggingService() {
        stopService(new Intent(this, GPSLoggingService.class));
    }

    /**
     * Manipulates the map once available.
     * This callback is triggered when the map is ready to be used.
     * This is where we can add markers or lines, add listeners or move the camera.
     * If Google Play services is not installed on the device, the user will be prompted to install
     * it inside the SupportMapFragment. This method will only be triggered once the user has
     * installed Google Play services and returned to the app.
     */

    @Override
    public void onMapReady(GoogleMap googleMap) {
        mMap = googleMap;
        mMap.setInfoWindowAdapter(new CustomInfoWindow());
        mMap.setOnInfoWindowClickListener(this);
        UiSettings settings = mMap.getUiSettings();
        settings.setCompassEnabled(true);
        settings.setMapToolbarEnabled(true);
        settings.setScrollGesturesEnabled(true);
        updateMapType();
        enableMyLocation();
        if (followingExistingRoute)
            drawPreviousRouteTrace();
        Log.d(LOG_TAG, "Thread: " + Thread.currentThread().getId() + "; onMapReady()");
    }

    @Override
    public void onInfoWindowClick(Marker marker) {
        //Toast.makeText(this, "Info Window Clicked", Toast.LENGTH_SHORT).show();
    }

    public class CustomInfoWindow implements GoogleMap.InfoWindowAdapter {
        @Override
        public View getInfoWindow(Marker marker) {
            return null;
        }

        @Override
        public View getInfoContents(Marker marker) {
            if (!marker.getTitle().equals("Milestone")) {
                return null;
            }

            View milestone = getLayoutInflater().inflate(R.layout.marker_info, null);
            TextView windowtitle = (TextView) milestone.findViewById(R.id.milestone_title);
            TextView windowdesc = (TextView) milestone.findViewById(R.id.milestone_desc);
            ImageView windowimage = (ImageView) milestone.findViewById(R.id.milestone_image);

            if (markerImageMap.get(marker.getId()) == null) {
                LinearLayout window = (LinearLayout) milestone.findViewById(R.id.marker_window);
                window.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT,
                        LinearLayout.LayoutParams.WRAP_CONTENT));
            }

            windowtitle.setText(marker.getTitle());
            windowdesc.setText(marker.getSnippet());
            windowimage.setImageBitmap(markerImageMap.get(marker.getId()));

            return milestone;
        }
    }

    @Override
    public boolean onMyLocationButtonClick() {
        Toast.makeText(this, "MyLocation button clicked", Toast.LENGTH_SHORT).show();
        // Return false so that we don't consume the event and the default behavior still occurs
        // (the camera animates to the user's current position).
        return false;
    }

    private void enableMyLocation() {
        if (mMap != null) {
            mMap.setMyLocationEnabled(true);
        }
    }

    @Override
    protected void onResumeFragments() {
        super.onResumeFragments();
    }

    private void updateLocation(Location location) {
        mLoggingService.updateGPSfrequency();
        LatLng coordinates = new LatLng(location.getLatitude(), location.getLongitude());
        mMap.moveCamera(CameraUpdateFactory.newLatLng(coordinates));
        //        mMap.animateCamera(CameraUpdateFactory.zoomTo(15.0f));
    }

    public class GPSUpdatesReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            Location location;
            Bundle extras = intent.getExtras();
            if (extras != null) {
                location = (Location) intent.getExtras().get(GPSLoggingService.locationUpdateCommand);
                updateLocation(location);
            }
            drawTrace(mLoggingService.currentRoute);
            Log.d(LOG_TAG, "Thread: " + Thread.currentThread().getId() + "; Location update received by UI");
        }

    }

    public class GPSLoggingServiceConnection implements ServiceConnection {
        @Override
        public void onServiceConnected(ComponentName className, IBinder binder) {
            mLoggingService = ((GPSLoggingService.LoggingBinder) binder).getService();
            setTrackingButtonIcon();
            //            mLoggingService.broadcastLastKnownLocation();

            Log.d(LOG_TAG, "Thread: " + Thread.currentThread().getId() + "; GPS Logging service connected");
            if (!mLoggingService.isTrackingActive() && !mLoggingService.isTrackingOnPause())
                drawSelectedRoute();
        }

        public void onServiceDisconnected(ComponentName className) {
        }
    }

    private void attachUICallbacks() {
        mGPSTrackingButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (mLoggingService != null) {
                    if (!mLoggingService.isTrackingActive()) {
                        if (mLoggingService.isTrackingOnPause()) {
                            startTracking();
                        } else {
                            showStartTrackingDialog();
                        }
                    } else {
                        mLoggingService.stopLocationTracking();
                        setTrackingButtonIcon();
                        showStopTrackingDialog();
                    }
                }
            }
        });
        mMilestoneButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (mLoggingService.isTrackingActive()) {
                    showAddMilestoneDialog();
                }
            }
        });
        mSettingsButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                startActivity(new Intent(getApplicationContext(), SettingsActivity.class));
            }
        });
        mShareButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                CaptureMapScreen();
            }
        });
        mStatisticsButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                String json = toJSON();
                if (json == null)
                    Toast.makeText(getApplicationContext(), "No current route!", Toast.LENGTH_SHORT).show();
                else {
                    Intent i = new Intent(getApplicationContext(), StatisticsActivity.class);
                    i.putExtra("current_route", json);
                    startActivity(i);
                }
            }
        });
        mHistoryButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                startActivityForResult(new Intent(getApplicationContext(), HistoryActivity.class),
                        PICK_ROUTE_REQUEST);
            }
        });
    }

    private void startTracking() {
        if (mLoggingService.isTrackingOnPause() == false)
            clearMap();
        mLoggingService.startLocationTracking();
        setTrackingButtonIcon();
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == RESULT_CANCELED)
            return;
        if (requestCode == PICK_ROUTE_REQUEST) {
            if (resultCode == RESULT_OK) {
                long routeId = data.getLongExtra(RouteEntry._ID, -1);
                selectedRoute = database.getRoute(routeId);
                if (!mLoggingService.isTrackingActive())
                    drawSelectedRoute();
            }
        }
    }

    private void drawSelectedRoute() {
        if (selectedRoute == null || selectedRoute.getId() == -1)
            return;
        clearMap();
        drawTrace(selectedRoute);
        moveMapCameraToRoute(selectedRoute);
    }

    private void setTrackingButtonIcon() {
        if (mLoggingService != null) {
            mGPSTrackingButton.setImageResource(mLoggingService.getTrackingButtonIcon());
            mMilestoneButton.setImageResource(mLoggingService.getMilestoneButtonIcon());
        }
    }

    private void initVisualTrace(boolean previousRoute) {
        PolylineOptions traceOptions = new PolylineOptions();
        int color = (!previousRoute) ? Color.BLUE : Color.rgb(255, 255, 153); //light green
        traceOptions.color(color);
        Polyline routeTrace = mMap.addPolyline(traceOptions);
        if (!previousRoute)
            visualRouteTrace = routeTrace;
        else
            previousVisualRouteTrace = routeTrace;
        routeTrace.setVisible(true);
    }

    private void drawTrace(Route route) {
        if (route == null || route.getId() == -1)
            return;
        if (visualRouteTrace == null)
            initVisualTrace(false);
        visualRouteTrace.setPoints(route.getPointsCoordinates());

        if (route.size() > 0 && initRoutePointMarker == null) {
            initRoutePointMarker = mMap
                    .addMarker(new MarkerOptions().position(route.getStartCoordinates()).title("Start"));
        }

        if (route.size() > 1) {
            if (lastRoutePointMarker != null)
                lastRoutePointMarker.remove();
            lastRoutePointMarker = mMap.addMarker(new MarkerOptions().position(route.getLastCoordinates())
                    .icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_AZURE)).title("End"));
        }

        for (Marker marker : milestonePointMarkers) {
            marker.remove();
        }
        milestonePointMarkers.clear();
        markerImageMap.clear();
        for (Milestone milestone : route.getMilestones()) {
            Marker marker = mMap.addMarker(new MarkerOptions().position(milestone.getLatLng())
                    .icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_YELLOW))
                    .title("Milestone").snippet(milestone.getNote()));
            markerImageMap.put(marker.getId(), milestone.getImage());
            milestonePointMarkers.add(marker);
        }
    }

    private void drawPreviousRouteTrace() {
        if (selectedRoute == null || selectedRoute.getId() == -1)
            return;
        followingExistingRoute = true;
        if (previousVisualRouteTrace == null) {
            initVisualTrace(true);
            previousVisualRouteTrace.setPoints(selectedRoute.getPointsCoordinates());
        }

        if (selectedRoute.size() > 0) {
            previousRoutePointMarkerStart = mMap
                    .addMarker(new MarkerOptions().position(selectedRoute.getStartCoordinates())
                            .icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_GREEN))
                            .title("Previous route start"));
        }

        if (selectedRoute.size() > 1) {
            previousRoutePointMarkerEnd = mMap
                    .addMarker(new MarkerOptions().position(selectedRoute.getLastCoordinates())
                            .icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_CYAN))
                            .title("Previous route end"));
        }
    }

    private void clearRouteFollowed() {
        if (previousRoutePointMarkerStart != null) {
            previousRoutePointMarkerStart.remove();
            previousRoutePointMarkerStart = null;
        }
        if (previousRoutePointMarkerEnd != null) {
            previousRoutePointMarkerEnd.remove();
            previousRoutePointMarkerEnd = null;
        }
        if (previousVisualRouteTrace != null) {
            previousVisualRouteTrace.remove();
            previousVisualRouteTrace = null;
        }
    }

    private void clearMap() {
        if (initRoutePointMarker != null) {
            initRoutePointMarker.remove();
            initRoutePointMarker = null;
        }
        if (lastRoutePointMarker != null) {
            lastRoutePointMarker.remove();
            lastRoutePointMarker = null;
        }
        if (visualRouteTrace != null) {
            visualRouteTrace.remove();
            visualRouteTrace = null;
        }
        for (Marker marker : milestonePointMarkers) {
            marker.remove();
        }
        milestonePointMarkers.clear();
    }

    private void moveMapCameraToRoute(Route route) {
        int padding = 100; // offset from edges of the map in pixels
        int animationDuration = 2000;
        int width = getResources().getDisplayMetrics().widthPixels;
        int height = getResources().getDisplayMetrics().heightPixels;
        if (route.getPointsCoordinates().size() > 0) {
            CameraUpdate cu = CameraUpdateFactory.newLatLngBounds(route.getLatLngBounds(), width, height, padding);
            mMap.animateCamera(cu, animationDuration, new GoogleMap.CancelableCallback() {
                @Override
                public void onFinish() {
                }

                @Override
                public void onCancel() {
                }
            });
        }
    }

    private void showSaveRouteDialog() {
        FragmentManager fm = getSupportFragmentManager();
        SaveRouteDialogFragment saveRouteDialog = new SaveRouteDialogFragment();
        saveRouteDialog.show(fm, "saveRouteDialog");
    }

    private void showStopTrackingDialog() {
        FragmentManager fm = getSupportFragmentManager();
        StopTrackingDialogFragment dialog = new StopTrackingDialogFragment();
        dialog.show(fm, "stopTrackingDialog");
    }

    private void showAddMilestoneDialog() {
        FragmentManager fm = getSupportFragmentManager();
        AddMilestoneDialogFragment dialog = new AddMilestoneDialogFragment();
        dialog.show(fm, "addMilestoneDialog");
    }

    private void showStartTrackingDialog() {
        FragmentManager fm = getSupportFragmentManager();
        StartTrackingDialogFragment dialog = new StartTrackingDialogFragment();
        dialog.show(fm, "startTrackingDialog");
    }

    /*
    * http://stackoverflow.com/questions/15571010/fragment-inner-class-should-be-static
    * Non static inner classes do hold a reference to their parent classes.
    * The problem with making a Fragement innerclass non-static is that you always
    * hold a reference to the Activity. The GarbageCollector cannot collect your Activity.
    * So you can 'leak' the Activity if for example the orientation changes.
    * Because the Fragment might still live and gets inserted in a new Activity.
    *
    * Lint parser gives error This fragment inner class should be static [ValidFragment]
    * */
    @SuppressLint("ValidFragment")
    public class SaveRouteDialogFragment extends DialogFragment {
        @Override
        public Dialog onCreateDialog(Bundle savedInstanceState) {
            AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
            LayoutInflater inflater = getActivity().getLayoutInflater();
            builder.setView(inflater.inflate(R.layout.dialog_save_route, null)).setTitle(R.string.saving_route)
                    .setPositiveButton(R.string.proceed, new DialogInterface.OnClickListener() {
                        public void onClick(DialogInterface dialog, int id) {
                            EditText descriptionField = (EditText) ((Dialog) dialog).findViewById(R.id.description);
                            String description = descriptionField.getText().toString();
                            mLoggingService.currentRoute.setDescription(description);

                            CheckBox privateCheckbox = (CheckBox) ((Dialog) dialog)
                                    .findViewById(R.id.checkbox_private);
                            mLoggingService.currentRoute.setPrivate(privateCheckbox.isChecked());
                            mLoggingService.saveRoute();

                            if (followingExistingRoute) {
                                followingExistingRoute = false;
                                clearRouteFollowed();
                            }

                            Toast.makeText(getActivity(), "Route saved", Toast.LENGTH_SHORT).show();
                            Log.d(LOG_TAG, "Thread: " + Thread.currentThread().getId() + "; route saved");
                        }
                    }).setNegativeButton(R.string.discard, new DialogInterface.OnClickListener() {
                        public void onClick(DialogInterface dialog, int id) {
                            clearMap();
                            Toast.makeText(getActivity(), "Route discarded", Toast.LENGTH_SHORT).show();
                            drawSelectedRoute();
                            Log.d(LOG_TAG, "Thread: " + Thread.currentThread().getId() + "; route discarded");
                        }
                    });
            return builder.create();
        }
    }

    @SuppressLint("ValidFragment")
    public class StopTrackingDialogFragment extends DialogFragment {
        @Override
        public Dialog onCreateDialog(Bundle savedInstanceState) {
            AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
            LayoutInflater inflater = getActivity().getLayoutInflater();
            builder.setTitle(R.string.stop_tracking_dialog)
                    .setPositiveButton(R.string.stop_tracking, new DialogInterface.OnClickListener() {
                        public void onClick(DialogInterface dialog, int id) {
                            showSaveRouteDialog();
                        }
                    }).setNegativeButton(R.string.pause_tracking, new DialogInterface.OnClickListener() {
                        public void onClick(DialogInterface dialog, int id) {
                            mLoggingService.setTrackingOnPause(true);
                            Toast.makeText(getActivity(), "Route tracking is paused", Toast.LENGTH_SHORT).show();
                            Log.d(LOG_TAG, "Thread: " + Thread.currentThread().getId() + "; Tracking is paused");
                        }
                    });
            return builder.create();
        }
    }

    @SuppressLint("ValidFragment")
    public class StartTrackingDialogFragment extends DialogFragment {
        @Override
        public Dialog onCreateDialog(Bundle savedInstanceState) {
            AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
            LayoutInflater inflater = getActivity().getLayoutInflater();
            builder.setTitle(R.string.start_tracking)
                    .setPositiveButton(R.string.follow_the_white_rabbit, new DialogInterface.OnClickListener() {
                        public void onClick(DialogInterface dialog, int id) {
                            drawPreviousRouteTrace();
                            startTracking();
                        }
                    }).setNegativeButton(R.string.new_route, new DialogInterface.OnClickListener() {
                        public void onClick(DialogInterface dialog, int id) {
                            startTracking();
                        }
                    });
            return builder.create();
        }
    }

    public void imageSelected(Bitmap image, String note) {
        mLoggingService.currentRoute.addMilestone(note, image);
    }

    /* send route info to statistics in JSON*/
    public String toJSON() {

        JSONObject jsonObj = new JSONObject();
        if (selectedRoute == null || selectedRoute.getId() == -1)
            return null;
        Route curr = selectedRoute;
        Log.d("maps:first_coord", String.valueOf(curr.getStartCoordinates()));
        float max_speed = 0, total_speed = 0;
        float distance = 0;
        ArrayList<LocationPoint> trace = curr.getTrace();
        for (int i = 0; i < curr.size(); i++) {
            Location location = trace.get(i);
            if (i > 0) {
                //Log.d(LOG_TAG, i-1 + " to " + i + ": " + location.distanceTo(trace.get(i-1)));
                distance += location.distanceTo(trace.get(i - 1));
            }
            if (location.getSpeed() > max_speed)
                max_speed = location.getSpeed();
            total_speed += location.getSpeed();
        }
        try {
            jsonObj.put("user_name", curr.getUser().getUsername());
            jsonObj.put("route_name", curr.getDescription());
            jsonObj.put("start_time", curr.getDateStartString() + ", " + curr.getTimeStart());
            jsonObj.put("end_time", curr.getDateEndString() + ", " + curr.getTimeEnd());
            jsonObj.put("duration", curr.getDurationString());
            jsonObj.put("distance", distance / 1000);
            jsonObj.put("aver_speed", total_speed / curr.size());
            jsonObj.put("max_speed", max_speed);
            jsonObj.put("num_waypoints", curr.size());
            jsonObj.put("private", curr.getPrivate() == true ? "True" : "False");

        } catch (JSONException e) {
            e.printStackTrace();
        }
        Log.d(LOG_TAG, "JSON String: " + jsonObj.toString());
        return jsonObj.toString();
    }

    /* take screenshot of the map view and share to social media */
    public void CaptureMapScreen() {
        GoogleMap.SnapshotReadyCallback callback = new GoogleMap.SnapshotReadyCallback() {
            Bitmap bitmap;

            @Override
            public void onSnapshotReady(Bitmap snapshot) {
                // TODO Auto-generated method stub
                bitmap = snapshot;
                String path = MediaStore.Images.Media.insertImage(getContentResolver(), bitmap, "route_screenshot",
                        null);
                Uri uri_image = Uri.parse(path);

                // share intent
                Intent sendIntent = new Intent();
                sendIntent.setAction(Intent.ACTION_SEND);
                sendIntent.putExtra(Intent.EXTRA_STREAM, uri_image);
                sendIntent.putExtra(Intent.EXTRA_TEXT, "My Route");
                sendIntent.putExtra(Intent.EXTRA_SUBJECT, "Hey! Check out my route on Goodhikes");
                sendIntent.setType("image/*");
                startActivity(Intent.createChooser(sendIntent, "Share to..."));

            }
        };

        mMap.snapshot(callback);

    }
}