content: package com.airbnb.android.airmapview;
  
  import android.graphics.Bitmap;
  import android.graphics.Point;
  import android.os.Bundle;
  import android.view.LayoutInflater;
  import android.view.View;
  import android.view.ViewGroup;
  
  import androidx.annotation.NonNull;
  import com.airbnb.android.airmapview.listeners.InfoWindowCreator;
  import com.airbnb.android.airmapview.listeners.OnCameraChangeListener;
  import com.airbnb.android.airmapview.listeners.OnInfoWindowClickListener;
  import com.airbnb.android.airmapview.listeners.OnLatLngScreenLocationCallback;
  import com.airbnb.android.airmapview.listeners.OnMapBoundsCallback;
  import com.airbnb.android.airmapview.listeners.OnMapClickListener;
  import com.airbnb.android.airmapview.listeners.OnMapLoadedListener;
  import com.airbnb.android.airmapview.listeners.OnMapMarkerClickListener;
  import com.airbnb.android.airmapview.listeners.OnMapMarkerDragListener;
  import com.airbnb.android.airmapview.listeners.OnSnapshotReadyListener;
  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.Projection;
  import com.google.android.gms.maps.SupportMapFragment;
  import com.google.android.gms.maps.UiSettings;
  import com.google.android.gms.maps.model.CameraPosition;
  import com.google.android.gms.maps.model.CircleOptions;
  import com.google.android.gms.maps.model.LatLng;
  import com.google.android.gms.maps.model.LatLngBounds;
  import com.google.android.gms.maps.model.Marker;
  import com.google.android.gms.maps.model.Polygon;
  import com.google.maps.android.geojson.GeoJsonLayer;
  import com.google.maps.android.geojson.GeoJsonPolygonStyle;
  
  import org.json.JSONException;
  import org.json.JSONObject;
  
  import java.util.HashMap;
  import java.util.Map;
  
  public class NativeGoogleMapFragment extends SupportMapFragment implements AirMapInterface {
    private GoogleMap googleMap;
    private OnMapLoadedListener onMapLoadedListener;
    private boolean myLocationEnabled;
    private GeoJsonLayer layerOnMap;
    private final Map<Marker, AirMapMarker<?>> markers = new HashMap<>();
  
    public static NativeGoogleMapFragment newInstance(AirGoogleMapOptions options) {
      return new NativeGoogleMapFragment().setArguments(options);
    }
  
    public NativeGoogleMapFragment setArguments(AirGoogleMapOptions options) {
      setArguments(options.toBundle());
      return this;
    }
  
    @Override public View onCreateView(LayoutInflater inflater, ViewGroup container,
        Bundle savedInstanceState) {
      View v = super.onCreateView(inflater, container, savedInstanceState);
  
      init();
  
      return v;
    }
  
    public void init() {
      getMapAsync(new OnMapReadyCallback() {
        @Override public void onMapReady(GoogleMap googleMap) {
          if (googleMap != null && getActivity() != null) {
            NativeGoogleMapFragment.this.googleMap = googleMap;
            UiSettings settings = NativeGoogleMapFragment.this.googleMap.getUiSettings();
            settings.setZoomControlsEnabled(false);
            settings.setMyLocationButtonEnabled(false);
            setMyLocationEnabled(myLocationEnabled);
  
            if (onMapLoadedListener != null) {
              onMapLoadedListener.onMapLoaded();
            }
          }
        }
      });
    }
  
    @Override public boolean isInitialized() {
      return googleMap != null && getActivity() != null;
    }
  
    @Override public void clearMarkers() {
      markers.clear();
      googleMap.clear();
    }
  
    @Override public void addMarker(AirMapMarker<?> airMarker) {
      Marker marker = googleMap.addMarker(airMarker.getMarkerOptions());
      airMarker.setGoogleMarker(marker);
      markers.put(marker, airMarker);
    }
  
    @Override public void moveMarker(AirMapMarker<?> marker, LatLng to) {
      marker.setLatLng(to);
      marker.getMarker().setPosition(to);
    }
  
    @Override public void removeMarker(AirMapMarker<?> marker) {
      Marker nativeMarker = marker.getMarker();
      if (nativeMarker != null) {
        nativeMarker.remove();
        markers.remove(nativeMarker);
      }
    }
  
    @Override public void setOnInfoWindowClickListener(final OnInfoWindowClickListener listener) {
      googleMap.setOnInfoWindowClickListener(new GoogleMap.OnInfoWindowClickListener() {
        @Override public void onInfoWindowClick(Marker marker) {
          AirMapMarker<?> airMarker = markers.get(marker);
          if (airMarker != null) {
            listener.onInfoWindowClick(airMarker);
          }
        }
      });
    }
  
    @Override public void setInfoWindowCreator(GoogleMap.InfoWindowAdapter adapter,
        InfoWindowCreator creator) {
      googleMap.setInfoWindowAdapter(adapter);
    }
  
    @Override public void drawCircle(LatLng latLng, int radius) {
      drawCircle(latLng, radius, CIRCLE_BORDER_COLOR);
    }
  
    @Override public void drawCircle(LatLng latLng, int radius, int borderColor) {
      drawCircle(latLng, radius, borderColor, CIRCLE_BORDER_WIDTH);
    }
  
    @Override public void drawCircle(LatLng latLng, int radius, int borderColor, int borderWidth) {
      drawCircle(latLng, radius, borderColor, borderWidth, CIRCLE_FILL_COLOR);
    }
  
    @Override public void drawCircle(LatLng latLng, int radius, int borderColor, int borderWidth,
        int fillColor) {
      googleMap.addCircle(new CircleOptions()
          .center(latLng)
          .strokeColor(borderColor)
          .strokeWidth(borderWidth)
          .fillColor(fillColor)
          .radius(radius));
    }
  
    @Override public void getMapScreenBounds(OnMapBoundsCallback callback) {
      final Projection projection = googleMap.getProjection();
      int hOffset = getResources().getDimensionPixelOffset(R.dimen.map_horizontal_padding);
      int vOffset = getResources().getDimensionPixelOffset(R.dimen.map_vertical_padding);
  
      LatLngBounds.Builder builder = LatLngBounds.builder();
      builder.include(projection.fromScreenLocation(new Point(hOffset, vOffset))); // top-left
      builder.include(projection.fromScreenLocation(
          new Point(getView().getWidth() - hOffset, vOffset))); // top-right
      builder.include(projection.fromScreenLocation(
          new Point(hOffset, getView().getHeight() - vOffset))); // bottom-left
      builder.include(projection.fromScreenLocation(new Point(getView().getWidth() - hOffset,
          getView().getHeight() - vOffset))); // bottom-right
  
      callback.onMapBoundsReady(builder.build());
    }
  
    @Override public void getScreenLocation(LatLng latLng, OnLatLngScreenLocationCallback callback) {
      callback.onLatLngScreenLocationReady(googleMap.getProjection().toScreenLocation(latLng));
    }
  
    @Override public void setCenter(LatLngBounds latLngBounds, int boundsPadding) {
      googleMap.moveCamera(CameraUpdateFactory.newLatLngBounds(latLngBounds, boundsPadding));
    }
  
    @Override public void setZoom(int zoom) {
      googleMap.animateCamera(
          CameraUpdateFactory.newLatLngZoom(googleMap.getCameraPosition().target, zoom));
    }
  
    @Override public void animateCenter(LatLng latLng) {
      googleMap.animateCamera(CameraUpdateFactory.newLatLng(latLng));
    }
  
    @Override public void setCenter(LatLng latLng) {
      googleMap.moveCamera(CameraUpdateFactory.newLatLng(latLng));
    }
  
    @Override public LatLng getCenter() {
      return googleMap.getCameraPosition().target;
    }
  
    @Override public int getZoom() {
      return (int) googleMap.getCameraPosition().zoom;
    }
  
    @Override
    public void setOnCameraChangeListener(final OnCameraChangeListener onCameraChangeListener) {
      googleMap.setOnCameraChangeListener(new GoogleMap.OnCameraChangeListener() {
        @Override public void onCameraChange(CameraPosition cameraPosition) {
          // camera change can occur programmatically.
          if (isResumed()) {
            onCameraChangeListener.onCameraChanged(cameraPosition.target, (int) cameraPosition.zoom);
          }
        }
      });
    }
  
    @Override public void setOnMapLoadedListener(OnMapLoadedListener onMapLoadedListener) {
      this.onMapLoadedListener = onMapLoadedListener;
    }
  
    @Override public void setCenterZoom(LatLng latLng, int zoom) {
      googleMap.moveCamera(CameraUpdateFactory.newLatLngZoom(latLng, zoom));
    }
  
    @Override public void animateCenterZoom(LatLng latLng, int zoom) {
      googleMap.animateCamera(CameraUpdateFactory.newLatLngZoom(latLng, zoom));
    }
  
    @Override public void setOnMarkerClickListener(final OnMapMarkerClickListener listener) {
      googleMap.setOnMarkerClickListener(new GoogleMap.OnMarkerClickListener() {
        @Override public boolean onMarkerClick(Marker marker) {
          AirMapMarker<?> airMarker = markers.get(marker);
          if (airMarker != null) {
            return listener.onMapMarkerClick(airMarker);
          }
          return false;
        }
      });
    }
  
    @Override public void setOnMarkerDragListener(final OnMapMarkerDragListener listener) {
      if (listener == null) {
        googleMap.setOnMarkerDragListener(null);
        return;
      }
      googleMap.setOnMarkerDragListener(new GoogleMap.OnMarkerDragListener() {
        @Override
        public void onMarkerDragStart(Marker marker) {
          listener.onMapMarkerDragStart(marker);
        }
  
        @Override
        public void onMarkerDrag(Marker marker) {
          listener.onMapMarkerDrag(marker);
        }
  
        @Override
        public void onMarkerDragEnd(Marker marker) {
          listener.onMapMarkerDragEnd(marker);
        }
      });
    }
  
    @Override public void setOnMapClickListener(final OnMapClickListener listener) {
      googleMap.setOnMapClickListener(new GoogleMap.OnMapClickListener() {
        @Override
        public void onMapClick(LatLng latLng) {
          listener.onMapClick(latLng);
        }
      });
    }
  
    @Override public void setPadding(int left, int top, int right, int bottom) {
      googleMap.setPadding(left, top, right, bottom);
    }
  
    @Override public void setMyLocationEnabled(boolean enabled) {
      if (myLocationEnabled != enabled) {
        myLocationEnabled = enabled;
        if (!RuntimePermissionUtils.checkLocationPermissions(getActivity(), this)) {
          myLocationEnabled = false;
        }
      }
    }
  
    @Override public void onLocationPermissionsGranted() {
      //noinspection MissingPermission
      googleMap.setMyLocationEnabled(myLocationEnabled);
    }
  
    @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
        @NonNull int[] grantResults) {
      super.onRequestPermissionsResult(requestCode, permissions, grantResults);
      RuntimePermissionUtils.onRequestPermissionsResult(this, requestCode, grantResults);
    }
  
    @Override public boolean isMyLocationEnabled() {
      return googleMap.isMyLocationEnabled();
    }
  
    @Override public void setMyLocationButtonEnabled(boolean enabled) {
      googleMap.getUiSettings().setMyLocationButtonEnabled(enabled);
    }
  
    @Override public void setMapToolbarEnabled(boolean enabled) {
      googleMap.getUiSettings().setMapToolbarEnabled(enabled);
    }
  
    @Override public <T> void addPolyline(AirMapPolyline<T> polyline) {
      polyline.addToGoogleMap(googleMap);
    }
  
    @Override public <T> void removePolyline(AirMapPolyline<T> polyline) {
      polyline.removeFromGoogleMap();
    }
  
    @Override public <T> void addPolygon(AirMapPolygon<T> polygon) {
      Polygon googlePolygon = googleMap.addPolygon(polygon.getPolygonOptions());
      polygon.setGooglePolygon(googlePolygon);
    }
  
    @Override public <T> void removePolygon(AirMapPolygon<T> polygon) {
      Polygon nativePolygon = polygon.getGooglePolygon();
      if (nativePolygon != null) {
        nativePolygon.remove();
      }
    }
  
    @Override public void setMapType(MapType type) {
      int nativeType = 0;
      switch (type) {
        case MAP_TYPE_NORMAL:
          nativeType = GoogleMap.MAP_TYPE_NORMAL;
          break;
        case MAP_TYPE_SATELLITE:
          nativeType = GoogleMap.MAP_TYPE_SATELLITE;
          break;
        case MAP_TYPE_TERRAIN:
          nativeType = GoogleMap.MAP_TYPE_TERRAIN;
          break;
      }
      googleMap.setMapType(nativeType);
    }
  
    /**
     * This method will return the google map if initialized. Will return null otherwise
     *
     * @return returns google map if initialized
     */
    public GoogleMap getGoogleMap() {
      return googleMap;
    }
  
    @Override
    public void setGeoJsonLayer(final AirMapGeoJsonLayer airMapGeoJsonLayer) throws JSONException {
      // clear any existing layers
      clearGeoJsonLayer();
  
      layerOnMap = new GeoJsonLayer(googleMap, new JSONObject(airMapGeoJsonLayer.geoJson));
      GeoJsonPolygonStyle style = layerOnMap.getDefaultPolygonStyle();
      style.setStrokeColor(airMapGeoJsonLayer.strokeColor);
      style.setStrokeWidth(airMapGeoJsonLayer.strokeWidth);
      style.setFillColor(airMapGeoJsonLayer.fillColor);
      layerOnMap.addLayerToMap();
    }
  
    @Override public void clearGeoJsonLayer() {
      if (layerOnMap == null) {
        return;
      }
      layerOnMap.removeLayerFromMap();
      layerOnMap = null;
    }
  
    @Override public void getSnapshot(final OnSnapshotReadyListener listener) {
      getGoogleMap().snapshot(new GoogleMap.SnapshotReadyCallback() {
        @Override public void onSnapshotReady(Bitmap bitmap) {
          listener.onSnapshotReady(bitmap);
        }
      });
    }
  
    @Override public void onDestroyView() {
      clearGeoJsonLayer();
      super.onDestroyView();
    }
  }
  