com.google.android.apps.mytracks.fragments.MapFragment.java Source code

Java tutorial

Introduction

Here is the source code for com.google.android.apps.mytracks.fragments.MapFragment.java

Source

/*
 * Copyright 2008 Google Inc.
 *
 * 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.google.android.apps.mytracks.fragments;

import com.google.android.apps.mytracks.MapOverlay;
import com.google.android.apps.mytracks.MyTracksApplication;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils.Factory;
import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.content.TrackDataHub;
import com.google.android.apps.mytracks.content.TrackDataListener;
import com.google.android.apps.mytracks.content.TrackDataListener.ProviderState;
import com.google.android.apps.mytracks.content.TrackDataType;
import com.google.android.apps.mytracks.content.Waypoint;
import com.google.android.apps.mytracks.stats.TripStatistics;
import com.google.android.apps.mytracks.util.ApiAdapterFactory;
import com.google.android.apps.mytracks.util.GeoRect;
import com.google.android.apps.mytracks.util.LocationUtils;
import com.nogago.android.tracks.R;
import com.nogago.android.tracks.TrackDetailActivity;

import android.content.Intent;
import android.location.Location;
import android.os.Bundle;
import android.provider.Settings;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageButton;
import android.widget.TextView;
import android.widget.Toast;

import java.util.EnumSet;
import java.util.List;

import org.osmdroid.util.GeoPoint;
import org.osmdroid.views.MapController;
import org.osmdroid.views.MapView;
import org.osmdroid.views.overlay.Overlay;

/**
 * A fragment to display map to the user.
 *
 * @author Leif Hendrik Wilden
 * @author Rodrigo Damazio
 */
public class MapFragment extends Fragment implements View.OnTouchListener, View.OnClickListener, TrackDataListener {

    public static final String MAP_FRAGMENT_TAG = "mapFragment";

    private static final String KEY_CURRENT_LOCATION = "currentLocation";
    private static final String KEY_KEEP_MY_LOCATION_VISIBLE = "keepMyLocationVisible";

    private TrackDataHub trackDataHub;

    // True to keep my location visible.
    private boolean keepMyLocationVisible;

    // True to zoom to my location. Only apply when keepMyLocationVisible is true.
    private boolean zoomToMyLocation;

    // The track id of the marker to show.
    private long markerTrackId;

    // The marker id to show
    private long markerId;

    // The current selected track id. Set in onSelectedTrackChanged.
    private long currentSelectedTrackId;

    // The current location. Set in onCurrentLocationChanged.
    private Location currentLocation;

    // UI elements
    private View mapViewContainer;
    private MapView mapView;
    private MapOverlay mapOverlay;
    private ImageButton myLocationImageButton;
    private TextView messageTextView;

    @Override
    public void onCreate(Bundle bundle) {
        super.onCreate(bundle);
        setHasOptionsMenu(true);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        mapViewContainer = ((TrackDetailActivity) getActivity()).getMapViewContainer();
        mapView = (MapView) mapViewContainer.findViewById(R.id.map_view);
        mapOverlay = new MapOverlay(getActivity());

        List<Overlay> overlays = mapView.getOverlays();
        overlays.clear();
        overlays.add(mapOverlay);

        mapView.requestFocus();
        mapView.setOnTouchListener(this);
        mapView.setBuiltInZoomControls(true);
        myLocationImageButton = (ImageButton) mapViewContainer.findViewById(R.id.map_my_location);
        myLocationImageButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                showMyLocation();
            }
        });
        messageTextView = (TextView) mapViewContainer.findViewById(R.id.map_message);

        ApiAdapterFactory.getApiAdapter().invalidMenu(getActivity());
        return mapViewContainer;
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        if (savedInstanceState != null) {
            keepMyLocationVisible = savedInstanceState.getBoolean(KEY_KEEP_MY_LOCATION_VISIBLE, false);
            currentLocation = (Location) savedInstanceState.getParcelable(KEY_CURRENT_LOCATION);
            if (currentLocation != null) {
                updateCurrentLocation();
            }
        }
    }

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

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putBoolean(KEY_KEEP_MY_LOCATION_VISIBLE, keepMyLocationVisible);
        if (currentLocation != null) {
            outState.putParcelable(KEY_CURRENT_LOCATION, currentLocation);
        }
    }

    @Override
    public void onPause() {
        super.onPause();
        pauseTrackDataHub();
    }

    @Override
    public void onDestroyView() {
        super.onDestroyView();
        ViewGroup parentViewGroup = (ViewGroup) mapViewContainer.getParent();
        if (parentViewGroup != null) {
            parentViewGroup.removeView(mapViewContainer);
        }
    }

    @Override
    public void onCreateOptionsMenu(Menu menu, MenuInflater menuInflator) {
        menuInflator.inflate(R.menu.map, menu);
    }

    @Override
    public void onPrepareOptionsMenu(Menu menu) {
        int titleId = R.string.menu_satellite_mode;
        if (mapView != null) {
            titleId = mapView.isSatellite() ? R.string.menu_map_mode : R.string.menu_satellite_mode;
        }
        menu.findItem(R.id.map_satellite_mode).setTitle(titleId);
        super.onPrepareOptionsMenu(menu);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem menuItem) {
        if (mapView != null && menuItem.getItemId() == R.id.map_satellite_mode) {
            mapView.setSatellite(!mapView.isSatellite());
            return true;
        }
        return super.onOptionsItemSelected(menuItem);
    }

    /**
     * Shows my location.
     */
    private void showMyLocation() {
        updateTrackDataHub();
        keepMyLocationVisible = true;
        zoomToMyLocation = true;
        if (currentLocation != null) {
            updateCurrentLocation();
        }
    }

    /**
     * Shows the marker.
     * 
     * @param id the marker id
     */
    private void showMarker(long id) {
        MyTracksProviderUtils MyTracksProviderUtils = Factory.get(getActivity());
        Waypoint waypoint = MyTracksProviderUtils.getWaypoint(id);
        if (waypoint != null && waypoint.getLocation() != null) {
            keepMyLocationVisible = false;
            GeoPoint center = new GeoPoint((int) (waypoint.getLocation().getLatitude() * 1E6),
                    (int) (waypoint.getLocation().getLongitude() * 1E6));
            mapView.getController().setCenter(center);
            mapView.getController().setZoom(mapView.getMaxZoomLevel());
            mapView.invalidate();
        }
    }

    /**
     * Shows the marker.
     *
     * @param trackId the track id
     * @param id the marker id
     */
    public void showMarker(long trackId, long id) {
        /*
         * Synchronize to prevent race condition in changing markerTrackId and
         * markerId variables.
         */
        synchronized (this) {
            if (trackId == currentSelectedTrackId) {
                showMarker(id);
                markerTrackId = -1L;
                markerId = -1L;
                return;
            }
            markerTrackId = trackId;
            markerId = id;
        }
    }

    @Override
    public boolean onTouch(View view, MotionEvent event) {
        if (keepMyLocationVisible && event.getAction() == MotionEvent.ACTION_MOVE) {
            if (!isVisible(currentLocation)) {
                /*
                 * Only set to false when no longer visible. Thus can keep showing the
                 * current location with the next location update.
                 */
                keepMyLocationVisible = false;
            }
        }
        return false;
    }

    @Override
    public void onClick(View v) {
        if (v == messageTextView) {
            Intent intent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS);
            startActivity(intent);
        }
    }

    @Override
    public void onProviderStateChange(ProviderState state) {
        final int messageId;
        final boolean isGpsDisabled;
        switch (state) {
        case DISABLED:
            messageId = R.string.gps_need_to_enable;
            isGpsDisabled = true;
            break;
        case NO_FIX:
        case BAD_FIX:
            messageId = R.string.gps_wait_for_signal;
            isGpsDisabled = false;
            break;
        case GOOD_FIX:
            messageId = -1;
            isGpsDisabled = false;
            break;
        default:
            throw new IllegalArgumentException("Unexpected state: " + state);
        }

        getActivity().runOnUiThread(new Runnable() {
            @Override
            public void run() {
                if (messageId != -1) {
                    messageTextView.setText(messageId);
                    messageTextView.setVisibility(View.VISIBLE);

                    if (isGpsDisabled) {
                        Toast.makeText(getActivity(), R.string.gps_not_found, Toast.LENGTH_LONG).show();

                        // Click to show the location source settings
                        messageTextView.setOnClickListener(MapFragment.this);
                    } else {
                        messageTextView.setOnClickListener(null);
                    }
                } else {
                    messageTextView.setVisibility(View.GONE);
                }
            }
        });
    }

    @Override
    public void onCurrentLocationChanged(Location location) {
        currentLocation = location;
        updateCurrentLocation();
    }

    @Override
    public void onCurrentHeadingChanged(double heading) {
        if (mapOverlay.setHeading((float) heading)) {
            mapView.postInvalidate();
        }
    }

    @Override
    public void onSelectedTrackChanged(final Track track, final boolean isRecording) {
        getActivity().runOnUiThread(new Runnable() {
            @Override
            public void run() {
                boolean hasTrack = track != null;
                mapOverlay.setTrackDrawingEnabled(hasTrack);

                if (hasTrack) {
                    synchronized (this) {
                        /*
                         * Synchronize to prevent race condition in changing markerTrackId
                         * and markerId variables.
                         */
                        currentSelectedTrackId = track.getId();
                        updateMap(track);
                    }
                    mapOverlay.setShowEndMarker(!isRecording);
                }
                mapView.invalidate();
            }
        });
    }

    @Override
    public void onTrackUpdated(Track track) {
        // We don't care.
    }

    @Override
    public void clearTrackPoints() {
        mapOverlay.clearPoints();
    }

    @Override
    public void onNewTrackPoint(Location location) {
        if (LocationUtils.isValidLocation(location)) {
            mapOverlay.addLocation(location);
        }
    }

    @Override
    public void onSampledOutTrackPoint(Location loc) {
        // We don't care.
    }

    @Override
    public void onSegmentSplit() {
        mapOverlay.addSegmentSplit();
    }

    @Override
    public void onNewTrackPointsDone() {
        mapView.postInvalidate();
    }

    @Override
    public void clearWaypoints() {
        mapOverlay.clearWaypoints();
    }

    @Override
    public void onNewWaypoint(Waypoint waypoint) {
        if (waypoint != null && LocationUtils.isValidLocation(waypoint.getLocation())) {
            // TODO: Optimize locking inside addWaypoint
            mapOverlay.addWaypoint(waypoint);
        }
    }

    @Override
    public void onNewWaypointsDone() {
        mapView.postInvalidate();
    }

    @Override
    public boolean onUnitsChanged(boolean metric) {
        // We don't care.
        return false;
    }

    @Override
    public boolean onReportSpeedChanged(boolean reportSpeed) {
        // We don't care.
        return false;
    }

    /**
     * Resumes the trackDataHub. Needs to be synchronized because trackDataHub can be
     * accessed by multiple threads.
     */
    private synchronized void resumeTrackDataHub() {
        trackDataHub = ((MyTracksApplication) getActivity().getApplication()).getTrackDataHub();
        trackDataHub.registerTrackDataListener(this,
                EnumSet.of(TrackDataType.SELECTED_TRACK, TrackDataType.WAYPOINTS_TABLE,
                        TrackDataType.TRACK_POINTS_TABLE, TrackDataType.LOCATION, TrackDataType.COMPASS));
    }

    /**
     * Pauses the trackDataHub. Needs to be synchronized because trackDataHub can be
     * accessed by multiple threads. 
     */
    private synchronized void pauseTrackDataHub() {
        trackDataHub.unregisterTrackDataListener(this);
        trackDataHub = null;
    }

    /**
     * Updates the trackDataHub. Needs to be synchronized because trackDataHub can be
     * accessed by multiple threads. 
     */
    private synchronized void updateTrackDataHub() {
        if (trackDataHub != null) {
            trackDataHub.forceUpdateLocation();
        }
    }

    /**
     * Updates the map by either zooming to the requested marker or showing the track.
     *
     * @param track the track
     */
    private void updateMap(Track track) {
        if (track.getId() == markerTrackId) {
            // Show the marker
            showMarker(markerId);

            markerTrackId = -1L;
            markerId = -1L;
        } else {
            // Show the track
            showTrack(track);
        }
    }

    /**
     * Returns true if the location is visible.
     *
     * @param location the location
     */
    private boolean isVisible(Location location) {
        if (location == null || mapView == null) {
            return false;
        }
        GeoPoint mapCenter = mapView.getMapCenter();
        int latitudeSpan = mapView.getLatitudeSpan();
        int longitudeSpan = mapView.getLongitudeSpan();

        /*
         * The bottom of the mapView is obscured by the zoom controls, subtract its
         * height from the visible area.
         */
        GeoPoint zoomControlBottom = mapView.getProjection().fromPixels(0, mapView.getHeight());
        GeoPoint zoomControlTop = mapView.getProjection().fromPixels(0,
                mapView.getHeight() - mapView.getZoomButtonsController().getZoomControls().getHeight());
        int zoomControlMargin = Math.abs(zoomControlTop.getLatitudeE6() - zoomControlBottom.getLatitudeE6());
        GeoRect geoRect = new GeoRect(mapCenter, latitudeSpan, longitudeSpan);
        geoRect.top += zoomControlMargin;

        GeoPoint geoPoint = LocationUtils.getGeoPoint(location);
        return geoRect.contains(geoPoint);
    }

    /**
     * Updates the current location and centers it if necessary.
     */
    private void updateCurrentLocation() {
        if (mapOverlay == null || mapView == null) {
            return;
        }

        mapOverlay.setMyLocation(currentLocation);
        mapView.postInvalidate();

        if (currentLocation != null && keepMyLocationVisible && !isVisible(currentLocation)) {
            GeoPoint geoPoint = LocationUtils.getGeoPoint(currentLocation);
            MapController mapController = mapView.getController();
            mapController.animateTo(geoPoint);
            if (zoomToMyLocation) {
                // Only zoom in the first time we show the location.
                zoomToMyLocation = false;
                if (mapView.getZoomLevel() < mapView.getMaxZoomLevel()) {
                    mapController.setZoom(mapView.getMaxZoomLevel());
                }
            }
        }
    }

    /**
     * Shows the track.
     *
     * @param track the track
     */
    private void showTrack(Track track) {
        if (mapView == null || track == null || track.getNumberOfPoints() < 2) {
            return;
        }

        TripStatistics tripStatistics = track.getTripStatistics();
        int bottom = tripStatistics.getBottom();
        int left = tripStatistics.getLeft();
        int latitudeSpanE6 = tripStatistics.getTop() - bottom;
        int longitudeSpanE6 = tripStatistics.getRight() - left;
        if (latitudeSpanE6 > 0 && latitudeSpanE6 < 180E6 && longitudeSpanE6 > 0 && longitudeSpanE6 < 360E6) {
            keepMyLocationVisible = false;
            GeoPoint center = new GeoPoint(bottom + latitudeSpanE6 / 2, left + longitudeSpanE6 / 2);
            if (LocationUtils.isValidGeoPoint(center)) {
                mapView.getController().setCenter(center);
                mapView.getController().zoomToSpan(latitudeSpanE6, longitudeSpanE6);
            }
        }
    }
}