Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ fun runOnMainLooper(forceQueue: Boolean = false, method: () -> Unit) {
class GoogleMapImpl(private val context: Context, var options: GoogleMapOptions) : IGoogleMapDelegate.Stub() {

internal val mapContext = MapContext(context)
private val compatAdapter = MapCompatAdapter(context)

val view: FrameLayout
var map: HuaweiMap? = null
Expand Down Expand Up @@ -721,7 +722,8 @@ class GoogleMapImpl(private val context: Context, var options: GoogleMapOptions)
Log.w(TAG, "onCreate: init TextureMapView error ", it)
}.getOrDefault(MapView(mapContext, options.toHms())).apply { visibility = View.INVISIBLE }
this.mapView = mapView
view.addView(mapView)
view.addView(compatAdapter.wrapMapView(mapContext, mapView))

mapView.onCreate(savedInstanceState?.toHms())
mapView.getMapAsync(this::initMap)

Expand Down Expand Up @@ -815,14 +817,8 @@ class GoogleMapImpl(private val context: Context, var options: GoogleMapOptions)
}
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
}
if (options.liteMode && compatAdapter.interceptLiteModeClick(view)) {
return@setOnMapClickListener
}
mapClickListener?.onMapClick(latlng.toGms())
} catch (e: Exception) {
Expand All @@ -831,14 +827,8 @@ class GoogleMapImpl(private val context: Context, var options: GoogleMapOptions)
}
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
}
if (options.liteMode && compatAdapter.interceptLiteModeLongClick(view)) {
return@setOnMapLongClickListener
}
mapLongClickListener?.onMapLongClick(latlng.toGms())
} catch (e: Exception) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
/*
* SPDX-FileCopyrightText: 2026 microG Project Team
* SPDX-License-Identifier: Apache-2.0
*/

package org.microg.gms.maps.hms.utils

import android.content.Context
import android.util.Log
import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout

/**
* Adapter that applies app-specific compatibility fixes for HMS Maps
* based on the calling application's package name.
*/
class MapCompatAdapter(context: Context) {

private val callerPackageName: String = context.applicationContext?.packageName ?: context.packageName

/**
* Wraps the mapView in a traversal-blocking container if needed.
* Some apps traverse the view hierarchy and encounter HMS internal views,
* causing crashes or unexpected behavior.
*
* @return the wrapper view to add to the root, or mapView itself if no wrapping is needed.
*/
fun wrapMapView(mapContext: Context, mapView: View): View {
if (!needsViewTraversalBlock().also { Log.d(TAG, "$callerPackageName need wrapMapView ? $it") }) return mapView

val blocker = object : FrameLayout(mapContext) {
override fun getChildCount(): Int = 0
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
val lp = mapView.layoutParams
?: LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
val childW = getChildMeasureSpec(widthMeasureSpec, 0, lp.width)
val childH = getChildMeasureSpec(heightMeasureSpec, 0, lp.height)
mapView.measure(childW, childH)
setMeasuredDimension(
resolveSize(mapView.measuredWidth, widthMeasureSpec),
resolveSize(mapView.measuredHeight, heightMeasureSpec)
)
}

override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
mapView.layout(0, 0, right - left, bottom - top)
}
}
blocker.addView(
mapView,
ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
)
return blocker
}

/**
* Intercepts map click events in liteMode for apps that need special handling.
* @return true if the click was intercepted and handled, false to proceed normally.
*/
fun interceptLiteModeClick(rootView: View): Boolean {
return findLiteModeParent(rootView)?.let {
it.performClick()
true
} ?: false
}

/**
* Intercepts map long-click events in liteMode for apps that need special handling.
* @return true if the long-click was intercepted and handled, false to proceed normally.
*/
fun interceptLiteModeLongClick(rootView: View): Boolean {
return findLiteModeParent(rootView)?.let {
it.performLongClick()
true
} ?: false
}

/**
* Determines if the mapView needs to be wrapped in a traversal-blocking container.
*/
private fun needsViewTraversalBlock(): Boolean {
return callerPackageName in VIEW_TRAVERSAL_BLOCK_PACKAGES
}

/**
* Finds the parent view that should receive redirected click events in liteMode.
* Returns null if no redirection is needed for the current app.
*/
private fun findLiteModeParent(rootView: View): ViewGroup? {
val targetClass = LITE_MODE_CLICK_REDIRECT[callerPackageName] ?: return null
val parentView = rootView.parent?.parent ?: return null
if (parentView::class.qualifiedName == targetClass) {
return parentView as? ViewGroup
}
return null
}

companion object {
private const val TAG = "MapCompatAdapter"
private val VIEW_TRAVERSAL_BLOCK_PACKAGES = setOf(
"com.studioeleven.windfinder",
)
private val LITE_MODE_CLICK_REDIRECT = mapOf(
"com.microsoft.teams" to "com.microsoft.teams.location.ui.map.MapViewLite",
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,13 @@

package org.microg.gms.maps.hms.utils

import android.annotation.SuppressLint
import android.content.Context
import android.content.ContextWrapper
import android.content.Intent
import android.content.SharedPreferences
import android.content.res.Configuration
import android.content.res.Resources
import android.view.LayoutInflater
import androidx.annotation.RequiresApi
import com.huawei.hms.maps.MapClientIdentify
Expand Down Expand Up @@ -74,6 +77,75 @@ class MapContext(private val context: Context) : ContextWrapper(context.createPa
return appContext.createDeviceProtectedStorageContext()
}

private val appRes: Resources get() = appContext.resources

internal val mergedResources: Resources by lazy(LazyThreadSafetyMode.NONE) {
val gmsRes = try { super.getResources() } catch (e: Exception) { return@lazy appRes }
@SuppressLint("DiscouragedApi")
@Suppress("DEPRECATION")
object : Resources(gmsRes.assets, gmsRes.displayMetrics, gmsRes.configuration) {
override fun getText(id: Int): CharSequence {
return try {
gmsRes.getText(id)
} catch (e: Exception) {
try {
appRes.getText(id)
} catch (e2: Exception) {
""
}
}
}

override fun getText(id: Int, def: CharSequence?): CharSequence {
return try {
gmsRes.getText(id, def)
} catch (e: Exception) {
try {
appRes.getText(id, def)
} catch (e2: Exception) {
def ?: ""
}
}
}

override fun getString(id: Int): String {
return try {
gmsRes.getString(id)
} catch (e: Exception) {
try {
appRes.getString(id)
} catch (e2: Exception) {
""
}
}
}

override fun getString(id: Int, vararg formatArgs: Any?): String {
return try {
gmsRes.getString(id, *formatArgs)
} catch (e: Exception) {
try {
appRes.getString(id, *formatArgs)
} catch (e2: Exception) {
""
}
}
}
}
}

override fun getResources(): Resources = mergedResources

override fun createConfigurationContext(overrideConfiguration: Configuration): Context {
val configCtx = super.createConfigurationContext(overrideConfiguration)
return object : ContextWrapper(configCtx) {
override fun getResources(): Resources = mergedResources
override fun createConfigurationContext(configuration: Configuration): Context {
return this@MapContext.createConfigurationContext(configuration)
}
}
}

companion object {
val TAG = "GmsMapContext"
}
Expand Down