Compile against SDK 33 (#2023)

* Compile against SDK 33

* autofill-parser: fix warnings for SDK 33 upgrade

* app: fix warnings for SDK 33 upgrade

* Mark all clipboard content as sensitive from crypto activities

* Skip Snackbar on Android 13 and above

* detekt: raise `TooManyFunctions` limit to 15
This commit is contained in:
Harsh Shandilya 2022-07-20 01:29:12 +05:30 committed by GitHub
parent 9cd2f5f446
commit ade73fd5bc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
29 changed files with 210 additions and 71 deletions

View file

@ -22,6 +22,9 @@ import app.passwordstore.databinding.ActivityOreoAutofillPublisherChangedBinding
import app.passwordstore.util.autofill.AutofillMatcher import app.passwordstore.util.autofill.AutofillMatcher
import app.passwordstore.util.autofill.AutofillPublisherChangedException import app.passwordstore.util.autofill.AutofillPublisherChangedException
import app.passwordstore.util.extensions.asLog import app.passwordstore.util.extensions.asLog
import app.passwordstore.util.extensions.getApplicationInfoCompat
import app.passwordstore.util.extensions.getPackageInfoCompat
import app.passwordstore.util.extensions.getParcelableExtraCompat
import app.passwordstore.util.extensions.viewBinding import app.passwordstore.util.extensions.viewBinding
import com.github.androidpasswordstore.autofillparser.FormOrigin import com.github.androidpasswordstore.autofillparser.FormOrigin
import com.github.androidpasswordstore.autofillparser.computeCertificatesHash import com.github.androidpasswordstore.autofillparser.computeCertificatesHash
@ -93,7 +96,8 @@ class AutofillPublisherChangedActivity : AppCompatActivity() {
this@AutofillPublisherChangedActivity, this@AutofillPublisherChangedActivity,
FormOrigin.App(appPackage) FormOrigin.App(appPackage)
) )
val fillResponse = intent.getParcelableExtra<FillResponse>(EXTRA_FILL_RESPONSE_AFTER_RESET) val fillResponse =
intent.getParcelableExtraCompat<FillResponse>(EXTRA_FILL_RESPONSE_AFTER_RESET)
setResult( setResult(
RESULT_OK, RESULT_OK,
Intent().apply { putExtra(AutofillManager.EXTRA_AUTHENTICATION_RESULT, fillResponse) } Intent().apply { putExtra(AutofillManager.EXTRA_AUTHENTICATION_RESULT, fillResponse) }
@ -106,11 +110,13 @@ class AutofillPublisherChangedActivity : AppCompatActivity() {
private fun showPackageInfo() { private fun showPackageInfo() {
runCatching { runCatching {
with(binding) { with(binding) {
val packageInfo = packageManager.getPackageInfo(appPackage, PackageManager.GET_META_DATA) val packageInfo =
packageManager.getPackageInfoCompat(appPackage, PackageManager.GET_META_DATA)
val installTime = DateUtils.getRelativeTimeSpanString(packageInfo.firstInstallTime) val installTime = DateUtils.getRelativeTimeSpanString(packageInfo.firstInstallTime)
warningAppInstallDate.text = warningAppInstallDate.text =
getString(R.string.oreo_autofill_warning_publisher_install_time, installTime) getString(R.string.oreo_autofill_warning_publisher_install_time, installTime)
val appInfo = packageManager.getApplicationInfo(appPackage, PackageManager.GET_META_DATA) val appInfo =
packageManager.getApplicationInfoCompat(appPackage, PackageManager.GET_META_DATA)
warningAppName.text = warningAppName.text =
getString( getString(
R.string.oreo_autofill_warning_publisher_app_name, R.string.oreo_autofill_warning_publisher_app_name,

View file

@ -10,6 +10,7 @@ import android.content.Intent
import android.content.SharedPreferences import android.content.SharedPreferences
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.os.PersistableBundle
import android.view.WindowManager import android.view.WindowManager
import androidx.annotation.CallSuper import androidx.annotation.CallSuper
import androidx.annotation.StringRes import androidx.annotation.StringRes
@ -68,8 +69,12 @@ open class BasePgpActivity : AppCompatActivity() {
) { ) {
val clipboard = clipboard ?: return val clipboard = clipboard ?: return
val clip = ClipData.newPlainText("pgp_handler_result_pm", text) val clip = ClipData.newPlainText("pgp_handler_result_pm", text)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
clip.description.extras =
PersistableBundle().apply { putBoolean("android.content.extra.IS_SENSITIVE", true) }
}
clipboard.setPrimaryClip(clip) clipboard.setPrimaryClip(clip)
if (showSnackbar) { if (showSnackbar && Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
snackbar(message = resources.getString(snackbarTextRes)) snackbar(message = resources.getString(snackbarTextRes))
} }
} }
@ -80,7 +85,7 @@ open class BasePgpActivity : AppCompatActivity() {
* clearing the clipboard. * clearing the clipboard.
*/ */
fun copyPasswordToClipboard(password: String?) { fun copyPasswordToClipboard(password: String?) {
copyTextToClipboard(password, showSnackbar = false) copyTextToClipboard(password)
val clearAfter = settings.getString(PreferenceKeys.GENERAL_SHOW_TIME)?.toIntOrNull() ?: 45 val clearAfter = settings.getString(PreferenceKeys.GENERAL_SHOW_TIME)?.toIntOrNull() ?: 45
@ -95,9 +100,6 @@ open class BasePgpActivity : AppCompatActivity() {
} else { } else {
startService(service) startService(service)
} }
snackbar(message = resources.getString(R.string.clipboard_password_toast_text, clearAfter))
} else {
snackbar(message = resources.getString(R.string.clipboard_password_no_clear_toast_text))
} }
} }

View file

@ -78,7 +78,7 @@ class DecryptActivity : BasePgpActivity() {
override fun onOptionsItemSelected(item: MenuItem): Boolean { override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) { when (item.itemId) {
android.R.id.home -> onBackPressed() android.R.id.home -> onBackPressedDispatcher.onBackPressed()
R.id.edit_password -> editPassword() R.id.edit_password -> editPassword()
R.id.share_password_as_plaintext -> shareAsPlaintext() R.id.share_password_as_plaintext -> shareAsPlaintext()
R.id.copy_password -> copyPasswordToClipboard(passwordEntry?.password) R.id.copy_password -> copyPasswordToClipboard(passwordEntry?.password)

View file

@ -262,7 +262,7 @@ class PasswordCreationActivity : BasePgpActivity() {
when (item.itemId) { when (item.itemId) {
android.R.id.home -> { android.R.id.home -> {
setResult(RESULT_CANCELED) setResult(RESULT_CANCELED)
onBackPressed() onBackPressedDispatcher.onBackPressed()
} }
R.id.save_password -> { R.id.save_password -> {
copy = false copy = false

View file

@ -49,7 +49,7 @@ class SelectFolderActivity : AppCompatActivity(R.layout.select_folder_layout) {
when (item.itemId) { when (item.itemId) {
android.R.id.home -> { android.R.id.home -> {
setResult(RESULT_CANCELED) setResult(RESULT_CANCELED)
onBackPressed() onBackPressedDispatcher.onBackPressed()
} }
R.id.crypto_select -> selectFolder() R.id.crypto_select -> selectFolder()
else -> return super.onOptionsItemSelected(item) else -> return super.onOptionsItemSelected(item)

View file

@ -69,7 +69,7 @@ class GitConfigActivity : BaseGitActivity() {
override fun onOptionsItemSelected(item: MenuItem): Boolean { override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) { return when (item.itemId) {
android.R.id.home -> { android.R.id.home -> {
onBackPressed() onBackPressedDispatcher.onBackPressed()
true true
} }
else -> super.onOptionsItemSelected(item) else -> super.onOptionsItemSelected(item)

View file

@ -202,7 +202,7 @@ class GitServerConfigActivity : BaseGitActivity() {
override fun onOptionsItemSelected(item: MenuItem): Boolean { override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) { return when (item.itemId) {
android.R.id.home -> { android.R.id.home -> {
onBackPressed() onBackPressedDispatcher.onBackPressed()
true true
} }
else -> super.onOptionsItemSelected(item) else -> super.onOptionsItemSelected(item)

View file

@ -6,6 +6,7 @@
package app.passwordstore.ui.onboarding.activity package app.passwordstore.ui.onboarding.activity
import android.os.Bundle import android.os.Bundle
import androidx.activity.addCallback
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import app.passwordstore.R import app.passwordstore.R
@ -14,13 +15,11 @@ class OnboardingActivity : AppCompatActivity(R.layout.activity_onboarding) {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
supportActionBar?.hide() supportActionBar?.hide()
} val callback = onBackPressedDispatcher.addCallback(enabled = false) { finishAffinity() }
supportFragmentManager.addOnBackStackChangedListener {
override fun onBackPressed() { if (supportFragmentManager.backStackEntryCount == 0) {
if (supportFragmentManager.backStackEntryCount == 0) { callback.isEnabled = true
finishAffinity() }
} else {
super.onBackPressed()
} }
} }
} }

View file

@ -4,7 +4,6 @@
*/ */
package app.passwordstore.ui.passwords package app.passwordstore.ui.passwords
import android.annotation.SuppressLint
import android.content.ComponentName import android.content.ComponentName
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
@ -14,7 +13,6 @@ import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import android.view.MenuItem.OnActionExpandListener import android.view.MenuItem.OnActionExpandListener
import android.view.WindowManager import android.view.WindowManager
import androidx.activity.result.contract.ActivityResultContracts.RequestPermission
import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult
import androidx.activity.viewModels import androidx.activity.viewModels
import androidx.appcompat.widget.SearchView import androidx.appcompat.widget.SearchView
@ -77,18 +75,6 @@ class PasswordStore : BaseGitActivity() {
ViewModelProvider.AndroidViewModelFactory(application) ViewModelProvider.AndroidViewModelFactory(application)
} }
private val storagePermissionRequest =
registerForActivityResult(RequestPermission()) { granted ->
if (granted) checkLocalRepository()
}
private val directorySelectAction =
registerForActivityResult(StartActivityForResult()) { result ->
if (result.resultCode == RESULT_OK) {
checkLocalRepository()
}
}
private val listRefreshAction = private val listRefreshAction =
registerForActivityResult(StartActivityForResult()) { result -> registerForActivityResult(StartActivityForResult()) { result ->
if (result.resultCode == RESULT_OK) { if (result.resultCode == RESULT_OK) {
@ -196,7 +182,6 @@ class PasswordStore : BaseGitActivity() {
return super.onKeyDown(keyCode, event) return super.onKeyDown(keyCode, event)
} }
@SuppressLint("NewApi")
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.activity_pwdstore) setContentView(R.layout.activity_pwdstore)
@ -313,12 +298,14 @@ class PasswordStore : BaseGitActivity() {
} }
} }
R.id.refresh -> refreshPasswordList() R.id.refresh -> refreshPasswordList()
android.R.id.home -> onBackPressed() android.R.id.home -> onBackPressedDispatcher.onBackPressed()
else -> return super.onOptionsItemSelected(item) else -> return super.onOptionsItemSelected(item)
} }
return true return true
} }
@Deprecated("Deprecated in Java")
@Suppress("DEPRECATION")
override fun onBackPressed() { override fun onBackPressed() {
if (getPasswordFragment()?.onBackPressedInActivity() != true) super.onBackPressed() if (getPasswordFragment()?.onBackPressedInActivity() != true) super.onBackPressed()
} }

View file

@ -10,6 +10,7 @@ import android.view.MenuItem
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import app.passwordstore.R import app.passwordstore.R
import app.passwordstore.databinding.ActivityPreferenceRecyclerviewBinding import app.passwordstore.databinding.ActivityPreferenceRecyclerviewBinding
import app.passwordstore.util.extensions.getParcelableCompat
import app.passwordstore.util.extensions.viewBinding import app.passwordstore.util.extensions.viewBinding
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import de.Maxr1998.modernpreferences.Preference import de.Maxr1998.modernpreferences.Preference
@ -84,7 +85,7 @@ class SettingsActivity : AppCompatActivity() {
} }
} }
savedInstanceState savedInstanceState
?.getParcelable<PreferencesAdapter.SavedState>("adapter") ?.getParcelableCompat<PreferencesAdapter.SavedState>("adapter")
?.let(adapter::loadSavedState) ?.let(adapter::loadSavedState)
binding.preferenceRecyclerView.adapter = adapter binding.preferenceRecyclerView.adapter = adapter
} }
@ -106,6 +107,8 @@ class SettingsActivity : AppCompatActivity() {
} }
} }
@Deprecated("Deprecated in Java")
@Suppress("DEPRECATION")
override fun onBackPressed() { override fun onBackPressed() {
if (!preferencesAdapter.goBack()) super.onBackPressed() if (!preferencesAdapter.goBack()) super.onBackPressed()
} }

View file

@ -103,7 +103,7 @@ class SshKeyGenActivity : AppCompatActivity() {
override fun onOptionsItemSelected(item: MenuItem): Boolean { override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) { return when (item.itemId) {
android.R.id.home -> { android.R.id.home -> {
onBackPressed() onBackPressedDispatcher.onBackPressed()
true true
} }
else -> super.onOptionsItemSelected(item) else -> super.onOptionsItemSelected(item)

View file

@ -7,9 +7,11 @@ package app.passwordstore.util.autofill
import android.content.Context import android.content.Context
import android.content.IntentSender import android.content.IntentSender
import android.os.Build
import android.service.autofill.Dataset import android.service.autofill.Dataset
import android.service.autofill.FillCallback import android.service.autofill.FillCallback
import android.service.autofill.FillResponse import android.service.autofill.FillResponse
import android.service.autofill.Presentations
import android.service.autofill.SaveInfo import android.service.autofill.SaveInfo
import android.view.inputmethod.InlineSuggestionsRequest import android.view.inputmethod.InlineSuggestionsRequest
import android.widget.inline.InlinePresentationSpec import android.widget.inline.InlinePresentationSpec
@ -60,6 +62,23 @@ constructor(
intentSender: IntentSender, intentSender: IntentSender,
metadata: DatasetMetadata, metadata: DatasetMetadata,
imeSpec: InlinePresentationSpec?, imeSpec: InlinePresentationSpec?,
): Dataset {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
makeIntentDataSetTiramisu(context, action, intentSender, metadata, imeSpec)
} else {
makeIntentDataSetR(context, action, intentSender, metadata, imeSpec)
}
}
/** Helper for creating an Autofill [Dataset]s for Android R and above. */
@RequiresApi(Build.VERSION_CODES.R)
@Suppress("DEPRECATION")
private fun makeIntentDataSetR(
context: Context,
action: AutofillAction,
intentSender: IntentSender,
metadata: DatasetMetadata,
imeSpec: InlinePresentationSpec?,
): Dataset { ): Dataset {
return Dataset.Builder(makeRemoteView(context, metadata)).run { return Dataset.Builder(makeRemoteView(context, metadata)).run {
fillWith(scenario, action, credentials = null) fillWith(scenario, action, credentials = null)
@ -74,6 +93,30 @@ constructor(
} }
} }
/** Helper for creating Autofill [Dataset]s for Android Tiramisu and above. */
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
private fun makeIntentDataSetTiramisu(
context: Context,
action: AutofillAction,
intentSender: IntentSender,
metadata: DatasetMetadata,
imeSpec: InlinePresentationSpec?,
): Dataset {
val presentationsBuilder = Presentations.Builder()
if (imeSpec != null) {
val inlinePresentation = makeInlinePresentation(context, imeSpec, metadata)
if (inlinePresentation != null) {
presentationsBuilder.setInlinePresentation(inlinePresentation)
}
}
val presentations = presentationsBuilder.build()
return Dataset.Builder(presentations).run {
fillWith(scenario, action, credentials = null)
setAuthentication(intentSender)
build()
}
}
private fun makeMatchDataset( private fun makeMatchDataset(
context: Context, context: Context,
file: File, file: File,

View file

@ -54,6 +54,7 @@ constructor(
private val scenarioSupportsSave = scenario.hasPasswordFieldsToSave private val scenarioSupportsSave = scenario.hasPasswordFieldsToSave
private val canBeSaved = saveFlags != null && scenarioSupportsSave private val canBeSaved = saveFlags != null && scenarioSupportsSave
@Suppress("DEPRECATION")
private fun makeIntentDataset( private fun makeIntentDataset(
context: Context, context: Context,
action: AutofillAction, action: AutofillAction,
@ -212,7 +213,7 @@ constructor(
if (Build.VERSION.SDK_INT >= 28) { if (Build.VERSION.SDK_INT >= 28) {
Dataset.Builder() Dataset.Builder()
} else { } else {
Dataset.Builder(makeRemoteView(context, makeEmptyMetadata())) @Suppress("DEPRECATION") Dataset.Builder(makeRemoteView(context, makeEmptyMetadata()))
} }
return builder.run { return builder.run {
if (scenario != null) fillWith(scenario, action, credentials) if (scenario != null) fillWith(scenario, action, credentials)

View file

@ -10,7 +10,14 @@ import android.content.ClipboardManager
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.SharedPreferences import android.content.SharedPreferences
import android.content.pm.ApplicationInfo
import android.content.pm.PackageInfo
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.content.pm.PackageManager.ApplicationInfoFlags
import android.content.pm.PackageManager.PackageInfoFlags
import android.os.Build
import android.os.Bundle
import android.os.Parcelable
import android.util.Base64 import android.util.Base64
import android.util.TypedValue import android.util.TypedValue
import android.view.View import android.view.View
@ -132,3 +139,45 @@ fun SharedPreferences.getString(key: String): String? = getString(key, null)
fun String.base64(): String { fun String.base64(): String {
return Base64.encodeToString(encodeToByteArray(), Base64.NO_WRAP) return Base64.encodeToString(encodeToByteArray(), Base64.NO_WRAP)
} }
inline fun <reified T> Bundle.getParcelableCompat(key: String): T? {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
getParcelable(key, T::class.java)
} else {
@Suppress("DEPRECATION") getParcelable(key)
}
}
inline fun <reified T : Parcelable> Bundle.getParcelableArrayListCompat(
key: String
): ArrayList<T>? {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
getParcelableArrayList(key, T::class.java)
} else {
@Suppress("DEPRECATION") getParcelableArrayList(key)
}
}
inline fun <reified T : Parcelable> Intent.getParcelableExtraCompat(key: String): T? {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
getParcelableExtra(key, T::class.java)
} else {
@Suppress("DEPRECATION") getParcelableExtra(key)
}
}
fun PackageManager.getPackageInfoCompat(packageName: String, flags: Int): PackageInfo {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
getPackageInfo(packageName, PackageInfoFlags.of(flags.toLong()))
} else {
@Suppress("DEPRECATION") getPackageInfo(packageName, flags)
}
}
fun PackageManager.getApplicationInfoCompat(packageName: String, flags: Int): ApplicationInfo {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
getApplicationInfo(packageName, ApplicationInfoFlags.of(flags.toLong()))
} else {
@Suppress("DEPRECATION") getApplicationInfo(packageName, flags)
}
}

View file

@ -39,7 +39,11 @@ class ClipboardService : Service() {
when (intent.action) { when (intent.action) {
ACTION_CLEAR -> { ACTION_CLEAR -> {
clearClipboard() clearClipboard()
stopForeground(true) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
stopForeground(STOP_FOREGROUND_REMOVE)
} else {
@Suppress("DEPRECATION") stopForeground(true)
}
stopSelf() stopSelf()
return super.onStartCommand(intent, flags, startId) return super.onStartCommand(intent, flags, startId)
} }
@ -55,7 +59,11 @@ class ClipboardService : Service() {
withContext(Dispatchers.IO) { startTimer(time) } withContext(Dispatchers.IO) { startTimer(time) }
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
clearClipboard() clearClipboard()
stopForeground(true) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
stopForeground(STOP_FOREGROUND_REMOVE)
} else {
@Suppress("DEPRECATION") stopForeground(true)
}
stopSelf() stopSelf()
} }
} }

View file

@ -17,6 +17,7 @@ import androidx.core.content.getSystemService
import androidx.documentfile.provider.DocumentFile import androidx.documentfile.provider.DocumentFile
import app.passwordstore.R import app.passwordstore.R
import app.passwordstore.data.repo.PasswordRepository import app.passwordstore.data.repo.PasswordRepository
import app.passwordstore.util.extensions.getParcelableExtraCompat
import java.time.LocalDateTime import java.time.LocalDateTime
import java.time.format.DateTimeFormatter import java.time.format.DateTimeFormatter
import java.util.Calendar import java.util.Calendar
@ -29,7 +30,7 @@ class PasswordExportService : Service() {
if (intent != null) { if (intent != null) {
when (intent.action) { when (intent.action) {
ACTION_EXPORT_PASSWORD -> { ACTION_EXPORT_PASSWORD -> {
val uri = intent.getParcelableExtra<Uri>("uri") val uri = intent.getParcelableExtraCompat<Uri>("uri")
if (uri != null) { if (uri != null) {
val targetDirectory = DocumentFile.fromTreeUri(applicationContext, uri) val targetDirectory = DocumentFile.fromTreeUri(applicationContext, uri)

View file

@ -33,8 +33,6 @@
<string name="git_commit_move_text">Benenne %1$s in %2$s um.</string> <string name="git_commit_move_text">Benenne %1$s in %2$s um.</string>
<string name="git_commit_move_multiple_text">Verschiebe mehrere Passwörter nach %1$s.</string> <string name="git_commit_move_multiple_text">Verschiebe mehrere Passwörter nach %1$s.</string>
<!-- PGPHandler --> <!-- PGPHandler -->
<string name="clipboard_password_toast_text">Passwort ist in der Zwischenablage, du hast %d Sekunden, um es einzufügen.</string>
<string name="clipboard_password_no_clear_toast_text">Passwort wurde in die Zwischenablage kopiert</string>
<string name="clipboard_copied_text">In die Zwischenablage kopiert</string> <string name="clipboard_copied_text">In die Zwischenablage kopiert</string>
<string name="file_toast_text">Bitte setze einen Pfad</string> <string name="file_toast_text">Bitte setze einen Pfad</string>
<string name="path_toast_text">Bitte setze einen Pfad</string> <string name="path_toast_text">Bitte setze einen Pfad</string>

View file

@ -37,8 +37,6 @@
<string name="git_commit_move_text">Renommer %1$sà %2$s. </string> <string name="git_commit_move_text">Renommer %1$sà %2$s. </string>
<string name="git_commit_move_multiple_text">Déplacement de mots de passe vers %1$s.</string> <string name="git_commit_move_multiple_text">Déplacement de mots de passe vers %1$s.</string>
<!-- PGPHandler --> <!-- PGPHandler -->
<string name="clipboard_password_toast_text">Mot de passe copié dans le presse papier, vous avez %d secondes pour coller celui-ci.</string>
<string name="clipboard_password_no_clear_toast_text">Mot de passe copié dans le presse-papiers</string>
<string name="clipboard_copied_text">Copié dans le presse-papiers</string> <string name="clipboard_copied_text">Copié dans le presse-papiers</string>
<string name="file_toast_text">Veuillez fournir un nom de fichier</string> <string name="file_toast_text">Veuillez fournir un nom de fichier</string>
<string name="path_toast_text">Veuillez fournir un chemin d\'accès au fichier</string> <string name="path_toast_text">Veuillez fournir un chemin d\'accès au fichier</string>

View file

@ -37,8 +37,6 @@
<string name="git_commit_move_text">Mudar nome %1$s a %2$s.</string> <string name="git_commit_move_text">Mudar nome %1$s a %2$s.</string>
<string name="git_commit_move_multiple_text">Mover varios contrasinais a %1$s.</string> <string name="git_commit_move_multiple_text">Mover varios contrasinais a %1$s.</string>
<!-- PGPHandler --> <!-- PGPHandler -->
<string name="clipboard_password_toast_text">Contrasinal copiado ao portapapeis, tes %d segundos para pegala nalgures.</string>
<string name="clipboard_password_no_clear_toast_text">Contrasinal copiado ao portapapeis</string>
<string name="clipboard_copied_text">Copiada ó portapapeis</string> <string name="clipboard_copied_text">Copiada ó portapapeis</string>
<string name="file_toast_text">Debes proporcionar un nome de ficheiro</string> <string name="file_toast_text">Debes proporcionar un nome de ficheiro</string>
<string name="path_toast_text">Por favor indica a ruta ao ficheiro</string> <string name="path_toast_text">Por favor indica a ruta ao ficheiro</string>

View file

@ -37,8 +37,6 @@
<string name="git_commit_move_text">Rinomina %1$s in %2$s.</string> <string name="git_commit_move_text">Rinomina %1$s in %2$s.</string>
<string name="git_commit_move_multiple_text">Sposta più password in %1$s.</string> <string name="git_commit_move_multiple_text">Sposta più password in %1$s.</string>
<!-- PGPHandler --> <!-- PGPHandler -->
<string name="clipboard_password_toast_text">Password copiata negli appunti, hai %d secondi per incollarla da qualche parte.</string>
<string name="clipboard_password_no_clear_toast_text">Password copiata negli appunti</string>
<string name="clipboard_copied_text">Copiato negli appunti</string> <string name="clipboard_copied_text">Copiato negli appunti</string>
<string name="file_toast_text">Sei pregato di fornire il nome di un file</string> <string name="file_toast_text">Sei pregato di fornire il nome di un file</string>
<string name="path_toast_text">Sei pregato di fornire il percorso di un file</string> <string name="path_toast_text">Sei pregato di fornire il percorso di un file</string>

View file

@ -37,8 +37,6 @@
<string name="git_commit_move_text">Renomear %1$s para %2$s.</string> <string name="git_commit_move_text">Renomear %1$s para %2$s.</string>
<string name="git_commit_move_multiple_text">Mova múltiplas senhas para %1$s.</string> <string name="git_commit_move_multiple_text">Mova múltiplas senhas para %1$s.</string>
<!-- PGPHandler --> <!-- PGPHandler -->
<string name="clipboard_password_toast_text">Senha copiada para área de transferência, você tem %d segundos para colá-la em algum lugar.</string>
<string name="clipboard_password_no_clear_toast_text">Senha copiada para área de transferência</string>
<string name="clipboard_copied_text">Copiado para a área de transferência</string> <string name="clipboard_copied_text">Copiado para a área de transferência</string>
<string name="file_toast_text">Por favor, informe um nome de arquivo</string> <string name="file_toast_text">Por favor, informe um nome de arquivo</string>
<string name="path_toast_text">Por favor, forneça o caminho do arquivo</string> <string name="path_toast_text">Por favor, forneça o caminho do arquivo</string>

View file

@ -41,8 +41,6 @@
<string name="git_commit_move_text">Переименовать %1$sв%2$s.</string> <string name="git_commit_move_text">Переименовать %1$sв%2$s.</string>
<string name="git_commit_move_multiple_text">Переместить несколько паролей в %1$s.</string> <string name="git_commit_move_multiple_text">Переместить несколько паролей в %1$s.</string>
<!-- PGPHandler --> <!-- PGPHandler -->
<string name="clipboard_password_toast_text">Пароль скопирован в буфер обмена, у вас есть %d секунд чтобы вставить его.</string>
<string name="clipboard_password_no_clear_toast_text">Пароль скопирован в буфер обмена</string>
<string name="clipboard_copied_text">Скопировано в буфер обмена</string> <string name="clipboard_copied_text">Скопировано в буфер обмена</string>
<string name="file_toast_text">Пожалуйста, укажите имя файла</string> <string name="file_toast_text">Пожалуйста, укажите имя файла</string>
<string name="path_toast_text">Пожалуйста, задайте путь к файлу</string> <string name="path_toast_text">Пожалуйста, задайте путь к файлу</string>

View file

@ -45,8 +45,6 @@
<string name="git_commit_move_multiple_text">Move multiple passwords to %1$s.</string> <string name="git_commit_move_multiple_text">Move multiple passwords to %1$s.</string>
<!-- PGPHandler --> <!-- PGPHandler -->
<string name="clipboard_password_toast_text">Password copied to clipboard, you have %d seconds to paste it somewhere.</string>
<string name="clipboard_password_no_clear_toast_text">Password copied to clipboard</string>
<string name="clipboard_copied_text">Copied to clipboard</string> <string name="clipboard_copied_text">Copied to clipboard</string>
<string name="file_toast_text">Please provide a file name</string> <string name="file_toast_text">Please provide a file name</string>
<string name="path_toast_text">Please provide a file path</string> <string name="path_toast_text">Please provide a file path</string>

View file

@ -7,7 +7,9 @@ package com.github.androidpasswordstore.autofillparser
import android.app.assist.AssistStructure import android.app.assist.AssistStructure
import android.content.Context import android.content.Context
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.content.pm.PackageManager.ApplicationInfoFlags
import android.net.Uri import android.net.Uri
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.view.autofill.AutofillId import android.view.autofill.AutofillId
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
@ -41,7 +43,15 @@ public sealed class FormOrigin(public open val identifier: String) {
is Web -> identifier is Web -> identifier
is App -> { is App -> {
val info = val info =
context.packageManager.getApplicationInfo(identifier, PackageManager.GET_META_DATA) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
context.packageManager.getApplicationInfo(
identifier,
ApplicationInfoFlags.of(PackageManager.GET_META_DATA.toLong())
)
} else {
@Suppress("DEPRECATION")
context.packageManager.getApplicationInfo(identifier, PackageManager.GET_META_DATA)
}
val label = context.packageManager.getApplicationLabel(info) val label = context.packageManager.getApplicationLabel(info)
if (untrusted) "$label" else "$label" if (untrusted) "$label" else "$label"
} }

View file

@ -54,7 +54,15 @@ public fun computeCertificatesHash(context: Context, appPackage: String): String
val stableHashOld = stableHash(signaturesOld.map { it.toByteArray() }) val stableHashOld = stableHash(signaturesOld.map { it.toByteArray() })
if (Build.VERSION.SDK_INT >= 28) { if (Build.VERSION.SDK_INT >= 28) {
val info = val info =
context.packageManager.getPackageInfo(appPackage, PackageManager.GET_SIGNING_CERTIFICATES) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
context.packageManager.getPackageInfo(
appPackage,
PackageManager.PackageInfoFlags.of(PackageManager.GET_SIGNING_CERTIFICATES.toLong())
)
} else {
@Suppress("DEPRECATION")
context.packageManager.getPackageInfo(appPackage, PackageManager.GET_SIGNING_CERTIFICATES)
}
val signaturesNew = val signaturesNew =
info.signingInfo.signingCertificateHistory ?: info.signingInfo.apkContentsSigners info.signingInfo.signingCertificateHistory ?: info.signingInfo.apkContentsSigners
val stableHashNew = stableHash(signaturesNew.map { it.toByteArray() }) val stableHashNew = stableHash(signaturesNew.map { it.toByteArray() })

View file

@ -5,8 +5,11 @@
package com.github.androidpasswordstore.autofillparser package com.github.androidpasswordstore.autofillparser
import android.app.assist.AssistStructure import android.app.assist.AssistStructure
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.os.Parcelable
import android.service.autofill.Dataset import android.service.autofill.Dataset
import android.service.autofill.Field
import android.view.autofill.AutofillId import android.view.autofill.AutofillId
import android.view.autofill.AutofillValue import android.view.autofill.AutofillValue
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
@ -54,17 +57,19 @@ public sealed class AutofillScenario<out T : Any> {
return try { return try {
Builder<AutofillId>() Builder<AutofillId>()
.apply { .apply {
username = clientState.getParcelable(BUNDLE_KEY_USERNAME_ID) username = clientState.getParcelableCompat(BUNDLE_KEY_USERNAME_ID)
fillUsername = clientState.getBoolean(BUNDLE_KEY_FILL_USERNAME) fillUsername = clientState.getBoolean(BUNDLE_KEY_FILL_USERNAME)
otp = clientState.getParcelable(BUNDLE_KEY_OTP_ID) otp = clientState.getParcelableCompat(BUNDLE_KEY_OTP_ID)
currentPassword.addAll( currentPassword.addAll(
clientState.getParcelableArrayList(BUNDLE_KEY_CURRENT_PASSWORD_IDS) ?: emptyList() clientState.getParcelableArrayListCompat(BUNDLE_KEY_CURRENT_PASSWORD_IDS)
?: emptyList()
) )
newPassword.addAll( newPassword.addAll(
clientState.getParcelableArrayList(BUNDLE_KEY_NEW_PASSWORD_IDS) ?: emptyList() clientState.getParcelableArrayListCompat(BUNDLE_KEY_NEW_PASSWORD_IDS) ?: emptyList()
) )
genericPassword.addAll( genericPassword.addAll(
clientState.getParcelableArrayList(BUNDLE_KEY_GENERIC_PASSWORD_IDS) ?: emptyList() clientState.getParcelableArrayListCompat(BUNDLE_KEY_GENERIC_PASSWORD_IDS)
?: emptyList()
) )
} }
.build() .build()
@ -73,6 +78,24 @@ public sealed class AutofillScenario<out T : Any> {
null null
} }
} }
private inline fun <reified T> Bundle.getParcelableCompat(key: String): T? {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
getParcelable(key, T::class.java)
} else {
@Suppress("DEPRECATION") getParcelable(key)
}
}
private inline fun <reified T : Parcelable> Bundle.getParcelableArrayListCompat(
key: String
): ArrayList<T>? {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
getParcelableArrayList(key, T::class.java)
} else {
@Suppress("DEPRECATION") getParcelableArrayList(key)
}
}
} }
internal class Builder<T : Any> { internal class Builder<T : Any> {
@ -231,7 +254,11 @@ public fun Dataset.Builder.fillWith(
scenario.otp -> credentialsToFill.otp scenario.otp -> credentialsToFill.otp
else -> credentialsToFill.password else -> credentialsToFill.password
} }
setValue(field, AutofillValue.forText(value)) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
setField(field, Field.Builder().setValue(AutofillValue.forText(value)).build())
} else {
@Suppress("DEPRECATION") setValue(field, AutofillValue.forText(value))
}
} }
} }

View file

@ -7,6 +7,7 @@ package com.github.androidpasswordstore.autofillparser
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.content.pm.PackageManager.ResolveInfoFlags
import android.net.Uri import android.net.Uri
import android.os.Build import android.os.Build
import android.provider.Settings import android.provider.Settings
@ -247,7 +248,15 @@ public fun getInstalledBrowsersWithAutofillSupportLevel(
): List<Pair<String, BrowserAutofillSupportLevel>> { ): List<Pair<String, BrowserAutofillSupportLevel>> {
val testWebIntent = Intent(Intent.ACTION_VIEW).apply { data = Uri.parse("https://example.org") } val testWebIntent = Intent(Intent.ACTION_VIEW).apply { data = Uri.parse("https://example.org") }
val installedBrowsers = val installedBrowsers =
context.packageManager.queryIntentActivities(testWebIntent, PackageManager.MATCH_ALL) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
context.packageManager.queryIntentActivities(
testWebIntent,
ResolveInfoFlags.of(PackageManager.MATCH_ALL.toLong())
)
} else {
@Suppress("DEPRECATION")
context.packageManager.queryIntentActivities(testWebIntent, PackageManager.MATCH_ALL)
}
return installedBrowsers return installedBrowsers
.map { it to getBrowserAutofillSupportLevel(context, it.activityInfo.packageName) } .map { it to getBrowserAutofillSupportLevel(context, it.activityInfo.packageName) }
.filter { it.first.isDefault || it.second != BrowserAutofillSupportLevel.None } .filter { it.first.isDefault || it.second != BrowserAutofillSupportLevel.None }

View file

@ -10,7 +10,7 @@ import org.gradle.kotlin.dsl.configure
object AndroidCommon { object AndroidCommon {
fun configure(project: Project) { fun configure(project: Project) {
project.extensions.configure<TestedExtension> { project.extensions.configure<TestedExtension> {
setCompileSdkVersion(32) setCompileSdkVersion(33)
defaultConfig { defaultConfig {
minSdk = 23 minSdk = 23
targetSdk = 31 targetSdk = 31

View file

@ -158,11 +158,11 @@ complexity:
TooManyFunctions: TooManyFunctions:
active: true active: true
excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
thresholdInFiles: 11 thresholdInFiles: 15
thresholdInClasses: 11 thresholdInClasses: 15
thresholdInInterfaces: 11 thresholdInInterfaces: 15
thresholdInObjects: 11 thresholdInObjects: 15
thresholdInEnums: 11 thresholdInEnums: 15
ignoreDeprecated: false ignoreDeprecated: false
ignorePrivate: false ignorePrivate: false
ignoreOverridden: false ignoreOverridden: false