mirror of
https://github.com/microg/android_packages_apps_GmsCore
synced 2026-08-06 14:26:05 -04:00
Webview: Fix the exception after multi-process access (#2684)
This commit is contained in:
parent
498e63ff53
commit
9f216e3cf8
6 changed files with 92 additions and 35 deletions
|
|
@ -20,5 +20,9 @@
|
|||
android:exported="false"
|
||||
android:process=":ui"
|
||||
android:theme="@style/Theme.AppCompat.Light.Dialog.Alert.NoActionBar" />
|
||||
<service
|
||||
android:name="org.microg.gms.firebase.auth.ReCaptchaOverlayService"
|
||||
android:exported="false"
|
||||
android:process=":ui" />
|
||||
</application>
|
||||
</manifest>
|
||||
|
|
|
|||
|
|
@ -371,7 +371,7 @@ class FirebaseAuthServiceImpl(private val context: Context, override val lifecyc
|
|||
Log.d(TAG, "sendVerificationCode")
|
||||
val reCaptchaToken = when {
|
||||
request.request.recaptchaToken != null -> request.request.recaptchaToken
|
||||
ReCaptchaOverlay.isSupported(context) -> ReCaptchaOverlay.awaitToken(context, apiKey, getAuthorizedDomain())
|
||||
ReCaptchaOverlayService.isSupported(context) -> ReCaptchaOverlayService.awaitToken(context, apiKey, getAuthorizedDomain())
|
||||
ReCaptchaActivity.isSupported(context) -> ReCaptchaActivity.awaitToken(context, apiKey, getAuthorizedDomain())
|
||||
else -> throw RuntimeException("No recaptcha token available")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -69,11 +69,6 @@ class ReCaptchaActivity : AppCompatActivity() {
|
|||
}
|
||||
|
||||
companion object {
|
||||
const val EXTRA_TOKEN = "token"
|
||||
const val EXTRA_API_KEY = "api_key"
|
||||
const val EXTRA_HOSTNAME = "hostname"
|
||||
const val EXTRA_RESULT_RECEIVER = "receiver"
|
||||
|
||||
class ReCaptchaCallback(val activity: ReCaptchaActivity) {
|
||||
@JavascriptInterface
|
||||
fun onReCaptchaToken(token: String) {
|
||||
|
|
|
|||
|
|
@ -7,8 +7,15 @@ package org.microg.gms.firebase.auth
|
|||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.app.Service
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.ServiceConnection
|
||||
import android.graphics.PixelFormat
|
||||
import android.os.Bundle
|
||||
import android.os.IBinder
|
||||
import android.os.ResultReceiver
|
||||
import android.provider.Settings
|
||||
import android.util.DisplayMetrics
|
||||
import android.util.Log
|
||||
|
|
@ -20,19 +27,39 @@ import android.widget.FrameLayout
|
|||
import org.microg.gms.firebase.auth.core.R
|
||||
import org.microg.gms.profile.Build
|
||||
import org.microg.gms.profile.ProfileManager
|
||||
import kotlin.coroutines.Continuation
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.resumeWithException
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
|
||||
|
||||
private const val TAG = "GmsFirebaseAuthCaptcha"
|
||||
|
||||
class ReCaptchaOverlay(val context: Context, val apiKey: String, val hostname: String?, val continuation: Continuation<String>) {
|
||||
class ReCaptchaOverlayService : Service() {
|
||||
|
||||
val windowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
|
||||
var finished = false
|
||||
var container: View? = null
|
||||
private var receiver: ResultReceiver? = null
|
||||
private var hostname: String? = null
|
||||
private var apiKey: String? = null
|
||||
|
||||
private var finished = false
|
||||
private var container: View? = null
|
||||
private var windowManager: WindowManager? = null
|
||||
|
||||
override fun onBind(intent: Intent): IBinder? {
|
||||
init(intent)
|
||||
return null
|
||||
}
|
||||
|
||||
override fun onUnbind(intent: Intent?): Boolean {
|
||||
finishResult(Activity.RESULT_CANCELED)
|
||||
return super.onUnbind(intent)
|
||||
}
|
||||
|
||||
private fun init(intent: Intent) {
|
||||
apiKey = intent.getStringExtra(EXTRA_API_KEY) ?: return finishResult(Activity.RESULT_CANCELED)
|
||||
receiver = intent.getParcelableExtra(EXTRA_RESULT_RECEIVER)
|
||||
hostname = intent.getStringExtra(EXTRA_HOSTNAME) ?: "localhost:5000"
|
||||
windowManager = getSystemService(Context.WINDOW_SERVICE) as WindowManager
|
||||
show()
|
||||
}
|
||||
|
||||
@SuppressLint("SetJavaScriptEnabled", "AddJavascriptInterface")
|
||||
private fun show() {
|
||||
|
|
@ -53,11 +80,11 @@ class ReCaptchaOverlay(val context: Context, val apiKey: String, val hostname: S
|
|||
params.x = 0
|
||||
params.y = 0
|
||||
|
||||
val interceptorLayout: FrameLayout = object : FrameLayout(context) {
|
||||
val interceptorLayout: FrameLayout = object : FrameLayout(this) {
|
||||
override fun dispatchKeyEvent(event: KeyEvent): Boolean {
|
||||
if (event.action == KeyEvent.ACTION_DOWN) {
|
||||
if (event.keyCode == KeyEvent.KEYCODE_BACK || event.keyCode == KeyEvent.KEYCODE_HOME) {
|
||||
cancel()
|
||||
finishResult(Activity.RESULT_CANCELED)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
|
@ -65,15 +92,15 @@ class ReCaptchaOverlay(val context: Context, val apiKey: String, val hostname: S
|
|||
}
|
||||
}
|
||||
|
||||
val inflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as? LayoutInflater?
|
||||
val inflater = getSystemService(Context.LAYOUT_INFLATER_SERVICE) as? LayoutInflater?
|
||||
if (inflater != null) {
|
||||
val container = inflater.inflate(R.layout.activity_recaptcha, interceptorLayout)
|
||||
this.container = container
|
||||
container.setBackgroundResource(androidx.appcompat.R.drawable.abc_dialog_material_background)
|
||||
val pad = (5.0 * (context.resources.displayMetrics.densityDpi.toFloat() / DisplayMetrics.DENSITY_DEFAULT)).toInt()
|
||||
val pad = (5.0 * (resources.displayMetrics.densityDpi.toFloat() / DisplayMetrics.DENSITY_DEFAULT)).toInt()
|
||||
container.setOnTouchListener { v, _ ->
|
||||
v.performClick()
|
||||
cancel()
|
||||
finishResult(Activity.RESULT_CANCELED)
|
||||
return@setOnTouchListener true
|
||||
}
|
||||
val view = container.findViewById<WebView>(R.id.web)
|
||||
|
|
@ -84,44 +111,63 @@ class ReCaptchaOverlay(val context: Context, val apiKey: String, val hostname: S
|
|||
settings.setSupportZoom(false)
|
||||
settings.displayZoomControls = false
|
||||
settings.cacheMode = WebSettings.LOAD_NO_CACHE
|
||||
ProfileManager.ensureInitialized(context)
|
||||
ProfileManager.ensureInitialized(this)
|
||||
settings.userAgentString = Build.generateWebViewUserAgentString(settings.userAgentString)
|
||||
view.addJavascriptInterface(ReCaptchaCallback(this), "MyCallback")
|
||||
val captcha = context.assets.open("recaptcha.html").bufferedReader().readText().replace("%apikey%", apiKey)
|
||||
val captcha = assets.open("recaptcha.html").bufferedReader().readText().replace("%apikey%", apiKey!!)
|
||||
view.loadDataWithBaseURL("https://$hostname/", captcha, null, null, "https://$hostname/")
|
||||
windowManager.addView(container, params)
|
||||
windowManager?.addView(container, params)
|
||||
}
|
||||
}
|
||||
|
||||
fun cancel() {
|
||||
fun finishResult(resultCode: Int, token: String? = null) {
|
||||
if (!finished) {
|
||||
finished = true
|
||||
continuation.resumeWithException(RuntimeException("User cancelled"))
|
||||
receiver?.send(resultCode, token?.let { Bundle().apply { putString(EXTRA_TOKEN, it) } })
|
||||
}
|
||||
close()
|
||||
}
|
||||
|
||||
fun close() {
|
||||
container?.let { windowManager.removeView(it) }
|
||||
container?.let { windowManager?.removeView(it) }
|
||||
}
|
||||
|
||||
companion object {
|
||||
class ReCaptchaCallback(val overlay: ReCaptchaOverlay) {
|
||||
|
||||
private val recaptchaServiceConnection = object : ServiceConnection {
|
||||
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
|
||||
Log.d(TAG, "onReCaptchaToken: onServiceConnected: $name")
|
||||
}
|
||||
|
||||
override fun onServiceDisconnected(name: ComponentName?) {
|
||||
Log.d(TAG, "onReCaptchaToken: onServiceDisconnected: $name")
|
||||
}
|
||||
}
|
||||
|
||||
class ReCaptchaCallback(private val overlay: ReCaptchaOverlayService) {
|
||||
@JavascriptInterface
|
||||
fun onReCaptchaToken(token: String) {
|
||||
Log.d(TAG, "onReCaptchaToken: $token")
|
||||
if (!overlay.finished) {
|
||||
overlay.finished = true
|
||||
overlay.continuation.resume(token)
|
||||
}
|
||||
overlay.close()
|
||||
overlay.finishResult(Activity.RESULT_OK, token)
|
||||
}
|
||||
}
|
||||
|
||||
fun isSupported(context: Context): Boolean = android.os.Build.VERSION.SDK_INT < 23 || Settings.canDrawOverlays(context)
|
||||
|
||||
suspend fun awaitToken(context: Context, apiKey: String, hostname: String? = null) = suspendCoroutine<String> { continuation ->
|
||||
ReCaptchaOverlay(context, apiKey, hostname ?: "localhost:5000", continuation).show()
|
||||
suspend fun awaitToken(context: Context, apiKey: String, hostname: String? = null) = suspendCoroutine { continuation ->
|
||||
val intent = Intent(context, ReCaptchaOverlayService::class.java)
|
||||
val resultReceiver = object : ResultReceiver(null) {
|
||||
override fun onReceiveResult(resultCode: Int, resultData: Bundle?) {
|
||||
context.unbindService(recaptchaServiceConnection)
|
||||
try {
|
||||
if (resultCode == Activity.RESULT_OK) {
|
||||
continuation.resume(resultData?.getString(EXTRA_TOKEN)!!)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
continuation.resumeWithException(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
intent.putExtra(EXTRA_API_KEY, apiKey)
|
||||
intent.putExtra(EXTRA_RESULT_RECEIVER, resultReceiver)
|
||||
intent.putExtra(EXTRA_HOSTNAME, hostname)
|
||||
context.bindService(intent, recaptchaServiceConnection, BIND_AUTO_CREATE)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
/**
|
||||
* SPDX-FileCopyrightText: 2024 microG Project Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package org.microg.gms.firebase.auth
|
||||
|
||||
const val EXTRA_TOKEN = "token"
|
||||
const val EXTRA_API_KEY = "api_key"
|
||||
const val EXTRA_HOSTNAME = "hostname"
|
||||
const val EXTRA_RESULT_RECEIVER = "receiver"
|
||||
|
|
@ -165,6 +165,7 @@
|
|||
|
||||
<activity
|
||||
android:name="org.microg.vending.billing.ui.PlayWebViewActivity"
|
||||
android:process=":ui"
|
||||
android:exported="false" />
|
||||
|
||||
<service
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue