de.grobox.liberario.activities.MapActivity.java Source code

Java tutorial

Introduction

Here is the source code for de.grobox.liberario.activities.MapActivity.java

Source

/*    Transportr
 *    Copyright (C) 2013 - 2016 Torsten Grote
 *
 *    This program 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.
 *
 *    This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package de.grobox.liberario.activities;

import android.Manifest;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.ColorStateList;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.PorterDuff;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.location.GpsStatus;
import android.location.LocationManager;
import android.os.Bundle;
import android.os.SystemClock;
import android.support.annotation.NonNull;
import android.support.design.widget.FloatingActionButton;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.ActionBar;
import android.support.v7.widget.Toolbar;
import android.util.Log;
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.view.WindowManager;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;

import org.osmdroid.api.IMapController;
import org.osmdroid.events.MapEventsReceiver;
import org.osmdroid.util.BoundingBox;
import org.osmdroid.util.GeoPoint;
import org.osmdroid.views.MapView;
import org.osmdroid.views.overlay.MapEventsOverlay;
import org.osmdroid.views.overlay.Marker;
import org.osmdroid.views.overlay.Polyline;
import org.osmdroid.views.overlay.infowindow.InfoWindow;
import org.osmdroid.views.overlay.mylocation.GpsMyLocationProvider;
import org.osmdroid.views.overlay.mylocation.MyLocationNewOverlay;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import de.grobox.liberario.favorites.locations.FavoriteLocation;
import de.grobox.liberario.settings.Preferences;
import de.grobox.liberario.R;
import de.grobox.liberario.networks.TransportNetwork;
import de.grobox.liberario.data.LocationDb;
import de.grobox.liberario.utils.TransportrUtils;
import de.schildbach.pte.NetworkProvider;
import de.schildbach.pte.dto.Line;
import de.schildbach.pte.dto.Location;
import de.schildbach.pte.dto.Point;
import de.schildbach.pte.dto.Product;
import de.schildbach.pte.dto.Stop;
import de.schildbach.pte.dto.Trip;
import de.schildbach.pte.dto.Trip.Leg;
import de.schildbach.pte.dto.Trip.Public;

public class MapActivity extends TransportrActivity implements MapEventsReceiver {
    private MapView map;
    private Menu menu;
    private FloatingActionButton fab;
    private List<GeoPoint> points = new ArrayList<>();
    private GpsMyLocationProvider locationProvider;
    private GpsController gpsController;
    private FabController fabController;
    private TransportNetwork network;

    public final static String SHOW_AREA = "de.grobox.liberario.MapActivity.SHOW_AREA";
    public final static String SHOW_LOCATIONS = "de.grobox.liberario.MapActivity.SHOW_LOCATIONS";
    public final static String SHOW_TRIP = "de.grobox.liberario.MapActivity.SHOW_TRIP";

    public final static String LOCATION = "de.schildbach.pte.dto.Location";
    public final static String LOCATIONS = "List<de.schildbach.pte.dto.Location>";
    public final static String TRIP = "de.schildbach.pte.dto.Trip";

    private enum MarkerType {
        BEGIN, CHANGE, STOP, END, WALK
    }

    private enum LocationProvider {
        NONE, GPS, NETWORK, BOTH
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_map);

        getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);

        network = Preferences.getTransportNetwork(this);

        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        if (toolbar != null) {
            toolbar.setSubtitle(network.getName(this));
            setSupportActionBar(toolbar);

            ActionBar actionBar = getSupportActionBar();
            if (actionBar != null)
                actionBar.setDisplayHomeAsUpEnabled(true);
        }

        map = (MapView) findViewById(R.id.map);
        if (map != null) {
            map.setMultiTouchControls(true);
            map.setTilesScaledToDpi(true);
        }

        fab = (FloatingActionButton) findViewById(R.id.gpsFab);
        fabController = new FabController();

        gpsController = new GpsController();
        gpsController.onRestoreInstanceState(savedInstanceState);

        if (ContextCompat.checkSelfPermission(this,
                Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
            // Should we show an explanation?
            if (ActivityCompat.shouldShowRequestPermissionRationale(this,
                    Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
                Toast.makeText(this, R.string.permission_denied_map, Toast.LENGTH_LONG).show();
                supportFinishAfterTransition();
            } else {
                Toast.makeText(this, R.string.permission_explanation_map, Toast.LENGTH_LONG).show();
                ActivityCompat.requestPermissions(this, new String[] { Manifest.permission.WRITE_EXTERNAL_STORAGE },
                        MainActivity.PR_WRITE_EXTERNAL_STORAGE);
            }
            return;
        }

        setupMap();
    }

    private void setupMap() {
        MapEventsOverlay mapEventsOverlay = new MapEventsOverlay(this);
        map.getOverlays().add(0, mapEventsOverlay);

        Intent intent = getIntent();
        if (intent != null) {
            String action = intent.getAction();
            if (action != null) {
                //noinspection IfCanBeSwitch
                if (action.equals(SHOW_AREA)) {
                    showArea();
                } else if (action.equals(SHOW_LOCATIONS)) {
                    @SuppressWarnings("unchecked")
                    List<Location> locations = (ArrayList<Location>) intent.getSerializableExtra(LOCATIONS);
                    Location myLoc = (Location) intent.getSerializableExtra(LOCATION);
                    showLocations(locations, myLoc);
                } else if (action.equals(SHOW_TRIP)) {
                    Trip trip = (Trip) intent.getSerializableExtra(TRIP);
                    showTrip(trip);
                }
            }
        }
        map.getOverlays().add(gpsController.getOverlay());
        setViewSpan();
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        if (gpsController != null)
            gpsController.onSaveInstanceState(outState);
    }

    @Override
    public void onResume() {
        super.onResume();
        if (gpsController != null)
            gpsController.onResume();
    }

    @Override
    public void onPause() {
        super.onPause();
        if (gpsController != null)
            gpsController.onPause();
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[],
            @NonNull int[] grantResults) {
        switch (requestCode) {
        case MainActivity.PR_WRITE_EXTERNAL_STORAGE: {
            // If request is cancelled, the result arrays are empty.
            if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                setupMap();
                // for some reason, this needs to be called again here
                setViewSpan();
            } else {
                Toast.makeText(this, R.string.permission_denied_map, Toast.LENGTH_LONG).show();
                supportFinishAfterTransition();
            }
            break;
        }
        case MainActivity.PR_ACCESS_FINE_LOCATION_MAPS: {
            // If request is cancelled, the result arrays are empty.
            if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                gpsController.toggle();
            } else {
                Toast.makeText(this, R.string.permission_denied_gps, Toast.LENGTH_LONG).show();
            }
            break;
        }
        }
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu items for use in the action bar
        MenuInflater inflater = getMenuInflater();
        inflater.inflate(R.menu.map_actions, menu);
        this.menu = menu;

        MenuItem gpsItem = this.menu.findItem(R.id.action_use_gps);

        if (gpsController != null) {
            if (gpsController.isActive()) {
                gpsItem.setIcon(TransportrUtils.getToolbarDrawable(this, R.drawable.ic_gps_off));
            } else {
                gpsItem.setIcon(TransportrUtils.getToolbarDrawable(this, R.drawable.ic_gps));
            }
        } else {
            TransportrUtils.fixToolbarIcon(this, gpsItem);
        }

        return super.onCreateOptionsMenu(menu);
    }

    @Override
    public boolean onOptionsItemSelected(final MenuItem item) {
        // Handle presses on the action bar items
        switch (item.getItemId()) {
        case android.R.id.home:
            onBackPressed();

            return true;
        case R.id.action_use_gps:
            gpsController.toggle();

            return true;
        case R.id.action_show_all:
            setViewSpan();

            return true;
        default:
            return super.onOptionsItemSelected(item);
        }
    }

    @Override
    public boolean singleTapConfirmedHelper(GeoPoint p) {
        InfoWindow.closeAllInfoWindowsOn(map);
        return true;
    }

    @Override
    public boolean longPressHelper(GeoPoint p) {
        String loc_str = TransportrUtils.getCoordinationName(p.getLatitude(), p.getLongitude());
        Marker marker = new Marker(map);
        marker.setIcon(new ColorDrawable(Color.TRANSPARENT));
        marker.setPosition(p);
        marker.setTitle(getString(R.string.location) + ": " + loc_str);
        marker.setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_CENTER);
        marker.setInfoWindowAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_CENTER);
        marker.setRelatedObject(Location.coord(p.getLatitudeE6(), p.getLongitudeE6()));
        marker.setInfoWindow(new LocationInfoWindow(map));
        map.getOverlays().add(marker);

        // center map smoothly where clicked
        map.getController().animateTo(p);

        // show info window
        marker.showInfoWindow();

        return true;
    }

    private void showArea() {
        getAreaAndZoom();
    }

    private void getAreaAndZoom() {
        runOnThread(new Runnable() {
            @Override
            public void run() {
                // first try to get area from favourites
                List<FavoriteLocation> favs = LocationDb.getFavLocationList(MapActivity.this);
                ArrayList<GeoPoint> geoPoints = new ArrayList<>(favs.size());
                for (FavoriteLocation fav : favs) {
                    Location loc = fav.getLocation();
                    if (loc.hasLocation()) {
                        geoPoints.add(new GeoPoint(loc.getLatAsDouble(), loc.getLonAsDouble()));
                    }
                }

                // if two few favourites, get area from network provider
                if (geoPoints.size() < 2) {
                    Point[] area = null;
                    try {
                        area = network.getNetworkProvider().getArea();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                    if (area != null) {
                        geoPoints = new ArrayList<>(area.length);
                        for (Point point : area) {
                            geoPoints.add(new GeoPoint(point.getLatAsDouble(), point.getLonAsDouble()));
                        }
                    }
                }

                // display area if there's any
                if (geoPoints.size() > 1) {
                    BoundingBox box = BoundingBox.fromGeoPoints(geoPoints);
                    zoomToArea(box);
                }
            }
        });
    }

    private void zoomToArea(final BoundingBox box) {
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                IMapController mapController = map.getController();
                mapController.setCenter(box.getCenter());
                mapController.setZoom(16);
                map.zoomToBoundingBox(box, true);
            }
        });
    }

    private void showLocations(List<Location> locations, Location myLocation) {
        // mark locations on map
        for (Location loc : locations) {
            if (loc.hasLocation()) {
                markLocation(loc, ContextCompat.getDrawable(this, R.drawable.ic_marker_station));
            }
        }

        // include my location in view span calculation if available
        if (myLocation != null) {
            points.add(new GeoPoint(myLocation.getLatAsDouble(), myLocation.getLonAsDouble()));
        }

        gpsController.setLocation(myLocation);
    }

    private void showTrip(Trip trip) {
        int width = getResources().getDisplayMetrics().densityDpi / 32;
        // draw leg path first, so it is always at the bottom
        for (Leg leg : trip.legs) {
            // add path if it is missing
            if (leg.path == null)
                calculatePath(leg);
            if (leg.path == null)
                continue;

            // draw leg path first, so it is always at the bottom
            Polyline polyline = new Polyline();
            List<GeoPoint> geoPoints = new ArrayList<>(leg.path.size());
            for (Point point : leg.path) {
                geoPoints.add(new GeoPoint(point.getLatAsDouble(), point.getLonAsDouble()));
            }
            polyline.setPoints(geoPoints);
            polyline.setWidth(width);
            if (leg instanceof Public) {
                Line line = ((Public) leg).line;
                polyline.setColor(getBackgroundColor(MarkerType.CHANGE, line));
                if (line != null)
                    polyline.setTitle(line.id);
            } else {
                polyline.setColor(getBackgroundColor(MarkerType.WALK, null));
                polyline.setTitle(getString(R.string.walk));
            }
            map.getOverlays().add(polyline);
        }

        // Now draw intermediate stops on top of path
        for (Leg leg : trip.legs) {
            if (leg instanceof Public) {
                Public public_leg = (Public) leg;

                if (public_leg.intermediateStops != null) {
                    Drawable stop_drawable = getMarkerDrawable(MarkerType.STOP, public_leg.line);
                    for (Stop stop : public_leg.intermediateStops) {
                        if (stop.location != null) {
                            markLocation(stop.location, stop_drawable);
                        }
                    }
                }
            }
        }

        // At last, draw the beginning, the end and the changing stations
        int i = 1;
        for (Leg leg : trip.legs) {
            // Draw public transportation stations
            if (leg instanceof Public) {
                Public public_leg = (Public) leg;

                // Draw first station or change station
                if (i == 1 || (i == 2 && trip.legs.get(0) instanceof Trip.Individual)) {
                    markLocation(leg.departure, getMarkerDrawable(MarkerType.BEGIN, public_leg.line));
                } else {
                    markLocation(leg.departure, getMarkerDrawable(MarkerType.CHANGE, public_leg.line));
                }

                // Draw final station only at the end or if end is walking
                if (i == trip.legs.size()
                        || (i == trip.legs.size() - 1 && trip.legs.get(i) instanceof Trip.Individual)) {
                    markLocation(leg.arrival, getMarkerDrawable(MarkerType.END, public_leg.line));
                }
            }
            // Walking
            else if (leg instanceof Trip.Individual) {
                if (i != trip.legs.size() || trip.legs.size() == 1) {
                    markLocation(leg.departure, getMarkerDrawable(MarkerType.WALK, null));
                }
                // draw walking icon for arrival only at the end of the trip
                if (i == trip.legs.size()) {
                    markLocation(leg.arrival, getMarkerDrawable(MarkerType.WALK, null));
                }
            }

            i += 1;
        }
        // turn off hardware rendering to work around this issue:
        // https://github.com/MKergall/osmbonuspack/issues/168
        map.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
    }

    private void calculatePath(Leg leg) {
        if (leg.path == null)
            leg.path = new ArrayList<>();

        if (leg.departure != null && leg.departure.hasLocation()) {
            leg.path.add(new Point(leg.departure.lat, leg.departure.lon));
        }

        if (leg instanceof Public && ((Public) leg).intermediateStops != null) {
            //noinspection ConstantConditions
            for (Stop stop : ((Public) leg).intermediateStops) {
                if (stop.location != null && stop.location.hasLocation()) {
                    leg.path.add(new Point(stop.location.lat, stop.location.lon));
                }
            }
        }

        if (leg.arrival != null && leg.arrival.hasLocation()) {
            leg.path.add(new Point(leg.arrival.lat, leg.arrival.lon));
        }
    }

    private Drawable getMarkerDrawable(MarkerType type, Line line) {
        // Get colors
        int bg = getBackgroundColor(type, line);
        int fg = getForegroundColor(type, line);

        // Get Drawable
        int res;
        if (type == MarkerType.BEGIN) {
            res = R.drawable.ic_marker_trip_begin;
        } else if (type == MarkerType.CHANGE) {
            res = R.drawable.ic_marker_trip_change;
        } else if (type == MarkerType.END) {
            res = R.drawable.ic_marker_trip_end;
        } else if (type == MarkerType.WALK) {
            res = R.drawable.ic_marker_trip_walk;
        } else {
            res = R.drawable.ic_marker_intermediate_stop;
        }
        LayerDrawable drawable = (LayerDrawable) ContextCompat.getDrawable(this, res);
        if (drawable != null) {
            drawable.getDrawable(0).mutate().setColorFilter(bg, PorterDuff.Mode.MULTIPLY);
            drawable.getDrawable(1).mutate().setColorFilter(fg, PorterDuff.Mode.SRC_IN);
        }

        return drawable;
    }

    private int getBackgroundColor(MarkerType type, Line line) {
        int bg;
        if (type != MarkerType.WALK) {
            if (line != null && line.style != null && line.style.backgroundColor != 0) {
                bg = line.style.backgroundColor;
            } else {
                bg = ContextCompat.getColor(this, R.color.accent);
            }
        } else {
            bg = ContextCompat.getColor(this, R.color.walking);
        }
        return bg;
    }

    private int getForegroundColor(MarkerType type, Line line) {
        int fg;
        if (type != MarkerType.WALK) {
            if (line != null && line.style != null && line.style.foregroundColor != 0) {
                fg = line.style.foregroundColor;
            } else {
                fg = ContextCompat.getColor(this, android.R.color.white);
            }
        } else {
            fg = ContextCompat.getColor(this, android.R.color.black);
        }
        return fg;
    }

    private void setViewSpan() {
        if (points.size() == 0)
            return;

        double maxLat = -180;
        double minLat = 180;
        double maxLon = -180;
        double minLon = 180;

        for (GeoPoint point : points) {
            maxLat = Math.max(point.getLatitude(), maxLat);
            minLat = Math.min(point.getLatitude(), minLat);
            maxLon = Math.max(point.getLongitude(), maxLon);
            minLon = Math.min(point.getLongitude(), minLon);
        }

        if (gpsController.getLocation() != null) {
            maxLat = Math.max(gpsController.getLocation().getLatitude(), maxLat);
            minLat = Math.min(gpsController.getLocation().getLatitude(), minLat);
            maxLon = Math.max(gpsController.getLocation().getLongitude(), maxLon);
            minLon = Math.min(gpsController.getLocation().getLongitude(), minLon);
        }

        double center_lat = (maxLat + minLat) / 2;
        double center_lon = (maxLon + minLon) / 2;
        final GeoPoint center = new GeoPoint(center_lat, center_lon);

        IMapController mapController = map.getController();
        mapController.setCenter(center);
        mapController.setZoom(18);
        mapController.zoomToSpan(maxLat - minLat, maxLon - minLon);
    }

    private void markLocation(Location loc, Drawable drawable) {
        GeoPoint pos = new GeoPoint(loc.getLatAsDouble(), loc.getLonAsDouble());

        Log.i(getClass().getSimpleName(), "Mark location: " + loc.toString());

        points.add(pos);
        Marker marker = new Marker(map);
        marker.setIcon(drawable);
        marker.setPosition(pos);
        marker.setTitle(TransportrUtils.getLocationName(loc));
        marker.setInfoWindow(new LocationInfoWindow(map));
        marker.setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_CENTER);
        marker.setInfoWindowAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_CENTER);
        marker.setRelatedObject(loc);
        map.getOverlays().add(marker);
    }

    private class LocationInfoWindow extends InfoWindow {

        LocationInfoWindow(MapView mapView) {
            super(R.layout.location_info_window, mapView);

            // close it when clicking on the bubble
            mView.setOnTouchListener(new View.OnTouchListener() {
                @Override
                public boolean onTouch(View v, MotionEvent e) {
                    if (e.getAction() == MotionEvent.ACTION_UP) {
                        close();
                    }
                    return true;
                }
            });
        }

        @Override
        public void onOpen(Object item) {
            Marker marker = (Marker) item;

            // close all other windows before opening this one
            closeAllInfoWindowsOn(mMapView);

            ((TextView) mView.findViewById(R.id.locationTitle)).setText(marker.getTitle());

            final Location loc = (Location) marker.getRelatedObject();

            ViewGroup productsView = (ViewGroup) mView.findViewById(R.id.productsView);
            productsView.removeAllViews();

            // Add product icons if available
            if (loc.products != null && loc.products.size() > 0) {
                for (Product product : loc.products) {
                    ImageView image = new ImageView(productsView.getContext());
                    image.setImageDrawable(TransportrUtils.getTintedDrawable(productsView.getContext(),
                            TransportrUtils.getDrawableForProduct(product)));
                    productsView.addView(image);
                }
            }

            TextView fromHere = ((TextView) mView.findViewById(R.id.fromHere));
            TextView toHere = ((TextView) mView.findViewById(R.id.toHere));
            if (network.getNetworkProvider().hasCapabilities(NetworkProvider.Capability.TRIPS)) {
                // From Here
                fromHere.setCompoundDrawables(
                        TransportrUtils.getTintedDrawable(MapActivity.this, fromHere.getCompoundDrawables()[0]),
                        null, null, null);
                fromHere.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        TransportrUtils.presetDirections(MapActivity.this, loc, null, null);
                    }
                });

                // To Here
                toHere.setCompoundDrawables(
                        TransportrUtils.getTintedDrawable(MapActivity.this, toHere.getCompoundDrawables()[0]), null,
                        null, null);
                toHere.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        TransportrUtils.presetDirections(MapActivity.this, null, null, loc);
                    }
                });
            } else {
                fromHere.setVisibility(View.GONE);
                toHere.setVisibility(View.GONE);
            }

            // Departures
            TextView departures = ((TextView) mView.findViewById(R.id.departures));
            if (loc.hasId()
                    && network.getNetworkProvider().hasCapabilities(NetworkProvider.Capability.DEPARTURES)) {
                departures.setCompoundDrawables(
                        TransportrUtils.getTintedDrawable(MapActivity.this, departures.getCompoundDrawables()[0]),
                        null, null, null);
                departures.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        TransportrUtils.findDepartures(MapActivity.this, loc);
                    }
                });
            } else {
                departures.setVisibility(View.GONE);
            }

            // Nearby Stations
            TextView nearbyStations = ((TextView) mView.findViewById(R.id.nearbyStations));
            if (loc.hasLocation()
                    && network.getNetworkProvider().hasCapabilities(NetworkProvider.Capability.NEARBY_LOCATIONS)) {
                nearbyStations.setCompoundDrawables(TransportrUtils.getTintedDrawable(MapActivity.this,
                        nearbyStations.getCompoundDrawables()[0]), null, null, null);
                nearbyStations.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        TransportrUtils.findNearbyStations(MapActivity.this, loc);
                    }
                });
            } else {
                nearbyStations.setVisibility(View.GONE);
            }
        }

        @Override
        public void onClose() {
        }
    }

    private Bitmap getBitmap(Drawable drawable) {
        Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(),
                Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(bitmap);
        drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
        drawable.draw(canvas);
        return bitmap;
    }

    private class GpsController implements GpsStatus.Listener {
        private final LocationManager locationManager;
        private final MyLocationNewOverlay myLocationOverlay;
        private boolean gpsWasOn = false, hasFix = false;
        private long lastLocationTime = 0;
        private final static int GPS_FIX_LOST_TIME = 2500;
        private final static String GPS_WAS_ON = "de.grobox.liberario.MapActivity.GPS_WAS_ON";

        private GpsController() {
            locationProvider = new GpsMyLocationProvider(MapActivity.this) {
                @Override
                public void onLocationChanged(final android.location.Location location) {
                    super.onLocationChanged(location);
                    if (location.getProvider().equals(LocationManager.GPS_PROVIDER)) {
                        lastLocationTime = SystemClock.elapsedRealtime();
                    }
                    fabController.onLocationChanged();
                }

                @Override
                public void onProviderDisabled(final String provider) {
                    LocationProvider providerState = getActiveLocationProviders();
                    if (providerState == LocationProvider.NONE && isActive()) {
                        turnOff();
                    }
                }
            };

            locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);

            // create my location overlay that shows the current position and updates automatically
            myLocationOverlay = new MyLocationNewOverlay(locationProvider, map) {
                @Override
                public void setDirectionArrow(final Bitmap personBitmap, final Bitmap directionArrowBitmap) {
                    super.setDirectionArrow(personBitmap, directionArrowBitmap);
                    mCirclePaint.setColor(ContextCompat.getColor(MapActivity.this, R.color.holo_red_light));
                }

                @Override
                public boolean onTouchEvent(final MotionEvent event, final MapView mapView) {
                    if (event.getAction() == MotionEvent.ACTION_MOVE) {
                        fabController.onMapMove();
                    }
                    return super.onTouchEvent(event, mapView);
                }
            };
            myLocationOverlay.setDrawAccuracyEnabled(true);

            // set new icons
            Bitmap person = getBitmap(ContextCompat.getDrawable(MapActivity.this, R.drawable.map_position));
            Bitmap directionArrow = getBitmap(
                    ContextCompat.getDrawable(MapActivity.this, R.drawable.map_position_bearing));
            myLocationOverlay.setDirectionArrow(person, directionArrow);

            // properly position person icon
            float scale = getResources().getDisplayMetrics().density;
            myLocationOverlay.setPersonHotspot(12.0f * scale + 0.5f, 12.0f * scale + 0.5f);
        }

        @Override
        public void onGpsStatusChanged(int event) {
            switch (event) {
            case GpsStatus.GPS_EVENT_SATELLITE_STATUS:
                boolean hasFixNow = false;
                if (getLocation() != null) {
                    hasFixNow = (SystemClock.elapsedRealtime() - lastLocationTime) < GPS_FIX_LOST_TIME;
                }
                if (!hasFixNow && hasFix) {
                    onFixLost();
                }
                if (hasFixNow && !hasFix) {
                    onFixReacquired();
                }
                hasFix = hasFixNow;
                break;
            case GpsStatus.GPS_EVENT_FIRST_FIX:
                hasFix = true;
                break;
            }
        }

        private void onResume() {
            if (gpsWasOn) {
                gpsWasOn = false;
                toggle();
            }
        }

        private void onPause() {
            if (myLocationOverlay != null && isActive()) {
                gpsWasOn = true;
                toggle();
            }
        }

        private void onSaveInstanceState(Bundle state) {
            // remember whether GPS was on or not, so it can be re-activated
            state.putBoolean(GPS_WAS_ON, gpsWasOn);
        }

        private void onRestoreInstanceState(Bundle state) {
            if (state != null) {
                gpsWasOn = state.getBoolean(GPS_WAS_ON);
            }
        }

        private void onFixLost() {
            myLocationOverlay.setPersonIcon(
                    getBitmap(ContextCompat.getDrawable(MapActivity.this, R.drawable.map_position_no_fix)));
            map.postInvalidate();
            fabController.onFixLost();
        }

        private void onFixReacquired() {
            myLocationOverlay
                    .setPersonIcon(getBitmap(ContextCompat.getDrawable(MapActivity.this, R.drawable.map_position)));
            map.postInvalidate();
        }

        private MyLocationNewOverlay getOverlay() {
            return myLocationOverlay;
        }

        private boolean isActive() {
            return myLocationOverlay.isMyLocationEnabled();
        }

        private boolean hasFix() {
            return hasFix;
        }

        private GeoPoint getLocation() {
            return myLocationOverlay.getMyLocation();
        }

        private void setLocation(Location l) {
            if (l != null) {
                // create temporary location object with last known position
                android.location.Location tmp_loc = new android.location.Location("");
                tmp_loc.setLatitude(l.getLatAsDouble());
                tmp_loc.setLongitude(l.getLonAsDouble());

                // set last known position
                locationProvider.onLocationChanged(tmp_loc);
            }
        }

        private void toggle() {
            if (ContextCompat.checkSelfPermission(MapActivity.this,
                    Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
                // Should we show an explanation?
                if (ActivityCompat.shouldShowRequestPermissionRationale(MapActivity.this,
                        Manifest.permission.ACCESS_FINE_LOCATION)) {
                    Toast.makeText(MapActivity.this, R.string.permission_explanation_gps, Toast.LENGTH_LONG).show();
                } else {
                    ActivityCompat.requestPermissions(MapActivity.this,
                            new String[] { Manifest.permission.ACCESS_FINE_LOCATION },
                            MainActivity.PR_ACCESS_FINE_LOCATION_MAPS);
                }
                return;
            }

            // check we have location providers available
            LocationProvider provider = getActiveLocationProviders();
            if (provider == LocationProvider.NONE) {
                Toast.makeText(MapActivity.this, R.string.error_no_location_provider, Toast.LENGTH_SHORT).show();
                return;
            } else if (provider == LocationProvider.NETWORK) {
                Toast.makeText(MapActivity.this, R.string.position_no_gps, Toast.LENGTH_SHORT).show();
            }

            if (isActive()) {
                turnOff();
            } else {
                turnOn();
            }
        }

        private void turnOn() {
            //noinspection MissingPermission
            locationManager.addGpsStatusListener(this);
            myLocationOverlay.enableMyLocation();
            fabController.show();
            if (menu != null) {
                MenuItem gpsItem = menu.findItem(R.id.action_use_gps);
                gpsItem.setIcon(TransportrUtils.getToolbarDrawable(MapActivity.this, R.drawable.ic_gps_off));
            }
        }

        private void turnOff() {
            locationManager.removeGpsStatusListener(this);
            myLocationOverlay.disableMyLocation();
            fabController.hide();
            if (menu != null) {
                MenuItem gpsItem = menu.findItem(R.id.action_use_gps);
                gpsItem.setIcon(TransportrUtils.getToolbarDrawable(MapActivity.this, R.drawable.ic_gps));
            }
        }

        private LocationProvider getActiveLocationProviders() {
            List<String> providers = locationManager.getProviders(true);
            boolean providerGps = providers.contains(LocationManager.GPS_PROVIDER);
            boolean providerNetwork = providers.contains(LocationManager.NETWORK_PROVIDER);
            if (providerGps && providerNetwork)
                return LocationProvider.BOTH;
            else if (providerGps)
                return LocationProvider.GPS;
            else if (providerNetwork)
                return LocationProvider.NETWORK;
            return LocationProvider.NONE;
        }
    }

    private class FabController implements View.OnClickListener {
        private final ColorStateList fabBg = ColorStateList
                .valueOf(ContextCompat.getColor(MapActivity.this, R.color.fabBackground));
        private final ColorStateList fabBgMoved = ColorStateList
                .valueOf(ContextCompat.getColor(MapActivity.this, R.color.fabBackgroundMoved));
        private final int fabFg = ContextCompat.getColor(MapActivity.this, R.color.fabForegroundInitial);
        private final int fabFgMoved = ContextCompat.getColor(MapActivity.this, R.color.fabForegroundMoved);
        private final int fabFgFollow = ContextCompat.getColor(MapActivity.this, R.color.fabForegroundFollow);

        private FabController() {
            fab.hide();
            fab.setOnClickListener(this);
            init();
        }

        private void init() {
            fab.setBackgroundTintList(fabBg);
            fab.getDrawable().setColorFilter(fabFg, PorterDuff.Mode.SRC_IN);
        }

        @Override
        public void onClick(View v) {
            if (gpsController.getLocation() != null) {
                map.getController().animateTo(gpsController.getLocation());
                gpsController.getOverlay().enableFollowLocation();
                if (gpsController.hasFix) {
                    fab.setBackgroundTintList(fabBg);
                    fab.getDrawable().setColorFilter(fabFgFollow, PorterDuff.Mode.SRC_ATOP);
                }
            } else {
                Toast.makeText(MapActivity.this, R.string.position_not_yet_known, Toast.LENGTH_SHORT).show();
            }
        }

        private void show() {
            fab.show();
        }

        private void hide() {
            fab.hide();
        }

        private void onMapMove() {
            if (gpsController.hasFix()) {
                fab.setBackgroundTintList(fabBgMoved);
                fab.getDrawable().setColorFilter(fabFgMoved, PorterDuff.Mode.SRC_ATOP);
            } else {
                init();
            }
        }

        private void onFixLost() {
            init();
        }

        private void onLocationChanged() {
            if (gpsController.hasFix() && gpsController.getOverlay().isFollowLocationEnabled()) {
                fab.setBackgroundTintList(fabBg);
                fab.getDrawable().setColorFilter(fabFgFollow, PorterDuff.Mode.SRC_ATOP);
            } else {
                onMapMove();
            }
        }
    }

}