diff options
author | Harsh Shandilya <me@msfjarvis.dev> | 2020-12-05 06:07:18 +0530 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-12-05 06:07:18 +0530 |
commit | 5e66d99c852ea67a88b650c03b0e8d55e83eccde (patch) | |
tree | aee323a08253fe7b863976eff92a1bd412bd1430 /app/src/nonFree/java/dev | |
parent | 8eb55f18a11d6b2f155c7c9d48ca833313ee13db (diff) |
Refactor package structure (#1233)
* idea: default test runner to Gradle
* Kick off package structure revamp
* Reparent all classes under dev.msfjarvis.aps
Signed-off-by: Harsh Shandilya <me@msfjarvis.dev>
Diffstat (limited to 'app/src/nonFree/java/dev')
-rw-r--r-- | app/src/nonFree/java/dev/msfjarvis/aps/autofill/oreo/ui/AutofillSmsActivity.kt | 160 |
1 files changed, 160 insertions, 0 deletions
diff --git a/app/src/nonFree/java/dev/msfjarvis/aps/autofill/oreo/ui/AutofillSmsActivity.kt b/app/src/nonFree/java/dev/msfjarvis/aps/autofill/oreo/ui/AutofillSmsActivity.kt new file mode 100644 index 00000000..f223cc79 --- /dev/null +++ b/app/src/nonFree/java/dev/msfjarvis/aps/autofill/oreo/ui/AutofillSmsActivity.kt @@ -0,0 +1,160 @@ +/* + * Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved. + * SPDX-License-Identifier: GPL-3.0-only + */ +package dev.msfjarvis.aps.autofill.oreo.ui + +import android.app.Activity +import android.app.PendingIntent +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.content.IntentSender +import android.os.Build +import android.os.Bundle +import android.view.autofill.AutofillManager +import androidx.annotation.RequiresApi +import androidx.appcompat.app.AppCompatActivity +import androidx.lifecycle.lifecycleScope +import com.github.ajalt.timberkt.e +import com.github.ajalt.timberkt.w +import com.github.androidpasswordstore.autofillparser.AutofillAction +import com.github.androidpasswordstore.autofillparser.Credentials +import com.github.michaelbull.result.onFailure +import com.github.michaelbull.result.runCatching +import com.google.android.gms.auth.api.phone.SmsCodeRetriever +import com.google.android.gms.auth.api.phone.SmsRetriever +import com.google.android.gms.common.ConnectionResult +import com.google.android.gms.common.GoogleApiAvailability +import com.google.android.gms.common.api.ResolvableApiException +import com.google.android.gms.tasks.Task +import dev.msfjarvis.aps.util.autofill.AutofillResponseBuilder +import dev.msfjarvis.aps.databinding.ActivityOreoAutofillSmsBinding +import dev.msfjarvis.aps.util.extensions.viewBinding +import java.util.concurrent.ExecutionException +import kotlin.coroutines.resume +import kotlin.coroutines.resumeWithException +import kotlin.coroutines.suspendCoroutine +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext + +suspend fun <T> Task<T>.suspendableAwait() = suspendCoroutine<T> { cont -> + addOnSuccessListener { result: T -> + cont.resume(result) + } + addOnFailureListener { e -> + // Unwrap specific exceptions (e.g. ResolvableApiException) from ExecutionException. + val cause = (e as? ExecutionException)?.cause ?: e + cont.resumeWithException(cause) + } +} + +@RequiresApi(Build.VERSION_CODES.O) +class AutofillSmsActivity : AppCompatActivity() { + + companion object { + + private var fillOtpFromSmsRequestCode = 1 + + fun shouldOfferFillFromSms(context: Context): Boolean { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) + return false + val googleApiAvailabilityInstance = GoogleApiAvailability.getInstance() + val googleApiStatus = googleApiAvailabilityInstance.isGooglePlayServicesAvailable(context) + if (googleApiStatus != ConnectionResult.SUCCESS) { + w { "Google Play Services unavailable or not updated: ${googleApiAvailabilityInstance.getErrorString(googleApiStatus)}" } + return false + } + // https://developer.android.com/guide/topics/text/autofill-services#sms-autofill + if (googleApiAvailabilityInstance.getApkVersion(context) < 190056000) { + w { "Google Play Service 19.0.56 or higher required for SMS OTP Autofill" } + return false + } + return true + } + + fun makeFillOtpFromSmsIntentSender(context: Context): IntentSender { + val intent = Intent(context, AutofillSmsActivity::class.java) + return PendingIntent.getActivity( + context, + fillOtpFromSmsRequestCode++, + intent, + PendingIntent.FLAG_CANCEL_CURRENT + ).intentSender + } + } + + private val binding by viewBinding(ActivityOreoAutofillSmsBinding::inflate) + + private lateinit var clientState: Bundle + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(binding.root) + setResult(RESULT_CANCELED) + binding.cancelButton.setOnClickListener { + finish() + } + } + + override fun onStart() { + super.onStart() + clientState = intent?.getBundleExtra(AutofillManager.EXTRA_CLIENT_STATE) ?: run { + e { "AutofillSmsActivity started without EXTRA_CLIENT_STATE" } + finish() + return + } + + registerReceiver(smsCodeRetrievedReceiver, IntentFilter(SmsCodeRetriever.SMS_CODE_RETRIEVED_ACTION), SmsRetriever.SEND_PERMISSION, null) + lifecycleScope.launch { + waitForSms() + } + } + + // Retry starting the SMS code retriever after a permission request. + @Suppress("DEPRECATION") + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + super.onActivityResult(requestCode, resultCode, data) + if (resultCode != Activity.RESULT_OK) + return + lifecycleScope.launch { + waitForSms() + } + } + + private suspend fun waitForSms() { + val smsClient = SmsCodeRetriever.getAutofillClient(this@AutofillSmsActivity) + runCatching { + withContext(Dispatchers.IO) { + smsClient.startSmsCodeRetriever().suspendableAwait() + } + }.onFailure { e -> + if (e is ResolvableApiException) { + e.startResolutionForResult(this@AutofillSmsActivity, 1) + } else { + e(e) + withContext(Dispatchers.Main) { + finish() + } + } + } + } + + private val smsCodeRetrievedReceiver = object : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + val smsCode = intent.getStringExtra(SmsCodeRetriever.EXTRA_SMS_CODE) + val fillInDataset = AutofillResponseBuilder.makeFillInDataset( + this@AutofillSmsActivity, + Credentials(null, null, smsCode), + clientState, + AutofillAction.FillOtpFromSms + ) + setResult(RESULT_OK, Intent().apply { + putExtra(AutofillManager.EXTRA_AUTHENTICATION_RESULT, fillInDataset) + }) + finish() + } + } +} |