com.hangulo.powercontact.MapViewFragment.java Source code

Java tutorial

Introduction

Here is the source code for com.hangulo.powercontact.MapViewFragment.java

Source

/*
 * Copyright (C) 2015 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.hangulo.powercontact;

import android.Manifest;
import android.app.Activity;
import android.content.Context;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;

import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.provider.ContactsContract;
import android.provider.MediaStore;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v4.app.Fragment;
import android.support.v4.content.ContextCompat;
import android.support.v4.graphics.drawable.RoundedBitmapDrawable;
import android.support.v4.graphics.drawable.RoundedBitmapDrawableFactory;
import android.text.SpannableString;
import android.text.style.ForegroundColorSpan;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;

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.model.BitmapDescriptorFactory;
import com.google.android.gms.maps.model.CameraPosition;
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.maps.android.clustering.Cluster;
import com.google.maps.android.clustering.ClusterManager;

import com.google.maps.android.clustering.view.DefaultClusterRenderer;
import com.google.maps.android.ui.IconGenerator;
import com.hangulo.powercontact.model.PowerContactAddress;
import com.hangulo.powercontact.util.CircleTransform;
import com.hangulo.powercontact.util.PermissionUtils;
import com.squareup.picasso.Picasso;

import java.io.IOException;
import java.util.ArrayList;
/*
*   ================================================
*        Android Devlelopment Nanodegree
*        Project 8: Capstone, Stage 2 - Build
*        PowerContact by Kwanghyun Jung (ihangulo@gmail.com)
*   ================================================
*
*   date : Apr. 4th 2016
*
*    MapViewFragment.java
*    -------------
*    Map view
*
*/

/*
    
http://developer.android.com/reference/android/location/Geocoder.html
http://wptrafficanalyzer.in/blog/locating-user-input-address-in-google-maps-android-api-v2-with-geocoding-api/
    
https://github.com/udacity/google-play-services/tree/master/LocationLessons_Final/LocationLesson2_1
    
    
* Help site  ? ?
http://developer.android.com/training/location/display-address.html  (Displaying a Location Address)
https://github.com/googlesamples/android-play-location
    
    
// https://github.com/googlemaps/android-samples/blob/master/ApiDemos/app/src/main/java/com/example/mapdemo/MyLocationDemoActivity.java
    
    
 */
public class MapViewFragment extends Fragment implements OnMapReadyCallback,
        ActivityCompat.OnRequestPermissionsResultCallback, GoogleMap.OnMyLocationButtonClickListener,

        ClusterManager.OnClusterClickListener<PowerContactAddress>,
        ClusterManager.OnClusterInfoWindowClickListener<PowerContactAddress>,
        ClusterManager.OnClusterItemClickListener<PowerContactAddress>,
        ClusterManager.OnClusterItemInfoWindowClickListener<PowerContactAddress> {

    private final String LOG_TAG = MapViewFragment.class.getSimpleName();
    final static int DEFAULT_ZOOM_SIZE = 17; // default zoom level
    private static final int LOCATION_PERMISSION_REQUEST_CODE = 1;
    final static String CAMERA_POSITION_KEY = "CAMERA_POSITION";

    final static String NEED_SHOW_INFO_WINDOW_KEY = "NEED_SHOW_INFO_WINDOW_KEY";
    final static String NEED_TO_CLICK_ITEM_KEY = "NEED_TO_CLICK_ITEM_KEY";
    final static String NEED_TO_REFRESH_DATA_KEY = "NEED_TO_REFRESH_DATA_KEY";

    GoogleMap mMap;
    boolean mTwoPane;

    int mMarkerType = 1; //
    // 0: default marker 1:name 2: photo+name 3: photo  0:?  1:? 2:+? (?) 3: (  ?)

    private boolean mPermissionDenied = false;
    private ClusterManager<PowerContactAddress> mClusterManager;
    IconGenerator mIconFactory;

    View mPhotoProfile; //  ? ?  ? 

    View rootView;

    LatLng mCurrentLatLng; //= new LatLng(37.4789134,126.8773348) ; // ?  
    float mZoomLevel = 17.0f; // defalut zoom level
    static CameraPosition savedCameraPosition; //  ? ?

    PowerContactClusterRenderer mClusterRenderer;

    PowerContactAddress mLastClickedItem = null; // save last marker
    boolean needRefreshData = false; // ?? ??   (onMapReady?)
    boolean needShowInfoWindowMarker = false; //    ? ? ? ? ? .. (? )
    PowerContactAddress mNeedToclickItem; //    ?

    // callbacks --------------------------------
    MapViewCallback mCallback; // callback

    public interface MapViewCallback {
        void selectListMarkerItem(PowerContactAddress item); //  ?  ?? ...?(scroll & change color)

        void searchListMarkerItemByCoord(LatLng data); // ??   ?    .

        LatLng getCurrentLatLng(); //   ?.

        void onMyLocationButtonClick(); //   ??.. .

        void requestLocationPermissions(int requestCode); // ??  (>23)

        ArrayList<PowerContactAddress> getContactAddress();

        int getMarkerType(); //  ? .
    }
    // callbacks --------------------------------

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

        mTwoPane = getResources().getBoolean(R.bool.two_pane); // two_pane mode check
        rootView = inflater.inflate(R.layout.fragment_map, container, false);

        if (rootView == null)
            return null;

        if (savedInstanceState != null) {
            savedCameraPosition = savedInstanceState.getParcelable(CAMERA_POSITION_KEY);
            needShowInfoWindowMarker = savedInstanceState.getBoolean(NEED_SHOW_INFO_WINDOW_KEY, false);
            mNeedToclickItem = savedInstanceState.getParcelable(NEED_TO_CLICK_ITEM_KEY);
            needRefreshData = savedInstanceState.getBoolean(NEED_TO_REFRESH_DATA_KEY, false);
        }

        Log.v(LOG_TAG, "Mapview created");

        return rootView;
    }

    // ? ?   (?)
    public void setClusterManager(ArrayList<PowerContactAddress> powerContactArrayList, int markerType) {

        Log.v(LOG_TAG, "setClusterManager..  ");

        if (mClusterManager == null) {
            needRefreshData = true; // --> call when onMapReady()
            return; // error or reset
        }

        Log.v(LOG_TAG, "setClusterManager.. mClusterManager not null");
        mMarkerType = markerType;

        mClusterManager.clearItems(); // delete previous data

        if (mMap != null)
            mMap.clear();

        mClusterManager.addItems(powerContactArrayList);
        mClusterManager.cluster(); // force to be clustering

        needRefreshData = false; // reset
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        setUpMapIfNeeded();
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        //  ?? , zoom level? . (?  .)
        if (mMap != null) {
            savedCameraPosition = mMap.getCameraPosition();

            outState.putParcelable(CAMERA_POSITION_KEY, savedCameraPosition);
            // ?? show?  ?? .

        }

        outState.putBoolean(NEED_SHOW_INFO_WINDOW_KEY, needShowInfoWindowMarker);
        outState.putParcelable(NEED_TO_CLICK_ITEM_KEY, mNeedToclickItem);
        outState.putBoolean(NEED_TO_REFRESH_DATA_KEY, needRefreshData);

        //float zoom= cameraPosition.zoom;
        super.onSaveInstanceState(outState);
    }

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        // http://stackoverflow.com/questions/32083053/android-fragment-onattach-deprecated
        if (context instanceof Activity) {
            try {
                mCallback = (MapViewCallback) context;
            } catch (ClassCastException e) {
                throw new ClassCastException(
                        getActivity().toString() + " must implement OnHeadlineSelectedListener");
            }
        }
    }

    private void setUpMapIfNeeded() {
        // Do a null check to confirm that we have not already instantiated the map.
        if (mMap == null) {
            // Try to obtain the map from the SupportMapFragment.
            //mMap = ((SupportMapFragment) getActivity().getSupportFragmentManager().findFragmentById(R.id.map)).getMap();

            // http://codentrick.com/retrieve-supportmapfragment-when-show-google-map-on-android-fragment/
            SupportMapFragment mapFrag = (SupportMapFragment) getChildFragmentManager().findFragmentById(R.id.map);
            if (mapFrag != null) {
                // mMap = mapFrag.getMap();
                mapFrag.getMapAsync(this);
            }
        }
    }

    private void setUpClusterMap() {
        //   ??

        Log.v(LOG_TAG, "setupclustermap()... ");
        if (mMap == null)
            return; // error

        mClusterManager = new ClusterManager<>(getActivity(), mMap);
        //?  ? .
        mClusterRenderer = new PowerContactClusterRenderer();
        mClusterManager.setRenderer(mClusterRenderer);

        //mMap.setOnCameraChangeListener(mClusterManager); // ? ... 2016.2.21 
        // http://stackoverflow.com/questions/28560816/how-to-add-2-listeners-to-map
        mMap.setOnCameraChangeListener(new GoogleMap.OnCameraChangeListener() {
            @Override
            public void onCameraChange(CameraPosition cameraPosition) {
                mZoomLevel = cameraPosition.zoom;
                mClusterManager.onCameraChange(cameraPosition);
            }
        });

        mMap.setOnMarkerClickListener(mClusterManager);

        // Setting an info window adapter allows us to change the both the contents and look of the
        // info window.
        mMap.setInfoWindowAdapter(new CustomInfoWindowAdapter());

        mMap.setOnInfoWindowClickListener(mClusterManager);
        mClusterManager.setOnClusterClickListener(this);
        mClusterManager.setOnClusterInfoWindowClickListener(this);
        mClusterManager.setOnClusterItemClickListener(this);
        mClusterManager.setOnClusterItemInfoWindowClickListener(this);

        mIconFactory = new IconGenerator(getActivity());

        if (savedCameraPosition != null) {// if there is saved position ? ? ...
            mMap.moveCamera(CameraUpdateFactory.newCameraPosition(savedCameraPosition));
            mClusterManager.onCameraChange(savedCameraPosition); // move ??.. 2016.3.25
        }

    }

    // ?    http://stackoverflow.com/questions/23363188/does-the-google-maps-android-api-utility-cluster-manager-have-a-minimum-number-o

    // Show From Memory
    // Fetch From Database
    // --- ? ?
    /**
     * Draws profile photos inside markers (using IconGenerator).
     * When there are multiple people in the cluster, draw multiple photos (using MultiDrawable).
     */
    private class PowerContactClusterRenderer extends DefaultClusterRenderer<PowerContactAddress> {
        private final IconGenerator mIconGenerator = new IconGenerator(getActivity());
        private final IconGenerator mClusterIconGenerator = new IconGenerator(getActivity());

        public PowerContactClusterRenderer() {
            super(getActivity(), mMap, mClusterManager);

            mPhotoProfile = getActivity().getLayoutInflater().inflate(R.layout.marker_photo_profile, null); //  

            if (mPhotoProfile == null || mClusterIconGenerator == null)
                return;

            mClusterIconGenerator.setContentView(mPhotoProfile); // ? multiProfile
            mClusterIconGenerator.setBackground(null);
        }

        @Override
        protected void onBeforeClusterItemRendered(PowerContactAddress person, MarkerOptions markerOptions) {

            Bitmap icon;

            switch (mMarkerType) {

            case Constants.MARKER_TYPE_DEFAULT: // default marker
                // https://developers.google.com/android/reference/com/google/android/gms/maps/model/BitmapDescriptorFactory
                //markerOptions.icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_ORANGE)); can change color
                markerOptions.icon(BitmapDescriptorFactory.defaultMarker()).title(person.getName())
                        .snippet(person.getAddr());
                break;

            case Constants.MARKER_TYPE_NAME_ONLY: // name only mode
                markerOptions.icon(BitmapDescriptorFactory.fromBitmap(mIconFactory.makeIcon(person.getName())))
                        .anchor(mIconFactory.getAnchorU(), mIconFactory.getAnchorV()).title(person.getName())
                        .snippet(person.getAddr());
                break;

            case Constants.MARKER_TYPE_NAME_WITH_PHOTO: {//  photo + name

                ImageView mClusterImageView = (ImageView) mPhotoProfile.findViewById(R.id.image_custom_marker); //  ? 
                Uri photoUri;

                if (person.getPhoto() != null && person.getPhoto().length() > 0) { // if there is photo
                    photoUri = Uri.parse(person.getPhoto());

                    Picasso.with(getActivity()).load(photoUri).transform(new CircleTransform())
                            .error(R.drawable.ic_person_40dp).into(mClusterImageView);

                } else {
                    Picasso.with(getActivity()).load(R.drawable.ic_person_40dp).transform(new CircleTransform())
                            .into(mClusterImageView);
                }

                String title = person.getName(); // ? ?? ?  ??  .
                TextView titleUi = ((TextView) mPhotoProfile.findViewById(R.id.text_custom_marker));
                if (titleUi != null)
                    if (title != null) {
                        titleUi.setText(title);
                    } else {
                        titleUi.setText("");
                    }

                final Drawable TRANSPARENT_DRAWABLE = new ColorDrawable(Color.TRANSPARENT); // 
                mIconGenerator.setBackground(TRANSPARENT_DRAWABLE);

                icon = mClusterIconGenerator.makeIcon();

                markerOptions.icon(BitmapDescriptorFactory.fromBitmap(icon)).title(person.getName())
                        .anchor(0.5f, 0.5f).anchor(mIconFactory.getAnchorU(), mIconFactory.getAnchorV())
                        .snippet(person.getAddr());

            }
                break;

            case Constants.MARKER_TYPE_PHOTO_ONLY: {// ...
                RoundedBitmapDrawable circularBitmapDrawable;
                Bitmap bitmap;
                Uri photoUri;
                try {
                    if (person.getPhoto() != null && person.getPhoto().length() > 0) {
                        photoUri = Uri.parse(person.getPhoto());
                        icon = MediaStore.Images.Media.getBitmap(getContext().getContentResolver(), photoUri);
                    } else {
                        icon = BitmapFactory.decodeResource(getResources(), R.drawable.ic_person_40dp); // default
                    }

                    // circiled drawable ? ~!
                    circularBitmapDrawable = RoundedBitmapDrawableFactory.create(getContext().getResources(), icon);
                    circularBitmapDrawable.setCircular(true);
                    circularBitmapDrawable.setAntiAlias(true);

                    // convert to bitmap
                    // http://stackoverflow.com/questions/18053156/set-image-from-drawable-as-marker-in-google-map-version-2
                    Canvas canvas = new Canvas();
                    bitmap = Bitmap.createBitmap(circularBitmapDrawable.getIntrinsicWidth(),
                            circularBitmapDrawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
                    canvas.setBitmap(bitmap);
                    circularBitmapDrawable.setBounds(0, 0, circularBitmapDrawable.getIntrinsicWidth(),
                            circularBitmapDrawable.getIntrinsicHeight());
                    circularBitmapDrawable.draw(canvas);

                } catch (IOException e) {
                    e.printStackTrace(); // errror
                    break;
                }

                markerOptions.icon(BitmapDescriptorFactory.fromBitmap(bitmap)).title(person.getName())
                        .anchor(0.5f, 0.5f).snippet(person.getAddr());
            }
                break;

            case Constants.MARKER_TYPE_SMALL_CIRCLE:
                // tiny marker  ?  (drawable)
                markerOptions.icon(BitmapDescriptorFactory.fromResource(R.drawable.point_circle))
                        .title(person.getName()).snippet(person.getAddr());
                break;
            }
        }

        @Override
        protected void onBeforeClusterRendered(Cluster<PowerContactAddress> cluster, MarkerOptions markerOptions) {
            IconGenerator iconFactory = new IconGenerator(getActivity());
            // , ? ? ??  ? ? .
            if (cluster.getSize() > 1 && isLocationAllSame(cluster)) {
                // IconGeneratorDemoActivity ?  

                iconFactory.setStyle(IconGenerator.STYLE_PURPLE);
                iconFactory.setTextAppearance(R.style.ClusterText);
                markerOptions
                        .icon(BitmapDescriptorFactory
                                .fromBitmap(mIconFactory.makeIcon(String.valueOf(cluster.getSize()))))
                        .anchor(mIconFactory.getAnchorU(), mIconFactory.getAnchorV());

            } else { // ? ?  .

                super.onBeforeClusterRendered(cluster, markerOptions);
            }

        }
        // when clustring? --> more than 10 point same / but below zoom level 15.. then don't clustering

        //  ?? ?
        // 10 ?? ? , but zoom Level? ?  ??? ? ? .
        // ??.. 15 ? .
        // // 
        // https://developers.google.com/maps/documentation/android-api/views
        //            1: World
        //            5: Landmass/continent
        //            10: City
        //            15: Streets
        //            20: Buildings
        @Override
        protected boolean shouldRenderAsCluster(Cluster cluster) {

            // 1.? ? ? -->  ??
            // 2. 10 ?? ?? -->  ??
            // 3.  ? 16?.. --> ??
            if (cluster.getSize() > 1)
                if (isLocationAllSame(cluster) || ((cluster.getSize() > 9 && mZoomLevel < 17)))
                    return true;

            return false;

        }
    } //---end of rendere

    @Override
    public boolean onClusterClick(Cluster<PowerContactAddress> cluster) {

        if (isLocationAllSame(cluster)) { // same location data?
            //Toast.makeText(getActivity(), "same data", Toast.LENGTH_SHORT).show();
            mCallback.searchListMarkerItemByCoord(cluster.getPosition()); //   .

        } else { // if not same location data

            // http://stackoverflow.com/questions/25395357/android-how-to-uncluster-on-single-tap-on-a-cluster-marker-maps-v2
            // when click cluster then zoom
            // ? ?  
            // ZoomLevel
            // https://developers.google.com/maps/documentation/android-api/views
            // 1: World   5: Landmass/continent   10: City     15: Streets       20: Buildings

            if (mMap.getCameraPosition().zoom < 20)
                mMap.animateCamera(CameraUpdateFactory.newLatLngZoom(cluster.getPosition(),
                        (float) Math.floor(mMap.getCameraPosition().zoom + 1)), 300, null);
        }
        return true;
    }

    @Override
    public void onClusterInfoWindowClick(Cluster<PowerContactAddress> cluster) {
        // Does nothing, but you could go to a list of the users.
    }

    // ? ?

    @Override
    public boolean onClusterItemClick(PowerContactAddress item) {
        //  ?  .
        // change listview position
        mCallback.selectListMarkerItem(item); // scroll listview upto this item
        mLastClickedItem = item; // save( )

        //        //  .
        //        // This causes the marker at Adelaide to change color and alpha.
        //        final Random mRandom = new Random();
        //        Marker marker = mClusterRenderer.getMarker(item); // ?    .....!!!!!
        //        marker.setIcon(BitmapDescriptorFactory.defaultMarker(mRandom.nextFloat() * 360));

        return false;
    }

    //  ? ? ? 
    public boolean onClusterItemClickFromListView(final PowerContactAddress item) {

        Log.v(LOG_TAG, "onClusterItemClickFromListView(): will click");

        // ? ? ? ?
        if (mMap == null) { // ?  

            Log.v(LOG_TAG, "onClusterItemClickFromListView(): mMap=null / wait....");

            needShowInfoWindowMarker = true; //
            mNeedToclickItem = item;
            return false;
        }

        makeClusterItemClickFromOthers(item, -1);

        return true;
    }
    //  ? ? ?  (? )

    float cameraZoomLevel;

    public void makeClusterItemClickFromOthers(final PowerContactAddress item, float zoomLevel) {
        if (zoomLevel == -1) {
            cameraZoomLevel = mMap.getCameraPosition().zoom; // get current zoom level

        } else
            cameraZoomLevel = zoomLevel;

        mMap.animateCamera(CameraUpdateFactory.newLatLngZoom(item.getPosition(), cameraZoomLevel), 100,
                new GoogleMap.CancelableCallback() {
                    @Override
                    public void onFinish() {
                        Log.v(LOG_TAG, "onClusterItemClickFromListView()::  onFinish");
                        Handler handler = new Handler();
                        handler.postDelayed(new Runnable() {
                            @Override
                            public void run() {
                                //  ? .
                                Log.v(LOG_TAG, "start");
                                Marker myMarker = searchMarkersFromArrayList(item);
                                if (myMarker != null) {
                                    // ? ui thread.
                                    myMarker.showInfoWindow(); // ? .
                                    Log.v(LOG_TAG, "onClusterItemClickFromListView()::  ok. showinfowindow()"
                                            + cameraZoomLevel);
                                } else { // ...
                                    if (cameraZoomLevel < mMap.getMaxZoomLevel()) {
                                        makeClusterItemClickFromOthers(item, mMap.getMaxZoomLevel()); // recursive
                                    }
                                }
                                Log.v(LOG_TAG, "onClusterItemClickFromListView():: end " + cameraZoomLevel + "/");

                            }
                        }, 500);
                    }

                    @Override
                    public void onCancel() {

                    }
                });
    }

    // ?? ?? ? ? ? ? 
    public boolean isLocationAllSame(Cluster<PowerContactAddress> cluster) {
        PowerContactAddress prevItem = null;
        for (PowerContactAddress item : cluster.getItems()) {
            if (prevItem == null)
                prevItem = item;
            else {
                //firstName = firstName + "," +item.getName();
                if (!prevItem.getLatLng().equals(item.getLatLng())) {
                    return false;
                }
            }
        }
        return true;
    }

    // item?   .
    // search marker on ArrayList and return it
    public Marker searchMarkersFromArrayList(PowerContactAddress Addressitem) {

        if (mClusterManager == null) {
            Log.v(LOG_TAG, "mClusterManager=null");
            return null;
        }

        java.util.Collection<Marker> userCollection = mClusterManager.getMarkerCollection().getMarkers();
        //MarkerManager.Collection markerCollection =  mClusterManager.getMarkerCollection();

        if (userCollection == null)
            return null;

        for (Marker marker : userCollection) {
            if (marker.getPosition().latitude == Addressitem.getPosition().latitude // ?
                    && marker.getPosition().longitude == Addressitem.getPosition().longitude // ?
                    && marker.getTitle().equals(Addressitem.getName()) // ? // ? ?,??  ?   . ()
                    && marker.getSnippet().equals(Addressitem.getAddr())) // 
                return marker;
        }

        return null; // not found
    }

    //    address item? . 1:1 ??? . (?/ ? ??  )
    // ?, ? ?   .
    // find PowerContactAddress item from marer.
    // suppose that there is no contact item have same name & same address.. --> will imporve next version
    // also this marker is drawed on screen already.
    public PowerContactAddress searchAddressItemFromMarker(Marker marker) {

        // , ? ???
        //      ? .
        //java.util.Collection<Marker> userCollection = mClusterManager.getMarkerCollection().getMarkers();
        //MarkerManager.Collection markerCollection =  mClusterManager.getMarkerCollection();

        for (PowerContactAddress addressItem : mCallback.getContactAddress()) {
            if (marker.getPosition().latitude == addressItem.getPosition().latitude // ?
                    && marker.getPosition().longitude == addressItem.getPosition().longitude // ?
                    && marker.getTitle().equals(addressItem.getName()) // ? // ? ?,??  ?   . ()
                    && marker.getSnippet().equals(addressItem.getAddr())) // 
                return addressItem;
        }

        return null; // not found
    }

    // ??? ? customize ?  https://developers.google.com/maps/documentation/android-api/infowindows
    // ? ?? ? ?
    //  marker?  ??  ? URL?  ?..
    // http://androidfreakers.blogspot.kr/2013/08/display-custom-info-window-with.html
    @Override
    public void onClusterItemInfoWindowClick(PowerContactAddress item) {

        Uri lookupUri = ContactsContract.Contacts.getLookupUri(item.getContact_id(), item.getLookup_key()); //  (lookup) // http://developer.android.com/reference/android/provider/ContactsContract.Contacts.html#getLookupUri(long, java.lang.String)

        if (lookupUri != null)
            ContactsContract.QuickContact.showQuickContact(getActivity(), mPhotoProfile, lookupUri,
                    ContactsContract.QuickContact.MODE_LARGE, null);

        if (MainActivity.mPowerContactSettings.isDemoMode())
            Toast.makeText(getActivity(), R.string.msg_demo_mode_clusteritem_clicked, Toast.LENGTH_SHORT).show();

    }

    /**
     * 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. In this case,
     * we just add a marker near Sydney, Australia.
     * 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.setOnMyLocationButtonClickListener(this);

        enableMyLocation(); // activate current location button

        mMap.getUiSettings().setCompassEnabled(true);
        mMap.getUiSettings().setZoomControlsEnabled(true);

        mCallback.getCurrentLatLng(); // if getLocation then callback moveMapLocation()
        Log.v(LOG_TAG, "onMapReady... setupclustermap()");
        setUpClusterMap();

        if (needRefreshData && mCallback != null) {
            setClusterManager(mCallback.getContactAddress(), mCallback.getMarkerType());
        }

        // ? ??? ?? ?  ?...   ? ..?
        if (needShowInfoWindowMarker) { //   ?..  , ? ?   
            if (mNeedToclickItem != null) {
                makeClusterItemClickFromOthers(mNeedToclickItem, -1);
                needShowInfoWindowMarker = false;
                mNeedToclickItem = null;
            }
        }
    }

    /**
     * https://github.com/googlemaps/android-samples/blob/master/ApiDemos/app/src/main/java/com/example/mapdemo/MyLocationDemoActivity.java
     * Enables the My Location layer if the fine location permission has been granted.
     */
    public void enableMyLocation() {

        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
            mMap.setMyLocationEnabled(true);
        } else {
            if (ContextCompat.checkSelfPermission(getActivity(),
                    Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
                mMap.setMyLocationEnabled(true); //  ?.. ? grant? ?.
            }
        }
    }

    @Override
    public boolean onMyLocationButtonClick() {

        mCallback.onMyLocationButtonClick(); // if getLocation then callback moveMapLocation()
        // 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;
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
            @NonNull int[] grantResults) {
        if (requestCode != LOCATION_PERMISSION_REQUEST_CODE) {
            return;
        }

        if (PermissionUtils.isPermissionGranted(permissions, grantResults,
                Manifest.permission.ACCESS_FINE_LOCATION)) {
            // Enable the my location layer if the permission has been granted.
            enableMyLocation();
        } else {
            // Display the missing permission error dialog when the fragments resume.
            mPermissionDenied = true;
        }
    }

    @Override
    public void onResume() {
        super.onResume();
        if (mPermissionDenied) {
            // Permission was not granted, display error dialog.
            showMissingPermissionError();
            mPermissionDenied = false;
        }
    }

    /**
     * Displays a dialog with error message explaining that the location permission is missing.
     */
    private void showMissingPermissionError() {
        PermissionUtils.PermissionDeniedDialog.newInstance(true).show(getActivity().getSupportFragmentManager(),
                "dialog");
    }

    // ? (main?) ?  .
    public void moveMapLocation(LatLng location) {
        if (location == null)
            return;

        mCurrentLatLng = location;
        //   mCurrentLatLng = location;

        if (mMap != null)
            mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(location, DEFAULT_ZOOM_SIZE)); // ? ??!
    }

    // ??? customize
    // Infowindow customize

    // googlemap-android-samples API? MarkerDemoActivity? 
    // get this from  googlemap-android-samples API :MarkerDemoActivity
    //  Demonstrates customizing the info window and/or its contents.
    class CustomInfoWindowAdapter implements GoogleMap.InfoWindowAdapter {

        private final View mContents;

        CustomInfoWindowAdapter() {
            mContents = getActivity().getLayoutInflater().inflate(R.layout.custom_info_contents, null);
        }

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

            // if want to custom window, then like this
            // before this -->      mWindow = getActivity().getLayoutInflater().inflate(R.layout.custom_info_window, null);
            //            render(marker, mWindow);
            //            return mWindow;

        }

        @Override
        public View getInfoContents(Marker marker) {
            render(marker, mContents);
            return mContents;

            // if default info contants, then return null
        }

        private void render(Marker marker, View view) {
            int badge;

            //  marker contactList ?? .
            PowerContactAddress contactItem = searchAddressItemFromMarker(marker);

            ImageView imgBadge = (ImageView) view.findViewById(R.id.badge);
            badge = R.drawable.ic_person_40dp; // ?  ??, ... ?  ?...
            if (contactItem != null && contactItem.getPhoto() != null && contactItem.getPhoto().length() > 0) { // ?

                Uri photoUri = Uri.parse(contactItem.getPhoto());

                // http://stackoverflow.com/questions/26112150/android-create-circular-image-with-picasso
                Picasso.with(getActivity()).load(photoUri).transform(new CircleTransform()).error(badge)
                        .into(imgBadge);
            } else {
                Picasso.with(getActivity()).load(badge).transform(new CircleTransform()).placeholder(badge)
                        .error(badge).into(imgBadge);

            }

            //   Marker   item?  .
            // , ? ??   ? ?

            String title = marker.getTitle(); // ? ?? ?  ??  .
            TextView titleUi = ((TextView) view.findViewById(R.id.title));
            if (title != null) {
                // Spannable string allows us to edit the formatting of the text.
                SpannableString titleText = new SpannableString(title);
                titleText.setSpan(new ForegroundColorSpan(Color.BLUE), 0, titleText.length(), 0);
                titleUi.setText(titleText);
            } else
                titleUi.setText("");

            String snippet = marker.getSnippet(); // ?   .
            TextView snippetUi = ((TextView) view.findViewById(R.id.snippet));

            if (snippet == null)
                snippet = "";
            snippetUi.setText(snippet);

        }
    }
}