Skip to content

Commit 2421cb1

Browse files
committed
HmsMaps: Added compatibility adapter
1 parent ab709bd commit 2421cb1

3 files changed

Lines changed: 187 additions & 17 deletions

File tree

play-services-maps/core/hms/src/main/kotlin/org/microg/gms/maps/hms/GoogleMap.kt

Lines changed: 7 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ fun runOnMainLooper(forceQueue: Boolean = false, method: () -> Unit) {
6161
class GoogleMapImpl(private val context: Context, var options: GoogleMapOptions) : IGoogleMapDelegate.Stub() {
6262

6363
internal val mapContext = MapContext(context)
64+
private val compatAdapter = MapCompatAdapter(context)
6465

6566
val view: FrameLayout
6667
var map: HuaweiMap? = null
@@ -721,7 +722,8 @@ class GoogleMapImpl(private val context: Context, var options: GoogleMapOptions)
721722
Log.w(TAG, "onCreate: init TextureMapView error ", it)
722723
}.getOrDefault(MapView(mapContext, options.toHms())).apply { visibility = View.INVISIBLE }
723724
this.mapView = mapView
724-
view.addView(mapView)
725+
view.addView(compatAdapter.wrapMapView(mapContext, mapView))
726+
725727
mapView.onCreate(savedInstanceState?.toHms())
726728
mapView.getMapAsync(this::initMap)
727729

@@ -815,14 +817,8 @@ class GoogleMapImpl(private val context: Context, var options: GoogleMapOptions)
815817
}
816818
map.setOnMapClickListener { latlng ->
817819
try {
818-
if (options.liteMode) {
819-
val parentView = view.parent?.parent
820-
// TODO hms not support disable click listener when liteMode, this just fix for teams
821-
if (parentView != null && parentView::class.qualifiedName.equals("com.microsoft.teams.location.ui.map.MapViewLite")) {
822-
val clickView = parentView as ViewGroup
823-
clickView.performClick()
824-
return@setOnMapClickListener
825-
}
820+
if (options.liteMode && compatAdapter.interceptLiteModeClick(view)) {
821+
return@setOnMapClickListener
826822
}
827823
mapClickListener?.onMapClick(latlng.toGms())
828824
} catch (e: Exception) {
@@ -831,14 +827,8 @@ class GoogleMapImpl(private val context: Context, var options: GoogleMapOptions)
831827
}
832828
map.setOnMapLongClickListener { latlng ->
833829
try {
834-
if (options.liteMode) {
835-
val parentView = view.parent?.parent
836-
// TODO hms not support disable click listener when liteMode, this just fix for teams
837-
if (parentView != null && parentView::class.qualifiedName.equals("com.microsoft.teams.location.ui.map.MapViewLite")) {
838-
val clickView = parentView as ViewGroup
839-
clickView.performLongClick()
840-
return@setOnMapLongClickListener
841-
}
830+
if (options.liteMode && compatAdapter.interceptLiteModeLongClick(view)) {
831+
return@setOnMapLongClickListener
842832
}
843833
mapLongClickListener?.onMapLongClick(latlng.toGms())
844834
} catch (e: Exception) {
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
/*
2+
* SPDX-FileCopyrightText: 2026 microG Project Team
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package org.microg.gms.maps.hms.utils
7+
8+
import android.content.Context
9+
import android.util.Log
10+
import android.view.View
11+
import android.view.ViewGroup
12+
import android.widget.FrameLayout
13+
14+
/**
15+
* Adapter that applies app-specific compatibility fixes for HMS Maps
16+
* based on the calling application's package name.
17+
*/
18+
class MapCompatAdapter(context: Context) {
19+
20+
private val callerPackageName: String = context.applicationContext?.packageName ?: context.packageName
21+
22+
/**
23+
* Wraps the mapView in a traversal-blocking container if needed.
24+
* Some apps traverse the view hierarchy and encounter HMS internal views,
25+
* causing crashes or unexpected behavior.
26+
*
27+
* @return the wrapper view to add to the root, or mapView itself if no wrapping is needed.
28+
*/
29+
fun wrapMapView(mapContext: Context, mapView: View): View {
30+
if (!needsViewTraversalBlock().also { Log.d(TAG, "$callerPackageName need wrapMapView ? $it") }) return mapView
31+
32+
val blocker = object : FrameLayout(mapContext) {
33+
override fun getChildCount(): Int = 0
34+
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
35+
val lp = mapView.layoutParams
36+
?: LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
37+
val childW = getChildMeasureSpec(widthMeasureSpec, 0, lp.width)
38+
val childH = getChildMeasureSpec(heightMeasureSpec, 0, lp.height)
39+
mapView.measure(childW, childH)
40+
setMeasuredDimension(
41+
resolveSize(mapView.measuredWidth, widthMeasureSpec),
42+
resolveSize(mapView.measuredHeight, heightMeasureSpec)
43+
)
44+
}
45+
46+
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
47+
mapView.layout(0, 0, right - left, bottom - top)
48+
}
49+
}
50+
blocker.addView(
51+
mapView,
52+
ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
53+
)
54+
return blocker
55+
}
56+
57+
/**
58+
* Intercepts map click events in liteMode for apps that need special handling.
59+
* @return true if the click was intercepted and handled, false to proceed normally.
60+
*/
61+
fun interceptLiteModeClick(rootView: View): Boolean {
62+
return findLiteModeParent(rootView)?.let {
63+
it.performClick()
64+
true
65+
} ?: false
66+
}
67+
68+
/**
69+
* Intercepts map long-click events in liteMode for apps that need special handling.
70+
* @return true if the long-click was intercepted and handled, false to proceed normally.
71+
*/
72+
fun interceptLiteModeLongClick(rootView: View): Boolean {
73+
return findLiteModeParent(rootView)?.let {
74+
it.performLongClick()
75+
true
76+
} ?: false
77+
}
78+
79+
/**
80+
* Determines if the mapView needs to be wrapped in a traversal-blocking container.
81+
*/
82+
private fun needsViewTraversalBlock(): Boolean {
83+
return callerPackageName in VIEW_TRAVERSAL_BLOCK_PACKAGES
84+
}
85+
86+
/**
87+
* Finds the parent view that should receive redirected click events in liteMode.
88+
* Returns null if no redirection is needed for the current app.
89+
*/
90+
private fun findLiteModeParent(rootView: View): ViewGroup? {
91+
val targetClass = LITE_MODE_CLICK_REDIRECT[callerPackageName] ?: return null
92+
val parentView = rootView.parent?.parent ?: return null
93+
if (parentView::class.qualifiedName == targetClass) {
94+
return parentView as? ViewGroup
95+
}
96+
return null
97+
}
98+
99+
companion object {
100+
private const val TAG = "MapCompatAdapter"
101+
private val VIEW_TRAVERSAL_BLOCK_PACKAGES = setOf(
102+
"com.studioeleven.windfinder",
103+
)
104+
private val LITE_MODE_CLICK_REDIRECT = mapOf(
105+
"com.microsoft.teams" to "com.microsoft.teams.location.ui.map.MapViewLite",
106+
)
107+
}
108+
}

play-services-maps/core/hms/src/main/kotlin/org/microg/gms/maps/hms/utils/MapContext.kt

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,13 @@
55

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

8+
import android.annotation.SuppressLint
89
import android.content.Context
910
import android.content.ContextWrapper
1011
import android.content.Intent
1112
import android.content.SharedPreferences
13+
import android.content.res.Configuration
14+
import android.content.res.Resources
1215
import android.view.LayoutInflater
1316
import androidx.annotation.RequiresApi
1417
import com.huawei.hms.maps.MapClientIdentify
@@ -74,6 +77,75 @@ class MapContext(private val context: Context) : ContextWrapper(context.createPa
7477
return appContext.createDeviceProtectedStorageContext()
7578
}
7679

80+
private val appRes: Resources get() = appContext.resources
81+
82+
internal val mergedResources: Resources by lazy(LazyThreadSafetyMode.NONE) {
83+
val gmsRes = try { super.getResources() } catch (e: Exception) { return@lazy appRes }
84+
@SuppressLint("DiscouragedApi")
85+
@Suppress("DEPRECATION")
86+
object : Resources(gmsRes.assets, gmsRes.displayMetrics, gmsRes.configuration) {
87+
override fun getText(id: Int): CharSequence {
88+
return try {
89+
gmsRes.getText(id)
90+
} catch (e: Exception) {
91+
try {
92+
appRes.getText(id)
93+
} catch (e2: Exception) {
94+
""
95+
}
96+
}
97+
}
98+
99+
override fun getText(id: Int, def: CharSequence?): CharSequence {
100+
return try {
101+
gmsRes.getText(id, def)
102+
} catch (e: Exception) {
103+
try {
104+
appRes.getText(id, def)
105+
} catch (e2: Exception) {
106+
def ?: ""
107+
}
108+
}
109+
}
110+
111+
override fun getString(id: Int): String {
112+
return try {
113+
gmsRes.getString(id)
114+
} catch (e: Exception) {
115+
try {
116+
appRes.getString(id)
117+
} catch (e2: Exception) {
118+
""
119+
}
120+
}
121+
}
122+
123+
override fun getString(id: Int, vararg formatArgs: Any?): String {
124+
return try {
125+
gmsRes.getString(id, *formatArgs)
126+
} catch (e: Exception) {
127+
try {
128+
appRes.getString(id, *formatArgs)
129+
} catch (e2: Exception) {
130+
""
131+
}
132+
}
133+
}
134+
}
135+
}
136+
137+
override fun getResources(): Resources = mergedResources
138+
139+
override fun createConfigurationContext(overrideConfiguration: Configuration): Context {
140+
val configCtx = super.createConfigurationContext(overrideConfiguration)
141+
return object : ContextWrapper(configCtx) {
142+
override fun getResources(): Resources = mergedResources
143+
override fun createConfigurationContext(configuration: Configuration): Context {
144+
return this@MapContext.createConfigurationContext(configuration)
145+
}
146+
}
147+
}
148+
77149
companion object {
78150
val TAG = "GmsMapContext"
79151
}

0 commit comments

Comments
 (0)