Repo Created

This commit is contained in:
Fr4nz D13trich 2025-11-15 17:44:12 +01:00
parent eb305e2886
commit a8c22c65db
4784 changed files with 329907 additions and 2 deletions

View file

@ -0,0 +1,44 @@
/*
* SPDX-FileCopyrightText: 2023 microG Project Team
* SPDX-License-Identifier: Apache-2.0
*/
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
dependencies {
api project(':play-services-auth-api-phone')
implementation project(':play-services-base-core')
implementation "androidx.appcompat:appcompat:$appcompatVersion"
}
android {
namespace "org.microg.gms.auth.phone"
compileSdkVersion androidCompileSdk
buildToolsVersion "$androidBuildVersionTools"
defaultConfig {
versionName version
minSdkVersion androidMinSdk
targetSdkVersion androidTargetSdk
}
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
}
compileOptions {
sourceCompatibility = 1.8
targetCompatibility = 1.8
}
kotlinOptions {
jvmTarget = 1.8
}
lintOptions {
disable 'MissingTranslation', 'GetLocales'
}
}

View file

@ -0,0 +1,36 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ SPDX-FileCopyrightText: 2023 microG Project Team
~ SPDX-License-Identifier: Apache-2.0
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.RECEIVE_SMS" />
<application>
<activity
android:name="org.microg.gms.auth.phone.UserConsentPromptActivity"
android:exported="true"
android:process=":ui"
android:theme="@style/Theme.AppCompat.DayNight.Dialog.Alert.NoActionBar" />
<activity
android:name="org.microg.gms.auth.phone.AskPermissionActivity"
android:excludeFromRecents="true"
android:exported="false"
android:process=":ui"
android:theme="@style/Theme.Translucent" />
<service
android:name="org.microg.gms.auth.phone.SmsRetrieverService"
android:exported="true">
<intent-filter>
<action android:name="com.google.android.gms.auth.api.phone.service.SmsRetrieverApiService.START" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</service>
</application>
</manifest>

View file

@ -0,0 +1,54 @@
/*
* SPDX-FileCopyrightText: 2023 microG Project Team
* SPDX-License-Identifier: Apache-2.0
*/
package org.microg.gms.auth.phone
import android.Manifest
import android.content.Intent
import android.os.Build.VERSION.SDK_INT
import android.os.Bundle
import android.os.Message
import android.os.Messenger
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.core.os.bundleOf
private const val TAG = "AskPermission"
private const val REQUEST_CODE_PERMISSION = 101
private val ALLOWED_PERMISSIONS = setOf(Manifest.permission.RECEIVE_SMS, Manifest.permission.READ_CONTACTS)
class AskPermissionActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val permissions = intent.getStringArrayExtra(EXTRA_PERMISSIONS) ?: arrayOf(Manifest.permission.RECEIVE_SMS)
Log.d(TAG, "Requesting permissions: ${permissions.toList()}")
if (SDK_INT < 23 || permissions.any { it !in ALLOWED_PERMISSIONS }) {
sendReply(RESULT_CANCELED)
finish()
} else {
ActivityCompat.requestPermissions(this, permissions, REQUEST_CODE_PERMISSION)
}
}
private fun sendReply(code: Int = RESULT_OK, extras: Bundle = Bundle.EMPTY) {
intent.getParcelableExtra<Messenger>(EXTRA_MESSENGER)?.let {
it.send(Message.obtain().apply {
what = code
data = extras
})
}
setResult(code, Intent().apply { putExtras(extras) })
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
if (requestCode == REQUEST_CODE_PERMISSION) {
sendReply(extras = bundleOf(EXTRA_GRANT_RESULTS to grantResults))
finish()
} else {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
}
}
}

View file

@ -0,0 +1,359 @@
/*
* SPDX-FileCopyrightText: 2023 microG Project Team
* SPDX-License-Identifier: Apache-2.0
*/
package org.microg.gms.auth.phone
import android.Manifest.permission.READ_CONTACTS
import android.Manifest.permission.RECEIVE_SMS
import android.annotation.TargetApi
import android.app.Activity
import android.app.AlarmManager
import android.app.PendingIntent
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.content.pm.PackageManager.PERMISSION_GRANTED
import android.database.Cursor
import android.os.*
import android.os.Build.VERSION.SDK_INT
import android.provider.ContactsContract
import android.provider.ContactsContract.CommonDataKinds.Phone
import android.provider.Telephony
import android.telephony.SmsMessage
import android.text.TextUtils
import android.util.Base64
import android.util.Log
import androidx.core.app.PendingIntentCompat
import androidx.core.content.ContextCompat
import androidx.core.os.bundleOf
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import com.google.android.gms.auth.api.phone.SmsRetriever
import com.google.android.gms.common.api.CommonStatusCodes
import com.google.android.gms.common.api.Status
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import org.microg.gms.auth.phone.SmsRetrieverRequestType.RETRIEVER
import org.microg.gms.auth.phone.SmsRetrieverRequestType.USER_CONSENT
import org.microg.gms.common.Constants
import org.microg.gms.utils.getSignatures
import java.nio.charset.StandardCharsets
import java.security.MessageDigest
import java.util.concurrent.atomic.AtomicInteger
private const val TAG = "SmsRetrieverCore"
private const val ACTION_SMS_RETRIEVE_TIMEOUT = "org.microg.gms.auth.phone.ACTION_SMS_RETRIEVE_TIMEOUT"
private const val EXTRA_REQUEST_ID = "requestId"
private const val TIMEOUT = 1000 * 60 * 5 // 5 minutes
private const val MESSAGE_MAX_LEN = 140
class SmsRetrieverCore(private val context: Context, override val lifecycle: Lifecycle) : LifecycleOwner, DefaultLifecycleObserver {
private val requests: HashMap<Int, SmsRetrieverRequest> = hashMapOf()
private val requestIdCounter = AtomicInteger(0)
private lateinit var timeoutBroadcastReceiver: BroadcastReceiver
private lateinit var smsBroadcastReceiver: BroadcastReceiver
private var requestCode = 0
private val alarmManager: AlarmManager
get() = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
init {
lifecycle.addObserver(this)
}
@TargetApi(19)
private fun configureBroadcastListenersIfNeeded() {
synchronized(this) {
if (!this::timeoutBroadcastReceiver.isInitialized) {
val intentFilter = IntentFilter(ACTION_SMS_RETRIEVE_TIMEOUT)
timeoutBroadcastReceiver = TimeoutReceiver()
ContextCompat.registerReceiver(context, timeoutBroadcastReceiver, intentFilter, ContextCompat.RECEIVER_NOT_EXPORTED)
}
if (!this::smsBroadcastReceiver.isInitialized) {
val intentFilter = IntentFilter(Telephony.Sms.Intents.SMS_RECEIVED_ACTION)
intentFilter.priority = 999
smsBroadcastReceiver = SmsReceiver()
context.registerReceiver(smsBroadcastReceiver, intentFilter)
}
}
}
private suspend fun ensureReady(permissions: Array<String>): Boolean {
if (SDK_INT < 19) throw RuntimeException("Version not supported")
if (!ensurePermission(permissions)) return false
configureBroadcastListenersIfNeeded()
return true
}
suspend fun startSmsRetriever(packageName: String) {
val appHashString = getHashString(packageName)
if (!ensureReady(arrayOf(RECEIVE_SMS)))
throw RuntimeException("Initialization failed")
if (anyOtherPackageHasHashString(packageName, appHashString))
throw RuntimeException("Collision in hash string, can't use SMS Retriever API")
if (requests.values.any { it.packageName == packageName && it.appHashString == appHashString && it.type == RETRIEVER })
throw RuntimeException("App already listening")
val request = SmsRetrieverRequest(
id = requestIdCounter.incrementAndGet(),
type = RETRIEVER,
packageName = packageName,
appHashString = appHashString,
timeoutPendingIntent = getTimeoutPendingIntent(context, packageName)
)
requests[request.id] = request
alarmManager.set(AlarmManager.RTC, request.creation + TIMEOUT, request.timeoutPendingIntent)
}
suspend fun startWithConsentPrompt(packageName: String, senderPhoneNumber: String?) {
if (!ensureReady(arrayOf(RECEIVE_SMS, READ_CONTACTS)))
throw RuntimeException("Initialization failed")
if (requests.values.any { it.packageName == packageName && it.senderPhoneNumber == senderPhoneNumber && it.type == USER_CONSENT })
throw RuntimeException("App already listening")
val request = SmsRetrieverRequest(
id = requestIdCounter.incrementAndGet(),
type = USER_CONSENT,
packageName = packageName,
senderPhoneNumber = senderPhoneNumber,
timeoutPendingIntent = getTimeoutPendingIntent(context, packageName)
)
requests[request.id] = request
alarmManager.set(AlarmManager.RTC, request.creation + TIMEOUT, request.timeoutPendingIntent)
}
fun hasOngoingUserConsentRequest(): Boolean {
return requests.values.any { it.type == USER_CONSENT }
}
private fun sendRetrieverBroadcast(request: SmsRetrieverRequest, messageBody: String) {
sendReply(request, Status.SUCCESS, bundleOf(SmsRetriever.EXTRA_SMS_MESSAGE to messageBody))
}
private fun sendUserConsentBroadcast(request: SmsRetrieverRequest, messageBody: String) {
val userConsentIntent = Intent(context, UserConsentPromptActivity::class.java)
userConsentIntent.setPackage(Constants.GMS_PACKAGE_NAME)
userConsentIntent.putExtra(EXTRA_MESSENGER, Messenger(object : Handler(Looper.getMainLooper()) {
override fun handleMessage(msg: Message) {
if (Binder.getCallingUid() == Process.myUid()) {
if (msg.what == MSG_REQUEST_MESSAGE_BODY) {
msg.replyTo?.send(Message.obtain().apply {
what = 1
data = bundleOf("message" to messageBody)
})
} else if (msg.what == MSG_CONSUME_MESSAGE) {
finishRequest(request)
}
}
}
}))
sendReply(request, Status.SUCCESS, bundleOf(SmsRetriever.EXTRA_CONSENT_INTENT to userConsentIntent), false)
}
private fun getTimeoutPendingIntent(context: Context, packageName: String): PendingIntent {
val intent = Intent(ACTION_SMS_RETRIEVE_TIMEOUT)
intent.setPackage(packageName)
return PendingIntentCompat.getBroadcast(context, ++requestCode, intent, 0, false)!!
}
private fun tryHandleIncomingMessageAsRetrieverMessage(messageBody: String): Boolean {
for (request in requests.values) {
if (request.type == RETRIEVER) {
// 11-digit hash code that uniquely identifies your app
if (request.appHashString.isNullOrBlank() || !messageBody.contains(request.appHashString)) continue
sendRetrieverBroadcast(request, messageBody)
return true
}
}
return false
}
private fun tryHandleIncomingMessageAsUserConsentMessage(senderPhoneNumber: String?, messageBody: String): Boolean {
val senderPhoneNumber = senderPhoneNumber ?: return false
// 4-10 digit alphanumeric code containing at least one number
if (messageBody.split("[^A-Za-z0-9]".toRegex()).none { it.length in 4..10 && it.any(Char::isDigit) }) return false
// Sender cannot be in the user's Contacts list
if (isPhoneNumberInContacts(context, senderPhoneNumber)) return false
for (request in requests.values) {
if (request.type == USER_CONSENT) {
if (!request.senderPhoneNumber.isNullOrBlank() && request.senderPhoneNumber != senderPhoneNumber) continue
sendUserConsentBroadcast(request, messageBody)
return true
}
}
return false
}
private fun handleIncomingSmsMessage(senderPhoneNumber: String?, messageBody: String) {
Log.d(TAG, "handleIncomingSmsMessage: senderPhoneNumber:$senderPhoneNumber messageBody: $messageBody")
if (messageBody.isBlank()) return
if (tryHandleIncomingMessageAsRetrieverMessage(messageBody)) return
if (tryHandleIncomingMessageAsUserConsentMessage(senderPhoneNumber, messageBody)) return
}
fun handleTimeout(requestId: Int) {
val request = requests[requestId] ?: return
sendReply(request, Status(CommonStatusCodes.TIMEOUT))
}
private fun sendReply(request: SmsRetrieverRequest, status: Status, extras: Bundle = Bundle.EMPTY, finish: Boolean = true) {
Log.d(TAG, "Send reply to ${request.packageName} ${CommonStatusCodes.getStatusCodeString(status.statusCode)}")
val intent = Intent(SmsRetriever.SMS_RETRIEVED_ACTION)
intent.setPackage(request.packageName)
intent.putExtras(extras)
intent.putExtra(SmsRetriever.EXTRA_STATUS, status)
context.sendBroadcast(intent)
if (finish) finishRequest(request)
}
fun finishRequest(request: SmsRetrieverRequest) {
alarmManager.cancel(request.timeoutPendingIntent)
requests.remove(request.id)
}
override fun onDestroy(owner: LifecycleOwner) {
super.onDestroy(owner)
if (this::smsBroadcastReceiver.isInitialized) context.unregisterReceiver(smsBroadcastReceiver)
if (this::timeoutBroadcastReceiver.isInitialized) context.unregisterReceiver(timeoutBroadcastReceiver)
for (request in requests.values) {
sendReply(request, Status(CommonStatusCodes.TIMEOUT))
}
requests.clear()
}
@TargetApi(19)
private inner class SmsReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
if (Telephony.Sms.Intents.SMS_RECEIVED_ACTION == intent.action) {
val messages = Telephony.Sms.Intents.getMessagesFromIntent(intent)
val messageBodyBuilder = StringBuilder()
var senderPhoneNumber: String? = null
for (message in messages) {
messageBodyBuilder.append(message.messageBody)
senderPhoneNumber = message.originatingAddress
}
try {
handleIncomingSmsMessage(senderPhoneNumber, messageBodyBuilder.toString())
} catch (e: Exception) {
Log.w(TAG, "Error handling incoming SMS", e)
}
}
}
}
private inner class TimeoutReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val requestId = intent.getIntExtra(EXTRA_REQUEST_ID, -1)
if (requestId != -1) {
handleTimeout(requestId)
}
}
}
@TargetApi(19)
fun getHashString(packageName: String): String {
val signature =
context.packageManager.getSignatures(packageName).firstOrNull()?.toCharsString() ?: throw RuntimeException("No signature found for $packageName")
val appInfo = "$packageName $signature"
val messageDigest = MessageDigest.getInstance("SHA-256")
messageDigest.update(appInfo.toByteArray(StandardCharsets.UTF_8))
return Base64.encodeToString(messageDigest.digest(), Base64.NO_PADDING or Base64.NO_WRAP).substring(0, 11)
}
private fun anyOtherPackageHasHashString(packageName: String, hashString: String): Boolean {
val collision = context.packageManager.getInstalledPackages(0)
.firstOrNull { it.packageName != packageName && getHashString(it.packageName) == hashString } ?: return false
Log.w(TAG, "Hash string collision between $packageName and ${collision.packageName} (both are $hashString)")
return true
}
private fun isPhoneNumberInContacts(context: Context, phoneNumber: String): Boolean {
fun normalizePhoneNumber(input: String): String {
var output = ""
if (!TextUtils.isEmpty(input)) {
// only keep digits
val temp = input.replace("[^0-9]".toRegex(), "")
// trim leading zeroes
output = temp.replaceFirst("^0*".toRegex(), "")
}
return output
}
val normalizePhoneNumber = normalizePhoneNumber(phoneNumber)
var cursor: Cursor? = null
try {
cursor = context.contentResolver.query(Phone.CONTENT_URI, arrayOf(Phone.NUMBER), null, null, null) ?: return false
while (cursor.moveToNext()) {
val addressIndex = cursor.getColumnIndex(Phone.NUMBER)
val contactPhoneNumber = normalizePhoneNumber(cursor.getString(addressIndex))
if (!TextUtils.isEmpty(normalizePhoneNumber) && !TextUtils.isEmpty(contactPhoneNumber) && normalizePhoneNumber == contactPhoneNumber) {
return true
}
}
} catch (e: Exception) {
Log.w(TAG, e)
} finally {
cursor?.close()
}
return false
}
private val activePermissionRequestLock = Mutex()
private var activePermissionRequest: Deferred<Boolean>? = null
private suspend fun ensurePermission(permissions: Array<String>): Boolean {
if (SDK_INT < 23)
return true
if (permissions.all { ContextCompat.checkSelfPermission(context, it) == PERMISSION_GRANTED })
return true
val (completable, deferred) = activePermissionRequestLock.withLock {
if (activePermissionRequest == null) {
val completable = CompletableDeferred<Boolean>()
activePermissionRequest = completable
completable to activePermissionRequest!!
} else {
null to activePermissionRequest!!
}
}
if (completable != null) {
val intent = Intent(context, AskPermissionActivity::class.java)
intent.putExtra(EXTRA_MESSENGER, Messenger(object : Handler(Looper.getMainLooper()) {
override fun handleMessage(msg: Message) {
if (msg.what == Activity.RESULT_OK) {
val grantResults = msg.data?.getIntArray(EXTRA_GRANT_RESULTS) ?: IntArray(0)
completable.complete(grantResults.size == permissions.size && grantResults.all { it == PERMISSION_GRANTED })
} else {
completable.complete(false)
}
}
}))
intent.putExtra(EXTRA_PERMISSIONS, permissions)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
context.startActivity(intent)
}
return deferred.await()
}
}

View file

@ -0,0 +1,22 @@
/*
* SPDX-FileCopyrightText: 2023 microG Project Team
* SPDX-License-Identifier: Apache-2.0
*/
package org.microg.gms.auth.phone
import android.app.PendingIntent
data class SmsRetrieverRequest(
val id: Int,
val type: SmsRetrieverRequestType,
val packageName: String,
val timeoutPendingIntent: PendingIntent,
val appHashString: String? = null,
val creation: Long = System.currentTimeMillis(),
val senderPhoneNumber: String? = null
)
enum class SmsRetrieverRequestType {
RETRIEVER, USER_CONSENT
}

View file

@ -0,0 +1,131 @@
/*
* SPDX-FileCopyrightText: 2023 microG Project Team
* SPDX-License-Identifier: Apache-2.0
*/
package org.microg.gms.auth.phone
import android.util.Log
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope
import com.google.android.gms.auth.api.phone.SmsRetrieverStatusCodes
import com.google.android.gms.auth.api.phone.internal.IAutofillPermissionStateCallback
import com.google.android.gms.auth.api.phone.internal.IOngoingSmsRequestCallback
import com.google.android.gms.auth.api.phone.internal.ISmsRetrieverApiService
import com.google.android.gms.auth.api.phone.internal.ISmsRetrieverResultCallback
import com.google.android.gms.common.Feature
import com.google.android.gms.common.api.CommonStatusCodes
import com.google.android.gms.common.api.Status
import com.google.android.gms.common.api.internal.IStatusCallback
import com.google.android.gms.common.internal.ConnectionInfo
import com.google.android.gms.common.internal.GetServiceRequest
import com.google.android.gms.common.internal.IGmsCallbacks
import org.microg.gms.BaseService
import org.microg.gms.common.GmsService
import org.microg.gms.common.PackageUtils
private const val TAG = "SmsRetrieverService"
private val FEATURES = arrayOf(
Feature("sms_retrieve", 1),
Feature("user_consent", 3)
)
class SmsRetrieverService : BaseService(TAG, GmsService.SMS_RETRIEVER) {
private val smsRetriever = SmsRetrieverCore(this, lifecycle)
override fun handleServiceRequest(callback: IGmsCallbacks, request: GetServiceRequest, service: GmsService) {
val packageName = PackageUtils.getAndCheckCallingPackage(this, request.packageName)
?: throw IllegalArgumentException("Missing package name")
callback.onPostInitCompleteWithConnectionInfo(
CommonStatusCodes.SUCCESS,
SmsRetrieverServiceImpl(smsRetriever, packageName, lifecycle),
ConnectionInfo().apply { features = FEATURES }
)
}
}
class SmsRetrieverServiceImpl(private val smsRetriever: SmsRetrieverCore, private val packageName: String, override val lifecycle: Lifecycle) :
ISmsRetrieverApiService.Stub(), LifecycleOwner {
override fun startSmsRetriever(callback: ISmsRetrieverResultCallback) {
Log.d(TAG, "startSmsRetriever()")
lifecycleScope.launchWhenStarted {
val status = try {
smsRetriever.startSmsRetriever(packageName)
Status.SUCCESS
} catch (e: Exception) {
Status(CommonStatusCodes.INTERNAL_ERROR, e.message)
}
try {
callback.onResult(status)
} catch (e: Exception) {
Log.w(TAG, "Failed delivering $status for startSmsRetriever()", e)
}
}
}
override fun startWithConsentPrompt(senderPhoneNumber: String?, callback: ISmsRetrieverResultCallback) {
Log.d(TAG, "startWithConsentPrompt($senderPhoneNumber)")
lifecycleScope.launchWhenStarted {
val status = try {
smsRetriever.startWithConsentPrompt(packageName, senderPhoneNumber)
Status.SUCCESS
} catch (e: Exception) {
Status(CommonStatusCodes.INTERNAL_ERROR, e.message)
}
try {
callback.onResult(status)
} catch (e: Exception) {
Log.w(TAG, "Failed delivering $status for startWithConsentPrompt()", e)
}
}
}
override fun startSmsCodeAutofill(callback: IStatusCallback) {
Log.d(TAG, "startSmsCodeAutofill()")
try {
callback.onResult(Status(SmsRetrieverStatusCodes.API_NOT_AVAILABLE))
} catch (e: Exception) {
Log.w(TAG, "Failed delivering result for startSmsCodeAutofill()", e)
}
}
override fun checkAutofillPermissionState(callback: IAutofillPermissionStateCallback) {
Log.d(TAG, "checkAutofillPermissionState()")
try {
callback.onCheckPermissionStateResult(Status.SUCCESS, 1)
} catch (e: Exception) {
Log.w(TAG, "Failed delivering result for checkAutofillPermissionState()", e)
}
}
override fun checkOngoingSmsRequest(packageName: String?, callback: IOngoingSmsRequestCallback) {
Log.d(TAG, "checkOngoingSmsRequest($packageName)")
lifecycleScope.launchWhenStarted {
val result = try {
smsRetriever.hasOngoingUserConsentRequest()
} catch (e: Exception) {
true
}
try {
callback.onHasOngoingSmsRequestResult(Status.SUCCESS, result)
} catch (e: Exception) {
Log.w(TAG, "Failed delivering $result for checkOngoingSmsRequest()", e)
}
}
}
override fun startSmsCodeBrowser(callback: IStatusCallback) {
Log.d(TAG, "startSmsCodeBrowser()")
try {
callback.onResult(Status(SmsRetrieverStatusCodes.API_NOT_AVAILABLE))
} catch (e: Exception) {
Log.w(TAG, "Failed delivering result for startSmsCodeBrowser()", e)
}
}
}

View file

@ -0,0 +1,79 @@
/*
* SPDX-FileCopyrightText: 2023 microG Project Team
* SPDX-License-Identifier: Apache-2.0
*/
package org.microg.gms.auth.phone
import android.annotation.TargetApi
import android.content.Intent
import android.os.*
import android.text.Html
import android.view.Gravity
import android.view.ViewGroup.LayoutParams
import android.widget.Button
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import com.google.android.gms.auth.api.phone.SmsRetriever
import org.microg.gms.ui.buildAlertDialog
import org.microg.gms.utils.getApplicationLabel
private const val TAG = "UserConsentPrompt"
class UserConsentPromptActivity : AppCompatActivity() {
private val messenger: Messenger?
get() = intent.getParcelableExtra(EXTRA_MESSENGER)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val callingPackage = callingActivity?.packageName ?: return finish()
val messenger = messenger ?: return finish()
messenger.send(Message.obtain().apply {
what = MSG_REQUEST_MESSAGE_BODY
replyTo = Messenger(object : Handler(Looper.getMainLooper()) {
override fun handleMessage(msg: Message) {
if (msg.what == MSG_REQUEST_MESSAGE_BODY) {
val message = msg.data.getString("message") ?: return
showConsentDialog(callingPackage, message)
}
}
})
})
}
@TargetApi(16)
private fun showConsentDialog(callingPackage: String, message: String) {
val view = layoutInflater.inflate(R.layout.dialog_sms_user_consent, null)
val dialog = buildAlertDialog()
.setCancelable(false)
.setView(view)
.create()
val appName = packageManager.getApplicationLabel(callingPackage)
view.findViewById<TextView>(android.R.id.title).text = Html.fromHtml(getString(R.string.sms_user_consent_title, Html.escapeHtml(appName)))
view.findViewById<TextView>(android.R.id.text1).text = message
view.findViewById<Button>(android.R.id.button2).setOnClickListener {
dialog.cancel()
}
dialog.setOnCancelListener {
setResult(RESULT_CANCELED)
finish()
}
view.findViewById<Button>(android.R.id.button1).setOnClickListener {
dialog.dismiss()
setResult(RESULT_OK, Intent().apply {
putExtra(SmsRetriever.EXTRA_SMS_MESSAGE, message)
})
messenger?.send(Message.obtain().apply {
what = MSG_CONSUME_MESSAGE
})
finish()
}
if (!dialog.isShowing) {
dialog.window?.setGravity(Gravity.BOTTOM)
dialog.window?.setLayout(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)
dialog.show()
}
}
}

View file

@ -0,0 +1,13 @@
/*
* SPDX-FileCopyrightText: 2023 microG Project Team
* SPDX-License-Identifier: Apache-2.0
*/
package org.microg.gms.auth.phone
const val MSG_REQUEST_MESSAGE_BODY = 1
const val MSG_CONSUME_MESSAGE = 2
const val EXTRA_MESSENGER = "messenger"
const val EXTRA_PERMISSIONS = "permissions"
const val EXTRA_GRANT_RESULTS = "grantResults"

View file

@ -0,0 +1,57 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ SPDX-FileCopyrightText: 2023 microG Project Team
~ SPDX-License-Identifier: Apache-2.0
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="bottom"
android:gravity="bottom|center"
android:padding="24dp"
android:orientation="vertical">
<TextView
android:id="@android:id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAlignment="textStart"
android:gravity="start"
style="@style/TextAppearance.AppCompat.Title"
tools:text="Allow App to read the message below and enter the code?" />
<TextView
android:id="@android:id/text1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAlignment="textStart"
android:layout_marginTop="10dp"
android:gravity="start"
style="@style/TextAppearance.AppCompat.Small"
tools:text="OTP is 123456 to accept transaction with App. Do not share for security reasons" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:layout_gravity="end"
android:orientation="horizontal">
<Button
android:id="@android:id/button2"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:text="@string/sms_user_consent_deny"
style="@style/Widget.AppCompat.Button.Borderless" />
<Button
android:id="@android:id/button1"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:layout_marginLeft="8dp"
android:layout_marginStart="8dp"
android:text="@string/sms_user_consent_allow"
style="@style/Widget.AppCompat.Button.Colored" />
</LinearLayout>
</LinearLayout>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="sms_user_consent_deny">رفض</string>
<string name="sms_user_consent_allow">سماح</string>
<string name="sms_user_consent_title">السماح ل<b>%s</b> بقراءة الرسالة أدناه وإدخال الرمز؟</string>
</resources>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="sms_user_consent_allow">Permitir</string>
<string name="sms_user_consent_title">¿Quies permitir que l\'aplicación <b>%s</b> llea\'l mensaxe d\'abaxo ya introduza\'l códigu\?</string>
<string name="sms_user_consent_deny">Negar</string>
</resources>

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="sms_user_consent_title"><b>%s</b>-ə aşağıdakı mesajı oxumağa və kodu daxil etməyə icazə verilsin?</string>
</resources>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="sms_user_consent_title">Дазволіць <b>%s</b> прачытаць паведамленне і ўвесці код?</string>
<string name="sms_user_consent_allow">Дазволіць</string>
<string name="sms_user_consent_deny">Адхіліць</string>
</resources>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="sms_user_consent_deny">Zakázat</string>
<string name="sms_user_consent_title">Povolit aplikaci <b>%s</b> zobrazit zprávu níže a zadat kód?</string>
<string name="sms_user_consent_allow">Povolit</string>
</resources>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="sms_user_consent_allow">Erlauben</string>
<string name="sms_user_consent_deny">Ablehnen</string>
<string name="sms_user_consent_title"><b>%s</b> erlauben, die folgende Nachricht zu lesen und den Code einzugeben?</string>
</resources>

View file

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<resources></resources>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="sms_user_consent_title">¿Permitir que <b>%s</b> lean el mensaje de abajo e introduzcan el código?</string>
<string name="sms_user_consent_allow">Permitir</string>
<string name="sms_user_consent_deny">Denegar</string>
</resources>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="sms_user_consent_allow">پذیرفتن</string>
<string name="sms_user_consent_deny">رد کردن</string>
<string name="sms_user_consent_title">به <b>%s</b> اجازه دهید پیام زیر را بخواند و کد را وارد کنید؟</string>
</resources>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="sms_user_consent_title">Sallitaanko <b>%s</b> lukea seuraavan viestin ja syöttää koodin?</string>
<string name="sms_user_consent_allow">Salli</string>
<string name="sms_user_consent_deny">Kiellä</string>
</resources>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="sms_user_consent_title">Payagan ang <b>%s</b> na basahin ang mensahe sa ibaba at ilagay ang code?</string>
<string name="sms_user_consent_allow">Payagan</string>
<string name="sms_user_consent_deny">Tanggihan</string>
</resources>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="sms_user_consent_title">Autoriser <b>%s</b> à lire le message ci-dessous et saisir le code?</string>
<string name="sms_user_consent_allow">Autoriser</string>
<string name="sms_user_consent_deny">Refuser</string>
</resources>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="sms_user_consent_deny">Diúltaigh</string>
<string name="sms_user_consent_title">An bhfuil cead ag <b>%s</b> an teachtaireacht thíos a léamh agus an cód a chur isteach?</string>
<string name="sms_user_consent_allow">Ceadaigh</string>
</resources>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="sms_user_consent_allow">Izinkan</string>
<string name="sms_user_consent_title">Izinkan <b>%s</b> untuk membaca pesan di bawah ini dan memasukkan kode?</string>
<string name="sms_user_consent_deny">Tolak</string>
</resources>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="sms_user_consent_title">Leyfa <b>%s</b> að lesa skilaboðin hér fyrir neðan og setja inn kóðann?</string>
<string name="sms_user_consent_deny">Hafna</string>
<string name="sms_user_consent_allow">Leyfa</string>
</resources>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="sms_user_consent_allow">Consenti</string>
<string name="sms_user_consent_title">Consentire a <b>%s</b> di leggere il seguente messaggio e inserire il codice\?</string>
<string name="sms_user_consent_deny">Nega</string>
</resources>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="sms_user_consent_allow">許可</string>
<string name="sms_user_consent_title"><b>%s</b> に以下のメッセージの読み取りを許可してコードを入力させますか?</string>
<string name="sms_user_consent_deny">拒否</string>
</resources>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="sms_user_consent_title"><b>%s</b>가 아래의 메시지를 읽고 코드를 입력하도록 허용할까요?</string>
<string name="sms_user_consent_allow">허용</string>
<string name="sms_user_consent_deny">거부</string>
</resources>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="sms_user_consent_allow">Atļaut</string>
<string name="sms_user_consent_deny">Liegt</string>
</resources>

View file

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<resources></resources>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="sms_user_consent_title">താഴെയുള്ള സന്ദേശം വായിച്ച് കോഡ് നൽകാൻ <b>%s</b> നെ അനുവദിക്കണോ?</string>
<string name="sms_user_consent_allow">അനുവദിക്കുക</string>
<string name="sms_user_consent_deny">നിരസിക്കുക</string>
</resources>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="sms_user_consent_deny">Nekt</string>
<string name="sms_user_consent_title">La <b>%s</b> lese meldingen under og legge inn koden?</string>
<string name="sms_user_consent_allow">Tillat</string>
</resources>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="sms_user_consent_title"><b>%s</b> toestaan het onderstaande bericht te lezen en de code in te voeren?</string>
<string name="sms_user_consent_allow">Toestaan</string>
<string name="sms_user_consent_deny">Niet toestaan</string>
</resources>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="sms_user_consent_allow">Zezwól</string>
<string name="sms_user_consent_title">Zezwolić <b>%s</b> na odczyt poniższej wiadomości i wprowadzenie kodu?</string>
<string name="sms_user_consent_deny">Odmów</string>
</resources>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="sms_user_consent_title">Permitir que <b>%s</b> leia a mensagem abaixo e insira o código?</string>
<string name="sms_user_consent_allow">Permitir</string>
<string name="sms_user_consent_deny">Negar</string>
</resources>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="sms_user_consent_title">Permitir que <b>%s</b> leia a mensagem abaixo e insira o código?</string>
<string name="sms_user_consent_allow">Permitir</string>
<string name="sms_user_consent_deny">Recusar</string>
</resources>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="sms_user_consent_allow">Permite</string>
<string name="sms_user_consent_title">Permiți ca <b>%s</b> să citească mesajul de mai jos și să introducă codul\?</string>
<string name="sms_user_consent_deny">Refuză</string>
</resources>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="sms_user_consent_title">Разрешить <b>%s</b> прочитать сообщение ниже и ввести код?</string>
<string name="sms_user_consent_allow">Разрешить</string>
<string name="sms_user_consent_deny">Отклонить</string>
</resources>

View file

@ -0,0 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
</resources>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="sms_user_consent_allow">Дозволи</string>
<string name="sms_user_consent_title">Дозволити <b>%s</b> да прочита поруку испод и унесе кôд\?</string>
<string name="sms_user_consent_deny">Одбиј</string>
</resources>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="sms_user_consent_allow">Tillåt</string>
<string name="sms_user_consent_title">Vill du låta <b>%s</b> läsa meddelandet nedan och ange koden?</string>
<string name="sms_user_consent_deny">Neka</string>
</resources>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="sms_user_consent_title">கீழே உள்ள செய்தியைப் படித்து குறியீட்டை உள்ளிட <b>%s </b> ஐ அனுமதிக்கவா?</string>
<string name="sms_user_consent_allow">இசைவு</string>
<string name="sms_user_consent_deny">மறு</string>
</resources>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="sms_user_consent_title">อนุญาตให้ <b>%s</b> อ่านข้อความด้านล่างและกรอกรหัส?</string>
<string name="sms_user_consent_allow">อนุญาต</string>
<string name="sms_user_consent_deny">ปฏิเสธ</string>
</resources>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="sms_user_consent_allow">İzin ver</string>
<string name="sms_user_consent_title"><b>%s</b> uygulamasının aşağıdaki mesajı okumasına ve kodu girmesine izin veriyor musunuz?</string>
<string name="sms_user_consent_deny">Reddet</string>
</resources>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="sms_user_consent_title"><b>%s</b> نىڭ تۆۋەندىكى مەزمۇننى ئوقۇپ ۋە كودنى كىرگۈزۈشىگە يول قويامدۇ؟</string>
<string name="sms_user_consent_deny">رەت قىل</string>
<string name="sms_user_consent_allow">يول قوي</string>
</resources>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="sms_user_consent_allow">Дозволити</string>
<string name="sms_user_consent_title">Дозволити <b>%s</b> прочитати наведене нижче повідомлення та ввести код?</string>
<string name="sms_user_consent_deny">Відхилити</string>
</resources>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="sms_user_consent_deny">Từ chối</string>
<string name="sms_user_consent_title">Cho phép <b>%s</b> đọc tin nhắn bên dưới và nhập mã?</string>
<string name="sms_user_consent_allow">Chấp nhận</string>
</resources>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="sms_user_consent_title">允许<b>%s</b>读取以下消息并输入代码?</string>
<string name="sms_user_consent_allow">允许</string>
<string name="sms_user_consent_deny">拒绝</string>
</resources>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="sms_user_consent_title">允許「<b>%s</b>」閱讀以下訊息並輸入代碼?</string>
<string name="sms_user_consent_deny">拒絕</string>
<string name="sms_user_consent_allow">允許</string>
</resources>

View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ SPDX-FileCopyrightText: 2023 microG Project Team
~ SPDX-License-Identifier: Apache-2.0
-->
<resources>
<string name="sms_user_consent_title">Allow <b>%s</b> to read the message below and enter the code?</string>
<string name="sms_user_consent_allow">Allow</string>
<string name="sms_user_consent_deny">Deny</string>
</resources>