Repo Created
This commit is contained in:
parent
eb305e2886
commit
a8c22c65db
4784 changed files with 329907 additions and 2 deletions
44
play-services-auth-api-phone/core/build.gradle
Normal file
44
play-services-auth-api-phone/core/build.gradle
Normal 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'
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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"
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
</resources>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
Loading…
Add table
Add a link
Reference in a new issue