diff --git a/firebase-auth/core/src/main/AndroidManifest.xml b/firebase-auth/core/src/main/AndroidManifest.xml
index b684a0c298..89a122f6e8 100644
--- a/firebase-auth/core/src/main/AndroidManifest.xml
+++ b/firebase-auth/core/src/main/AndroidManifest.xml
@@ -20,5 +20,9 @@
android:exported="false"
android:process=":ui"
android:theme="@style/Theme.AppCompat.Light.Dialog.Alert.NoActionBar" />
+
diff --git a/firebase-auth/core/src/main/kotlin/org/microg/gms/firebase/auth/FirebaseAuthService.kt b/firebase-auth/core/src/main/kotlin/org/microg/gms/firebase/auth/FirebaseAuthService.kt
index 72b599f983..afcb0d4df2 100644
--- a/firebase-auth/core/src/main/kotlin/org/microg/gms/firebase/auth/FirebaseAuthService.kt
+++ b/firebase-auth/core/src/main/kotlin/org/microg/gms/firebase/auth/FirebaseAuthService.kt
@@ -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")
}
diff --git a/firebase-auth/core/src/main/kotlin/org/microg/gms/firebase/auth/ReCaptchaActivity.kt b/firebase-auth/core/src/main/kotlin/org/microg/gms/firebase/auth/ReCaptchaActivity.kt
index d52b136541..4311eb3e38 100644
--- a/firebase-auth/core/src/main/kotlin/org/microg/gms/firebase/auth/ReCaptchaActivity.kt
+++ b/firebase-auth/core/src/main/kotlin/org/microg/gms/firebase/auth/ReCaptchaActivity.kt
@@ -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) {
diff --git a/firebase-auth/core/src/main/kotlin/org/microg/gms/firebase/auth/ReCaptchaOverlay.kt b/firebase-auth/core/src/main/kotlin/org/microg/gms/firebase/auth/ReCaptchaOverlayService.kt
similarity index 50%
rename from firebase-auth/core/src/main/kotlin/org/microg/gms/firebase/auth/ReCaptchaOverlay.kt
rename to firebase-auth/core/src/main/kotlin/org/microg/gms/firebase/auth/ReCaptchaOverlayService.kt
index c7c2cda986..b04ac1c351 100644
--- a/firebase-auth/core/src/main/kotlin/org/microg/gms/firebase/auth/ReCaptchaOverlay.kt
+++ b/firebase-auth/core/src/main/kotlin/org/microg/gms/firebase/auth/ReCaptchaOverlayService.kt
@@ -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) {
+class ReCaptchaOverlayService : Service() {
+
+ 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)
+ }
- val windowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
- var finished = false
- var container: View? = null
+ 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(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 { 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)
}
}
}
diff --git a/firebase-auth/core/src/main/kotlin/org/microg/gms/firebase/auth/extensions.kt b/firebase-auth/core/src/main/kotlin/org/microg/gms/firebase/auth/extensions.kt
new file mode 100644
index 0000000000..66fd1b1f7a
--- /dev/null
+++ b/firebase-auth/core/src/main/kotlin/org/microg/gms/firebase/auth/extensions.kt
@@ -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"
\ No newline at end of file
diff --git a/vending-app/src/main/AndroidManifest.xml b/vending-app/src/main/AndroidManifest.xml
index eb12ae7378..c22d29a3a0 100644
--- a/vending-app/src/main/AndroidManifest.xml
+++ b/vending-app/src/main/AndroidManifest.xml
@@ -164,6 +164,7 @@