cc.softwarefactory.lokki.android.fragments.MapViewFragment.java Source code

Java tutorial

Introduction

Here is the source code for cc.softwarefactory.lokki.android.fragments.MapViewFragment.java

Source

/*
Copyright (c) 2014-2015 F-Secure
See LICENSE for details
*/
package cc.softwarefactory.lokki.android.fragments;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.DashPathEffect;
import android.graphics.Paint;
import android.graphics.Point;
import android.graphics.drawable.Drawable;
import android.location.Location;
import android.location.LocationManager;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.content.LocalBroadcastManager;
import android.support.v7.app.AlertDialog;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.Button;
import android.widget.ImageView;

import com.androidquery.AQuery;
import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.SupportMapFragment;
import com.google.android.gms.maps.model.BitmapDescriptorFactory;
import com.google.android.gms.maps.model.Circle;
import com.google.android.gms.maps.model.CircleOptions;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.Marker;
import com.google.android.gms.maps.model.MarkerOptions;

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

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;

import cc.softwarefactory.lokki.android.MainApplication;
import cc.softwarefactory.lokki.android.R;
import cc.softwarefactory.lokki.android.activities.FirstTimeActivity;
import cc.softwarefactory.lokki.android.utilities.DialogUtils;
import cc.softwarefactory.lokki.android.utilities.Utils;
import cc.softwarefactory.lokki.android.utilities.map.MapUserTypes;
import cc.softwarefactory.lokki.android.utilities.map.MapUtils;

public class MapViewFragment extends Fragment {

    private static final String TAG = "MapViewFragment";
    private SupportMapFragment fragment;
    private GoogleMap map;
    private HashMap<String, Marker> markerMap;
    private AQuery aq;
    private static Boolean cancelAsyncTasks = false;
    private Context context;
    private Boolean firstTimeZoom = true;
    private ArrayList<Circle> placesOverlay;
    private double radiusMultiplier = 0.9; // Dont want to fill the screen from edge to edge...

    public MapViewFragment() {
        markerMap = new HashMap<>();
        placesOverlay = new ArrayList<>();
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {

        View rootView = inflater.inflate(R.layout.activity_map, container, false);
        aq = new AQuery(getActivity(), rootView);
        context = getActivity().getApplicationContext();
        return rootView;

    }

    @Override
    public void onDestroyView() {
        // Trying to clean up properties (not to hold anything coming from the map (and avoid mem leaks).
        super.onDestroyView();
        fragment = null;
        map = null;
        aq = null;
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) { // This method guarantees that the fragment is loaded in the parent activity!

        Log.e(TAG, "onActivityCreated");
        super.onActivityCreated(savedInstanceState);

        FragmentManager fm = getChildFragmentManager();
        fragment = (SupportMapFragment) fm.findFragmentById(R.id.map);
        if (fragment == null) {
            fragment = SupportMapFragment.newInstance();
            //fragment = SupportMapFragment.newInstance(new GoogleMapOptions().useViewLifecycleInFragment(true)); // The map is destroyed when fragment is destroyed. Releasing memory
            fm.beginTransaction().replace(R.id.map, fragment).commit();
        }

        //setHasOptionsMenu(true);
    }

    @Override
    public void onResume() { // onResume is called after onActivityCreated, when the fragment is loaded 100%

        Log.e(TAG, "onResume");
        super.onResume();
        if (map == null) {
            Log.e(TAG, "Map null. creating it.");
            setUpMap();
            setupAddPlacesOverlay();
        } else {
            Log.e(TAG, "Map already exists. Nothing to do.");
        }

        LocalBroadcastManager.getInstance(context).registerReceiver(mMessageReceiver,
                new IntentFilter("LOCATION-UPDATE"));
        LocalBroadcastManager.getInstance(context).registerReceiver(placesUpdateReceiver,
                new IntentFilter("PLACES-UPDATE"));

        checkLocationServiceStatus();

        new UpdateMap().execute(MapUserTypes.All);
        cancelAsyncTasks = false;
        if (MainApplication.places != null) {
            updatePlaces();
        }
    }

    private void checkLocationServiceStatus() {
        LocationManager lm = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
        boolean gps = lm.isProviderEnabled(LocationManager.GPS_PROVIDER);
        boolean network = lm.isProviderEnabled(LocationManager.NETWORK_PROVIDER);

        if (!gps && !network && !MainApplication.locationDisabledPromptShown) {
            promptLocationService();
            MainApplication.locationDisabledPromptShown = true;
        }
    }

    private void promptLocationService() {
        new AlertDialog.Builder(getActivity()).setTitle(R.string.location_services_disabled)
                .setMessage(R.string.gps_disabled).setCancelable(true)
                .setPositiveButton(R.string.settings, new DialogInterface.OnClickListener() {
                    public void onClick(final DialogInterface dialog, final int id) {
                        startActivity(new Intent(android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS));
                    }
                }).setNegativeButton(R.string.ignore, null).show();
    }

    private void setUpMap() {

        map = fragment.getMap();

        if (map == null) {
            return;
        }

        removeMarkers();

        map.setMapType(MainApplication.mapTypes[MainApplication.mapType]);
        map.setInfoWindowAdapter(new MyInfoWindowAdapter()); // Set the windowInfo view for each marker
        map.setMyLocationEnabled(true);
        map.setIndoorEnabled(true);
        map.setBuildingsEnabled(true);
        map.getUiSettings().setZoomControlsEnabled(false);

        map.setOnMarkerClickListener(new GoogleMap.OnMarkerClickListener() {
            @Override
            public boolean onMarkerClick(Marker marker) {
                if (!marker.isInfoWindowShown()) {
                    marker.showInfoWindow();
                    MainApplication.emailBeingTracked = marker.getTitle();

                }
                return true;
            }
        });

        map.setOnMapClickListener(new GoogleMap.OnMapClickListener() {
            @Override
            public void onMapClick(LatLng latLng) {
                MainApplication.emailBeingTracked = null;
            }
        });

        // Set long click to add a place
        map.setOnMapLongClickListener(new GoogleMap.OnMapLongClickListener() {
            @Override
            public void onMapLongClick(LatLng latLng) {

                setAddPlacesVisible(true);
            }
        });

        map.setOnMyLocationButtonClickListener(new GoogleMap.OnMyLocationButtonClickListener() {
            @Override
            public boolean onMyLocationButtonClick() {
                MainApplication.locationDisabledPromptShown = false;
                checkLocationServiceStatus();
                return false;
            }
        });
    }

    private void setupAddPlacesOverlay() {
        // todo these should probably be initialized once...
        Button cancelButton = (Button) getView().findViewById(R.id.cancel_add_place_button);
        cancelButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                setAddPlacesVisible(false);
            }
        });

        Button addPlaceButton = (Button) getView().findViewById(R.id.add_place_button);
        addPlaceButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                int mapWidth = fragment.getView().getWidth();
                int mapHeight = fragment.getView().getHeight()
                        - getView().findViewById(R.id.add_place_buttons).getHeight();

                Location middleSideLocation;
                if (mapWidth > mapHeight) {
                    middleSideLocation = MapUtils.convertToLocation(
                            map.getProjection().fromScreenLocation(new Point(mapWidth / 2, 0)), "middleSide");
                } else {
                    middleSideLocation = MapUtils.convertToLocation(
                            map.getProjection().fromScreenLocation(new Point(0, mapHeight / 2)), "middleSide");
                }

                LatLng centerLatLng = map.getProjection().fromScreenLocation(getAddPlaceCircleCenter());
                int radius = (int) middleSideLocation
                        .distanceTo(MapUtils.convertToLocation(centerLatLng, "center"));
                DialogUtils.addPlace(getActivity(), centerLatLng, (int) (radius * radiusMultiplier));
            }
        });
    }

    public void setAddPlacesVisible(boolean visible) {
        if (visible) {
            ((ImageView) getView().findViewById(R.id.addPlaceCircle))
                    .setImageDrawable(new AddPlaceCircleDrawable());
            showAddPlaceButtons();
        } else {
            ((ImageView) getView().findViewById(R.id.addPlaceCircle)).setImageDrawable(null);
            hideAddPlaceButtons();
        }
    }

    private void showAddPlaceButtons() {
        Animation slideUp = AnimationUtils.loadAnimation(this.getActivity().getApplicationContext(),
                R.anim.add_place_buttons_show);
        getView().findViewById(R.id.add_place_buttons).startAnimation(slideUp);
        getView().findViewById(R.id.add_place_overlay).setVisibility(View.VISIBLE);
    }

    private void hideAddPlaceButtons() {
        Animation slideDown = AnimationUtils.loadAnimation(this.getActivity().getApplicationContext(),
                R.anim.add_place_buttons_hide);
        slideDown.setAnimationListener(new Animation.AnimationListener() {
            @Override
            public void onAnimationStart(Animation animation) {

            }

            @Override
            public void onAnimationEnd(Animation animation) {
                getView().findViewById(R.id.add_place_overlay).setVisibility(View.INVISIBLE);
            }

            @Override
            public void onAnimationRepeat(Animation animation) {

            }
        });
        getView().findViewById(R.id.add_place_buttons).startAnimation(slideDown);
    }

    private Point getAddPlaceCircleCenter() {
        int mapCenterX = fragment.getView().getWidth() / 2;
        int mapCenterY = (fragment.getView().getHeight()
                - getView().findViewById(R.id.add_place_buttons).getHeight()) / 2;

        return new Point(mapCenterX, mapCenterY);
    }

    private void removeMarkers() {

        Log.e(TAG, "removeMarkers");
        for (Marker m : markerMap.values()) {
            m.remove();
        }
        markerMap.clear();
    }

    @Override
    public void onPause() {
        super.onPause();
        LocalBroadcastManager.getInstance(context).unregisterReceiver(mMessageReceiver);
        LocalBroadcastManager.getInstance(context).unregisterReceiver(placesUpdateReceiver);
    }

    private BroadcastReceiver mMessageReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            Log.e(TAG, "BroadcastReceiver onReceive");
            Bundle extras = intent.getExtras();
            if (extras == null || !extras.containsKey("current-location")) {
                new UpdateMap().execute(MapUserTypes.All);
            }
        }
    };

    private BroadcastReceiver placesUpdateReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            Log.e(TAG, "placesUpdateReceiver onReceive");
            updatePlaces();
        }
    };

    private void updatePlaces() {

        Log.e(TAG, "updatePlaces");
        if (map == null) {
            return;
        }

        removePlaces();

        try {
            Iterator<String> keys = MainApplication.places.keys();
            while (keys.hasNext()) {
                String key = keys.next();
                JSONObject placeObj = MainApplication.places.getJSONObject(key);
                Circle circle = map.addCircle(
                        new CircleOptions().center(new LatLng(placeObj.getDouble("lat"), placeObj.getDouble("lon")))
                                .radius(placeObj.getInt("rad")).strokeWidth(0)
                                .fillColor(getResources().getColor(R.color.place_circle)));
                placesOverlay.add(circle);
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    private void removePlaces() {

        Log.e(TAG, "removePlaces");
        for (Circle circle : placesOverlay) {
            circle.remove();
        }
        placesOverlay.clear();
    }

    class UpdateMap extends AsyncTask<MapUserTypes, Void, HashMap<String, Location>> {

        @Override
        protected HashMap<String, Location> doInBackground(MapUserTypes... params) {

            if (MainApplication.dashboard == null) {
                return null;
            }

            MapUserTypes who = params[0];
            Log.e(TAG, "UpdateMap update for all users: " + who);

            try {
                JSONObject iCanSee = MainApplication.dashboard.getJSONObject("icansee");
                JSONObject idMapping = MainApplication.dashboard.getJSONObject("idmapping");
                HashMap<String, Location> markerData = new HashMap<>();

                if (who == MapUserTypes.User || who == MapUserTypes.All) {
                    markerData.put(MainApplication.userAccount,
                            convertToLocation(MainApplication.dashboard.getJSONObject("location"))); // User himself
                }

                if (who == MapUserTypes.Others || who == MapUserTypes.All) {
                    Iterator keys = iCanSee.keys();
                    while (keys.hasNext()) {
                        String key = (String) keys.next();
                        JSONObject data = iCanSee.getJSONObject(key);
                        JSONObject location = data.getJSONObject("location");
                        String email = (String) idMapping.get(key);
                        Log.e(TAG, "I can see: " + email + " => " + data);

                        if (MainApplication.iDontWantToSee != null && MainApplication.iDontWantToSee.has(email)) {
                            Log.e(TAG, "I dont want to see: " + email);
                        } else {
                            Location loc = convertToLocation(location);
                            if (loc == null) {
                                Log.e(TAG, "No location could be parsed for: " + email);
                            }
                            markerData.put(email, loc);
                        }
                    }
                }
                return markerData;

            } catch (JSONException e) {
                e.printStackTrace();
            }
            return null;
        }

        @Override
        protected void onPostExecute(HashMap<String, Location> markerDataResult) {

            Log.e(TAG, "cancelAsyncTasks: " + cancelAsyncTasks);
            super.onPostExecute(markerDataResult);
            if (markerDataResult != null && !cancelAsyncTasks && isAdded()) {
                for (String email : markerDataResult.keySet()) {
                    Log.e(TAG, "marker to update: " + email);
                    if (markerDataResult.get(email) != null) {
                        new LoadMarkerAsync(markerDataResult.get(email), email).execute();
                    }
                }
            }
        }
    }

    private Location convertToLocation(JSONObject locationObj) {

        Location myLocation = new Location("fused");
        try {
            if (locationObj.length() == 0) {
                return null;
            }
            double lat = locationObj.getDouble("lat");
            double lon = locationObj.getDouble("lon");
            float acc = (float) locationObj.getDouble("acc");
            Long time = locationObj.getLong("time");
            myLocation.setLatitude(lat);
            myLocation.setLongitude(lon);
            myLocation.setAccuracy(acc);
            myLocation.setTime(time);
            return myLocation;
        } catch (JSONException e) {
            e.printStackTrace();
        }
        return null;
    }

    class MyInfoWindowAdapter implements GoogleMap.InfoWindowAdapter {

        @Override
        public View getInfoWindow(Marker marker) {
            return null;
        }

        @Override
        public View getInfoContents(Marker marker) {

            if (!aq.isExist() || cancelAsyncTasks || !isAdded()) {
                return null;
            }

            View myContentsView = getActivity().getLayoutInflater().inflate(R.layout.map_info_window, null);
            AQuery aq = new AQuery(myContentsView);

            String name = Utils.getNameFromEmail(context, marker.getTitle());
            aq.id(R.id.contact_name).text(name);
            aq.id(R.id.timestamp).text(Utils.timestampText(marker.getSnippet()));

            return myContentsView;
        }
    }

    public Bitmap getMarkerBitmap(String email, Boolean accurate, Boolean recent) {

        Log.e(TAG, "getMarkerBitmap");

        // Add cache checking logic
        Bitmap markerImage = MainApplication.avatarCache.get(email + ":" + accurate + ":" + recent);
        if (markerImage != null) {
            Log.e(TAG, "Marker IN cache: " + email + ":" + accurate + ":" + recent);
            return markerImage;
        } else {
            Log.e(TAG, "Marker NOT in cache. Processing: " + email + ":" + accurate + ":" + recent);
        }

        Log.e(TAG, "AvatarLoader not in cache. Fetching it. Email: " + email);
        // Get avatars
        Bitmap userImage = Utils.getPhotoFromEmail(context, email);
        if (userImage == null) {
            userImage = BitmapFactory.decodeResource(getResources(), R.drawable.default_avatar);
        } else {
            userImage = Utils.getRoundedCornerBitmap(userImage, 50);
        }

        // Marker colors, etc.
        Log.e(TAG, "userImage size: " + userImage);
        View markerView = ((LayoutInflater) getActivity().getSystemService(Context.LAYOUT_INFLATER_SERVICE))
                .inflate(R.layout.map_marker, null);

        aq = new AQuery(markerView);
        aq.id(R.id.user_image).image(userImage);
        Log.e(TAG, "aq in place");

        if (email.equals(MainApplication.userAccount)) {
            aq.id(R.id.marker_frame).image(R.drawable.pointers_android_pointer_green);
        } else if (!recent || !accurate) {
            aq.id(R.id.marker_frame).image(R.drawable.pointers_android_pointer_orange);
        }

        Log.e(TAG, "Image set. Calling createDrawableFromView");

        markerImage = createDrawableFromView(markerView);
        MainApplication.avatarCache.put(email + ":" + accurate + ":" + recent, markerImage);
        return markerImage;
    }

    // Convert a view to bitmap
    public Bitmap createDrawableFromView(View view) {

        Log.e(TAG, "createDrawableFromView");
        DisplayMetrics displayMetrics = new DisplayMetrics();
        getActivity().getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
        view.setLayoutParams(new WindowManager.LayoutParams(WindowManager.LayoutParams.WRAP_CONTENT,
                WindowManager.LayoutParams.WRAP_CONTENT));
        view.measure(displayMetrics.widthPixels, displayMetrics.heightPixels);
        view.layout(0, 0, displayMetrics.widthPixels, displayMetrics.heightPixels);
        view.buildDrawingCache();
        Bitmap bitmap = Bitmap.createBitmap(view.getMeasuredWidth(), view.getMeasuredHeight(),
                Bitmap.Config.ARGB_8888);

        Canvas canvas = new Canvas(bitmap);
        view.draw(canvas);

        return bitmap;
    }

    class LoadMarkerAsync extends AsyncTask<Void, Void, Bitmap> {

        Location position;
        LatLng latLng;
        String email;
        String time;
        Boolean accurate;
        Boolean recent;

        public LoadMarkerAsync(Location position, String email) {

            this.email = email;
            this.position = position;
        }

        @Override
        protected Bitmap doInBackground(Void... params) {

            if (position == null || email == null) {
                return null;
            }
            Log.e(TAG, "LoadMarkerAsync - Email: " + email + ", Position: " + position);
            latLng = new LatLng(position.getLatitude(), position.getLongitude());
            time = String.valueOf(position.getTime());
            accurate = Math.round(position.getAccuracy()) < 100;
            recent = (System.currentTimeMillis() - position.getTime()) < 60 * 60 * 1000;

            try {
                return getMarkerBitmap(email, accurate, recent);
            } catch (Exception ex) {
                ex.printStackTrace();
            }
            return null;
        }

        @Override
        protected void onPostExecute(Bitmap bitmapResult) {

            super.onPostExecute(bitmapResult);
            if (bitmapResult == null || cancelAsyncTasks || !isAdded() || map == null) {
                return;
            }
            Marker marker = markerMap.get(email);
            Boolean isNew = false;
            if (marker != null) {
                Log.e(TAG, "onPostExecute - updating marker: " + email);
                marker.setPosition(latLng);
                marker.setSnippet(time);
                marker.setIcon(BitmapDescriptorFactory.fromBitmap(bitmapResult));

            } else {
                Log.e(TAG, "onPostExecute - creating marker: " + email);
                marker = map.addMarker(new MarkerOptions().position(latLng).title(email).snippet(time)
                        .icon(BitmapDescriptorFactory.fromBitmap(bitmapResult)));
                Log.e(TAG, "onPostExecute - marker created");
                markerMap.put(email, marker);
                Log.e(TAG, "onPostExecute - marker in map stored. markerMap: " + markerMap.size());
                isNew = true;
            }

            if (marker.getTitle().equals(MainApplication.emailBeingTracked)) {
                marker.showInfoWindow();
                Log.e(TAG, "onPostExecute - showInfoWindow open");
                if (isNew) {
                    map.moveCamera(CameraUpdateFactory.newLatLngZoom(marker.getPosition(), 16));
                } else {
                    map.moveCamera(CameraUpdateFactory.newLatLng(marker.getPosition()));
                }
            } else if (firstTimeZoom && MainApplication.emailBeingTracked == null
                    && MainApplication.userAccount != null
                    && marker.getTitle().equals(MainApplication.userAccount)) {
                firstTimeZoom = false;
                map.moveCamera(CameraUpdateFactory.newLatLngZoom(marker.getPosition(), 16));
            }
        }

    }

    @Override
    public void onDestroy() {

        // TODO: Cancel ALL Async tasks
        cancelAsyncTasks = true;
        super.onDestroy();
    }

    @Override
    public void onLowMemory() {
        super.onLowMemory();
        Log.e(TAG, "---------------------------------------------------------");
        Log.e(TAG, "onLowMemory");
        Log.e(TAG, "---------------------------------------------------------");
        startActivityForResult(new Intent(context, FirstTimeActivity.class), -1);
    }

    private class AddPlaceCircleDrawable extends Drawable {

        public static final int STROKE_WIDTH = 12;
        public final float[] DASH_INTERVALS = new float[] { 49, 36 };

        @Override
        public void draw(Canvas canvas) {
            Point mapCenter = getAddPlaceCircleCenter();
            int radius = Math.min(mapCenter.x, mapCenter.y);

            Paint circlePaint = new Paint();
            circlePaint.setColor(getResources().getColor(R.color.add_place_circle));
            circlePaint.setAntiAlias(true);
            circlePaint.setStrokeWidth(STROKE_WIDTH);
            DashPathEffect dashPath = new DashPathEffect(DASH_INTERVALS, 1.0f);
            circlePaint.setPathEffect(dashPath);
            circlePaint.setStyle(Paint.Style.STROKE);
            canvas.drawCircle(mapCenter.x, mapCenter.y, (int) (radius * radiusMultiplier - STROKE_WIDTH),
                    circlePaint);
        }

        @Override
        public void setAlpha(int alpha) {

        }

        @Override
        public void setColorFilter(ColorFilter cf) {

        }

        @Override
        public int getOpacity() {
            return 0;
        }
    }
}