Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Verified Commit 73aca6ea authored by Marvin W.'s avatar Marvin W. 🐿️
Browse files

Various fixes and improvements to Maps API

parent 73fd85a8
Loading
Loading
Loading
Loading
Compare 7197b832 to bfe649fc
Original line number Diff line number Diff line
Subproject commit 7197b8320b4e6d55d15b76d4faf05adaba36bf72
Subproject commit bfe649fc36a40e2498f1ecf123edfb9278fe0a1d
+292 −96
Original line number Diff line number Diff line
@@ -16,18 +16,20 @@

package org.microg.gms.maps.mapbox

import android.annotation.SuppressLint
import android.content.Context
import android.location.Location
import android.os.Bundle
import android.os.Parcel
import android.os.RemoteException
import android.support.annotation.IdRes
import android.support.annotation.Keep
import android.support.v4.util.LongSparseArray
import android.util.DisplayMetrics
import android.util.Log
import android.view.Gravity
import android.view.View
import android.widget.FrameLayout
import android.widget.RelativeLayout
import com.google.android.gms.dynamic.IObjectWrapper
import com.google.android.gms.maps.GoogleMapOptions
import com.google.android.gms.maps.internal.*
@@ -38,6 +40,7 @@ import com.mapbox.mapboxsdk.LibraryLoader
import com.mapbox.mapboxsdk.Mapbox
import com.mapbox.mapboxsdk.R
import com.mapbox.mapboxsdk.camera.CameraUpdate
import com.mapbox.mapboxsdk.constants.MapboxConstants
import com.mapbox.mapboxsdk.maps.MapView
import com.mapbox.mapboxsdk.maps.MapboxMap
import com.mapbox.mapboxsdk.maps.Style
@@ -53,9 +56,9 @@ import org.microg.gms.maps.mapbox.utils.MultiArchLoader
import org.microg.gms.maps.mapbox.utils.toGms
import org.microg.gms.maps.mapbox.utils.toMapbox

fun <T : Any> LongSparseArray<T>.values() = (0..size()).map { valueAt(it) }.mapNotNull { it }
private fun <T : Any> LongSparseArray<T>.values() = (0..size()).map { valueAt(it) }.mapNotNull { it }

class GoogleMapImpl(private val context: Context, private val options: GoogleMapOptions) : IGoogleMapDelegate.Stub() {
class GoogleMapImpl(private val context: Context, var options: GoogleMapOptions) : IGoogleMapDelegate.Stub() {

    val view: FrameLayout
    var map: MapboxMap? = null
@@ -64,10 +67,10 @@ class GoogleMapImpl(private val context: Context, private val options: GoogleMap
        get() = context.resources.displayMetrics.densityDpi.toFloat() / DisplayMetrics.DENSITY_DEFAULT

    private var mapView: MapView?
    private var created = false
    private var initialized = false
    private val initializedCallbackList = mutableListOf<IOnMapReadyCallback>()
    private val mapLock = Object()
    val markers = mutableMapOf<Long, MarkerImpl>()

    private var cameraChangeListener: IOnCameraChangeListener? = null
    private var cameraMoveListener: IOnCameraMoveListener? = null
@@ -79,47 +82,130 @@ class GoogleMapImpl(private val context: Context, private val options: GoogleMap
    private var markerClickListener: IOnMarkerClickListener? = null
    private var markerDragListener: IOnMarkerDragListener? = null

    var circleManager: CircleManager? = null
    var lineManager: LineManager? = null
    var fillManager: FillManager? = null

    var circleManager: CircleManager? = null
    val pendingCircles = mutableSetOf<CircleImpl>()
    var circleId = 0L

    var symbolManager: SymbolManager? = null
    var storedMapType: Int = MAP_TYPE_NORMAL
    val pendingMarkers = mutableSetOf<MarkerImpl>()
    val markers = mutableMapOf<Long, MarkerImpl>()
    var markerId = 0L

    var storedMapType: Int = options.mapType
    val waitingCameraUpdates = mutableListOf<CameraUpdate>()

    init {
        val mapContext = MapContext(context)
        LibraryLoader.setLibraryLoader(MultiArchLoader(mapContext, context))
        Mapbox.getInstance(mapContext, BuildConfig.MAPBOX_KEY)

        val fakeWatermark = View(mapContext)
        fakeWatermark.layoutParams = object : RelativeLayout.LayoutParams(0, 0) {
            @SuppressLint("RtlHardcoded")
            override fun addRule(verb: Int, subject: Int) {
                super.addRule(verb, subject)
                val rules = this.rules
                var gravity = 0
                if (rules[RelativeLayout.ALIGN_PARENT_BOTTOM] == RelativeLayout.TRUE) gravity = gravity or Gravity.BOTTOM
                if (rules[RelativeLayout.ALIGN_PARENT_TOP] == RelativeLayout.TRUE) gravity = gravity or Gravity.TOP
                if (rules[RelativeLayout.ALIGN_PARENT_LEFT] == RelativeLayout.TRUE) gravity = gravity or Gravity.LEFT
                if (rules[RelativeLayout.ALIGN_PARENT_RIGHT] == RelativeLayout.TRUE) gravity = gravity or Gravity.RIGHT
                if (rules[RelativeLayout.ALIGN_PARENT_START] == RelativeLayout.TRUE) gravity = gravity or Gravity.START
                if (rules[RelativeLayout.ALIGN_PARENT_END] == RelativeLayout.TRUE) gravity = gravity or Gravity.END
                map?.uiSettings?.logoGravity = gravity
            }
        }
        this.view = object : FrameLayout(mapContext) {
            @Keep
            fun <T : View> findViewTraversal(@IdRes id: Int): T? {
                return null
            }

            @Keep
            fun <T : View> findViewWithTagTraversal(tag: Any): T? {
                if ("GoogleWatermark" == tag) {
                    return try {
                        @Suppress("UNCHECKED_CAST")
                        fakeWatermark as T
                    } catch (e: ClassCastException) {
                        null
                    }
                }
                return null
            }
        }
        this.mapView = MapView(mapContext)
        this.view.addView(this.mapView)
    }

    override fun getCameraPosition(): CameraPosition? = map?.cameraPosition?.toGms()
    override fun getMaxZoomLevel(): Float = map?.maxZoomLevel?.toFloat() ?: 20f
    override fun getMinZoomLevel(): Float = map?.minZoomLevel?.toFloat() ?: 1f

    override fun moveCamera(cameraUpdate: IObjectWrapper?) =
            cameraUpdate.unwrap<CameraUpdate>()?.let { map?.moveCamera(it) } ?: Unit
    override fun getMaxZoomLevel(): Float = (map?.maxZoomLevel?.toFloat() ?: 20f) + 1f
    override fun getMinZoomLevel(): Float = (map?.minZoomLevel?.toFloat() ?: 0f) + 1f

    override fun moveCamera(cameraUpdate: IObjectWrapper?) {
        val update = cameraUpdate.unwrap<CameraUpdate>() ?: return
        val map = this.map
        if (map != null) {
            map.moveCamera(update)
        } else {
            waitingCameraUpdates.add(update)
        }
    }

    override fun animateCamera(cameraUpdate: IObjectWrapper?) =
            cameraUpdate.unwrap<CameraUpdate>()?.let { map?.animateCamera(it) } ?: Unit
    override fun animateCamera(cameraUpdate: IObjectWrapper?) {
        val update = cameraUpdate.unwrap<CameraUpdate>() ?: return
        val map = this.map
        if (map != null) {
            map.animateCamera(update)
        } else {
            waitingCameraUpdates.add(update)
        }
    }

    override fun animateCameraWithCallback(cameraUpdate: IObjectWrapper?, callback: ICancelableCallback?) =
            cameraUpdate.unwrap<CameraUpdate>()?.let { map?.animateCamera(it, callback?.toMapbox()) }
                    ?: Unit
    override fun animateCameraWithCallback(cameraUpdate: IObjectWrapper?, callback: ICancelableCallback?) {
        val update = cameraUpdate.unwrap<CameraUpdate>() ?: return
        val map = this.map
        if (map != null) {
            map.animateCamera(update, callback?.toMapbox())
        } else {
            waitingCameraUpdates.add(update)
            callback?.onFinish()
        }
    }

    override fun animateCameraWithDurationAndCallback(cameraUpdate: IObjectWrapper?, duration: Int, callback: ICancelableCallback?) =
            cameraUpdate.unwrap<CameraUpdate>()?.let { map?.animateCamera(it, duration, callback?.toMapbox()) }
                    ?: Unit
    override fun animateCameraWithDurationAndCallback(cameraUpdate: IObjectWrapper?, duration: Int, callback: ICancelableCallback?) {
        val update = cameraUpdate.unwrap<CameraUpdate>() ?: return
        val map = this.map
        if (map != null) {
            map.animateCamera(update, duration, callback?.toMapbox())
        } else {
            waitingCameraUpdates.add(update)
            callback?.onFinish()
        }
    }

    override fun stopAnimation() = map?.cancelTransitions() ?: Unit

    override fun setMinZoomPreference(minZoom: Float) {
        map?.setMinZoomPreference(minZoom.toDouble() - 1)
    }

    override fun setMaxZoomPreference(maxZoom: Float) {
        map?.setMaxZoomPreference(maxZoom.toDouble() - 1)
    }

    override fun resetMinMaxZoomPreference() {
        map?.setMinZoomPreference(MapboxConstants.MINIMUM_ZOOM.toDouble())
        map?.setMaxZoomPreference(MapboxConstants.MAXIMUM_ZOOM.toDouble())
    }

    override fun setLatLngBoundsForCameraTarget(bounds: LatLngBounds?) {
        map?.setLatLngBoundsForCameraTarget(bounds?.toMapbox())
    }

    override fun addPolyline(options: PolylineOptions): IPolylineDelegate? {
        val lineOptions = LineOptions()
                .withLatLngs(options.points.map { it.toMapbox() })
@@ -131,26 +217,23 @@ class GoogleMapImpl(private val context: Context, private val options: GoogleMap


    override fun addPolygon(options: PolygonOptions): IPolygonDelegate? {
        Log.d(TAG, "unimplemented Method: addPolygon")
        return null
        val fillOptions = FillOptions()
                .withLatLngs(listOf(options.points.map { it.toMapbox() }))
                .withFillColor(ColorUtils.colorToRgbaString(options.fillColor))
                .withFillOpacity(if (options.isVisible) 1f else 0f)
        return fillManager?.let { PolygonImpl(this, it.create(fillOptions)) }
    }

    override fun addMarker(options: MarkerOptions): IMarkerDelegate? {
        var intBits = java.lang.Float.floatToIntBits(options.zIndex)
        if (intBits < 0) intBits = intBits xor 0x7fffffff

        val symbolOptions = SymbolOptions()
                .withIconOpacity(if (options.isVisible) options.alpha else 0f)
                .withIconRotate(options.rotation)
                .withZIndex(intBits)
                .withDraggable(options.isDraggable)

        options.position?.let { symbolOptions.withLatLng(it.toMapbox()) }
        options.icon?.remoteObject.unwrap<BitmapDescriptorImpl>()?.applyTo(symbolOptions, floatArrayOf(options.anchorU, options.anchorV), dpiFactor)

        val symbol = symbolManager?.create(symbolOptions) ?: return null
        val marker = MarkerImpl(this, symbol, floatArrayOf(options.anchorU, options.anchorV), options.icon?.remoteObject.unwrap<BitmapDescriptorImpl>(), options.alpha, options.title, options.snippet)
        markers.put(symbol.id, marker)
        val marker = MarkerImpl(this, "m${markerId++}", options)
        synchronized(this) {
            val symbolManager = symbolManager
            if (symbolManager == null) {
                pendingMarkers.add(marker)
            } else {
                marker.update(symbolManager)
            }
        }
        return marker
    }

@@ -165,16 +248,16 @@ class GoogleMapImpl(private val context: Context, private val options: GoogleMap
    }

    override fun addCircle(options: CircleOptions): ICircleDelegate? {
        val circleOptions = com.mapbox.mapboxsdk.plugins.annotation.CircleOptions()
                .withLatLng(options.center.toMapbox())
                .withCircleColor(ColorUtils.colorToRgbaString(options.fillColor))
                .withCircleRadius(options.radius.toFloat())
                .withCircleStrokeColor(ColorUtils.colorToRgbaString(options.strokeColor))
                .withCircleStrokeWidth(options.strokeWidth / dpiFactor)
                .withCircleOpacity(if (options.isVisible) 1f else 0f)
                .withCircleStrokeOpacity(if (options.isVisible) 1f else 0f)

        return circleManager?.let { CircleImpl(this, it.create(circleOptions)) }
        val circle = CircleImpl(this, "c${circleId++}", options)
        synchronized(this) {
            val circleManager = circleManager
            if (circleManager == null) {
                pendingCircles.add(circle)
            } else {
                circle.update(circleManager)
            }
        }
        return circle
    }

    override fun clear() {
@@ -208,20 +291,27 @@ class GoogleMapImpl(private val context: Context, private val options: GoogleMap
        val fills = fillManager?.annotations?.values()
        val symbols = symbolManager?.annotations?.values()
        val update: (Style) -> Unit = {
            circles?.let { circleManager?.update(it) }
            lines?.let { lineManager?.update(it) }
            fills?.let { fillManager?.update(it) }
            symbols?.let { symbolManager?.update(it) }
            circles?.let { runCatching { circleManager?.update(it) } }
            lines?.let { runCatching { lineManager?.update(it) } }
            fills?.let { runCatching { fillManager?.update(it) } }
            symbols?.let { runCatching { symbolManager?.update(it) } }
        }

        // TODO: Serve map styles locally
        when (storedMapType) {
            MAP_TYPE_NORMAL -> map?.setStyle(Style.Builder().fromUrl("mapbox://styles/microg/cjui4020201oo1fmca7yuwbor"), update)
            MAP_TYPE_SATELLITE -> map?.setStyle(Style.SATELLITE, update)
            MAP_TYPE_SATELLITE -> map?.setStyle(Style.Builder().fromUrl("mapbox://styles/microg/cjxgloted25ap1ct4uex7m6hi"), update)
            MAP_TYPE_TERRAIN -> map?.setStyle(Style.OUTDOORS, update)
            MAP_TYPE_HYBRID -> map?.setStyle(Style.SATELLITE_STREETS, update)
            else -> map?.setStyle(Style.LIGHT, update)
            MAP_TYPE_HYBRID -> map?.setStyle(Style.Builder().fromUrl("mapbox://styles/microg/cjxgloted25ap1ct4uex7m6hi"), update)
            //MAP_TYPE_NONE, MAP_TYPE_NORMAL,
            else -> map?.setStyle(Style.Builder().fromUrl("mapbox://styles/microg/cjui4020201oo1fmca7yuwbor"), update)
        }

        map?.let { BitmapDescriptorFactoryImpl.registerMap(it) }

    }

    override fun setWatermarkEnabled(watermark: Boolean) {
        map?.uiSettings?.isLogoEnabled = watermark
    }

    override fun isTrafficEnabled(): Boolean {
@@ -261,7 +351,10 @@ class GoogleMapImpl(private val context: Context, private val options: GoogleMap

    override fun setLocationSource(locationSource: ILocationSourceDelegate) {
        Log.d(TAG, "unimplemented Method: setLocationSource")
    }

    override fun setContentDescription(desc: String?) {
        mapView?.contentDescription = desc
    }

    override fun getUiSettings(): IUiSettingsDelegate? = map?.uiSettings?.let { UiSettingsImpl(it) }
@@ -360,80 +453,159 @@ class GoogleMapImpl(private val context: Context, private val options: GoogleMap
        cameraIdleListener = listener
    }

    fun onCreate(savedInstanceState: Bundle?) {
    override fun onCreate(savedInstanceState: Bundle?) {
        if (!created) {
            mapView?.onCreate(savedInstanceState?.toMapbox())
            mapView?.getMapAsync(this::initMap)
            created = true
        }
    }

    private fun hasSymbolAt(latlng: com.mapbox.mapboxsdk.geometry.LatLng): Boolean {
        val point = map?.projection?.toScreenLocation(latlng) ?: return false
        val features = map?.queryRenderedFeatures(point, SymbolManager.ID_GEOJSON_LAYER)
                ?: return false
        return !features.isEmpty()
        return features.isNotEmpty()
    }

    private fun initMap(map: MapboxMap) {
        if (this.map != null) return
        this.map = map

        map.addOnCameraIdleListener {
            try {
                cameraChangeListener?.onCameraChange(map.cameraPosition.toGms())
            } catch (e: Exception) {
                Log.w(TAG, e)
            }
        }
        map.addOnCameraIdleListener {
            try {
                cameraIdleListener?.onCameraIdle()
            } catch (e: Exception) {
                Log.w(TAG, e)
            }
        }
        map.addOnCameraMoveListener {
            try {
                cameraMoveListener?.onCameraMove()
            } catch (e: Exception) {
                Log.w(TAG, e)
            }
        }
        map.addOnCameraMoveStartedListener {
            try {
                cameraMoveStartedListener?.onCameraMoveStarted(it)
            } catch (e: Exception) {
                Log.w(TAG, e)
            }
        }
        map.addOnCameraMoveCancelListener {
            try {
                cameraMoveCanceledListener?.onCameraMoveCanceled()
            } catch (e: Exception) {
                Log.w(TAG, e)
            }
        }
        map.addOnMapClickListener { latlng ->
            try {
                mapClickListener?.let { if (!hasSymbolAt(latlng)) it.onMapClick(latlng.toGms()); }
            } catch (e: Exception) {
                Log.w(TAG, e)
            }
            false
        }
        map.addOnMapLongClickListener { latlng ->
            try {
                mapLongClickListener?.let { if (!hasSymbolAt(latlng)) it.onMapLongClick(latlng.toGms()); }
            } catch (e: Exception) {
                Log.w(TAG, e)
            }
            false
        }

        applyMapType()
        options.minZoomPreference?.let { if (it != 0f) map.setMinZoomPreference(it.toDouble()) }
        options.maxZoomPreference?.let { if (it != 0f) map.setMaxZoomPreference(it.toDouble()) }
        options.latLngBoundsForCameraTarget?.let { map.setLatLngBoundsForCameraTarget(it.toMapbox()) }
        options.compassEnabled?.let { map.uiSettings.isCompassEnabled = it }
        options.rotateGesturesEnabled?.let { map.uiSettings.isRotateGesturesEnabled = it }
        options.scrollGesturesEnabled?.let { map.uiSettings.isScrollGesturesEnabled = it }
        options.tiltGesturesEnabled?.let { map.uiSettings.isTiltGesturesEnabled = it }
        options.camera?.let { map.cameraPosition = it.toMapbox() }
        waitingCameraUpdates.forEach { map.moveCamera(it) }

        synchronized(mapLock) {
            Log.d(TAG, "Invoking ${initializedCallbackList.size} callbacks delayed, as map is initialized")
            for (callback in initializedCallbackList) {
                try {
                    callback.onMapReady(this)
                } catch (e: Exception) {
                    Log.w(TAG, e)
                }
            }
            initialized = true
        }

        map.getStyle {
            mapView?.let { view ->
                BitmapDescriptorFactoryImpl.registerMap(map)
                circleManager = CircleManager(view, map, it)
                lineManager = LineManager(view, map, it)
                lineManager?.lineCap = LINE_CAP_ROUND
                fillManager = FillManager(view, map, it)
                symbolManager = SymbolManager(view, map, it)
                lineManager?.lineCap = LINE_CAP_ROUND
                synchronized(this) {
                    val symbolManager = SymbolManager(view, map, it)
                    pendingMarkers.forEach { it.update(symbolManager) }
                    pendingMarkers.clear()
                    this.symbolManager = symbolManager
                }
                symbolManager?.iconAllowOverlap = true
                symbolManager?.addClickListener { markers[it.id]?.let { markerClickListener?.onMarkerClick(it) } }
                symbolManager?.addClickListener {
                    try {
                        markers[it.id]?.let { markerClickListener?.onMarkerClick(it) }
                    } catch (e: Exception) {
                        Log.w(TAG, e)
                    }
                }
                symbolManager?.addDragListener(object : OnSymbolDragListener {
                    override fun onAnnotationDragStarted(annotation: Symbol?) {
                        try {
                            markers[annotation?.id]?.let { markerDragListener?.onMarkerDragStart(it) }
                        } catch (e: Exception) {
                            Log.w(TAG, e)
                        }
                    }

                    override fun onAnnotationDrag(annotation: Symbol?) {
                        try {
                            markers[annotation?.id]?.let { markerDragListener?.onMarkerDrag(it) }
                        } catch (e: Exception) {
                            Log.w(TAG, e)
                        }
                    }

                    override fun onAnnotationDragFinished(annotation: Symbol?) {
                        try {
                            markers[annotation?.id]?.let { markerDragListener?.onMarkerDragEnd(it) }
                        } catch (e: Exception) {
                            Log.w(TAG, e)
                        }
                    }

                })
                map.addOnCameraIdleListener { cameraChangeListener?.onCameraChange(map.cameraPosition.toGms()) }
                map.addOnCameraIdleListener { cameraIdleListener?.onCameraIdle() }
                map.addOnCameraMoveListener { cameraMoveListener?.onCameraMove() }
                map.addOnCameraMoveStartedListener { cameraMoveStartedListener?.onCameraMoveStarted(it) }
                map.addOnCameraMoveCancelListener { cameraMoveCanceledListener?.onCameraMoveCanceled() }
                map.addOnMapClickListener {
                    val latlng = it
                    mapClickListener?.let { if (!hasSymbolAt(latlng)) it.onMapClick(latlng.toGms()); }
                    false
                }
                map.addOnMapLongClickListener {
                    val latlng = it
                    mapLongClickListener?.let { if (!hasSymbolAt(latlng)) it.onMapLongClick(latlng.toGms()); }
                    false
                }

                synchronized(mapLock) {
                    for (callback in initializedCallbackList) {
                        try {
                            callback.onMapReady(this)
                        } catch (e: RemoteException) {
                            Log.w(TAG, e)
                        }
                    }
                    initialized = true
            }
        }
    }

    override fun useViewLifecycleWhenInFragment(): Boolean {
        Log.d(TAG, "unimplemented Method: useViewLifecycleWhenInFragment")
        return false
    }

    fun onResume() = mapView?.onResume()
    fun onPause() = mapView?.onPause()
    fun onDestroy() {
    override fun onResume() = mapView?.onResume() ?: Unit
    override fun onPause() = mapView?.onPause() ?: Unit
    override fun onDestroy() {
        circleManager?.onDestroy()
        circleManager = null
        lineManager?.onDestroy()
@@ -442,6 +614,8 @@ class GoogleMapImpl(private val context: Context, private val options: GoogleMap
        fillManager = null
        symbolManager?.onDestroy()
        symbolManager = null
        pendingMarkers.clear()
        markers.clear()
        BitmapDescriptorFactoryImpl.unregisterMap(map)
        view.removeView(mapView)
        // TODO can crash?
@@ -449,8 +623,24 @@ class GoogleMapImpl(private val context: Context, private val options: GoogleMap
        mapView = null
    }

    fun onLowMemory() = mapView?.onLowMemory()
    fun onSaveInstanceState(outState: Bundle) {
    override fun onStart() {
        Log.d(TAG, "unimplemented Method: onStart")
    }

    override fun onStop() {
        Log.d(TAG, "unimplemented Method: onStop")
    }

    override fun onEnterAmbient(bundle: Bundle?) {
        Log.d(TAG, "unimplemented Method: onEnterAmbient")
    }

    override fun onExitAmbient() {
        Log.d(TAG, "unimplemented Method: onExitAmbient")
    }

    override fun onLowMemory() = mapView?.onLowMemory() ?: Unit
    override fun onSaveInstanceState(outState: Bundle) {
        val newBundle = Bundle()
        mapView?.onSaveInstanceState(newBundle)
        outState.putAll(newBundle.toGms())
@@ -459,8 +649,14 @@ class GoogleMapImpl(private val context: Context, private val options: GoogleMap
    fun getMapAsync(callback: IOnMapReadyCallback) {
        synchronized(mapLock) {
            if (initialized) {
                Log.d(TAG, "Invoking callback instantly, as map is initialized")
                try {
                    callback.onMapReady(this)
                } catch (e: Exception) {
                    Log.w(TAG, e)
                }
            } else {
                Log.d(TAG, "Delay callback invocation, as map is not yet initialized")
                initializedCallbackList.add(callback)
            }
        }
Loading