diff --git a/play-services-maps/core/hms/build.gradle b/play-services-maps/core/hms/build.gradle index 9ca2082e01..396b46de6e 100644 --- a/play-services-maps/core/hms/build.gradle +++ b/play-services-maps/core/hms/build.gradle @@ -9,6 +9,7 @@ apply plugin: 'kotlin-android' dependencies { implementation project(':play-services-base-core') implementation project(':play-services-maps') + implementation project(':play-services-location') implementation 'com.huawei.hms:maps:6.9.0.300' implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlinVersion" diff --git a/play-services-maps/core/hms/src/main/kotlin/org/microg/gms/maps/hms/GoogleMap.kt b/play-services-maps/core/hms/src/main/kotlin/org/microg/gms/maps/hms/GoogleMap.kt index 486aaa0ef2..17df50f50e 100644 --- a/play-services-maps/core/hms/src/main/kotlin/org/microg/gms/maps/hms/GoogleMap.kt +++ b/play-services-maps/core/hms/src/main/kotlin/org/microg/gms/maps/hms/GoogleMap.kt @@ -5,7 +5,9 @@ package org.microg.gms.maps.hms +import android.Manifest import android.content.Context +import android.content.pm.PackageManager import android.graphics.Bitmap import android.location.Location import android.os.* @@ -21,9 +23,12 @@ import android.widget.RelativeLayout import androidx.annotation.IdRes import androidx.annotation.Keep import androidx.collection.LongSparseArray +import androidx.core.app.ActivityCompat import com.google.android.gms.dynamic.IObjectWrapper import com.google.android.gms.dynamic.ObjectWrapper import com.google.android.gms.dynamic.unwrap +import com.google.android.gms.location.LocationListener +import com.google.android.gms.location.LocationServices import com.google.android.gms.maps.GoogleMap.MAP_TYPE_TERRAIN import com.google.android.gms.maps.GoogleMapOptions import com.google.android.gms.maps.internal.* @@ -31,6 +36,7 @@ import com.google.android.gms.maps.model.* import com.google.android.gms.maps.model.internal.* import com.huawei.hms.maps.CameraUpdate import com.huawei.hms.maps.HuaweiMap +import com.huawei.hms.maps.LocationSource import com.huawei.hms.maps.MapView import com.huawei.hms.maps.MapsInitializer import com.huawei.hms.maps.OnMapReadyCallback @@ -42,8 +48,12 @@ import com.huawei.hms.maps.internal.IOnPoiClickListener import com.huawei.hms.maps.model.Marker import org.microg.gms.maps.hms.model.* import org.microg.gms.maps.hms.utils.* +import com.google.android.gms.location.LocationRequest +import com.google.android.gms.location.Priority import java.util.concurrent.CopyOnWriteArrayList import java.util.concurrent.atomic.AtomicBoolean +import com.google.android.gms.maps.model.LatLng +import kotlin.math.abs private fun LongSparseArray.values() = (0 until size()).mapNotNull { valueAt(it) } @@ -102,6 +112,36 @@ class GoogleMapImpl(private val context: Context, var options: GoogleMapOptions) private var projectionImpl: ProjectionImpl? = null private var inDeveloperAnimation = false + private var locationEnabled: Boolean = false + private var isAddLocationCallback: Boolean = false + private var lastLocation: Location? = null + private var myLocationChangeListener: IOnMyLocationChangeListener? = null + + private val locationService by lazy { LocationServices.getFusedLocationProviderClient(context) } + private val locationCallback = LocationListener { location -> + lastLocation = location + try { + myLocationChangeListener?.onMyLocationChanged(ObjectWrapper.wrap(location)) + } catch (e: RemoteException) { + Log.w(TAG, "Failed to notify my-location listener", e) + } + val gcj02Location = Location(location).apply { + val hmsLatLng = LatLng(location.latitude, location.longitude).toHms() + latitude = hmsLatLng.latitude + longitude = hmsLatLng.longitude + } + mLocationChangedListener?.onLocationChanged(gcj02Location) + } + private var mLocationChangedListener: LocationSource.OnLocationChangedListener? = null + private var hwLocationSource: LocationSource = object : LocationSource { + override fun activate(listener: LocationSource.OnLocationChangedListener) { + mLocationChangedListener = listener + } + override fun deactivate() { + mLocationChangedListener = null + } + } + init { BitmapDescriptorFactoryImpl.initialize(context.resources) runOnMainLooper { @@ -223,7 +263,16 @@ class GoogleMapImpl(private val context: Context, var options: GoogleMapOptions) } override fun getCameraPosition(): CameraPosition { - return map?.cameraPosition?.toGms() ?: CameraPosition(LatLng(0.0, 0.0), 0f, 0f, 0f) + val raw = map?.cameraPosition?.toGms() ?: CameraPosition(LatLng(0.0, 0.0), 0f, 0f, 0f) + val shouldNormalizeOrigin = raw.zoom <= DEFAULT_HMS_MIN_ZOOM && + abs(raw.target.latitude) < INITIAL_CAMERA_ORIGIN_EPSILON && + abs(raw.target.longitude) < INITIAL_CAMERA_ORIGIN_EPSILON + val result = if (shouldNormalizeOrigin) { + CameraPosition(LatLng(0.0, 0.0), raw.zoom, raw.tilt, raw.bearing) + } else { + raw + } + return result } override fun getMaxZoomLevel(): Float = toHmsZoom(map?.maxZoomLevel ?: 18.toFloat()) override fun getMinZoomLevel(): Float = toHmsZoom(map?.minZoomLevel ?: 3.toFloat()) @@ -340,7 +389,7 @@ class GoogleMapImpl(private val context: Context, var options: GoogleMapOptions) override fun addMarker(options: MarkerOptions): IMarkerDelegate { val marker = MarkerImpl(this, "m${markerId++}", options) - if (map != null) { + if (map != null && initialized) { marker.update() } else { markers[marker.id] = marker @@ -425,16 +474,53 @@ class GoogleMapImpl(private val context: Context, var options: GoogleMapOptions) override fun setMyLocationEnabled(myLocation: Boolean) = afterInitialize { Log.d(TAG, "setMyLocationEnabled $myLocation") - it.isMyLocationEnabled = myLocation + synchronized(mapLock) { + locationEnabled = myLocation + try { + setLocationSource(null) + } catch (e: Exception) { + Log.w(TAG, e) + locationEnabled = false + } finally { + it.isMyLocationEnabled = locationEnabled + } + } } - override fun getMyLocation(): Location? { - Log.d(TAG, "deprecated Method: getMyLocation") - return null - } + override fun getMyLocation(): Location? = lastLocation override fun setLocationSource(locationSource: ILocationSourceDelegate?) = afterInitialize { - Log.d(TAG, "unimplemented Method: setLocationSource") + synchronized(mapLock) { + it.setLocationSource(hwLocationSource) + updateLocationEngineListener(locationEnabled) + } + } + + private fun updateLocationEngineListener(myLocation: Boolean) { + if (ActivityCompat.checkSelfPermission( + context, Manifest.permission.ACCESS_FINE_LOCATION + ) == PackageManager.PERMISSION_GRANTED || ActivityCompat.checkSelfPermission( + context, Manifest.permission.ACCESS_COARSE_LOCATION + ) == PackageManager.PERMISSION_GRANTED + ) { + if (myLocation) { + if (!isAddLocationCallback) { + isAddLocationCallback = true + locationService.requestLocationUpdates( + LocationRequest.Builder(DEFAULT_LOCATION_INTERVAL_MILLIS) + .setPriority(Priority.PRIORITY_HIGH_ACCURACY) + .setMinUpdateIntervalMillis(DEFAULT_LOCATION_INTERVAL_MILLIS) + .setMaxUpdateDelayMillis(DEFAULT_LOCATION_INTERVAL_MILLIS) + .build(), locationCallback, Looper.getMainLooper() + ) + } + } else { + if (isAddLocationCallback) { + isAddLocationCallback = false + locationService.removeLocationUpdates(locationCallback) + } + } + } } override fun setContentDescription(desc: String?) = afterInitialize { @@ -507,7 +593,7 @@ class GoogleMapImpl(private val context: Context, var options: GoogleMapOptions) override fun setOnMarkerClickListener(listener: IOnMarkerClickListener?) = afterInitialize { hmap -> hmap.setOnMarkerClickListener { - Log.d("GmsGoogleMap", "setOnMarkerClickListener marker id -> ${it.id}") + Log.d(TAG, "setOnMarkerClickListener marker id -> ${it.id}") listener?.onMarkerClick(markers[it.id]) ?: false } } @@ -557,6 +643,7 @@ class GoogleMapImpl(private val context: Context, var options: GoogleMapOptions) override fun setOnMyLocationChangeListener(listener: IOnMyLocationChangeListener?) = afterInitialize { Log.d(TAG, "deprecated Method: setOnMyLocationChangeListener") + myLocationChangeListener = listener } override fun setOnMyLocationButtonClickListener(listener: IOnMyLocationButtonClickListener?) = afterInitialize { @@ -630,11 +717,7 @@ class GoogleMapImpl(private val context: Context, var options: GoogleMapOptions) synchronized(mapLock) { if (loaded) { Log.d(TAG, "Invoking callback instantly, as map is loaded") - try { - scheduleExecute { callback.onMapLoaded() } - } catch (e: Exception) { - Log.w(TAG, e) - } + callback.scheduleExecute() } else { Log.d(TAG, "Delay callback invocation, as map is not yet loaded") loadedCallback = callback @@ -847,8 +930,7 @@ class GoogleMapImpl(private val context: Context, var options: GoogleMapOptions) } internalOnInitializedCallbackList.clear() fakeWatermark { Log.d(TAG_LOGO, "fakeWatermark success") } - scheduleExecute { loadedCallback?.onMapLoaded() } - + loadedCallback?.scheduleExecute() mapView?.visibility = View.VISIBLE } @@ -862,6 +944,7 @@ class GoogleMapImpl(private val context: Context, var options: GoogleMapOptions) override fun onPause() = mapView?.onPause() ?: Unit override fun onDestroy() { Log.d(TAG, "onDestroy") + locationService.removeLocationUpdates(locationCallback) initializedCallbackList.clear() internalOnInitializedCallbackList.clear() circles.map { it.value.remove() } @@ -877,6 +960,7 @@ class GoogleMapImpl(private val context: Context, var options: GoogleMapOptions) // TODO can crash? mapView?.onDestroy() mapView = null + mLocationChangedListener = null // Don't make it null; this object is not deleted immediately, and it may want to access map.* stuff //map = null @@ -884,6 +968,7 @@ class GoogleMapImpl(private val context: Context, var options: GoogleMapOptions) created = false initialized = false loaded = false + isAddLocationCallback = false } override fun onStart() { @@ -929,6 +1014,16 @@ class GoogleMapImpl(private val context: Context, var options: GoogleMapOptions) } } + private fun IOnMapLoadedCallback.scheduleExecute() { + Handler(Looper.getMainLooper()).postDelayed({ + try { + this.onMapLoaded() + } catch (e: Exception) { + Log.w(TAG, e) + } + }, ON_MAP_LOADED_CALLBACK_DELAY) + } + private var isInvokingInitializedCallbacks = AtomicBoolean(false) private fun tryRunUserInitializedCallbacks(tag: String = "") { @@ -955,7 +1050,7 @@ class GoogleMapImpl(private val context: Context, var options: GoogleMapOptions) Log.d("$TAG:$tag", "Invoking callback now, as map is initialized") val wasCallbackActive = isInvokingInitializedCallbacks.getAndSet(true) runOnMainLooper(forceQueue = wasCallbackActive) { - scheduleExecute { runCallbacks() } + runCallbacks() } if (!wasCallbackActive) isInvokingInitializedCallbacks.set(false) } else { @@ -975,17 +1070,14 @@ class GoogleMapImpl(private val context: Context, var options: GoogleMapOptions) } - private fun scheduleExecute(block:() -> Unit) { - Handler(Looper.getMainLooper()).postDelayed({ - try { block.invoke() } catch (_: Exception) {} - }, ON_MAP_CALLBACK_DELAY) - } - companion object { private const val TAG = "GmsGoogleMap" + private const val DEFAULT_HMS_MIN_ZOOM = 3f + private const val INITIAL_CAMERA_ORIGIN_EPSILON = 1e-5 private const val SNAPSHOT_OLD_VERSION_CODE = 4000000 private const val TAG_LOGO = "fakeWatermark" - private const val ON_MAP_CALLBACK_DELAY = 300L + private const val ON_MAP_LOADED_CALLBACK_DELAY = 500L + private const val DEFAULT_LOCATION_INTERVAL_MILLIS = 1000L } }