diff --git a/build.gradle b/build.gradle index e49cbac140..6ca6e4cdc6 100644 --- a/build.gradle +++ b/build.gradle @@ -39,11 +39,17 @@ buildscript { ext.localProperties = new Properties() try { - ext.localProperties.load(rootProject.file('local.properties').newDataInputStream()) + var stream = rootProject.file('local.properties').newDataInputStream() + ext.localProperties.load(stream) + stream.close() } catch (ignored) { // Ignore } + ext.hasModule = (String name, boolean enabledByDefault) -> { + return ext.localProperties.getProperty("modules." + name, enabledByDefault.toString()).toBoolean() + } + repositories { mavenCentral() google() @@ -111,6 +117,7 @@ subprojects { repositories { mavenCentral() google() + if (hasModule("hms", false)) maven {url 'https://developer.huawei.com/repo/'} } } diff --git a/play-services-core/build.gradle b/play-services-core/build.gradle index 8b97fa8cea..0112728623 100644 --- a/play-services-core/build.gradle +++ b/play-services-core/build.gradle @@ -6,13 +6,10 @@ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' -def hasModule(String name, boolean enabledByDefault) { - return localProperties.getProperty("modules." + name, enabledByDefault.toString()).toBoolean() -} - configurations { mapboxRuntimeOnly vtmRuntimeOnly + if (hasModule("hms", true)) hmsRuntimeOnly defaultRuntimeOnly } @@ -69,6 +66,7 @@ dependencies { defaultRuntimeOnly project(':play-services-location-core-provider') if (hasModule("nearby", true)) runtimeOnly project(':play-services-nearby-core-package') + if (hasModule("hms", false)) hmsRuntimeOnly project(':play-services-maps-core-hms') // AndroidX UI implementation "androidx.multidex:multidex:$multidexVersion" @@ -149,6 +147,9 @@ android { dimension 'target' versionNameSuffix "-hw" } + "hms" { + dimension 'maps' + } "mapbox" { dimension 'maps' } diff --git a/play-services-maps/core/hms/build.gradle b/play-services-maps/core/hms/build.gradle new file mode 100644 index 0000000000..9ca2082e01 --- /dev/null +++ b/play-services-maps/core/hms/build.gradle @@ -0,0 +1,61 @@ +/* + * SPDX-FileCopyrightText: 2023 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' + +dependencies { + implementation project(':play-services-base-core') + implementation project(':play-services-maps') + + implementation 'com.huawei.hms:maps:6.9.0.300' + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlinVersion" +} + +android { + namespace "org.microg.gms.maps.hms" + + compileSdkVersion androidCompileSdk + buildToolsVersion "$androidBuildVersionTools" + + defaultConfig { + versionName version + minSdkVersion androidMinSdk + targetSdkVersion androidTargetSdk + buildConfigField "String", "HMSMAP_KEY", "\"${localProperties.getProperty("hmsmap.key", "")}\"" + + ndk { + abiFilters "armeabi", "armeabi-v7a", "arm64-v8a" + } + } + + buildFeatures { + buildConfig = true + } + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + } + + lintOptions { + disable 'GradleCompatible' + } + + buildTypes { + release { + minifyEnabled true + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlinOptions { + jvmTarget = 1.8 + } +} diff --git a/play-services-maps/core/hms/proguard-rules.pro b/play-services-maps/core/hms/proguard-rules.pro new file mode 100644 index 0000000000..74a78e657e --- /dev/null +++ b/play-services-maps/core/hms/proguard-rules.pro @@ -0,0 +1,9 @@ +-ignorewarnings +-keepattributes *Annotation* +-keepattributes Exceptions +-keepattributes InnerClasses +-keepattributes Signature +-keepattributes SourceFile,LineNumberTable +-keep class com.huawei.hianalytics.**{*;} +-keep class com.huawei.updatesdk.**{*;} +-keep class com.huawei.hms.**{*;} \ No newline at end of file diff --git a/play-services-maps/core/hms/src/main/AndroidManifest.xml b/play-services-maps/core/hms/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..1e55b5b68f --- /dev/null +++ b/play-services-maps/core/hms/src/main/AndroidManifest.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/play-services-maps/core/hms/src/main/assets/.gitignore b/play-services-maps/core/hms/src/main/assets/.gitignore new file mode 100644 index 0000000000..47e1b18ecd --- /dev/null +++ b/play-services-maps/core/hms/src/main/assets/.gitignore @@ -0,0 +1 @@ +agconnect-services.json diff --git a/play-services-maps/core/hms/src/main/java/com/google/android/gms/dynamite/descriptors/com/google/android/gms/maps_dynamite/ModuleDescriptor.java b/play-services-maps/core/hms/src/main/java/com/google/android/gms/dynamite/descriptors/com/google/android/gms/maps_dynamite/ModuleDescriptor.java new file mode 100644 index 0000000000..edfbe6c36a --- /dev/null +++ b/play-services-maps/core/hms/src/main/java/com/google/android/gms/dynamite/descriptors/com/google/android/gms/maps_dynamite/ModuleDescriptor.java @@ -0,0 +1,14 @@ +/* + * SPDX-FileCopyrightText: 2023 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.dynamite.descriptors.com.google.android.gms.maps_dynamite; + +import androidx.annotation.Keep; + +@Keep +public class ModuleDescriptor { + public static final String MODULE_ID = "com.google.android.gms.maps_dynamite"; + public static final int MODULE_VERSION = 1; +} diff --git a/play-services-maps/core/hms/src/main/java/com/google/android/gms/maps/internal/CreatorImpl.java b/play-services-maps/core/hms/src/main/java/com/google/android/gms/maps/internal/CreatorImpl.java new file mode 100644 index 0000000000..a37ea464bb --- /dev/null +++ b/play-services-maps/core/hms/src/main/java/com/google/android/gms/maps/internal/CreatorImpl.java @@ -0,0 +1,79 @@ +/* + * SPDX-FileCopyrightText: 2023 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.maps.internal; + +import android.app.Activity; +import android.content.Context; +import android.content.res.Resources; +import android.os.Parcel; +import android.os.RemoteException; +import androidx.annotation.Keep; +import android.util.Log; + +import com.google.android.gms.dynamic.IObjectWrapper; +import com.google.android.gms.dynamic.ObjectWrapper; +import com.google.android.gms.maps.GoogleMapOptions; +import com.google.android.gms.maps.model.internal.IBitmapDescriptorFactoryDelegate; + +import com.huawei.hms.maps.MapsInitializer; +import org.microg.gms.maps.hms.CameraUpdateFactoryImpl; +import org.microg.gms.maps.hms.MapFragmentImpl; +import org.microg.gms.maps.hms.MapViewImpl; +import org.microg.gms.maps.hms.model.BitmapDescriptorFactoryImpl; + +@Keep +public class CreatorImpl extends ICreator.Stub { + private static final String TAG = "GmsMapCreator"; + + @Override + public void init(IObjectWrapper resources) { + initV2(resources, 0); + } + + @Override + public IMapFragmentDelegate newMapFragmentDelegate(IObjectWrapper activity) { + return new MapFragmentImpl(ObjectWrapper.unwrapTyped(activity, Activity.class)); + } + + @Override + public IMapViewDelegate newMapViewDelegate(IObjectWrapper context, GoogleMapOptions options) { + return new MapViewImpl(ObjectWrapper.unwrapTyped(context, Context.class), options); + } + + @Override + public ICameraUpdateFactoryDelegate newCameraUpdateFactoryDelegate() { + return new CameraUpdateFactoryImpl(); + } + + @Override + public IBitmapDescriptorFactoryDelegate newBitmapDescriptorFactoryDelegate() { + return BitmapDescriptorFactoryImpl.INSTANCE; + } + + @Override + public void initV2(IObjectWrapper resources, int flags) { + BitmapDescriptorFactoryImpl.INSTANCE.initialize(ObjectWrapper.unwrapTyped(resources, Resources.class)); + //ResourcesContainer.set((Resources) ObjectWrapper.unwrap(resources)); + Log.d(TAG, "initV2 " + flags); + } + + @Override + public int getRendererType() throws RemoteException { + return 2; + } + + @Override + public void logInitialization(IObjectWrapper context, int preferredRenderer) throws RemoteException { + Log.d(TAG, "HMS-based Map initialized (preferred renderer was " + preferredRenderer + ")"); + } + + @Override + public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException { + if (super.onTransact(code, data, reply, flags)) return true; + Log.d(TAG, "onTransact [unknown]: " + code + ", " + data + ", " + flags); + return false; + } +} diff --git a/play-services-maps/core/hms/src/main/kotlin/org/microg/gms/maps/hms/CameraUpdateFactory.kt b/play-services-maps/core/hms/src/main/kotlin/org/microg/gms/maps/hms/CameraUpdateFactory.kt new file mode 100644 index 0000000000..7be84bda55 --- /dev/null +++ b/play-services-maps/core/hms/src/main/kotlin/org/microg/gms/maps/hms/CameraUpdateFactory.kt @@ -0,0 +1,71 @@ +/* + * SPDX-FileCopyrightText: 2023 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.maps.hms + +import android.graphics.Point +import android.os.Parcel +import android.util.Log +import com.google.android.gms.dynamic.IObjectWrapper +import com.google.android.gms.dynamic.ObjectWrapper +import com.google.android.gms.maps.internal.ICameraUpdateFactoryDelegate +import com.google.android.gms.maps.model.CameraPosition +import com.google.android.gms.maps.model.LatLng +import com.google.android.gms.maps.model.LatLngBounds +import com.huawei.hms.maps.CameraUpdateFactory +import org.microg.gms.maps.hms.utils.toHms +import org.microg.gms.maps.hms.utils.toHmsZoom + +class CameraUpdateFactoryImpl : ICameraUpdateFactoryDelegate.Stub() { + + override fun zoomIn(): IObjectWrapper = ObjectWrapper.wrap(CameraUpdateFactory.zoomIn()) + override fun zoomOut(): IObjectWrapper = ObjectWrapper.wrap(CameraUpdateFactory.zoomOut()) + + override fun zoomTo(zoom: Float): IObjectWrapper = + ObjectWrapper.wrap(CameraUpdateFactory.zoomTo(toHmsZoom(zoom))) + + override fun zoomBy(zoomDelta: Float): IObjectWrapper = + ObjectWrapper.wrap(CameraUpdateFactory.zoomBy(zoomDelta)) + + override fun zoomByWithFocus(zoomDelta: Float, x: Int, y: Int): IObjectWrapper = + ObjectWrapper.wrap(CameraUpdateFactory.zoomBy(zoomDelta, Point(x, y))) + + override fun newCameraPosition(cameraPosition: CameraPosition): IObjectWrapper = + ObjectWrapper.wrap(CameraUpdateFactory.newCameraPosition(cameraPosition.toHms())) + + override fun newLatLng(latLng: LatLng): IObjectWrapper = + ObjectWrapper.wrap(CameraUpdateFactory.newLatLng(latLng.toHms())) + + override fun newLatLngZoom(latLng: LatLng, zoom: Float): IObjectWrapper = + ObjectWrapper.wrap(CameraUpdateFactory.newLatLngZoom(latLng.toHms(), + toHmsZoom(zoom) + )) + + override fun newLatLngBounds(bounds: LatLngBounds, padding: Int): IObjectWrapper = + ObjectWrapper.wrap(CameraUpdateFactory.newLatLngBounds(bounds.toHms(), padding)) + + override fun scrollBy(x: Float, y: Float): IObjectWrapper { + Log.d(TAG, "scrollBy: $x, $y") + // gms map: A positive value moves the camera downwards + // hms map: A positive value moves the camera upwards + return ObjectWrapper.wrap(CameraUpdateFactory.scrollBy(x, -y)) + } + + override fun newLatLngBoundsWithSize(bounds: LatLngBounds, width: Int, height: Int, padding: Int): IObjectWrapper = + ObjectWrapper.wrap(CameraUpdateFactory.newLatLngBounds(bounds.toHms(), width, height, padding)) + + override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean = + if (super.onTransact(code, data, reply, flags)) { + true + } else { + Log.d(TAG, "onTransact [unknown]: $code, $data, $flags"); false + } + + companion object { + private val TAG = "GmsCameraUpdate" + } +} + + 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 new file mode 100644 index 0000000000..da1d0e2ef1 --- /dev/null +++ b/play-services-maps/core/hms/src/main/kotlin/org/microg/gms/maps/hms/GoogleMap.kt @@ -0,0 +1,768 @@ +/* + * SPDX-FileCopyrightText: 2023 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.maps.hms + +import android.content.Context +import android.graphics.Bitmap +import android.location.Location +import android.os.* +import android.util.DisplayMetrics +import android.util.Log +import android.view.View +import android.view.ViewGroup +import android.widget.FrameLayout +import android.widget.LinearLayout +import androidx.collection.LongSparseArray +import com.google.android.gms.dynamic.IObjectWrapper +import com.google.android.gms.dynamic.unwrap +import com.google.android.gms.maps.GoogleMap.MAP_TYPE_HYBRID +import com.google.android.gms.maps.GoogleMap.MAP_TYPE_SATELLITE +import com.google.android.gms.maps.GoogleMap.MAP_TYPE_TERRAIN +import com.google.android.gms.maps.GoogleMapOptions +import com.google.android.gms.maps.internal.* +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.HuaweiMapOptions +import com.huawei.hms.maps.MapView +import com.huawei.hms.maps.MapsInitializer +import com.huawei.hms.maps.internal.IOnIndoorStateChangeListener +import com.huawei.hms.maps.internal.IOnInfoWindowCloseListener +import com.huawei.hms.maps.internal.IOnInfoWindowLongClickListener +import com.huawei.hms.maps.internal.IOnPoiClickListener +import com.huawei.hms.maps.model.Marker +import org.microg.gms.common.Constants +import org.microg.gms.maps.hms.model.* +import org.microg.gms.maps.hms.utils.* + + +private fun LongSparseArray.values() = (0 until size()).mapNotNull { valueAt(it) } + +fun runOnMainLooper(method: () -> Unit) { + if (Looper.myLooper() == Looper.getMainLooper()) { + method() + } else { + Handler(Looper.getMainLooper()).post { + method() + } + } +} + +class GoogleMapImpl(private val context: Context, var options: GoogleMapOptions) : IGoogleMapDelegate.Stub() { + + val view: FrameLayout + var map: HuaweiMap? = null + private set + val dpiFactor: Float + get() = context.resources.displayMetrics.densityDpi.toFloat() / DisplayMetrics.DENSITY_DEFAULT + + private var mapView: MapView? = null + private var created = false + private var initialized = false + private var loaded = false + private val mapLock = Object() + + private val initializedCallbackList = mutableListOf() + private var loadedCallback: IOnMapLoadedCallback? = null + private var cameraChangeListener: IOnCameraChangeListener? = null + private var cameraMoveListener: IOnCameraMoveListener? = null + private var cameraMoveCanceledListener: IOnCameraMoveCanceledListener? = null + private var cameraMoveStartedListener: IOnCameraMoveStartedListener? = null + private var cameraIdleListener: IOnCameraIdleListener? = null + private var mapClickListener: IOnMapClickListener? = null + private var mapLongClickListener: IOnMapLongClickListener? = null + + private val groundOverlays = mutableMapOf() + private val markers = mutableMapOf() + private val polylines = mutableMapOf() + private val polygons = mutableMapOf() + private val circles = mutableMapOf() + private val tileOverlays = mutableMapOf() + + private var storedMapType: Int = options.mapType + val waitingCameraUpdates = mutableListOf() + var locationEnabled: Boolean = false + + init { + val mapContext = MapContext(context) + BitmapDescriptorFactoryImpl.initialize(context.resources) + runOnMainLooper { + MapsInitializer.setApiKey(BuildConfig.HMSMAP_KEY) + } + + this.view = object : FrameLayout(mapContext) {} + } + + override fun getCameraPosition(): CameraPosition? = map?.cameraPosition?.toGms() + override fun getMaxZoomLevel(): Float = toHmsZoom(map?.maxZoomLevel ?: 18.toFloat()) + override fun getMinZoomLevel(): Float = toHmsZoom(map?.minZoomLevel ?: 3.toFloat()) + + override fun moveCamera(cameraUpdate: IObjectWrapper?) { + val update = cameraUpdate.unwrap() ?: return + synchronized(mapLock) { + if (initialized) { + this.map?.moveCamera(update) + } else { + waitingCameraUpdates.add(update) + } + } + } + + override fun animateCamera(cameraUpdate: IObjectWrapper?) { + val update = cameraUpdate.unwrap() ?: return + synchronized(mapLock) { + if (initialized) { + this.map?.animateCamera(update) + } else { + waitingCameraUpdates.add(update) + } + } + } + + fun afterInitialized(runnable: () -> Unit) { + initializedCallbackList.add(object : IOnMapReadyCallback { + override fun onMapReady(map: IGoogleMapDelegate?) { + runnable() + } + + override fun asBinder(): IBinder? = null + }) + } + + override fun animateCameraWithCallback(cameraUpdate: IObjectWrapper?, callback: ICancelableCallback?) { + val update = cameraUpdate.unwrap() ?: return + synchronized(mapLock) { + if (initialized) { + this.map?.animateCamera(update, callback?.toHms()) + } else { + waitingCameraUpdates.add(update) + afterInitialized { callback?.onFinish() } + } + } + } + + override fun animateCameraWithDurationAndCallback(cameraUpdate: IObjectWrapper?, duration: Int, callback: ICancelableCallback?) { + val update = cameraUpdate.unwrap() ?: return + synchronized(mapLock) { + if (initialized) { + this.map?.animateCamera(update, duration, callback?.toHms()) + } else { + waitingCameraUpdates.add(update) + afterInitialized { callback?.onFinish() } + } + } + } + + override fun stopAnimation() = map?.stopAnimation() ?: Unit + + override fun setMapStyle(options: MapStyleOptions?): Boolean { + Log.d(TAG, "unimplemented Method: setMapStyle ${options?.getJson()}") + return true + } + + override fun setMinZoomPreference(minZoom: Float) { + map?.setMinZoomPreference(toHmsZoom(minZoom)) + } + + override fun setMaxZoomPreference(maxZoom: Float) { + map?.setMaxZoomPreference(toHmsZoom(maxZoom)) + } + + override fun resetMinMaxZoomPreference() { + map?.setMinZoomPreference(3.toFloat()) + map?.setMaxZoomPreference(18.toFloat()) + } + + override fun setLatLngBoundsForCameraTarget(bounds: LatLngBounds?) { + map?.setLatLngBoundsForCameraTarget(bounds?.toHms()) + } + + override fun addPolyline(options: PolylineOptions): IPolylineDelegate? { + val polyline = map?.addPolyline(options.toHms()) ?: return null + val polylineImpl = PolylineImpl(polyline, options) + polylines[polylineImpl.id] = polylineImpl + return polylineImpl + } + + override fun addPolygon(options: PolygonOptions): IPolygonDelegate? { + val polygon = map?.addPolygon(options.toHms()) ?: return null + val polygonImpl = PolygonImpl(polygon) + polygons[polygonImpl.id] = polygonImpl + return polygonImpl + } + + override fun addMarker(options: MarkerOptions): IMarkerDelegate? { + val marker = map?.addMarker(options.toHms()) ?: return null + val markerImpl = MarkerImpl(marker) + markers[markerImpl.id] = markerImpl + return markerImpl + } + + override fun addGroundOverlay(options: GroundOverlayOptions): IGroundOverlayDelegate? { + Log.d(TAG, "Method: addGroundOverlay") + val groundOverlay = map?.addGroundOverlay(options.toHms()) ?: return null + val groundOverlayImpl = GroundOverlayImpl(groundOverlay) + groundOverlays[groundOverlayImpl.id] = groundOverlayImpl + return groundOverlayImpl + } + + override fun addTileOverlay(options: TileOverlayOptions): ITileOverlayDelegate? { + Log.d(TAG, "Method: addTileOverlay") + val tileOverlay = map?.addTileOverlay(options.toHms()) ?: return null + val tileOverlayImpl = TileOverlayImpl(tileOverlay) + tileOverlays[tileOverlayImpl.id] = tileOverlayImpl + return tileOverlayImpl + } + + override fun addCircle(options: CircleOptions): ICircleDelegate? { + val circle = map?.addCircle(options.toHms()) ?: return null + val circleImpl = CircleImpl(circle) + circles[circleImpl.id] = circleImpl + return circleImpl + } + + override fun clear() { + map?.clear() + } + + + override fun getMapType(): Int { + return map?.mapType ?: storedMapType + } + + override fun setMapType(type: Int) { + storedMapType = type + applyMapType() + } + + fun applyMapType() { + // TODO: Serve map styles locally + when (storedMapType) { + MAP_TYPE_SATELLITE -> map?.mapType = HuaweiMap.MAP_TYPE_SATELLITE + MAP_TYPE_TERRAIN -> map?.mapType = HuaweiMap.MAP_TYPE_TERRAIN + MAP_TYPE_HYBRID -> map?.mapType = HuaweiMap.MAP_TYPE_HYBRID + //MAP_TYPE_NONE, MAP_TYPE_NORMAL, + else -> map?.mapType = HuaweiMap.MAP_TYPE_NORMAL + } + // map?.let { BitmapDescriptorFactoryImpl.registerMap(it) } + } + + override fun isTrafficEnabled(): Boolean { + return map?.isTrafficEnabled ?: false + } + + override fun setTrafficEnabled(traffic: Boolean) { + Log.d(TAG, "setTrafficEnabled") + map?.isTrafficEnabled = traffic + } + + override fun isIndoorEnabled(): Boolean { + Log.d(TAG, "isIndoorEnabled") + return map?.isIndoorEnabled ?: false + } + + override fun setIndoorEnabled(indoor: Boolean) { + Log.d(TAG, "setIndoorEnabled") + map?.isIndoorEnabled = indoor + } + + override fun isMyLocationEnabled(): Boolean { + return map?.isMyLocationEnabled ?: false + } + + override fun setMyLocationEnabled(myLocation: Boolean) { + map?.isMyLocationEnabled = myLocation + } + + override fun getMyLocation(): Location? { + Log.d(TAG, "deprecated Method: getMyLocation") + return null + } + + override fun setLocationSource(locationSource: ILocationSourceDelegate?) { + Log.d(TAG, "unimplemented Method: setLocationSource") + } + + override fun setContentDescription(desc: String?) { + map?.setContentDescription(desc) + } + + override fun getUiSettings(): IUiSettingsDelegate? = map?.uiSettings?.let { UiSettingsImpl(it) } + + override fun getProjection(): IProjectionDelegate? = map?.projection?.let { + Log.d(TAG, "getProjection") + ProjectionImpl(it) + } + + override fun setOnCameraChangeListener(listener: IOnCameraChangeListener?) { + Log.d(TAG, "setOnCameraChangeListener"); + cameraChangeListener = listener + map?.setOnCameraIdleListener { + try { + cameraChangeListener?.onCameraChange(map?.cameraPosition?.toGms()) + } catch (e: Exception) { + Log.w(TAG, e) + } + } + } + + override fun setOnCircleClickListener(listener: IOnCircleClickListener?) { + Log.d(TAG, "setOnCircleClickListener") + map?.setOnCircleClickListener { listener?.onCircleClick(circles[it.id]) } + } + + override fun setOnGroundOverlayClickListener(listener: IOnGroundOverlayClickListener?) { + Log.d(TAG, "Method: setOnGroundOverlayClickListener") + map?.setOnGroundOverlayClickListener { listener?.onGroundOverlayClick(groundOverlays[it.id]) } + } + + override fun setOnInfoWindowLongClickListener(listener: com.google.android.gms.maps.internal.IOnInfoWindowLongClickListener?) { + Log.d(TAG,"Not yet implemented setInfoWindowLongClickListener") + } + + fun setOnIndoorStateChangeListener(listener: IOnIndoorStateChangeListener?) { + Log.d(TAG, "unimplemented Method: setOnIndoorStateChangeListener") + } + + override fun setOnMapClickListener(listener: IOnMapClickListener?) { + mapClickListener = listener + map?.setOnMapClickListener { latlng -> + try { + mapClickListener?.onMapClick(latlng.toGms()) + } catch (e: Exception) { + Log.w(TAG, e) + } + } + } + + override fun setOnMapLongClickListener(listener: IOnMapLongClickListener?) { + mapLongClickListener = listener + map?.setOnMapLongClickListener { latlng -> + try { + mapLongClickListener?.onMapLongClick(latlng.toGms()) + } catch (e: Exception) { + Log.w(TAG, e) + } + } + } + + override fun setOnMarkerClickListener(listener: IOnMarkerClickListener?) { + map?.setOnMarkerClickListener { listener?.onMarkerClick(markers[it.id]) ?: false } + } + + override fun setOnMarkerDragListener(listener: IOnMarkerDragListener?) { + map?.setOnMarkerDragListener(object : HuaweiMap.OnMarkerDragListener{ + override fun onMarkerDragStart(p0: Marker?) { + listener?.onMarkerDragStart(markers[p0?.id]) + } + + override fun onMarkerDrag(p0: Marker?) { + listener?.onMarkerDrag(markers[p0?.id]) + } + + override fun onMarkerDragEnd(p0: Marker?) { + listener?.onMarkerDragEnd(markers[p0?.id]) + } + }) + } + + override fun setOnInfoWindowClickListener(listener: IOnInfoWindowClickListener?) { + Log.d(TAG, "setOnInfoWindowClickListener") + map?.setOnInfoWindowClickListener { listener?.onInfoWindowClick(markers[it.id]) } + } + + fun setOnInfoWindowCloseListener(listener: IOnInfoWindowCloseListener?) { + Log.d(TAG, "unimplemented Method: setOnInfoWindowCloseListener") + } + + fun setOnInfoWindowLongClickListener(listener: IOnInfoWindowLongClickListener?) { + Log.d(TAG, "unimplemented Method: setOnInfoWindowLongClickListener") + } + + override fun setInfoWindowAdapter(adapter: IInfoWindowAdapter?) { + Log.d(TAG, "setInfoWindowAdapter") + map?.setInfoWindowAdapter(object : HuaweiMap.InfoWindowAdapter{ + override fun getInfoContents(p0: Marker?): View? { + return adapter?.getInfoContents(markers[p0?.id]).unwrap() + } + + override fun getInfoWindow(p0: Marker?): View? { + return adapter?.getInfoWindow(markers[p0?.id]).unwrap() + } + + }) + } + + override fun setOnMyLocationChangeListener(listener: IOnMyLocationChangeListener?) { + Log.d(TAG, "deprecated Method: setOnMyLocationChangeListener") + } + + override fun setOnMyLocationButtonClickListener(listener: IOnMyLocationButtonClickListener?) { + Log.d(TAG, "setOnMyLocationButtonClickListener") + map?.setOnMyLocationButtonClickListener { listener?.onMyLocationButtonClick() ?: false } + } + + override fun setOnMyLocationClickListener(listener: IOnMyLocationClickListener?) { + Log.d(TAG, "setOnMyLocationClickListener") + map?.setOnMyLocationClickListener { listener?.onMyLocationClick(it) } + } + + fun setOnPoiClickListener(listener: IOnPoiClickListener?) { + Log.d(TAG, "unimplemented Method: setOnPoiClickListener") + } + + override fun setOnPolygonClickListener(listener: IOnPolygonClickListener?) { + Log.d(TAG, "setOnPolygonClickListener") + map?.setOnPolygonClickListener { listener?.onPolygonClick(polygons[it.id]) } + } + + override fun setOnInfoWindowCloseListener(listener: com.google.android.gms.maps.internal.IOnInfoWindowCloseListener?) { + Log.d(TAG, "Not yet implemented setInfoWindowCloseListener") + } + + override fun setOnPolylineClickListener(listener: IOnPolylineClickListener?) { + Log.d(TAG, "unimplemented Method: setOnPolylineClickListener") + map?.setOnPolylineClickListener { listener?.onPolylineClick(polylines[it.id]) } + } + + override fun snapshot(callback: ISnapshotReadyCallback, bitmap: IObjectWrapper?) { + Log.d(TAG, "snapshot") + val hmsBitmap = bitmap.unwrap() ?: return + val hmsCallback = HuaweiMap.SnapshotReadyCallback { p0 -> callback.onBitmapReady(p0) } + map?.snapshot(hmsCallback, hmsBitmap) + } + + override fun snapshotForTest(callback: ISnapshotReadyCallback) { + Log.d(TAG, "snapshotForTest") + val hmsCallback = HuaweiMap.SnapshotReadyCallback { p0 -> callback.onBitmapReady(p0) } + map?.snapshot(hmsCallback) + } + + override fun setPadding(left: Int, top: Int, right: Int, bottom: Int) { + Log.d(TAG, "setPadding: $left $top $right $bottom") + map?.setPadding(left, top, right, bottom) + } + + override fun isBuildingsEnabled(): Boolean { + Log.d(TAG, "isBuildingsEnabled") + return map?.isBuildingsEnabled ?: true + } + + override fun setBuildingsEnabled(buildings: Boolean) { + Log.d(TAG, "setBuildingsEnabled: $buildings") + map?.isBuildingsEnabled = buildings + } + + override fun setOnMapLoadedCallback(callback: IOnMapLoadedCallback?) { + if (callback != null) { + synchronized(mapLock) { + if (loaded) { + Log.d(TAG, "Invoking callback instantly, as map is loaded") + try { + callback.onMapLoaded() + } catch (e: Exception) { + Log.w(TAG, e) + } + } else { + Log.d(TAG, "Delay callback invocation, as map is not yet loaded") + loadedCallback = callback + } + } + } else { + loadedCallback = null + } + } + + override fun setCameraMoveStartedListener(listener: IOnCameraMoveStartedListener?) { + Log.d(TAG, "setCameraMoveStartedListener") + cameraMoveStartedListener = listener + map?.setOnCameraMoveStartedListener { + try { + cameraMoveStartedListener?.onCameraMoveStarted(it) + } catch (e: Exception) { + Log.w(TAG, e) + } + } + } + + override fun setCameraMoveListener(listener: IOnCameraMoveListener?) { + Log.d(TAG, "setCameraMoveListener") + cameraMoveListener = listener + map?.setOnCameraMoveListener { + try { + cameraMoveListener?.onCameraMove() + } catch (e: Exception) { + Log.w(TAG, e) + } + } + } + + override fun setCameraMoveCanceledListener(listener: IOnCameraMoveCanceledListener?) { + Log.d(TAG, "setCameraMoveCanceledListener") + cameraMoveCanceledListener = listener + map?.setOnCameraMoveCanceledListener { + try { + cameraMoveCanceledListener?.onCameraMoveCanceled() + } catch (e: Exception) { + Log.w(TAG, e) + } + } + } + + override fun setCameraIdleListener(listener: IOnCameraIdleListener?) { + Log.d(TAG, "onCameraIdle: successful") + cameraIdleListener = listener + map?.setOnCameraIdleListener { + try { + cameraIdleListener?.onCameraIdle() + } catch (e: Exception) { + Log.w(TAG, e) + } + } + } + + override fun getTestingHelper(): IObjectWrapper? { + Log.d(TAG, "unimplemented Method: getTestingHelper") + return null + } + + override fun setWatermarkEnabled(watermark: Boolean) { + Log.d(TAG, "unimplemented Method: setWatermarkEnabled") + } + + override fun useViewLifecycleWhenInFragment(): Boolean { + Log.d(TAG, "unimplemented Method: useViewLifecycleWhenInFragment") + return false + } + + override fun onCreate(savedInstanceState: Bundle?) { + if (!created) { + Log.d(TAG, "create: ${context.packageName},\n$options") + val mapContext = MapContext(context) + MapsInitializer.initialize(mapContext) + val mapView = MapView(mapContext, options.toHms()) + this.mapView = mapView + view.addView(mapView) + mapView.onCreate(savedInstanceState?.toHms()) + view.viewTreeObserver.addOnGlobalLayoutListener { + if (!isFakeWatermark) { + fakeWatermark() + } + } + mapView.getMapAsync(this::initMap) + + created = true + } + } + + private var isFakeWatermark: Boolean = false + private fun fakeWatermark() { + Log.d(TAG_LOGO, "start") + try { + val view1 = view.getChildAt(0) as ViewGroup + val view2 = view1.getChildAt(0) as ViewGroup + val view4 = view2.getChildAt(1) + Log.d(TAG_LOGO, view4.toString()) + if (view4 is LinearLayout) { + view4.visibility = View.GONE + isFakeWatermark = true + } else { + throw Exception("LinearLayout not found") + } + } catch (tr: Throwable) { + Log.d(TAG_LOGO, "Throwable", tr) + } + } + + private fun getAllChildViews(view: View, index: Int): List? { + Log.d(TAG_LOGO, "getAllChildViews: $index, $view") + if (view is LinearLayout) { + Log.d(TAG_LOGO, "legal: $index") + view.visibility = View.GONE + } + val allChildren: MutableList = ArrayList() + if (view is ViewGroup) { + val vp = view + for (i in 0 until vp.childCount) { + val viewChild = vp.getChildAt(i) + Log.d(TAG_LOGO, "child:$index, $i, $viewChild") + allChildren.add(viewChild) + allChildren.addAll(getAllChildViews(viewChild, index + 1)!!) + } + } + return allChildren + } + + private fun initMap(map: HuaweiMap) { + if (this.map != null) return + + loaded = true + this.map = map + + map.setOnCameraIdleListener { + try { + cameraChangeListener?.onCameraChange(map.cameraPosition.toGms()) + } catch (e: Exception) { + Log.w(TAG, e) + } + } + map.setOnCameraIdleListener { + try { + cameraIdleListener?.onCameraIdle() + } catch (e: Exception) { + Log.w(TAG, e) + } + } + map.setOnCameraMoveListener { + try { + cameraMoveListener?.onCameraMove() + } catch (e: Exception) { + Log.w(TAG, e) + } + } + map.setOnCameraMoveStartedListener { + try { + cameraMoveStartedListener?.onCameraMoveStarted(it) + } catch (e: Exception) { + Log.w(TAG, e) + } + } + map.setOnCameraMoveCanceledListener { + try { + cameraMoveCanceledListener?.onCameraMoveCanceled() + } catch (e: Exception) { + Log.w(TAG, e) + } + } + map.setOnMapClickListener { latlng -> + try { + if (options.liteMode) { + val parentView = view.parent?.parent + // TODO hms not support disable click listener when liteMode, this just fix for teams + if (parentView != null && parentView::class.qualifiedName.equals("com.microsoft.teams.location.ui.map.MapViewLite")) { + val clickView = parentView as ViewGroup + clickView.performClick() + return@setOnMapClickListener + } + } + mapClickListener?.onMapClick(latlng.toGms()) + } catch (e: Exception) { + Log.w(TAG, e) + } + } + map.setOnMapLongClickListener { latlng -> + try { + if (options.liteMode) { + val parentView = view.parent?.parent + // TODO hms not support disable click listener when liteMode, this just fix for teams + if (parentView != null && parentView::class.qualifiedName.equals("com.microsoft.teams.location.ui.map.MapViewLite")) { + val clickView = parentView as ViewGroup + clickView.performLongClick() + return@setOnMapLongClickListener + } + } + mapLongClickListener?.onMapLongClick(latlng.toGms()) + } catch (e: Exception) { + Log.w(TAG, e) + } + } + + synchronized(mapLock) { + initialized = true + waitingCameraUpdates.forEach { map.moveCamera(it) } + val initializedCallbackList = ArrayList(initializedCallbackList) + 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) + } + } + } + } + + override fun onResume() = mapView?.onResume() ?: Unit + override fun onPause() = mapView?.onPause() ?: Unit + override fun onDestroy() { + Log.d(TAG, "onDestroy") + circles.map { it.value.remove() } + circles.clear() + polylines.map { it.value.remove() } + polylines.clear() + polygons.map { it.value.remove() } + polygons.clear() + markers.map { it.value.remove() } + markers.clear() +// BitmapDescriptorFactoryImpl.unregisterMap(map) + view.removeView(mapView) + // TODO can crash? + mapView?.onDestroy() + mapView = null + + // Don't make it null; this object is not deleted immediately, and it may want to access map.* stuff + //map = null + + created = false + initialized = false + loaded = false + } + + override fun onStart() { + mapView?.onStart() + } + + override fun onStop() { + mapView?.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()) + } + + 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) + } + } + } + + override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean = + if (super.onTransact(code, data, reply, flags)) { + Log.d(TAG, "onTransact: $code, $data, $flags") + true + } else { + Log.w(TAG, "onTransact [unknown]: $code, $data, $flags"); false + } + + companion object { + private const val TAG = "GmsGoogleMap" + + private const val TAG_LOGO = "fakeWatermark" + private const val MAX_TIMES = 300 + } +} diff --git a/play-services-maps/core/hms/src/main/kotlin/org/microg/gms/maps/hms/MapFragment.kt b/play-services-maps/core/hms/src/main/kotlin/org/microg/gms/maps/hms/MapFragment.kt new file mode 100644 index 0000000000..5bb62383b2 --- /dev/null +++ b/play-services-maps/core/hms/src/main/kotlin/org/microg/gms/maps/hms/MapFragment.kt @@ -0,0 +1,98 @@ +/* + * SPDX-FileCopyrightText: 2023 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.maps.hms + +import android.app.Activity +import android.os.Bundle +import android.os.Parcel +import android.util.Log +import android.view.ViewGroup +import com.google.android.gms.dynamic.IObjectWrapper +import com.google.android.gms.dynamic.ObjectWrapper +import com.google.android.gms.maps.GoogleMapOptions +import com.google.android.gms.maps.internal.IGoogleMapDelegate +import com.google.android.gms.maps.internal.IMapFragmentDelegate +import com.google.android.gms.maps.internal.IOnMapReadyCallback + +class MapFragmentImpl(private val activity: Activity) : IMapFragmentDelegate.Stub() { + + private var map: GoogleMapImpl? = null + private var options: GoogleMapOptions? = null + + override fun onInflate(activity: IObjectWrapper, options: GoogleMapOptions, savedInstanceState: Bundle?) { + Log.d(TAG, "onInflate: $options") + this.options = options + map?.options = options + } + + override fun onCreate(savedInstanceState: Bundle?) { + if (options == null) { + options = savedInstanceState?.getParcelable("MapOptions") + } + if (options == null) { + options = GoogleMapOptions() + } + Log.d(TAG, "onCreate: $options") + map = GoogleMapImpl(activity, options ?: GoogleMapOptions()) + } + + override fun onCreateView(layoutInflater: IObjectWrapper, container: IObjectWrapper, savedInstanceState: Bundle?): IObjectWrapper { + if (options == null) { + options = savedInstanceState?.getParcelable("MapOptions") + } + Log.d(TAG, "onCreateView: ${options?.camera?.target}") + if (map == null) { + map = GoogleMapImpl(activity, options ?: GoogleMapOptions()) + } + Log.d(TAG, "onCreateView: $options") + map!!.onCreate(savedInstanceState) + val view = map!!.view + val parent = view.parent as ViewGroup? + parent?.removeView(view) + return ObjectWrapper.wrap(view) + } + + override fun getMap(): IGoogleMapDelegate? = map + override fun onEnterAmbient(bundle: Bundle?) = map?.onEnterAmbient(bundle) ?: Unit + override fun onExitAmbient() = map?.onExitAmbient() ?: Unit + override fun onStart() = map?.onStart() ?: Unit + override fun onStop() = map?.onStop() ?: Unit + override fun onResume() = map?.onResume() ?: Unit + override fun onPause() = map?.onPause() ?: Unit + override fun onLowMemory() = map?.onLowMemory() ?: Unit + override fun isReady(): Boolean = this.map != null + override fun getMapAsync(callback: IOnMapReadyCallback) = map?.getMapAsync(callback) ?: Unit + + override fun onDestroyView() { + map?.onDestroy() + } + + override fun onDestroy() { + map?.onDestroy() + map = null + options = null + } + + override fun onSaveInstanceState(outState: Bundle) { + if (options != null) { + outState.putParcelable("MapOptions", options) + } + map?.onSaveInstanceState(outState) + } + + override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean { + if (super.onTransact(code, data, reply, flags)) { + return true + } else { + Log.d(TAG, "onTransact [unknown]: $code, $data, $flags") + return false + } + } + + companion object { + private val TAG = "GmsMapFragment" + } +} diff --git a/play-services-maps/core/hms/src/main/kotlin/org/microg/gms/maps/hms/MapView.kt b/play-services-maps/core/hms/src/main/kotlin/org/microg/gms/maps/hms/MapView.kt new file mode 100644 index 0000000000..79475cad06 --- /dev/null +++ b/play-services-maps/core/hms/src/main/kotlin/org/microg/gms/maps/hms/MapView.kt @@ -0,0 +1,57 @@ +/* + * SPDX-FileCopyrightText: 2023 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.maps.hms + +import android.content.Context +import android.os.Bundle +import android.os.Parcel +import android.util.Log +import com.google.android.gms.dynamic.IObjectWrapper +import com.google.android.gms.dynamic.ObjectWrapper +import com.google.android.gms.maps.GoogleMapOptions +import com.google.android.gms.maps.internal.IGoogleMapDelegate +import com.google.android.gms.maps.internal.IMapViewDelegate +import com.google.android.gms.maps.internal.IOnMapReadyCallback + +class MapViewImpl(private val context: Context, options: GoogleMapOptions?) : IMapViewDelegate.Stub() { + + private var options: GoogleMapOptions = options ?: GoogleMapOptions() + private var map: GoogleMapImpl? = null + + override fun onCreate(savedInstanceState: Bundle?) { + Log.d(TAG, "onCreate: $options") + map = GoogleMapImpl(context, options) + map!!.onCreate(savedInstanceState) + } + + override fun getMap(): IGoogleMapDelegate? = map + override fun onEnterAmbient(bundle: Bundle?) = map?.onEnterAmbient(bundle) ?: Unit + override fun onExitAmbient() = map?.onExitAmbient() ?: Unit + override fun onStart() = map?.onStart() ?: Unit + override fun onStop() = map?.onStop() ?: Unit + override fun onResume() = map?.onResume() ?: Unit + override fun onPause() = map?.onPause() ?: Unit + override fun onDestroy() { + map?.onDestroy() + map = null + } + + override fun onLowMemory() = map?.onLowMemory() ?: Unit + override fun onSaveInstanceState(outState: Bundle) = map?.onSaveInstanceState(outState) ?: Unit + override fun getView(): IObjectWrapper = ObjectWrapper.wrap(map?.view) + override fun getMapAsync(callback: IOnMapReadyCallback) = map?.getMapAsync(callback) ?: Unit + + override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean = + if (super.onTransact(code, data, reply, flags)) { + true + } else { + Log.d(TAG, "onTransact [unknown]: $code, $data, $flags"); false + } + + companion object { + private val TAG = "GmsMapView" + } +} diff --git a/play-services-maps/core/hms/src/main/kotlin/org/microg/gms/maps/hms/Projection.kt b/play-services-maps/core/hms/src/main/kotlin/org/microg/gms/maps/hms/Projection.kt new file mode 100644 index 0000000000..8e2643b480 --- /dev/null +++ b/play-services-maps/core/hms/src/main/kotlin/org/microg/gms/maps/hms/Projection.kt @@ -0,0 +1,55 @@ +/* + * SPDX-FileCopyrightText: 2023 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.maps.hms + +import android.graphics.Point +import android.util.Log +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.maps.internal.IProjectionDelegate +import com.google.android.gms.maps.model.LatLng +import com.google.android.gms.maps.model.VisibleRegion +import com.huawei.hms.maps.Projection +import org.microg.gms.maps.hms.utils.toGms +import org.microg.gms.maps.hms.utils.toHms + +class ProjectionImpl(private val projection: Projection) : IProjectionDelegate.Stub() { + + override fun fromScreenLocation(obj: IObjectWrapper?): LatLng? { + Log.d(TAG, "fromScreenLocation") + return try { + obj.unwrap()?.let { + projection.fromScreenLocation(it).toGms() + } + } catch (e: Exception) { + Log.d(TAG, "fromScreenLocation() used from outside UI thread on map with tilt or bearing, expect bugs") + LatLng(0.0, 0.0) + } + } + + override fun toScreenLocation(latLng: LatLng?): IObjectWrapper { + Log.d(TAG, "toScreenLocation: $latLng") + return try { + ObjectWrapper.wrap(latLng?.toHms()?.let { + projection.toScreenLocation(it).let { Point(it.x, it.y) } + }) + } catch (e: Exception) { + Log.d(TAG, "toScreenLocation() used from outside UI thread on map with tilt or bearing, expect bugs") + ObjectWrapper.wrap(Point(0, 0)) + } + } + + override fun getVisibleRegion(): VisibleRegion { + val visibleRegion = projection.visibleRegion + Log.d(TAG, "getVisibleRegion: $visibleRegion") + return visibleRegion.toGms() + } + + companion object { + private val TAG = "GmsMapProjection" + } +} diff --git a/play-services-maps/core/hms/src/main/kotlin/org/microg/gms/maps/hms/UiSettings.kt b/play-services-maps/core/hms/src/main/kotlin/org/microg/gms/maps/hms/UiSettings.kt new file mode 100644 index 0000000000..335064c145 --- /dev/null +++ b/play-services-maps/core/hms/src/main/kotlin/org/microg/gms/maps/hms/UiSettings.kt @@ -0,0 +1,109 @@ +/* + * SPDX-FileCopyrightText: 2023 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.maps.hms + +import android.os.Parcel +import android.util.Log + +import com.google.android.gms.maps.internal.IUiSettingsDelegate +import com.huawei.hms.maps.UiSettings + +class UiSettingsImpl(private val uiSettings: UiSettings) : IUiSettingsDelegate.Stub() { + + override fun setZoomControlsEnabled(zoom: Boolean) { + Log.d(TAG, "setZoomControlsEnabled: $zoom") + uiSettings.isZoomControlsEnabled = zoom + } + + override fun setCompassEnabled(compass: Boolean) { + uiSettings.isCompassEnabled = compass + } + + override fun setMyLocationButtonEnabled(locationButton: Boolean) { + uiSettings.isMyLocationButtonEnabled = locationButton + } + + override fun setScrollGesturesEnabled(scrollGestures: Boolean) { + uiSettings.isScrollGesturesEnabled = scrollGestures + } + + override fun setZoomGesturesEnabled(zoomGestures: Boolean) { + uiSettings.isZoomGesturesEnabled = zoomGestures + } + + override fun setTiltGesturesEnabled(tiltGestures: Boolean) { + uiSettings.isTiltGesturesEnabled = tiltGestures + } + + override fun setRotateGesturesEnabled(rotateGestures: Boolean) { + uiSettings.isRotateGesturesEnabled = rotateGestures + } + + override fun setAllGesturesEnabled(gestures: Boolean) { + uiSettings.setAllGesturesEnabled(gestures) + } + + override fun isZoomControlsEnabled(): Boolean { + Log.d(TAG, "isZoomControlsEnabled") + return uiSettings.isZoomControlsEnabled + } + + override fun isCompassEnabled(): Boolean = uiSettings.isCompassEnabled + + override fun isMyLocationButtonEnabled(): Boolean { + Log.d(TAG, "isMyLocationButtonEnabled") + return uiSettings.isMyLocationButtonEnabled + } + + override fun isScrollGesturesEnabled(): Boolean = uiSettings.isScrollGesturesEnabled + + override fun isZoomGesturesEnabled(): Boolean = uiSettings.isZoomGesturesEnabled + + override fun isTiltGesturesEnabled(): Boolean = uiSettings.isTiltGesturesEnabled + + override fun isRotateGesturesEnabled(): Boolean = uiSettings.isRotateGesturesEnabled + + override fun setIndoorLevelPickerEnabled(indoorLevelPicker: Boolean) { + Log.d(TAG, "setIndoorLevelPickerEnabled: $indoorLevelPicker") + uiSettings.isIndoorLevelPickerEnabled = indoorLevelPicker + } + + override fun isIndoorLevelPickerEnabled(): Boolean { + Log.d(TAG, "isIndoorLevelPickerEnabled") + return uiSettings.isIndoorLevelPickerEnabled + } + + override fun setMapToolbarEnabled(mapToolbar: Boolean) { + Log.d(TAG, "setMapToolbarEnabled: $mapToolbar") + uiSettings.isMapToolbarEnabled = mapToolbar + } + + override fun isMapToolbarEnabled(): Boolean { + Log.d(TAG, "isMapToolbarEnabled") + return uiSettings.isMapToolbarEnabled + } + + override fun setScrollGesturesEnabledDuringRotateOrZoom(scrollDuringZoom: Boolean) { + Log.d(TAG, "setScrollGesturesEnabledDuringRotateOrZoom: $scrollDuringZoom") + uiSettings.isScrollGesturesEnabledDuringRotateOrZoom = scrollDuringZoom + } + + override fun isScrollGesturesEnabledDuringRotateOrZoom(): Boolean { + Log.d(TAG, "isScrollGesturesEnabledDuringRotateOrZoom") + return uiSettings.isScrollGesturesEnabledDuringRotateOrZoom + } + + override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean = + if (super.onTransact(code, data, reply, flags)) { + true + } else { + Log.d(TAG, "onTransact [unknown]: $code, $data, $flags"); false + } + + companion object { + private val TAG = "GmsMapsUi" + } +} diff --git a/play-services-maps/core/hms/src/main/kotlin/org/microg/gms/maps/hms/model/BitmapDescriptorFactory.kt b/play-services-maps/core/hms/src/main/kotlin/org/microg/gms/maps/hms/model/BitmapDescriptorFactory.kt new file mode 100644 index 0000000000..d6838d065c --- /dev/null +++ b/play-services-maps/core/hms/src/main/kotlin/org/microg/gms/maps/hms/model/BitmapDescriptorFactory.kt @@ -0,0 +1,68 @@ +/* + * SPDX-FileCopyrightText: 2023 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.maps.hms.model + +import android.content.res.Resources +import android.graphics.* +import android.os.Parcel +import android.util.Log +import com.google.android.gms.dynamic.IObjectWrapper +import com.google.android.gms.dynamic.ObjectWrapper +import com.google.android.gms.maps.model.internal.IBitmapDescriptorFactoryDelegate +import com.huawei.hms.maps.HuaweiMap +import com.huawei.hms.maps.model.BitmapDescriptorFactory + + +object BitmapDescriptorFactoryImpl : IBitmapDescriptorFactoryDelegate.Stub() { + private val TAG = "GmsMapBitmap" + private var resources: Resources? = null + + fun initialize(resources: Resources?) { + BitmapDescriptorFactoryImpl.resources = resources ?: BitmapDescriptorFactoryImpl.resources + } + + override fun fromResource(resourceId: Int): IObjectWrapper? { + return BitmapFactory.decodeResource(resources, resourceId)?.let { + ObjectWrapper.wrap(BitmapDescriptorFactory.fromBitmap(it)) + } + } + + override fun fromAsset(assetName: String): IObjectWrapper? { + return resources?.assets?.open(assetName)?.let { + BitmapFactory.decodeStream(it) + ?.let { ObjectWrapper.wrap(BitmapDescriptorFactory.fromBitmap(it)) } + } + } + + override fun fromFile(fileName: String): IObjectWrapper? { + return BitmapFactory.decodeFile(fileName) + ?.let { ObjectWrapper.wrap(BitmapDescriptorFactory.fromBitmap(it)) } + } + + override fun defaultMarker(): IObjectWrapper? { + return ObjectWrapper.wrap(BitmapDescriptorFactory.defaultMarker()) + } + + override fun defaultMarkerWithHue(hue: Float): IObjectWrapper? { + return ObjectWrapper.wrap(BitmapDescriptorFactory.defaultMarker(hue)) + } + + override fun fromBitmap(bitmap: Bitmap): IObjectWrapper? { + return ObjectWrapper.wrap(BitmapDescriptorFactory.fromBitmap(bitmap)) + } + + override fun fromPath(absolutePath: String): IObjectWrapper? { + return BitmapFactory.decodeFile(absolutePath) + ?.let { ObjectWrapper.wrap(BitmapDescriptorFactory.fromBitmap(it)) } + } + + override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean = + if (super.onTransact(code, data, reply, flags)) { + true + } else { + Log.d(TAG, "onTransact [unknown]: $code, $data, $flags"); false + } +} diff --git a/play-services-maps/core/hms/src/main/kotlin/org/microg/gms/maps/hms/model/Circle.kt b/play-services-maps/core/hms/src/main/kotlin/org/microg/gms/maps/hms/model/Circle.kt new file mode 100644 index 0000000000..363f7f5269 --- /dev/null +++ b/play-services-maps/core/hms/src/main/kotlin/org/microg/gms/maps/hms/model/Circle.kt @@ -0,0 +1,123 @@ +/* + * SPDX-FileCopyrightText: 2023 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.maps.hms.model + +import android.os.Parcel +import android.util.Log +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.maps.model.LatLng +import com.google.android.gms.maps.model.PatternItem +import com.google.android.gms.maps.model.internal.ICircleDelegate +import com.huawei.hms.maps.model.Circle +import org.microg.gms.maps.hms.utils.toGms +import org.microg.gms.maps.hms.utils.toHms + +class CircleImpl(private val circle: Circle) : ICircleDelegate.Stub() { + + override fun remove() { + circle.remove() + } + + override fun getId(): String = circle.id + + override fun setCenter(center: LatLng) { + circle.center = center.toHms() + } + + override fun getCenter(): LatLng = circle.center.toGms() + + override fun setRadius(radius: Double) { + circle.radius = radius + } + + override fun getRadius(): Double = circle.radius + + override fun setStrokeWidth(width: Float) { + circle.strokeWidth = width + } + + override fun getStrokeWidth(): Float = circle.strokeWidth + + override fun setStrokeColor(color: Int) { + circle.strokeColor = color + } + + override fun getStrokeColor(): Int = circle.strokeColor + + override fun setTag(tag: IObjectWrapper) { + circle.setTag(tag.unwrap()) + } + + override fun getTag(): IObjectWrapper? { + return ObjectWrapper.wrap(circle.tag) + } + + override fun setStrokePattern(pattern: List?) { + circle.strokePattern = pattern?.map { it.toHms() } + } + + override fun getStrokePattern(): List? { + return circle.strokePattern?.map { it.toGms() } + } + + override fun setFillColor(color: Int) { + circle.fillColor = color + } + + override fun getFillColor(): Int = circle.fillColor + + override fun setZIndex(zIndex: Float) { + circle.zIndex = zIndex + } + + override fun getZIndex(): Float = circle.zIndex + + override fun setVisible(visible: Boolean) { + circle.isVisible = visible + } + + override fun isVisible(): Boolean = circle.isVisible + + override fun setClickable(clickable: Boolean) { + circle.isClickable = clickable + } + + override fun isClickable(): Boolean { + return circle.isClickable + } + + override fun equalsRemote(other: ICircleDelegate?): Boolean = equals(other) + + override fun hashCodeRemote(): Int = hashCode() + + override fun hashCode(): Int { + return id.hashCode() + } + + override fun toString(): String { + return id + } + + override fun equals(other: Any?): Boolean { + if (other is CircleImpl) { + return other.id == id + } + return false + } + + override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean = + if (super.onTransact(code, data, reply, flags)) { + true + } else { + Log.d(TAG, "onTransact [unknown]: $code, $data, $flags"); false + } + + companion object { + val TAG = "GmsMapCircle" + } +} \ No newline at end of file diff --git a/play-services-maps/core/hms/src/main/kotlin/org/microg/gms/maps/hms/model/GroundOverlay.kt b/play-services-maps/core/hms/src/main/kotlin/org/microg/gms/maps/hms/model/GroundOverlay.kt new file mode 100644 index 0000000000..4b0d97d4cd --- /dev/null +++ b/play-services-maps/core/hms/src/main/kotlin/org/microg/gms/maps/hms/model/GroundOverlay.kt @@ -0,0 +1,143 @@ +/* + * SPDX-FileCopyrightText: 2023 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.maps.hms.model + +import android.os.Parcel +import android.util.Log +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.maps.model.LatLng +import com.google.android.gms.maps.model.LatLngBounds +import com.google.android.gms.maps.model.internal.IGroundOverlayDelegate +import com.huawei.hms.maps.model.GroundOverlay +import org.microg.gms.maps.hms.utils.toGms +import org.microg.gms.maps.hms.utils.toHms + +class GroundOverlayImpl(private val groundOverlay: GroundOverlay) : IGroundOverlayDelegate.Stub() { + + override fun getId(): String { + return groundOverlay.id + } + + override fun getPosition(): LatLng? { + return groundOverlay.position?.toGms() + } + + override fun setPosition(pos: LatLng?) { + pos?.let { groundOverlay.position = it.toHms() } + } + + override fun getWidth(): Float { + return groundOverlay.width + } + + override fun getHeight(): Float { + return groundOverlay.height + } + + override fun setDimensions(width: Float, height: Float) { + groundOverlay.setDimensions(width, height) + } + + override fun getBounds(): LatLngBounds? { + return groundOverlay.bounds?.toGms() + } + + override fun getBearing(): Float { + return groundOverlay.bearing + } + + override fun setBearing(bearing: Float) { + groundOverlay.bearing = bearing + } + + override fun setZIndex(zIndex: Float) { + groundOverlay.zIndex = zIndex + } + + override fun getZIndex(): Float { + return groundOverlay.zIndex + } + + override fun isVisible(): Boolean { + return groundOverlay.isVisible + } + + override fun setVisible(visible: Boolean) { + groundOverlay.isVisible = visible + } + + override fun getTransparency(): Float { + return groundOverlay.transparency + } + + override fun setTransparency(transparency: Float) { + groundOverlay.transparency = transparency + } + + override fun setDimension(dimension: Float) { + groundOverlay.setDimensions(dimension) + } + + override fun setPositionFromBounds(bounds: LatLngBounds?) { + bounds?.let { groundOverlay.setPositionFromBounds(it.toHms()) } + } + + override fun getTag(): IObjectWrapper? { + return ObjectWrapper.wrap(groundOverlay.tag) + } + + override fun isClickable(): Boolean = groundOverlay.isClickable + + override fun setClickable(clickable: Boolean) { + groundOverlay.isClickable = clickable + } + + override fun setImage(obj: IObjectWrapper?) { + groundOverlay.setImage(obj.unwrap()) + } + + override fun setTag(tag: IObjectWrapper) { + groundOverlay.tag = tag.unwrap() + } + + override fun equalsRemote(other: IGroundOverlayDelegate?): Boolean { + return this == other + } + + override fun hashCode(): Int { + return groundOverlay.hashCode() + } + + override fun hashCodeRemote(): Int { + return hashCode() + } + +// override fun todo(obj: IObjectWrapper?) { +// Log.d(TAG, "Not yet implemented") +// } + + override fun equals(other: Any?): Boolean { + return groundOverlay == other + } + + override fun remove() { + Log.d(TAG, "Method: remove") + groundOverlay.remove() + } + + override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean = + if (super.onTransact(code, data, reply, flags)) { + true + } else { + Log.d(TAG, "onTransact [unknown]: $code, $data, $flags"); false + } + + companion object { + private val TAG = "GmsMapGroundOverlay" + } +} diff --git a/play-services-maps/core/hms/src/main/kotlin/org/microg/gms/maps/hms/model/Marker.kt b/play-services-maps/core/hms/src/main/kotlin/org/microg/gms/maps/hms/model/Marker.kt new file mode 100644 index 0000000000..bc5cf37831 --- /dev/null +++ b/play-services-maps/core/hms/src/main/kotlin/org/microg/gms/maps/hms/model/Marker.kt @@ -0,0 +1,143 @@ +/* + * SPDX-FileCopyrightText: 2023 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.maps.hms.model + +import android.os.Parcel +import android.util.Log +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.maps.model.LatLng +import com.google.android.gms.maps.model.internal.IMarkerDelegate +import com.huawei.hms.maps.model.Marker +import org.microg.gms.maps.hms.utils.toGms +import org.microg.gms.maps.hms.utils.toHms + +class MarkerImpl(private val marker: Marker) : IMarkerDelegate.Stub() { + + override fun remove() { + marker.remove() + } + + override fun getId(): String = marker.id + + override fun setPosition(position: LatLng?) { + marker.position = position?.toHms() + } + + override fun getPosition(): LatLng { + return marker.position.toGms() + } + + override fun setTitle(title: String?) { + marker.title = title + } + + override fun getTitle(): String? = marker.title + + override fun setSnippet(snippet: String?) { + marker.snippet = snippet + } + + override fun getSnippet(): String? = marker.snippet + + override fun setDraggable(draggable: Boolean) { + marker.isDraggable = draggable + } + + override fun isDraggable(): Boolean = marker.isDraggable + + override fun showInfoWindow() { + marker.showInfoWindow() + } + + override fun hideInfoWindow() { + marker.hideInfoWindow() + } + + override fun isInfoWindowShown(): Boolean { + return marker.isInfoWindowShown + } + + override fun setVisible(visible: Boolean) { + marker.isVisible = visible + } + + override fun isVisible(): Boolean = marker.isVisible + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other is IMarkerDelegate) return other.id == id + return false + } + + override fun equalsRemote(other: IMarkerDelegate?): Boolean = equals(other) + + override fun hashCode(): Int { + return id.hashCode() + } + + override fun toString(): String { + return "$id ($title)" + } + + override fun hashCodeRemote(): Int = hashCode() + + override fun setIcon(obj: IObjectWrapper?) { + marker.setIcon(obj.unwrap()) + } + + override fun setAnchor(x: Float, y: Float) { + marker.setMarkerAnchor(x, y) + } + + override fun setFlat(flat: Boolean) { + marker.isFlat = flat + } + + override fun isFlat(): Boolean { + return marker.isFlat + } + + override fun setRotation(rotation: Float) { + marker.rotation = rotation + } + + override fun getRotation(): Float = marker.rotation + + override fun setInfoWindowAnchor(x: Float, y: Float) { + marker.setInfoWindowAnchor(x, y) + } + + override fun setAlpha(alpha: Float) { + marker.alpha = alpha + } + + override fun getAlpha(): Float = marker.alpha + + override fun setZIndex(zIndex: Float) { + marker.zIndex = zIndex + } + + override fun getZIndex(): Float = marker.zIndex + + override fun setTag(obj: IObjectWrapper?) { + marker.tag = obj.unwrap() + } + + override fun getTag(): IObjectWrapper = ObjectWrapper.wrap(marker.tag) + + override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean = + if (super.onTransact(code, data, reply, flags)) { + true + } else { + Log.d(TAG, "onTransact [unknown]: $code, $data, $flags"); false + } + + companion object { + private val TAG = "GmsMapMarker" + } +} diff --git a/play-services-maps/core/hms/src/main/kotlin/org/microg/gms/maps/hms/model/Polygon.kt b/play-services-maps/core/hms/src/main/kotlin/org/microg/gms/maps/hms/model/Polygon.kt new file mode 100644 index 0000000000..ed3e287b0f --- /dev/null +++ b/play-services-maps/core/hms/src/main/kotlin/org/microg/gms/maps/hms/model/Polygon.kt @@ -0,0 +1,171 @@ +/* + * SPDX-FileCopyrightText: 2023 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.maps.hms.model + +import android.os.Parcel +import android.util.Log +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.maps.model.LatLng +import com.google.android.gms.maps.model.PatternItem +import com.google.android.gms.maps.model.internal.IPolygonDelegate +import com.huawei.hms.maps.model.Polygon +import org.microg.gms.maps.hms.utils.toGms +import org.microg.gms.maps.hms.utils.toHms +import org.microg.gms.utils.warnOnTransactionIssues + +class PolygonImpl(private val polygon: Polygon) : IPolygonDelegate.Stub() { + + override fun remove() { + polygon.remove() + } + + override fun getId(): String = polygon.id + + override fun setPoints(points: List) { + polygon.points = points.map { it.toHms() } + } + + override fun getPoints(): List = polygon.points.map { it.toGms() } + + override fun setHoles(holes: List?) { + if (holes == null) { + polygon.holes = emptyList() + } else { + val outHmsHoles: MutableList> = + ArrayList() + for (out in holes) { + if (out is List<*>) { + val inHmsHoles: MutableList = ArrayList() + for (inn in out) { + if (inn is LatLng) { + inHmsHoles.add(inn.toHms()) + } + } + outHmsHoles.add(inHmsHoles) + } + } + polygon.holes = outHmsHoles + } + } + + override fun getHoles(): List { + val outHoles = polygon.holes ?: return emptyList() + val outGmsHoles: MutableList> = ArrayList() + for (inHoles in outHoles) { + if (inHoles != null) { + val inGmsHoles: MutableList = ArrayList() + for (inHole in inHoles) { + inGmsHoles.add(inHole.toGms()) + } + outGmsHoles.add(inGmsHoles) + } + } + return outGmsHoles + } + + override fun setStrokeWidth(width: Float) { + polygon.strokeWidth = width + } + + override fun getStrokeWidth(): Float = polygon.strokeWidth + + override fun setStrokeColor(color: Int) { + polygon.strokeColor = color + } + + override fun getStrokeColor(): Int = polygon.strokeColor + + override fun setFillColor(color: Int) { + polygon.fillColor = color + } + + override fun getFillColor(): Int { + return polygon.fillColor + } + + override fun setZIndex(zIndex: Float) { + polygon.zIndex = zIndex + } + + override fun getZIndex(): Float { + return polygon.zIndex + } + + override fun setVisible(visible: Boolean) { + polygon.isVisible = visible + } + + override fun isVisible(): Boolean { + return polygon.isVisible + } + + override fun setGeodesic(geod: Boolean) { + polygon.isGeodesic = geod + } + + override fun isGeodesic(): Boolean { + return polygon.isGeodesic + } + + override fun getStrokeJointType(): Int { + return polygon.strokeJointType + } + + override fun getStrokePattern(): List? { + return polygon.strokePattern?.map { it.toGms() } + } + + override fun getTag(): IObjectWrapper { + return ObjectWrapper.wrap(polygon.tag) + } + + override fun isClickable(): Boolean { + return polygon.isClickable + } + + override fun setClickable(clickable: Boolean) { + polygon.isClickable = clickable + } + + override fun setStrokeJointType(jointType: Int) { + polygon.strokeJointType = jointType + } + + override fun setStrokePattern(pattern: List?) { + polygon.strokePattern = pattern?.map { it.toHms() } + } + + override fun setTag(tag: IObjectWrapper?) { + polygon.tag = tag.unwrap() + } + + override fun equalsRemote(other: IPolygonDelegate?): Boolean = equals(other) + + override fun hashCodeRemote(): Int = hashCode() + + override fun hashCode(): Int { + return id.hashCode() + } + + override fun toString(): String { + return id + } + + override fun equals(other: Any?): Boolean { + if (other is PolygonImpl) { + return other.id == id + } + return false + } + + override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean = warnOnTransactionIssues(code, reply, flags) { super.onTransact(code, data, reply, flags) } + + companion object { + private val TAG = "GmsMapPolygon" + } +} diff --git a/play-services-maps/core/hms/src/main/kotlin/org/microg/gms/maps/hms/model/Polyline.kt b/play-services-maps/core/hms/src/main/kotlin/org/microg/gms/maps/hms/model/Polyline.kt new file mode 100644 index 0000000000..14a2c85940 --- /dev/null +++ b/play-services-maps/core/hms/src/main/kotlin/org/microg/gms/maps/hms/model/Polyline.kt @@ -0,0 +1,190 @@ +/* + * SPDX-FileCopyrightText: 2023 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.maps.hms.model + +import android.os.Parcel +import android.util.Log +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.maps.model.* +import com.google.android.gms.maps.model.BitmapDescriptor +import com.google.android.gms.maps.model.CustomCap +import com.google.android.gms.maps.model.LatLng +import com.google.android.gms.maps.model.PatternItem +import com.google.android.gms.maps.model.PolylineOptions +import com.google.android.gms.maps.model.internal.IPolylineDelegate +import com.huawei.hms.maps.model.* +import com.huawei.hms.maps.model.ButtCap as HmsButtCap +import com.huawei.hms.maps.model.Cap as HmsCap +import com.huawei.hms.maps.model.CustomCap as HmsCustomCap +import com.huawei.hms.maps.model.RoundCap as HmsRoundCap +import com.huawei.hms.maps.model.SquareCap as HmsSquareCap +import org.microg.gms.maps.hms.utils.toGms +import org.microg.gms.maps.hms.utils.toGmsPolylineWidth +import org.microg.gms.maps.hms.utils.toHms +import org.microg.gms.maps.hms.utils.toHmsPolylineWidth + +class PolylineImpl(private val polyline: Polyline, polylineOptions: PolylineOptions) : IPolylineDelegate.Stub() { + + override fun remove() { + polyline.remove() + } + + override fun getId(): String = polyline.id + + override fun setPoints(points: List) { + polyline.points = points.map { it.toHms() } + } + + override fun getPoints(): List = polyline.points.map { it.toGms() } + + override fun setWidth(width: Float) { + polyline.width = toHmsPolylineWidth(width) + } + + override fun getWidth(): Float { + return toGmsPolylineWidth(polyline.width) + } + + override fun setColor(color: Int) { + polyline.color = color + } + + override fun getColor(): Int { + return polyline.color + } + + override fun setZIndex(zIndex: Float) { + Log.d(TAG, "setZIndex: $zIndex") + polyline.zIndex = zIndex + } + + override fun getZIndex(): Float { + Log.d(TAG, "getZIndex") + return polyline.zIndex + } + + override fun setVisible(visible: Boolean) { + polyline.isVisible = visible + } + + override fun isVisible(): Boolean { + return polyline.isVisible + } + + override fun setGeodesic(geod: Boolean) { + Log.d(TAG, "setGeodesic: $geod") + polyline.isGeodesic = geod + } + + override fun isGeodesic(): Boolean { + Log.d(TAG, "isGeodesic") + return polyline.isGeodesic + } + + override fun setClickable(clickable: Boolean) { + Log.d(TAG, "setClickable: $clickable") + polyline.isClickable = clickable + } + + override fun isClickable(): Boolean { + Log.d(TAG, "isClickable") + return polyline.isClickable + } + + override fun equalsRemote(other: IPolylineDelegate?): Boolean = equals(other) + + override fun hashCodeRemote(): Int = hashCode() + + override fun hashCode(): Int { + return id.hashCode() + } + + override fun toString(): String { + return id + } + + override fun equals(other: Any?): Boolean { + if (other is PolylineImpl) { + return other.id == id + } + return false + } + + override fun getPattern(): List? { + Log.d(TAG, "Method: getStrokePattern") + return polyline.pattern?.map { it.toGms() } + } + + override fun getTag(): IObjectWrapper { + return ObjectWrapper.wrap(polyline.tag) + } + + override fun setJointType(jointType: Int) { + polyline.jointType = jointType + } + + override fun getJointType(): Int { + return polyline.jointType + } + + override fun setPattern(pattern: List?) { + Log.d(TAG, "Method: setStrokePattern") + polyline.pattern = pattern?.map { it.toHms() } + } + + override fun setTag(tag: IObjectWrapper?) { + polyline.tag = tag.unwrap() + } + + override fun setEndCap(endCap: Cap) { + polyline.endCap = endCap.toHms() + } + + override fun getEndCap(): Cap { + return polyline.endCap.toGms() + } + + override fun setStartCap(startCap: Cap) { + polyline.startCap = startCap.toHms() + } + + override fun getStartCap(): Cap { + return polyline.startCap.toGms() + } + + private fun Cap.toHms(): HmsCap { + return when (this) { + is ButtCap -> HmsButtCap() + is SquareCap -> HmsSquareCap() + is RoundCap -> HmsRoundCap() + is CustomCap -> HmsCustomCap(bitmapDescriptor.remoteObject.unwrap(), refWidth) + else -> HmsButtCap() + } + } + + private fun com.huawei.hms.maps.model.Cap.toGms(): Cap { + return when (this) { + is HmsButtCap -> ButtCap() + is HmsSquareCap -> SquareCap() + is HmsRoundCap -> RoundCap() + is HmsCustomCap -> CustomCap(BitmapDescriptor(ObjectWrapper.wrap(bitmapDescriptor)), refWidth) + else -> ButtCap() + } + } + + override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean = + if (super.onTransact(code, data, reply, flags)) { + true + } else { + Log.d(TAG, "onTransact [unknown]: $code, $data, $flags"); false + } + + companion object { + private val TAG = "GmsMapPolyline" + } +} \ No newline at end of file diff --git a/play-services-maps/core/hms/src/main/kotlin/org/microg/gms/maps/hms/model/TileOverlay.kt b/play-services-maps/core/hms/src/main/kotlin/org/microg/gms/maps/hms/model/TileOverlay.kt new file mode 100644 index 0000000000..e7c5184b8a --- /dev/null +++ b/play-services-maps/core/hms/src/main/kotlin/org/microg/gms/maps/hms/model/TileOverlay.kt @@ -0,0 +1,81 @@ +/* + * SPDX-FileCopyrightText: 2023 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.maps.hms.model + +import android.os.Parcel +import android.util.Log +import com.google.android.gms.maps.model.internal.ITileOverlayDelegate +import com.huawei.hms.maps.model.TileOverlay + +class TileOverlayImpl(private val tileOverlay: TileOverlay) : ITileOverlayDelegate.Stub() { + + override fun clearTileCache() { + tileOverlay.clearTileCache() + } + + override fun equals(other: Any?): Boolean { + return tileOverlay == other + } + + override fun getFadeIn(): Boolean { + return tileOverlay.fadeIn + } + + override fun getId(): String { + return tileOverlay.id + } + + override fun getTransparency(): Float { + return tileOverlay.transparency + } + + override fun getZIndex(): Float { + return tileOverlay.zIndex + } + + override fun hashCode(): Int { + return tileOverlay.hashCode() + } + + override fun isVisible(): Boolean { + return tileOverlay.isVisible + } + + override fun remove() { + return tileOverlay.remove() + } + + override fun setFadeIn(fadeIn: Boolean) { + tileOverlay.fadeIn = fadeIn + } + + override fun setTransparency(transparency: Float) { + tileOverlay.transparency = transparency + } + + override fun setVisible(visible: Boolean) { + tileOverlay.isVisible = visible + } + + override fun setZIndex(zIndex: Float) { + tileOverlay.zIndex = zIndex + } + + override fun equalsRemote(other: ITileOverlayDelegate): Boolean = tileOverlay == (other as? TileOverlayImpl)?.tileOverlay + override fun hashCodeRemote(): Int = tileOverlay.hashCode() + + override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean = + if (super.onTransact(code, data, reply, flags)) { + Log.e(TAG, "onTransact [known]: $code, $data, $flags") + true + } else { + Log.d(TAG, "onTransact [unknown]: $code, $data, $flags"); false + } + + companion object { + private val TAG = "GmsMapTileOverlay" + } +} diff --git a/play-services-maps/core/hms/src/main/kotlin/org/microg/gms/maps/hms/utils/MapContext.kt b/play-services-maps/core/hms/src/main/kotlin/org/microg/gms/maps/hms/utils/MapContext.kt new file mode 100644 index 0000000000..e26384505c --- /dev/null +++ b/play-services-maps/core/hms/src/main/kotlin/org/microg/gms/maps/hms/utils/MapContext.kt @@ -0,0 +1,72 @@ +/* + * SPDX-FileCopyrightText: 2023 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.maps.hms.utils + +import android.content.Context +import android.content.ContextWrapper +import android.content.Intent +import android.content.SharedPreferences +import android.util.Log +import android.view.LayoutInflater +import androidx.annotation.RequiresApi +import org.microg.gms.common.Constants +import java.io.File + +class MapContext(private val context: Context) : ContextWrapper(context.createPackageContext(Constants.GMS_PACKAGE_NAME, Context.CONTEXT_INCLUDE_CODE or Context.CONTEXT_IGNORE_SECURITY)) { + private var layoutInflater: LayoutInflater? = null + private val appContext: Context + get() = context.applicationContext ?: context + + override fun getApplicationContext(): Context { + return this + } + + override fun getCacheDir(): File { + val cacheDir = File(appContext.cacheDir, "com.google.android.gms") + cacheDir.mkdirs() + return cacheDir + } + + override fun getFilesDir(): File { + val filesDir = File(appContext.filesDir, "com.google.android.gms") + filesDir.mkdirs() + return filesDir + } + + override fun getClassLoader(): ClassLoader { + return MapContext::class.java.classLoader!! + } + + override fun getSharedPreferences(name: String?, mode: Int): SharedPreferences { + return appContext.getSharedPreferences("com.google.android.gms_$name", mode) + } + + override fun getSystemService(name: String): Any? { + if (name == Context.LAYOUT_INFLATER_SERVICE) { + if (layoutInflater == null) { + layoutInflater = super.getSystemService(name) as LayoutInflater + layoutInflater?.cloneInContext(this)?.let { layoutInflater = it } + } + if (layoutInflater != null) { + return layoutInflater + } + } + return context.getSystemService(name) + } + + override fun startActivity(intent: Intent?) { + context.startActivity(intent) + } + + @RequiresApi(24) + override fun createDeviceProtectedStorageContext(): Context { + return appContext.createDeviceProtectedStorageContext() + } + + companion object { + val TAG = "GmsMapContext" + } +} \ No newline at end of file diff --git a/play-services-maps/core/hms/src/main/kotlin/org/microg/gms/maps/hms/utils/typeConverter.kt b/play-services-maps/core/hms/src/main/kotlin/org/microg/gms/maps/hms/utils/typeConverter.kt new file mode 100644 index 0000000000..64cb463f6b --- /dev/null +++ b/play-services-maps/core/hms/src/main/kotlin/org/microg/gms/maps/hms/utils/typeConverter.kt @@ -0,0 +1,227 @@ +/* + * SPDX-FileCopyrightText: 2023 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.maps.hms.utils + +import android.os.Bundle +import android.util.Log +import com.google.android.gms.dynamic.unwrap +import com.google.android.gms.maps.GoogleMapOptions +import com.google.android.gms.maps.internal.ICancelableCallback +import com.huawei.hms.maps.HuaweiMap +import com.huawei.hms.maps.HuaweiMapOptions +import com.huawei.hms.maps.model.* +import com.google.android.gms.maps.model.CameraPosition as GmsCameraPosition +import com.google.android.gms.maps.model.CircleOptions as GmsCircleOptions +import com.google.android.gms.maps.model.Dash as GmsDash +import com.google.android.gms.maps.model.Dot as GmsDot +import com.google.android.gms.maps.model.Gap as GmsGap +import com.google.android.gms.maps.model.GroundOverlayOptions as GmsGroundOverlayOptions +import com.google.android.gms.maps.model.LatLng as GmsLatLng +import com.google.android.gms.maps.model.LatLngBounds as GmsLatLngBounds +import com.google.android.gms.maps.model.MarkerOptions as GmsMarkerOptions +import com.google.android.gms.maps.model.PatternItem as GmsPatternItem +import com.google.android.gms.maps.model.PolygonOptions as GmsPolygonOptions +import com.google.android.gms.maps.model.PolylineOptions as GmsPolylineOptions +import com.google.android.gms.maps.model.Tile as GmsTile +import com.google.android.gms.maps.model.TileOverlayOptions as GmsTileOverlayOptions +import com.google.android.gms.maps.model.VisibleRegion as GmsVisibleRegion + +fun GmsCameraPosition.toHms(): CameraPosition { + return CameraPosition.Builder().target(target.toHms()).zoom(toHmsZoom(zoom)).tilt(tilt) + .bearing(toHmsBearing(bearing)).build() +} + +fun GmsCircleOptions.toHms(): CircleOptions = + CircleOptions().center(center.toHms()).clickable(isClickable).fillColor(fillColor) + .radius(radius).strokeColor(strokeColor).strokeWidth(strokeWidth).visible(isVisible) + .zIndex(zIndex) + +fun GmsPatternItem.toHms(): PatternItem { + return when (this) { + is GmsDash -> Dash(length) + is GmsDot -> Dot() + is GmsGap -> Gap(length) + else -> PatternItem(0,0f) + } +} + +fun GoogleMapOptions.toHms(): HuaweiMapOptions { + val huaweiMapOptions = HuaweiMapOptions() + camera?.let { huaweiMapOptions.camera(camera?.toHms()) } + if (maxZoomPreference != 0f) { + huaweiMapOptions.maxZoomPreference(toHmsZoom(maxZoomPreference)) + } + if (minZoomPreference != 0f) { + huaweiMapOptions.minZoomPreference(toHmsZoom(minZoomPreference)) + } + latLngBoundsForCameraTarget?.let { + huaweiMapOptions.latLngBoundsForCameraTarget( + latLngBoundsForCameraTarget?.toHms() + ) + } + + return huaweiMapOptions + .compassEnabled(isCompassEnabled) + .liteMode(liteMode) +// .mapType(mapType) + .rotateGesturesEnabled(rotateGesturesEnabled == true) + .scrollGesturesEnabled(scrollGesturesEnabled == true) + .tiltGesturesEnabled(tiltGesturesEnabled == true) + .useViewLifecycleInFragment(useViewLifecycleInFragment == true) + .zOrderOnTop(zOrderOnTop == true) + .zoomControlsEnabled(zoomControlsEnabled == true) + .zoomGesturesEnabled(zoomGesturesEnabled == true) +} + +fun GmsLatLng.toHms(): LatLng = + LatLng(latitude, longitude) + +fun GmsLatLngBounds.toHms(): LatLngBounds = + LatLngBounds( + LatLng(if(southwest.latitude.isNaN()) 0.0 else southwest.latitude, if(southwest.longitude.isNaN()) 0.0 else southwest.longitude), + LatLng(if(northeast.latitude.isNaN()) 0.0 else northeast.latitude, if(northeast.longitude.isNaN()) 0.0 else northeast.longitude) + ) + +fun ICancelableCallback.toHms(): HuaweiMap.CancelableCallback = + object : HuaweiMap.CancelableCallback { + override fun onFinish() = this@toHms.onFinish() + override fun onCancel() = this@toHms.onCancel() + } + +fun GmsMarkerOptions.toHms(): MarkerOptions { + val markerOptions = MarkerOptions() + icon?.let { markerOptions.icon(it.remoteObject.unwrap()) } + return markerOptions.alpha(alpha).anchorMarker(anchorU, anchorV).draggable(isDraggable) + .flat(isFlat).infoWindowAnchor(infoWindowAnchorU, infoWindowAnchorV) + .position(position.toHms()).rotation(rotation).snippet(snippet).title(title) + .visible(isVisible).zIndex(zIndex) +} + +fun GmsGroundOverlayOptions.toHms(): GroundOverlayOptions { + val groundOverlayOptions = GroundOverlayOptions() + groundOverlayOptions.anchor(anchorU, anchorV).bearing(bearing) + .clickable(isClickable) + .image(image.remoteObject.unwrap()) + .visible(isVisible) + .zIndex(zIndex) + if (height > 0) { + groundOverlayOptions.position(location.toHms(), width, height) + } else { + groundOverlayOptions.position(location.toHms(), width) + } + bounds?.let { groundOverlayOptions.positionFromBounds(it.toHms()) } + return groundOverlayOptions +} + +fun GmsTileOverlayOptions.toHms(): TileOverlayOptions { + return TileOverlayOptions().tileProvider(tileProvider?.let { TileProvider { x, y, zoom -> it.getTile(x, y, zoom)?.toHms() } }) + .fadeIn(fadeIn) + .visible(isVisible) + .transparency(transparency) + .zIndex(zIndex) +} + +fun GmsTile.toHms(): Tile = Tile(width, height, data) + +fun GmsPolygonOptions.toHms(): PolygonOptions { + val polygonOptions = PolygonOptions() + holes?.map { + val hole = it?.map { it?.toHms() } + polygonOptions.addHole(hole) + } + return polygonOptions.addAll(points.map { it.toHms() }) + .clickable(isClickable) + .fillColor(fillColor) + .geodesic(isGeodesic) + .strokeColor(strokeColor).strokeJointType(strokeJointType).strokeWidth(strokeWidth) + .visible(isVisible) + .zIndex(zIndex) +} + +fun GmsPolylineOptions.toHms(): PolylineOptions { + val polylineOptions = PolylineOptions() + polylineOptions.addAll(points.map { it.toHms() }) + return polylineOptions.clickable(isClickable).color(color).geodesic(isGeodesic) + .jointType(jointType).visible(isVisible).width(toHmsPolylineWidth(width)).zIndex(zIndex) +} + +fun toHmsPolylineWidth(gmsWidth: Float): Float = gmsWidth / 3 + +fun toHmsZoom(gmsZoom: Float?): Float { + if (gmsZoom == null) { + return 3f + } + if (gmsZoom < 3) { + return 3f + } else if (gmsZoom > 18) { + return 18f + } + return gmsZoom +} + +fun toHmsBearing(gmsBearing: Float): Float { + return 360 - gmsBearing +} + +fun Bundle.toHms(): Bundle { + val newBundle = Bundle(this) + val oldLoader = newBundle.classLoader + newBundle.classLoader = GmsLatLng::class.java.classLoader + for (key in newBundle.keySet()) { + when (val value = newBundle.get(key)) { + is GmsCameraPosition -> newBundle.putParcelable(key, value.toHms()) + is GmsLatLng -> newBundle.putParcelable(key, value.toHms()) + is GmsLatLngBounds -> newBundle.putParcelable(key, value.toHms()) + is Bundle -> newBundle.putBundle(key, value.toHms()) + } + } + newBundle.classLoader = oldLoader + return newBundle +} + +fun CameraPosition.toGms(): GmsCameraPosition = + GmsCameraPosition(target.toGms(), zoom, tilt, bearing) + +fun PatternItem.toGms(): GmsPatternItem = when (this) { + is Dot -> GmsDot() + is Dash -> GmsDash(length) + is Gap -> GmsGap(length) + else -> GmsGap(0f) +} + +fun LatLng.toGms(): GmsLatLng = GmsLatLng(latitude, longitude) + +fun LatLngBounds.toGms(): GmsLatLngBounds = GmsLatLngBounds( + GmsLatLng(southwest.latitude, southwest.longitude), + GmsLatLng(northeast.latitude, northeast.longitude) +) + +fun VisibleRegion.toGms(): GmsVisibleRegion = + GmsVisibleRegion( + nearLeft.toGms(), + nearRight.toGms(), + farLeft.toGms(), + farRight.toGms(), + latLngBounds.toGms() + ) + +fun toGmsPolylineWidth(hmsWidth: Float): Float = hmsWidth * 3 + +fun Bundle.toGms(): Bundle { + val newBundle = Bundle(this) + val oldLoader = newBundle.classLoader + newBundle.classLoader = LatLng::class.java.classLoader + for (key in newBundle.keySet()) { + when (val value = newBundle.get(key)) { + is CameraPosition -> newBundle.putParcelable(key, value.toGms()) + is LatLng -> newBundle.putParcelable(key, value.toGms()) + is LatLngBounds -> newBundle.putParcelable(key, value.toGms()) + is Bundle -> newBundle.putBundle(key, value.toGms()) + } + } + newBundle.classLoader = oldLoader + return newBundle +} diff --git a/play-services-maps/core/hms/src/main/res/drawable-hdpi/maps_default_marker.png b/play-services-maps/core/hms/src/main/res/drawable-hdpi/maps_default_marker.png new file mode 100644 index 0000000000..94c365a3e3 Binary files /dev/null and b/play-services-maps/core/hms/src/main/res/drawable-hdpi/maps_default_marker.png differ diff --git a/play-services-maps/core/hms/src/main/res/drawable-ldpi/maps_default_marker.png b/play-services-maps/core/hms/src/main/res/drawable-ldpi/maps_default_marker.png new file mode 100644 index 0000000000..a8ca2bf634 Binary files /dev/null and b/play-services-maps/core/hms/src/main/res/drawable-ldpi/maps_default_marker.png differ diff --git a/play-services-maps/core/hms/src/main/res/drawable-mdpi/maps_default_marker.png b/play-services-maps/core/hms/src/main/res/drawable-mdpi/maps_default_marker.png new file mode 100644 index 0000000000..c79736e707 Binary files /dev/null and b/play-services-maps/core/hms/src/main/res/drawable-mdpi/maps_default_marker.png differ diff --git a/play-services-maps/core/hms/src/main/res/drawable-xhdpi/maps_default_marker.png b/play-services-maps/core/hms/src/main/res/drawable-xhdpi/maps_default_marker.png new file mode 100644 index 0000000000..0b1b1975b4 Binary files /dev/null and b/play-services-maps/core/hms/src/main/res/drawable-xhdpi/maps_default_marker.png differ diff --git a/play-services-maps/core/hms/src/main/res/drawable-xxhdpi/maps_default_marker.png b/play-services-maps/core/hms/src/main/res/drawable-xxhdpi/maps_default_marker.png new file mode 100644 index 0000000000..709bf78d7b Binary files /dev/null and b/play-services-maps/core/hms/src/main/res/drawable-xxhdpi/maps_default_marker.png differ diff --git a/play-services-maps/core/hms/src/main/res/drawable-xxxhdpi/maps_default_marker.png b/play-services-maps/core/hms/src/main/res/drawable-xxxhdpi/maps_default_marker.png new file mode 100644 index 0000000000..0de5d255ae Binary files /dev/null and b/play-services-maps/core/hms/src/main/res/drawable-xxxhdpi/maps_default_marker.png differ diff --git a/play-services-maps/core/hms/src/main/res/values/strings.xml b/play-services-maps/core/hms/src/main/res/values/strings.xml new file mode 100644 index 0000000000..73862c416f --- /dev/null +++ b/play-services-maps/core/hms/src/main/res/values/strings.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/play-services-maps/src/main/java/com/google/android/gms/maps/model/TileOverlayOptions.java b/play-services-maps/src/main/java/com/google/android/gms/maps/model/TileOverlayOptions.java index dbf1b2e4fe..c2d88d49a8 100644 --- a/play-services-maps/src/main/java/com/google/android/gms/maps/model/TileOverlayOptions.java +++ b/play-services-maps/src/main/java/com/google/android/gms/maps/model/TileOverlayOptions.java @@ -71,6 +71,19 @@ public boolean getFadeIn() { * @return the {@link TileProvider} of the tile overlay. */ public TileProvider getTileProvider() { + if (tileProvider == null && tileProviderBinder != null) { + ITileProviderDelegate delegate = ITileProviderDelegate.Stub.asInterface(tileProviderBinder); + this.tileProvider = new TileProvider() { + @Override + public Tile getTile(int x, int y, int zoom) { + try { + return delegate.getTile(x, y, zoom); + } catch (RemoteException e) { + return null; + } + } + }; + } return tileProvider; } diff --git a/settings.gradle b/settings.gradle index a5b22be861..aceae3a68a 100644 --- a/settings.gradle +++ b/settings.gradle @@ -4,6 +4,18 @@ def sublude(name) { project(projectName).projectDir = file(name.substring(1).replace(':', '/')) } +def localProperties = new Properties() +try { + var stream = new File(rootDir, 'local.properties').newDataInputStream() + localProperties.load(stream) + stream.close() +} catch (ignored) { + // Ignore +} +def hasModule = (String name, boolean enabledByDefault) -> { + return localProperties.getProperty("modules." + name, enabledByDefault.toString()).toBoolean() +} + include ':fake-signature' include ':safe-parcel-processor' include ':vending-app' @@ -76,11 +88,12 @@ sublude ':play-services-location:core' sublude ':play-services-location:core:base' sublude ':play-services-location:core:provider' sublude ':play-services-location:core:system-api' +if (hasModule("hms", false)) sublude ':play-services-maps:core:hms' sublude ':play-services-maps:core:mapbox' sublude ':play-services-maps:core:vtm' sublude ':play-services-maps:core:vtm:microg-theme' -sublude ':play-services-nearby:core' -sublude ':play-services-nearby:core:package' +if (hasModule("nearby", true)) sublude ':play-services-nearby:core' +if (hasModule("nearby", true)) sublude ':play-services-nearby:core:package' sublude ':play-services-oss-licenses:core' sublude ':play-services-pay:core' sublude ':play-services-safetynet:core'