chore: reformat with trailing commas changes
This commit is contained in:
parent
de6cdfee0e
commit
c980c898de
100 changed files with 309 additions and 563 deletions
|
@ -77,11 +77,6 @@ constructor(
|
||||||
.withAsciiArmor(settings.getBoolean(PreferenceKeys.ASCII_ARMOR, false))
|
.withAsciiArmor(settings.getBoolean(PreferenceKeys.ASCII_ARMOR, false))
|
||||||
.build()
|
.build()
|
||||||
val keys = identities.map { id -> pgpKeyManager.getKeyById(id) }.filterValues()
|
val keys = identities.map { id -> pgpKeyManager.getKeyById(id) }.filterValues()
|
||||||
return pgpCryptoHandler.encrypt(
|
return pgpCryptoHandler.encrypt(keys, content, out, encryptionOptions)
|
||||||
keys,
|
|
||||||
content,
|
|
||||||
out,
|
|
||||||
encryptionOptions,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,35 +11,21 @@ import javax.inject.Inject
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
/** Implements a rudimentary [EncryptedSharedPreferences]-backed cache for GPG passphrases. */
|
/** Implements a rudimentary [EncryptedSharedPreferences]-backed cache for GPG passphrases. */
|
||||||
class PGPPassphraseCache
|
class PGPPassphraseCache @Inject constructor(private val dispatcherProvider: DispatcherProvider) {
|
||||||
@Inject
|
|
||||||
constructor(
|
|
||||||
private val dispatcherProvider: DispatcherProvider,
|
|
||||||
) {
|
|
||||||
|
|
||||||
suspend fun cachePassphrase(
|
suspend fun cachePassphrase(context: Context, identifier: PGPIdentifier, passphrase: String) {
|
||||||
context: Context,
|
|
||||||
identifier: PGPIdentifier,
|
|
||||||
passphrase: String,
|
|
||||||
) {
|
|
||||||
withContext(dispatcherProvider.io()) {
|
withContext(dispatcherProvider.io()) {
|
||||||
getPreferences(context).edit { putString(identifier.toString(), passphrase) }
|
getPreferences(context).edit { putString(identifier.toString(), passphrase) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun retrieveCachedPassphrase(
|
suspend fun retrieveCachedPassphrase(context: Context, identifier: PGPIdentifier): String? {
|
||||||
context: Context,
|
|
||||||
identifier: PGPIdentifier,
|
|
||||||
): String? {
|
|
||||||
return withContext(dispatcherProvider.io()) {
|
return withContext(dispatcherProvider.io()) {
|
||||||
getPreferences(context).getString(identifier.toString())
|
getPreferences(context).getString(identifier.toString())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun clearCachedPassphrase(
|
suspend fun clearCachedPassphrase(context: Context, identifier: PGPIdentifier) {
|
||||||
context: Context,
|
|
||||||
identifier: PGPIdentifier,
|
|
||||||
) {
|
|
||||||
withContext(dispatcherProvider.io()) {
|
withContext(dispatcherProvider.io()) {
|
||||||
getPreferences(context).edit { remove(identifier.toString()) }
|
getPreferences(context).edit { remove(identifier.toString()) }
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@ import app.passwordstore.data.passfile.Totp
|
||||||
class FieldItem(val key: String, val value: String, val action: ActionType) {
|
class FieldItem(val key: String, val value: String, val action: ActionType) {
|
||||||
enum class ActionType {
|
enum class ActionType {
|
||||||
COPY,
|
COPY,
|
||||||
HIDE
|
HIDE,
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class ItemType(val type: String, val label: String) {
|
enum class ItemType(val type: String, val label: String) {
|
||||||
|
|
|
@ -16,7 +16,7 @@ data class PasswordItem(
|
||||||
val parent: PasswordItem? = null,
|
val parent: PasswordItem? = null,
|
||||||
val type: Char,
|
val type: Char,
|
||||||
val file: File,
|
val file: File,
|
||||||
val rootDir: File
|
val rootDir: File,
|
||||||
) : Comparable<PasswordItem> {
|
) : Comparable<PasswordItem> {
|
||||||
|
|
||||||
val fullPathToParent = file.absolutePath.replace(rootDir.absolutePath, "").replace(file.name, "")
|
val fullPathToParent = file.absolutePath.replace(rootDir.absolutePath, "").replace(file.name, "")
|
||||||
|
|
|
@ -167,7 +167,7 @@ object PasswordRepository {
|
||||||
fun getPasswords(
|
fun getPasswords(
|
||||||
path: File,
|
path: File,
|
||||||
rootDir: File,
|
rootDir: File,
|
||||||
sortOrder: PasswordSortOrder
|
sortOrder: PasswordSortOrder,
|
||||||
): ArrayList<PasswordItem> {
|
): ArrayList<PasswordItem> {
|
||||||
// We need to recover the passwords then parse the files
|
// We need to recover the passwords then parse the files
|
||||||
val passList = getFilesList(path).also { it.sortBy { f -> f.name } }
|
val passList = getFilesList(path).also { it.sortBy { f -> f.name } }
|
||||||
|
|
|
@ -23,10 +23,7 @@ object KeyManagerModule {
|
||||||
@PGPKeyDir keyDir: String,
|
@PGPKeyDir keyDir: String,
|
||||||
dispatcherProvider: DispatcherProvider,
|
dispatcherProvider: DispatcherProvider,
|
||||||
): PGPKeyManager {
|
): PGPKeyManager {
|
||||||
return PGPKeyManager(
|
return PGPKeyManager(keyDir, dispatcherProvider.io())
|
||||||
keyDir,
|
|
||||||
dispatcherProvider.io(),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
|
|
|
@ -25,7 +25,7 @@ class PreferenceModule {
|
||||||
fileName,
|
fileName,
|
||||||
masterKeyAlias,
|
masterKeyAlias,
|
||||||
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
|
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
|
||||||
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
|
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -32,9 +32,7 @@ object DicewareModule {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
fun provideDie(
|
fun provideDie(intGenerator: RandomIntGenerator): Die {
|
||||||
intGenerator: RandomIntGenerator,
|
|
||||||
): Die {
|
|
||||||
return Die(6, intGenerator)
|
return Die(6, intGenerator)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -113,7 +113,7 @@ class AutofillDecryptActivity : BasePGPActivity() {
|
||||||
val cachedPassphrase =
|
val cachedPassphrase =
|
||||||
passphraseCache.retrieveCachedPassphrase(
|
passphraseCache.retrieveCachedPassphrase(
|
||||||
this@AutofillDecryptActivity,
|
this@AutofillDecryptActivity,
|
||||||
gpgIdentifiers.first()
|
gpgIdentifiers.first(),
|
||||||
)
|
)
|
||||||
if (cachedPassphrase != null) {
|
if (cachedPassphrase != null) {
|
||||||
decryptWithPassphrase(
|
decryptWithPassphrase(
|
||||||
|
@ -121,7 +121,7 @@ class AutofillDecryptActivity : BasePGPActivity() {
|
||||||
gpgIdentifiers,
|
gpgIdentifiers,
|
||||||
clientState,
|
clientState,
|
||||||
action,
|
action,
|
||||||
cachedPassphrase
|
cachedPassphrase,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
askPassphrase(filePath, gpgIdentifiers, clientState, action)
|
askPassphrase(filePath, gpgIdentifiers, clientState, action)
|
||||||
|
@ -169,12 +169,12 @@ class AutofillDecryptActivity : BasePGPActivity() {
|
||||||
this@AutofillDecryptActivity,
|
this@AutofillDecryptActivity,
|
||||||
credentials,
|
credentials,
|
||||||
clientState,
|
clientState,
|
||||||
action
|
action,
|
||||||
)
|
)
|
||||||
withContext(dispatcherProvider.main()) {
|
withContext(dispatcherProvider.main()) {
|
||||||
setResult(
|
setResult(
|
||||||
RESULT_OK,
|
RESULT_OK,
|
||||||
Intent().apply { putExtra(AutofillManager.EXTRA_AUTHENTICATION_RESULT, fillInDataset) }
|
Intent().apply { putExtra(AutofillManager.EXTRA_AUTHENTICATION_RESULT, fillInDataset) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -195,12 +195,7 @@ class AutofillDecryptActivity : BasePGPActivity() {
|
||||||
runCatching {
|
runCatching {
|
||||||
withContext(dispatcherProvider.io()) {
|
withContext(dispatcherProvider.io()) {
|
||||||
val outputStream = ByteArrayOutputStream()
|
val outputStream = ByteArrayOutputStream()
|
||||||
repository.decrypt(
|
repository.decrypt(password, identifiers, encryptedInput, outputStream)
|
||||||
password,
|
|
||||||
identifiers,
|
|
||||||
encryptedInput,
|
|
||||||
outputStream,
|
|
||||||
)
|
|
||||||
outputStream
|
outputStream
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -61,7 +61,7 @@ class AutofillFilterView : AppCompatActivity() {
|
||||||
|
|
||||||
fun makeMatchAndDecryptFileIntentSender(
|
fun makeMatchAndDecryptFileIntentSender(
|
||||||
context: Context,
|
context: Context,
|
||||||
formOrigin: FormOrigin
|
formOrigin: FormOrigin,
|
||||||
): IntentSender {
|
): IntentSender {
|
||||||
val intent =
|
val intent =
|
||||||
Intent(context, AutofillFilterView::class.java).apply {
|
Intent(context, AutofillFilterView::class.java).apply {
|
||||||
|
@ -193,7 +193,7 @@ class AutofillFilterView : AppCompatActivity() {
|
||||||
shouldMatch.text =
|
shouldMatch.text =
|
||||||
getString(
|
getString(
|
||||||
R.string.oreo_autofill_match_with,
|
R.string.oreo_autofill_match_with,
|
||||||
formOrigin.getPrettyIdentifier(applicationContext)
|
formOrigin.getPrettyIdentifier(applicationContext),
|
||||||
)
|
)
|
||||||
lifecycleScope.launch { handleSearchResults() }
|
lifecycleScope.launch { handleSearchResults() }
|
||||||
}
|
}
|
||||||
|
@ -222,7 +222,7 @@ class AutofillFilterView : AppCompatActivity() {
|
||||||
filterMode =
|
filterMode =
|
||||||
if (binding.strictDomainSearch.isChecked) FilterMode.StrictDomain else FilterMode.Fuzzy,
|
if (binding.strictDomainSearch.isChecked) FilterMode.StrictDomain else FilterMode.Fuzzy,
|
||||||
searchMode = SearchMode.RecursivelyInSubdirectories,
|
searchMode = SearchMode.RecursivelyInSubdirectories,
|
||||||
listMode = ListMode.FilesOnly
|
listMode = ListMode.FilesOnly,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -92,17 +92,17 @@ class AutofillPublisherChangedActivity : AppCompatActivity() {
|
||||||
resetButton.setOnClickListener {
|
resetButton.setOnClickListener {
|
||||||
AutofillMatcher.clearMatchesFor(
|
AutofillMatcher.clearMatchesFor(
|
||||||
this@AutofillPublisherChangedActivity,
|
this@AutofillPublisherChangedActivity,
|
||||||
FormOrigin.App(appPackage)
|
FormOrigin.App(appPackage),
|
||||||
)
|
)
|
||||||
val fillResponse =
|
val fillResponse =
|
||||||
IntentCompat.getParcelableExtra(
|
IntentCompat.getParcelableExtra(
|
||||||
intent,
|
intent,
|
||||||
EXTRA_FILL_RESPONSE_AFTER_RESET,
|
EXTRA_FILL_RESPONSE_AFTER_RESET,
|
||||||
FillResponse::class.java
|
FillResponse::class.java,
|
||||||
)
|
)
|
||||||
setResult(
|
setResult(
|
||||||
RESULT_OK,
|
RESULT_OK,
|
||||||
Intent().apply { putExtra(AutofillManager.EXTRA_AUTHENTICATION_RESULT, fillResponse) }
|
Intent().apply { putExtra(AutofillManager.EXTRA_AUTHENTICATION_RESULT, fillResponse) },
|
||||||
)
|
)
|
||||||
finish()
|
finish()
|
||||||
}
|
}
|
||||||
|
@ -122,7 +122,7 @@ class AutofillPublisherChangedActivity : AppCompatActivity() {
|
||||||
warningAppName.text =
|
warningAppName.text =
|
||||||
getString(
|
getString(
|
||||||
R.string.oreo_autofill_warning_publisher_app_name,
|
R.string.oreo_autofill_warning_publisher_app_name,
|
||||||
packageManager.getApplicationLabel(appInfo)
|
packageManager.getApplicationLabel(appInfo),
|
||||||
)
|
)
|
||||||
|
|
||||||
val currentHash =
|
val currentHash =
|
||||||
|
@ -131,7 +131,7 @@ class AutofillPublisherChangedActivity : AppCompatActivity() {
|
||||||
getString(
|
getString(
|
||||||
R.string.oreo_autofill_warning_publisher_advanced_info_template,
|
R.string.oreo_autofill_warning_publisher_advanced_info_template,
|
||||||
appPackage,
|
appPackage,
|
||||||
currentHash
|
currentHash,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,7 +47,7 @@ class AutofillSaveActivity : AppCompatActivity() {
|
||||||
fun makeSaveIntentSender(
|
fun makeSaveIntentSender(
|
||||||
context: Context,
|
context: Context,
|
||||||
credentials: Credentials?,
|
credentials: Credentials?,
|
||||||
formOrigin: FormOrigin
|
formOrigin: FormOrigin,
|
||||||
): IntentSender {
|
): IntentSender {
|
||||||
val identifier = formOrigin.getPrettyIdentifier(context, untrusted = false)
|
val identifier = formOrigin.getPrettyIdentifier(context, untrusted = false)
|
||||||
// Prevent directory traversals
|
// Prevent directory traversals
|
||||||
|
@ -58,12 +58,12 @@ class AutofillSaveActivity : AppCompatActivity() {
|
||||||
val folderName =
|
val folderName =
|
||||||
directoryStructure.getSaveFolderName(
|
directoryStructure.getSaveFolderName(
|
||||||
sanitizedIdentifier = sanitizedIdentifier,
|
sanitizedIdentifier = sanitizedIdentifier,
|
||||||
username = credentials?.username
|
username = credentials?.username,
|
||||||
)
|
)
|
||||||
val fileName =
|
val fileName =
|
||||||
directoryStructure.getSaveFileName(
|
directoryStructure.getSaveFileName(
|
||||||
username = credentials?.username,
|
username = credentials?.username,
|
||||||
identifier = identifier
|
identifier = identifier,
|
||||||
)
|
)
|
||||||
val intent =
|
val intent =
|
||||||
Intent(context, AutofillSaveActivity::class.java).apply {
|
Intent(context, AutofillSaveActivity::class.java).apply {
|
||||||
|
@ -76,7 +76,7 @@ class AutofillSaveActivity : AppCompatActivity() {
|
||||||
formOrigin.identifier.takeIf { formOrigin is FormOrigin.App },
|
formOrigin.identifier.takeIf { formOrigin is FormOrigin.App },
|
||||||
EXTRA_SHOULD_MATCH_WEB to
|
EXTRA_SHOULD_MATCH_WEB to
|
||||||
formOrigin.identifier.takeIf { formOrigin is FormOrigin.Web },
|
formOrigin.identifier.takeIf { formOrigin is FormOrigin.Web },
|
||||||
EXTRA_GENERATE_PASSWORD to (credentials == null)
|
EXTRA_GENERATE_PASSWORD to (credentials == null),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -84,7 +84,7 @@ class AutofillSaveActivity : AppCompatActivity() {
|
||||||
context,
|
context,
|
||||||
saveRequestCode++,
|
saveRequestCode++,
|
||||||
intent,
|
intent,
|
||||||
PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_IMMUTABLE,
|
||||||
)
|
)
|
||||||
.intentSender
|
.intentSender
|
||||||
}
|
}
|
||||||
|
@ -114,7 +114,7 @@ class AutofillSaveActivity : AppCompatActivity() {
|
||||||
PasswordCreationActivity.EXTRA_FILE_NAME to intent.getStringExtra(EXTRA_NAME),
|
PasswordCreationActivity.EXTRA_FILE_NAME to intent.getStringExtra(EXTRA_NAME),
|
||||||
PasswordCreationActivity.EXTRA_PASSWORD to intent.getStringExtra(EXTRA_PASSWORD),
|
PasswordCreationActivity.EXTRA_PASSWORD to intent.getStringExtra(EXTRA_PASSWORD),
|
||||||
PasswordCreationActivity.EXTRA_GENERATE_PASSWORD to
|
PasswordCreationActivity.EXTRA_GENERATE_PASSWORD to
|
||||||
intent.getBooleanExtra(EXTRA_GENERATE_PASSWORD, false)
|
intent.getBooleanExtra(EXTRA_GENERATE_PASSWORD, false),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -141,7 +141,7 @@ class AutofillSaveActivity : AppCompatActivity() {
|
||||||
this,
|
this,
|
||||||
credentials,
|
credentials,
|
||||||
clientState,
|
clientState,
|
||||||
AutofillAction.Generate
|
AutofillAction.Generate,
|
||||||
)
|
)
|
||||||
Intent().apply {
|
Intent().apply {
|
||||||
putExtra(AutofillManager.EXTRA_AUTHENTICATION_RESULT, fillInDataset)
|
putExtra(AutofillManager.EXTRA_AUTHENTICATION_RESULT, fillInDataset)
|
||||||
|
|
|
@ -90,7 +90,7 @@ open class BasePGPActivity : AppCompatActivity() {
|
||||||
fun copyTextToClipboard(
|
fun copyTextToClipboard(
|
||||||
text: String?,
|
text: String?,
|
||||||
showSnackbar: Boolean = true,
|
showSnackbar: Boolean = true,
|
||||||
@StringRes snackbarTextRes: Int = R.string.clipboard_copied_text
|
@StringRes snackbarTextRes: Int = R.string.clipboard_copied_text,
|
||||||
) {
|
) {
|
||||||
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)
|
||||||
|
|
|
@ -40,7 +40,7 @@ fun EditPasswordScreen(
|
||||||
onNavigationIconClick = onNavigateUp,
|
onNavigationIconClick = onNavigateUp,
|
||||||
backgroundColor = MaterialTheme.colorScheme.surface,
|
backgroundColor = MaterialTheme.colorScheme.surface,
|
||||||
)
|
)
|
||||||
},
|
}
|
||||||
) { paddingValues ->
|
) { paddingValues ->
|
||||||
Box(modifier = modifier.padding(paddingValues)) {
|
Box(modifier = modifier.padding(paddingValues)) {
|
||||||
Column(modifier = Modifier.padding(vertical = 8.dp, horizontal = 16.dp).fillMaxSize()) {
|
Column(modifier = Modifier.padding(vertical = 8.dp, horizontal = 16.dp).fillMaxSize()) {
|
||||||
|
@ -59,10 +59,7 @@ fun EditPasswordScreen(
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun ExtraContent(
|
private fun ExtraContent(entry: PasswordEntry, modifier: Modifier = Modifier) {
|
||||||
entry: PasswordEntry,
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
) {
|
|
||||||
TextField(
|
TextField(
|
||||||
value = entry.extraContentString,
|
value = entry.extraContentString,
|
||||||
onValueChange = {},
|
onValueChange = {},
|
||||||
|
@ -95,5 +92,5 @@ private fun createTestEntry() =
|
||||||
|URL: example.com
|
|URL: example.com
|
||||||
"""
|
"""
|
||||||
.trimMargin()
|
.trimMargin()
|
||||||
.encodeToByteArray()
|
.encodeToByteArray(),
|
||||||
)
|
)
|
||||||
|
|
|
@ -147,7 +147,7 @@ class PasswordCreationActivity : BasePGPActivity() {
|
||||||
otpImportButton.setOnClickListener {
|
otpImportButton.setOnClickListener {
|
||||||
supportFragmentManager.setFragmentResultListener(
|
supportFragmentManager.setFragmentResultListener(
|
||||||
OTP_RESULT_REQUEST_KEY,
|
OTP_RESULT_REQUEST_KEY,
|
||||||
this@PasswordCreationActivity
|
this@PasswordCreationActivity,
|
||||||
) { requestKey, bundle ->
|
) { requestKey, bundle ->
|
||||||
if (requestKey == OTP_RESULT_REQUEST_KEY) {
|
if (requestKey == OTP_RESULT_REQUEST_KEY) {
|
||||||
val contents = bundle.getString(RESULT)
|
val contents = bundle.getString(RESULT)
|
||||||
|
|
|
@ -46,7 +46,7 @@ fun ViewPasswordScreen(
|
||||||
onNavigationIconClick = onNavigateUp,
|
onNavigationIconClick = onNavigateUp,
|
||||||
backgroundColor = MaterialTheme.colorScheme.surface,
|
backgroundColor = MaterialTheme.colorScheme.surface,
|
||||||
)
|
)
|
||||||
},
|
}
|
||||||
) { paddingValues ->
|
) { paddingValues ->
|
||||||
Box(modifier = modifier.padding(paddingValues)) {
|
Box(modifier = modifier.padding(paddingValues)) {
|
||||||
Column(modifier = Modifier.padding(vertical = 8.dp, horizontal = 16.dp).fillMaxSize()) {
|
Column(modifier = Modifier.padding(vertical = 8.dp, horizontal = 16.dp).fillMaxSize()) {
|
||||||
|
@ -87,10 +87,7 @@ fun ViewPasswordScreen(
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun ExtraContent(
|
private fun ExtraContent(entry: PasswordEntry, modifier: Modifier = Modifier) {
|
||||||
entry: PasswordEntry,
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
) {
|
|
||||||
entry.extraContent.forEach { (label, value) ->
|
entry.extraContent.forEach { (label, value) ->
|
||||||
TextField(
|
TextField(
|
||||||
value = value,
|
value = value,
|
||||||
|
@ -107,11 +104,7 @@ private fun ExtraContent(
|
||||||
@Composable
|
@Composable
|
||||||
private fun ViewPasswordScreenPreview() {
|
private fun ViewPasswordScreenPreview() {
|
||||||
APSTheme {
|
APSTheme {
|
||||||
ViewPasswordScreen(
|
ViewPasswordScreen(entryName = "Test Entry", entry = createTestEntry(), onNavigateUp = {})
|
||||||
entryName = "Test Entry",
|
|
||||||
entry = createTestEntry(),
|
|
||||||
onNavigateUp = {},
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -126,5 +119,5 @@ private fun createTestEntry() =
|
||||||
|URL: example.com
|
|URL: example.com
|
||||||
"""
|
"""
|
||||||
.trimMargin()
|
.trimMargin()
|
||||||
.encodeToByteArray()
|
.encodeToByteArray(),
|
||||||
)
|
)
|
||||||
|
|
|
@ -54,7 +54,7 @@ private constructor(
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
inflater: LayoutInflater,
|
inflater: LayoutInflater,
|
||||||
container: ViewGroup?,
|
container: ViewGroup?,
|
||||||
savedInstanceState: Bundle?
|
savedInstanceState: Bundle?,
|
||||||
): View? {
|
): View? {
|
||||||
if (savedInstanceState != null) dismiss()
|
if (savedInstanceState != null) dismiss()
|
||||||
return layoutInflater.inflate(R.layout.basic_bottom_sheet, container, false)
|
return layoutInflater.inflate(R.layout.basic_bottom_sheet, container, false)
|
||||||
|
@ -157,9 +157,7 @@ private constructor(
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setOnDismissListener(
|
fun setOnDismissListener(onDismissListener: OnDismissListener): Builder {
|
||||||
onDismissListener: OnDismissListener,
|
|
||||||
): Builder {
|
|
||||||
this.onDismissListener = onDismissListener
|
this.onDismissListener = onDismissListener
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
|
@ -59,7 +59,7 @@ class DicewarePasswordGeneratorDialogFragment : DialogFragment() {
|
||||||
setPositiveButton(R.string.dialog_ok) { _, _ ->
|
setPositiveButton(R.string.dialog_ok) { _, _ ->
|
||||||
setFragmentResult(
|
setFragmentResult(
|
||||||
PasswordCreationActivity.PASSWORD_RESULT_REQUEST_KEY,
|
PasswordCreationActivity.PASSWORD_RESULT_REQUEST_KEY,
|
||||||
bundleOf(PasswordCreationActivity.RESULT to "${binding.passwordText.text}")
|
bundleOf(PasswordCreationActivity.RESULT to "${binding.passwordText.text}"),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
setNeutralButton(R.string.dialog_cancel) { _, _ -> }
|
setNeutralButton(R.string.dialog_cancel) { _, _ -> }
|
||||||
|
|
|
@ -38,7 +38,7 @@ class ItemCreationBottomSheet : BottomSheetDialogFragment() {
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
inflater: LayoutInflater,
|
inflater: LayoutInflater,
|
||||||
container: ViewGroup?,
|
container: ViewGroup?,
|
||||||
savedInstanceState: Bundle?
|
savedInstanceState: Bundle?,
|
||||||
): View? {
|
): View? {
|
||||||
if (savedInstanceState != null) dismiss()
|
if (savedInstanceState != null) dismiss()
|
||||||
return inflater.inflate(R.layout.item_create_sheet, container, false)
|
return inflater.inflate(R.layout.item_create_sheet, container, false)
|
||||||
|
|
|
@ -25,7 +25,7 @@ class OtpImportDialogFragment : DialogFragment() {
|
||||||
builder.setPositiveButton(android.R.string.ok) { _, _ ->
|
builder.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||||
setFragmentResult(
|
setFragmentResult(
|
||||||
PasswordCreationActivity.OTP_RESULT_REQUEST_KEY,
|
PasswordCreationActivity.OTP_RESULT_REQUEST_KEY,
|
||||||
bundleOf(PasswordCreationActivity.RESULT to getTOTPUri(binding))
|
bundleOf(PasswordCreationActivity.RESULT to getTOTPUri(binding)),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
val dialog = builder.create()
|
val dialog = builder.create()
|
||||||
|
|
|
@ -73,7 +73,7 @@ class PasswordGeneratorDialogFragment : DialogFragment() {
|
||||||
setPositiveButton(R.string.dialog_ok) { _, _ ->
|
setPositiveButton(R.string.dialog_ok) { _, _ ->
|
||||||
setFragmentResult(
|
setFragmentResult(
|
||||||
PasswordCreationActivity.PASSWORD_RESULT_REQUEST_KEY,
|
PasswordCreationActivity.PASSWORD_RESULT_REQUEST_KEY,
|
||||||
bundleOf(PasswordCreationActivity.RESULT to "${binding.passwordText.text}")
|
bundleOf(PasswordCreationActivity.RESULT to "${binding.passwordText.text}"),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
setNeutralButton(R.string.dialog_cancel) { _, _ -> }
|
setNeutralButton(R.string.dialog_cancel) { _, _ -> }
|
||||||
|
@ -123,7 +123,7 @@ class PasswordGeneratorDialogFragment : DialogFragment() {
|
||||||
PasswordOption.NoUppercaseLetters.takeIf { !isChecked(R.id.uppercase) },
|
PasswordOption.NoUppercaseLetters.takeIf { !isChecked(R.id.uppercase) },
|
||||||
PasswordOption.NoAmbiguousCharacters.takeIf { !isChecked(R.id.ambiguous) },
|
PasswordOption.NoAmbiguousCharacters.takeIf { !isChecked(R.id.ambiguous) },
|
||||||
PasswordOption.FullyRandom.takeIf { !isChecked(R.id.pronounceable) },
|
PasswordOption.FullyRandom.takeIf { !isChecked(R.id.pronounceable) },
|
||||||
PasswordOption.NoLowercaseLetters.takeIf { !isChecked(R.id.lowercase) }
|
PasswordOption.NoLowercaseLetters.takeIf { !isChecked(R.id.lowercase) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -28,7 +28,7 @@ class SelectFolderActivity : AppCompatActivity(R.layout.select_folder_layout) {
|
||||||
val args = Bundle()
|
val args = Bundle()
|
||||||
args.putString(
|
args.putString(
|
||||||
PasswordStore.REQUEST_ARG_PATH,
|
PasswordStore.REQUEST_ARG_PATH,
|
||||||
PasswordRepository.getRepositoryDirectory().absolutePath
|
PasswordRepository.getRepositoryDirectory().absolutePath,
|
||||||
)
|
)
|
||||||
|
|
||||||
passwordList.arguments = args
|
passwordList.arguments = args
|
||||||
|
|
|
@ -131,7 +131,7 @@ abstract class BaseGitActivity : AppCompatActivity() {
|
||||||
gitSettings.useMultiplexing = false
|
gitSettings.useMultiplexing = false
|
||||||
SSHException(
|
SSHException(
|
||||||
DisconnectReason.TOO_MANY_CONNECTIONS,
|
DisconnectReason.TOO_MANY_CONNECTIONS,
|
||||||
"The server does not support multiple Git operations per SSH session. Please try again, a slower fallback mode will be used."
|
"The server does not support multiple Git operations per SSH session. Please try again, a slower fallback mode will be used.",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
err.message?.contains("int org.eclipse.jgit.lib.AnyObjectId.w1") == true -> {
|
err.message?.contains("int org.eclipse.jgit.lib.AnyObjectId.w1") == true -> {
|
||||||
|
@ -143,7 +143,7 @@ abstract class BaseGitActivity : AppCompatActivity() {
|
||||||
err.disconnectReason == DisconnectReason.HOST_KEY_NOT_VERIFIABLE -> {
|
err.disconnectReason == DisconnectReason.HOST_KEY_NOT_VERIFIABLE -> {
|
||||||
SSHException(
|
SSHException(
|
||||||
DisconnectReason.HOST_KEY_NOT_VERIFIABLE,
|
DisconnectReason.HOST_KEY_NOT_VERIFIABLE,
|
||||||
"WARNING: The remote host key has changed. If this is expected, please go to Git server settings and clear the saved host key."
|
"WARNING: The remote host key has changed. If this is expected, please go to Git server settings and clear the saved host key.",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
|
|
|
@ -61,7 +61,7 @@ class GitConfigActivity : BaseGitActivity() {
|
||||||
Snackbar.make(
|
Snackbar.make(
|
||||||
binding.root,
|
binding.root,
|
||||||
getString(R.string.git_server_config_save_success),
|
getString(R.string.git_server_config_save_success),
|
||||||
Snackbar.LENGTH_SHORT
|
Snackbar.LENGTH_SHORT,
|
||||||
)
|
)
|
||||||
.show()
|
.show()
|
||||||
Handler(Looper.getMainLooper()).postDelayed(500) { finish() }
|
Handler(Looper.getMainLooper()).postDelayed(500) { finish() }
|
||||||
|
|
|
@ -94,7 +94,7 @@ class GitServerConfigActivity : BaseGitActivity() {
|
||||||
Snackbar.make(
|
Snackbar.make(
|
||||||
binding.root,
|
binding.root,
|
||||||
getString(R.string.clear_saved_host_key_success),
|
getString(R.string.clear_saved_host_key_success),
|
||||||
Snackbar.LENGTH_LONG
|
Snackbar.LENGTH_LONG,
|
||||||
)
|
)
|
||||||
.show()
|
.show()
|
||||||
it.isVisible = false
|
it.isVisible = false
|
||||||
|
@ -140,14 +140,14 @@ class GitServerConfigActivity : BaseGitActivity() {
|
||||||
val updateResult =
|
val updateResult =
|
||||||
gitSettings.updateConnectionSettingsIfValid(
|
gitSettings.updateConnectionSettingsIfValid(
|
||||||
newAuthMode = newAuthMode,
|
newAuthMode = newAuthMode,
|
||||||
newUrl = binding.serverUrl.text.toString().trim()
|
newUrl = binding.serverUrl.text.toString().trim(),
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
GitSettings.UpdateConnectionSettingsResult.FailedToParseUrl -> {
|
GitSettings.UpdateConnectionSettingsResult.FailedToParseUrl -> {
|
||||||
Snackbar.make(
|
Snackbar.make(
|
||||||
binding.root,
|
binding.root,
|
||||||
getString(R.string.git_server_config_save_error),
|
getString(R.string.git_server_config_save_error),
|
||||||
Snackbar.LENGTH_LONG
|
Snackbar.LENGTH_LONG,
|
||||||
)
|
)
|
||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
|
@ -175,7 +175,7 @@ class GitServerConfigActivity : BaseGitActivity() {
|
||||||
Snackbar.make(
|
Snackbar.make(
|
||||||
binding.root,
|
binding.root,
|
||||||
getString(R.string.git_server_config_save_success),
|
getString(R.string.git_server_config_save_success),
|
||||||
Snackbar.LENGTH_SHORT
|
Snackbar.LENGTH_SHORT,
|
||||||
)
|
)
|
||||||
.show()
|
.show()
|
||||||
Handler(Looper.getMainLooper()).postDelayed(500) { finish() }
|
Handler(Looper.getMainLooper()).postDelayed(500) { finish() }
|
||||||
|
@ -242,7 +242,7 @@ class GitServerConfigActivity : BaseGitActivity() {
|
||||||
val snackbar =
|
val snackbar =
|
||||||
snackbar(
|
snackbar(
|
||||||
message = getString(R.string.delete_directory_progress_text),
|
message = getString(R.string.delete_directory_progress_text),
|
||||||
length = Snackbar.LENGTH_INDEFINITE
|
length = Snackbar.LENGTH_INDEFINITE,
|
||||||
)
|
)
|
||||||
withContext(dispatcherProvider.io()) {
|
withContext(dispatcherProvider.io()) {
|
||||||
localDir.deleteRecursively()
|
localDir.deleteRecursively()
|
||||||
|
@ -255,7 +255,7 @@ class GitServerConfigActivity : BaseGitActivity() {
|
||||||
setResult(RESULT_OK)
|
setResult(RESULT_OK)
|
||||||
finish()
|
finish()
|
||||||
},
|
},
|
||||||
failure = { err -> promptOnErrorHandler(err) { finish() } }
|
failure = { err -> promptOnErrorHandler(err) { finish() } },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -58,11 +58,11 @@ class LaunchActivity : AppCompatActivity() {
|
||||||
getDecryptIntent().apply {
|
getDecryptIntent().apply {
|
||||||
putExtra(
|
putExtra(
|
||||||
BasePGPActivity.EXTRA_FILE_PATH,
|
BasePGPActivity.EXTRA_FILE_PATH,
|
||||||
intent.getStringExtra(BasePGPActivity.EXTRA_FILE_PATH)
|
intent.getStringExtra(BasePGPActivity.EXTRA_FILE_PATH),
|
||||||
)
|
)
|
||||||
putExtra(
|
putExtra(
|
||||||
BasePGPActivity.EXTRA_REPO_PATH,
|
BasePGPActivity.EXTRA_REPO_PATH,
|
||||||
intent.getStringExtra(BasePGPActivity.EXTRA_REPO_PATH)
|
intent.getStringExtra(BasePGPActivity.EXTRA_REPO_PATH),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
else Intent(this, PasswordStore::class.java).setAction(Intent.ACTION_VIEW)
|
else Intent(this, PasswordStore::class.java).setAction(Intent.ACTION_VIEW)
|
||||||
|
|
|
@ -156,7 +156,7 @@ class PasswordFragment : Fragment(R.layout.password_recycler_view) {
|
||||||
resources.getQuantityString(
|
resources.getQuantityString(
|
||||||
R.plurals.delete_title,
|
R.plurals.delete_title,
|
||||||
selection.size(),
|
selection.size(),
|
||||||
selection.size()
|
selection.size(),
|
||||||
)
|
)
|
||||||
actionMode!!.invalidate()
|
actionMode!!.invalidate()
|
||||||
} else {
|
} else {
|
||||||
|
@ -256,7 +256,7 @@ class PasswordFragment : Fragment(R.layout.password_recycler_view) {
|
||||||
val passwordItem = recyclerAdapter.getSelectedItems()[0]
|
val passwordItem = recyclerAdapter.getSelectedItems()[0]
|
||||||
shortcutHandler.addPinnedShortcut(
|
shortcutHandler.addPinnedShortcut(
|
||||||
passwordItem,
|
passwordItem,
|
||||||
passwordItem.createAuthEnabledIntent(requireContext())
|
passwordItem.createAuthEnabledIntent(requireContext()),
|
||||||
)
|
)
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
@ -372,7 +372,7 @@ class PasswordFragment : Fragment(R.layout.password_recycler_view) {
|
||||||
requireStore().clearSearch()
|
requireStore().clearSearch()
|
||||||
model.navigateTo(
|
model.navigateTo(
|
||||||
file,
|
file,
|
||||||
recyclerViewState = binding.passRecycler.layoutManager!!.onSaveInstanceState()
|
recyclerViewState = binding.passRecycler.layoutManager!!.onSaveInstanceState(),
|
||||||
)
|
)
|
||||||
requireStore().supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
requireStore().supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||||
}
|
}
|
||||||
|
|
|
@ -115,7 +115,7 @@ class PasswordStore : BaseGitActivity() {
|
||||||
getLongName(
|
getLongName(
|
||||||
requireNotNull(source.parent) { "$file has no parent" },
|
requireNotNull(source.parent) { "$file has no parent" },
|
||||||
repositoryPath,
|
repositoryPath,
|
||||||
basename
|
basename,
|
||||||
)
|
)
|
||||||
val destinationLongName = getLongName(target.absolutePath, repositoryPath, basename)
|
val destinationLongName = getLongName(target.absolutePath, repositoryPath, basename)
|
||||||
if (destinationFile.exists()) {
|
if (destinationFile.exists()) {
|
||||||
|
@ -127,7 +127,7 @@ class PasswordStore : BaseGitActivity() {
|
||||||
resources.getString(
|
resources.getString(
|
||||||
R.string.password_exists_message,
|
R.string.password_exists_message,
|
||||||
destinationLongName,
|
destinationLongName,
|
||||||
sourceLongName
|
sourceLongName,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.setPositiveButton(R.string.dialog_ok) { _, _ ->
|
.setPositiveButton(R.string.dialog_ok) { _, _ ->
|
||||||
|
@ -148,7 +148,7 @@ class PasswordStore : BaseGitActivity() {
|
||||||
getLongName(
|
getLongName(
|
||||||
requireNotNull(source.parent) { "$basename has no parent" },
|
requireNotNull(source.parent) { "$basename has no parent" },
|
||||||
repositoryPath,
|
repositoryPath,
|
||||||
basename
|
basename,
|
||||||
)
|
)
|
||||||
val destinationLongName = getLongName(target.absolutePath, repositoryPath, basename)
|
val destinationLongName = getLongName(target.absolutePath, repositoryPath, basename)
|
||||||
withContext(dispatcherProvider.main()) {
|
withContext(dispatcherProvider.main()) {
|
||||||
|
@ -156,8 +156,8 @@ class PasswordStore : BaseGitActivity() {
|
||||||
resources.getString(
|
resources.getString(
|
||||||
R.string.git_commit_move_text,
|
R.string.git_commit_move_text,
|
||||||
sourceLongName,
|
sourceLongName,
|
||||||
destinationLongName
|
destinationLongName,
|
||||||
),
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -166,7 +166,7 @@ class PasswordStore : BaseGitActivity() {
|
||||||
val relativePath = getRelativePath("${target.absolutePath}/", repoDir)
|
val relativePath = getRelativePath("${target.absolutePath}/", repoDir)
|
||||||
withContext(dispatcherProvider.main()) {
|
withContext(dispatcherProvider.main()) {
|
||||||
commitChange(
|
commitChange(
|
||||||
resources.getString(R.string.git_commit_move_multiple_text, relativePath),
|
resources.getString(R.string.git_commit_move_multiple_text, relativePath)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -340,10 +340,7 @@ class PasswordStore : BaseGitActivity() {
|
||||||
private fun runGitOperation(operation: GitOp) =
|
private fun runGitOperation(operation: GitOp) =
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
launchGitOperation(operation)
|
launchGitOperation(operation)
|
||||||
.fold(
|
.fold(success = { refreshPasswordList() }, failure = { promptOnErrorHandler(it) })
|
||||||
success = { refreshPasswordList() },
|
|
||||||
failure = { promptOnErrorHandler(it) },
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun checkLocalRepository() {
|
private fun checkLocalRepository() {
|
||||||
|
@ -414,7 +411,7 @@ class PasswordStore : BaseGitActivity() {
|
||||||
intent.putExtra(BasePGPActivity.EXTRA_FILE_PATH, currentDir.absolutePath)
|
intent.putExtra(BasePGPActivity.EXTRA_FILE_PATH, currentDir.absolutePath)
|
||||||
intent.putExtra(
|
intent.putExtra(
|
||||||
BasePGPActivity.EXTRA_REPO_PATH,
|
BasePGPActivity.EXTRA_REPO_PATH,
|
||||||
PasswordRepository.getRepositoryDirectory().absolutePath
|
PasswordRepository.getRepositoryDirectory().absolutePath,
|
||||||
)
|
)
|
||||||
listRefreshAction.launch(intent)
|
listRefreshAction.launch(intent)
|
||||||
}
|
}
|
||||||
|
@ -450,9 +447,7 @@ class PasswordStore : BaseGitActivity() {
|
||||||
item.file.toRelativeString(PasswordRepository.getRepositoryDirectory())
|
item.file.toRelativeString(PasswordRepository.getRepositoryDirectory())
|
||||||
}
|
}
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
commitChange(
|
commitChange(resources.getString(R.string.git_commit_remove_text, fmt))
|
||||||
resources.getString(R.string.git_commit_remove_text, fmt),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.setNegativeButton(resources.getString(R.string.dialog_no), null)
|
.setNegativeButton(resources.getString(R.string.dialog_no), null)
|
||||||
|
@ -487,7 +482,7 @@ class PasswordStore : BaseGitActivity() {
|
||||||
*/
|
*/
|
||||||
private fun renameCategory(
|
private fun renameCategory(
|
||||||
oldCategory: PasswordItem,
|
oldCategory: PasswordItem,
|
||||||
error: CategoryRenameError = CategoryRenameError.None
|
error: CategoryRenameError = CategoryRenameError.None,
|
||||||
) {
|
) {
|
||||||
val view = layoutInflater.inflate(R.layout.folder_dialog_fragment, null)
|
val view = layoutInflater.inflate(R.layout.folder_dialog_fragment, null)
|
||||||
val newCategoryEditText = view.findViewById<TextInputEditText>(R.id.folder_name_text)
|
val newCategoryEditText = view.findViewById<TextInputEditText>(R.id.folder_name_text)
|
||||||
|
@ -530,8 +525,8 @@ class PasswordStore : BaseGitActivity() {
|
||||||
resources.getString(
|
resources.getString(
|
||||||
R.string.git_commit_move_text,
|
R.string.git_commit_move_text,
|
||||||
oldCategory.name,
|
oldCategory.name,
|
||||||
newCategory.name
|
newCategory.name,
|
||||||
),
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,7 +54,7 @@ fun KeyList(
|
||||||
) {
|
) {
|
||||||
Image(
|
Image(
|
||||||
painter = painterResource(id = R.drawable.ic_launcher_foreground),
|
painter = painterResource(id = R.drawable.ic_launcher_foreground),
|
||||||
contentDescription = "Password Store logo"
|
contentDescription = "Password Store logo",
|
||||||
)
|
)
|
||||||
Text(stringResource(R.string.pgp_key_manager_no_keys_guidance))
|
Text(stringResource(R.string.pgp_key_manager_no_keys_guidance))
|
||||||
}
|
}
|
||||||
|
@ -81,7 +81,7 @@ private fun KeyItem(
|
||||||
onConfirm = {
|
onConfirm = {
|
||||||
onItemClick(identifier)
|
onItemClick(identifier)
|
||||||
isDeleting = false
|
isDeleting = false
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
val label =
|
val label =
|
||||||
when (identifier) {
|
when (identifier) {
|
||||||
|
@ -106,7 +106,7 @@ private fun KeyItem(
|
||||||
IconButton(onClick = { isDeleting = true }, modifier = Modifier.requiredSize(24.dp)) {
|
IconButton(onClick = { isDeleting = true }, modifier = Modifier.requiredSize(24.dp)) {
|
||||||
Icon(
|
Icon(
|
||||||
painter = painterResource(R.drawable.ic_delete_24dp),
|
painter = painterResource(R.drawable.ic_delete_24dp),
|
||||||
stringResource(id = R.string.delete)
|
stringResource(id = R.string.delete),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -148,7 +148,7 @@ private fun KeyListPreview() {
|
||||||
PGPIdentifier.fromString("0xB950AE2813841585"),
|
PGPIdentifier.fromString("0xB950AE2813841585"),
|
||||||
)
|
)
|
||||||
.toPersistentList(),
|
.toPersistentList(),
|
||||||
onItemClick = {}
|
onItemClick = {},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,10 +52,10 @@ class PGPKeyListActivity : ComponentActivity() {
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
painter = painterResource(R.drawable.ic_add_48dp),
|
painter = painterResource(R.drawable.ic_add_48dp),
|
||||||
stringResource(R.string.pref_import_pgp_key_title)
|
stringResource(R.string.pref_import_pgp_key_title),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
) { paddingValues ->
|
) { paddingValues ->
|
||||||
KeyList(
|
KeyList(
|
||||||
identifiers = viewModel.keys,
|
identifiers = viewModel.keys,
|
||||||
|
|
|
@ -22,10 +22,7 @@ class PasswordSettings(private val activity: FragmentActivity) : SettingsProvide
|
||||||
val values = activity.resources.getStringArray(R.array.pwgen_provider_values)
|
val values = activity.resources.getStringArray(R.array.pwgen_provider_values)
|
||||||
val labels = activity.resources.getStringArray(R.array.pwgen_provider_labels)
|
val labels = activity.resources.getStringArray(R.array.pwgen_provider_labels)
|
||||||
val items = values.zip(labels).map { SelectionItem(it.first, it.second, null) }
|
val items = values.zip(labels).map { SelectionItem(it.first, it.second, null) }
|
||||||
singleChoice(
|
singleChoice(PreferenceKeys.PREF_KEY_PWGEN_TYPE, items) {
|
||||||
PreferenceKeys.PREF_KEY_PWGEN_TYPE,
|
|
||||||
items,
|
|
||||||
) {
|
|
||||||
initialSelection = "diceware"
|
initialSelection = "diceware"
|
||||||
titleRes = R.string.pref_password_generator_type_title
|
titleRes = R.string.pref_password_generator_type_title
|
||||||
}
|
}
|
||||||
|
|
|
@ -103,7 +103,7 @@ class SettingsActivity : AppCompatActivity() {
|
||||||
BundleCompat.getParcelable(
|
BundleCompat.getParcelable(
|
||||||
savedInstanceState,
|
savedInstanceState,
|
||||||
"adapter",
|
"adapter",
|
||||||
PreferencesAdapter.SavedState::class.java
|
PreferencesAdapter.SavedState::class.java,
|
||||||
)
|
)
|
||||||
?.let(adapter::loadSavedState)
|
?.let(adapter::loadSavedState)
|
||||||
}
|
}
|
||||||
|
|
|
@ -126,7 +126,7 @@ class SshKeyGenActivity : AppCompatActivity() {
|
||||||
suspendCoroutine { cont ->
|
suspendCoroutine { cont ->
|
||||||
BiometricAuthenticator.authenticate(
|
BiometricAuthenticator.authenticate(
|
||||||
this@SshKeyGenActivity,
|
this@SshKeyGenActivity,
|
||||||
R.string.biometric_prompt_title_ssh_keygen
|
R.string.biometric_prompt_title_ssh_keygen,
|
||||||
) { result ->
|
) { result ->
|
||||||
// Do not cancel on failed attempts as these are handled by the
|
// Do not cancel on failed attempts as these are handled by the
|
||||||
// authenticator UI.
|
// authenticator UI.
|
||||||
|
|
|
@ -29,7 +29,7 @@ class SshKeyImportActivity : AppCompatActivity() {
|
||||||
Toast.makeText(
|
Toast.makeText(
|
||||||
this,
|
this,
|
||||||
resources.getString(R.string.ssh_key_success_dialog_title),
|
resources.getString(R.string.ssh_key_success_dialog_title),
|
||||||
Toast.LENGTH_LONG
|
Toast.LENGTH_LONG,
|
||||||
)
|
)
|
||||||
.show()
|
.show()
|
||||||
setResult(RESULT_OK)
|
setResult(RESULT_OK)
|
||||||
|
|
|
@ -23,7 +23,7 @@ class OnOffItemAnimator : DefaultItemAnimator() {
|
||||||
override fun animateAppearance(
|
override fun animateAppearance(
|
||||||
viewHolder: RecyclerView.ViewHolder,
|
viewHolder: RecyclerView.ViewHolder,
|
||||||
preLayoutInfo: ItemHolderInfo?,
|
preLayoutInfo: ItemHolderInfo?,
|
||||||
postLayoutInfo: ItemHolderInfo
|
postLayoutInfo: ItemHolderInfo,
|
||||||
): Boolean {
|
): Boolean {
|
||||||
return if (isEnabled) {
|
return if (isEnabled) {
|
||||||
super.animateAppearance(viewHolder, preLayoutInfo, postLayoutInfo)
|
super.animateAppearance(viewHolder, preLayoutInfo, postLayoutInfo)
|
||||||
|
@ -36,7 +36,7 @@ class OnOffItemAnimator : DefaultItemAnimator() {
|
||||||
oldHolder: RecyclerView.ViewHolder,
|
oldHolder: RecyclerView.ViewHolder,
|
||||||
newHolder: RecyclerView.ViewHolder,
|
newHolder: RecyclerView.ViewHolder,
|
||||||
preInfo: ItemHolderInfo,
|
preInfo: ItemHolderInfo,
|
||||||
postInfo: ItemHolderInfo
|
postInfo: ItemHolderInfo,
|
||||||
): Boolean {
|
): Boolean {
|
||||||
return if (isEnabled) {
|
return if (isEnabled) {
|
||||||
super.animateChange(oldHolder, newHolder, preInfo, postInfo)
|
super.animateChange(oldHolder, newHolder, preInfo, postInfo)
|
||||||
|
@ -48,7 +48,7 @@ class OnOffItemAnimator : DefaultItemAnimator() {
|
||||||
override fun animateDisappearance(
|
override fun animateDisappearance(
|
||||||
viewHolder: RecyclerView.ViewHolder,
|
viewHolder: RecyclerView.ViewHolder,
|
||||||
preLayoutInfo: ItemHolderInfo,
|
preLayoutInfo: ItemHolderInfo,
|
||||||
postLayoutInfo: ItemHolderInfo?
|
postLayoutInfo: ItemHolderInfo?,
|
||||||
): Boolean {
|
): Boolean {
|
||||||
return if (isEnabled) {
|
return if (isEnabled) {
|
||||||
super.animateDisappearance(viewHolder, preLayoutInfo, postLayoutInfo)
|
super.animateDisappearance(viewHolder, preLayoutInfo, postLayoutInfo)
|
||||||
|
@ -60,7 +60,7 @@ class OnOffItemAnimator : DefaultItemAnimator() {
|
||||||
override fun animatePersistence(
|
override fun animatePersistence(
|
||||||
viewHolder: RecyclerView.ViewHolder,
|
viewHolder: RecyclerView.ViewHolder,
|
||||||
preInfo: ItemHolderInfo,
|
preInfo: ItemHolderInfo,
|
||||||
postInfo: ItemHolderInfo
|
postInfo: ItemHolderInfo,
|
||||||
): Boolean {
|
): Boolean {
|
||||||
return if (isEnabled) {
|
return if (isEnabled) {
|
||||||
super.animatePersistence(viewHolder, preInfo, postInfo)
|
super.animatePersistence(viewHolder, preInfo, postInfo)
|
||||||
|
|
|
@ -57,7 +57,7 @@ object BiometricAuthenticator {
|
||||||
fun authenticate(
|
fun authenticate(
|
||||||
activity: FragmentActivity,
|
activity: FragmentActivity,
|
||||||
@StringRes dialogTitleRes: Int = R.string.biometric_prompt_title,
|
@StringRes dialogTitleRes: Int = R.string.biometric_prompt_title,
|
||||||
callback: (Result) -> Unit
|
callback: (Result) -> Unit,
|
||||||
) {
|
) {
|
||||||
val authCallback = createPromptAuthenticationCallback(activity, callback)
|
val authCallback = createPromptAuthenticationCallback(activity, callback)
|
||||||
val deviceHasKeyguard = activity.getSystemService<KeyguardManager>()?.isDeviceSecure == true
|
val deviceHasKeyguard = activity.getSystemService<KeyguardManager>()?.isDeviceSecure == true
|
||||||
|
@ -94,14 +94,14 @@ object BiometricAuthenticator {
|
||||||
callback(
|
callback(
|
||||||
Result.Failure(
|
Result.Failure(
|
||||||
errorCode,
|
errorCode,
|
||||||
activity.getString(R.string.biometric_auth_error_reason, errString)
|
activity.getString(R.string.biometric_auth_error_reason, errString),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
BiometricPrompt.ERROR_NO_SPACE ->
|
BiometricPrompt.ERROR_NO_SPACE ->
|
||||||
callback(
|
callback(
|
||||||
Result.Failure(
|
Result.Failure(
|
||||||
errorCode,
|
errorCode,
|
||||||
activity.getString(R.string.biometric_auth_error_reason, errString)
|
activity.getString(R.string.biometric_auth_error_reason, errString),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
BiometricPrompt.ERROR_CANCELED -> callback(Result.CanceledBySystem)
|
BiometricPrompt.ERROR_CANCELED -> callback(Result.CanceledBySystem)
|
||||||
|
@ -109,21 +109,21 @@ object BiometricAuthenticator {
|
||||||
callback(
|
callback(
|
||||||
Result.Failure(
|
Result.Failure(
|
||||||
errorCode,
|
errorCode,
|
||||||
activity.getString(R.string.biometric_auth_error_reason, errString)
|
activity.getString(R.string.biometric_auth_error_reason, errString),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
BiometricPrompt.ERROR_VENDOR ->
|
BiometricPrompt.ERROR_VENDOR ->
|
||||||
callback(
|
callback(
|
||||||
Result.Failure(
|
Result.Failure(
|
||||||
errorCode,
|
errorCode,
|
||||||
activity.getString(R.string.biometric_auth_error_reason, errString)
|
activity.getString(R.string.biometric_auth_error_reason, errString),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
BiometricPrompt.ERROR_LOCKOUT_PERMANENT ->
|
BiometricPrompt.ERROR_LOCKOUT_PERMANENT ->
|
||||||
callback(
|
callback(
|
||||||
Result.Failure(
|
Result.Failure(
|
||||||
errorCode,
|
errorCode,
|
||||||
activity.getString(R.string.biometric_auth_error_reason, errString)
|
activity.getString(R.string.biometric_auth_error_reason, errString),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
BiometricPrompt.ERROR_USER_CANCELED -> callback(Result.CanceledByUser)
|
BiometricPrompt.ERROR_USER_CANCELED -> callback(Result.CanceledByUser)
|
||||||
|
@ -138,7 +138,7 @@ object BiometricAuthenticator {
|
||||||
callback(
|
callback(
|
||||||
Result.Failure(
|
Result.Failure(
|
||||||
errorCode,
|
errorCode,
|
||||||
activity.getString(R.string.biometric_auth_error_reason, errString)
|
activity.getString(R.string.biometric_auth_error_reason, errString),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,15 +26,12 @@ import logcat.LogPriority.ERROR
|
||||||
import logcat.asLog
|
import logcat.asLog
|
||||||
import logcat.logcat
|
import logcat.logcat
|
||||||
|
|
||||||
class Api26AutofillResponseBuilder
|
class Api26AutofillResponseBuilder private constructor(form: FillableForm) :
|
||||||
private constructor(
|
AutofillResponseBuilder {
|
||||||
form: FillableForm,
|
|
||||||
) : AutofillResponseBuilder {
|
|
||||||
|
|
||||||
object Factory : AutofillResponseBuilder.Factory {
|
object Factory : AutofillResponseBuilder.Factory {
|
||||||
override fun create(
|
override fun create(form: FillableForm): AutofillResponseBuilder =
|
||||||
form: FillableForm,
|
Api26AutofillResponseBuilder(form)
|
||||||
): AutofillResponseBuilder = Api26AutofillResponseBuilder(form)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private val formOrigin = form.formOrigin
|
private val formOrigin = form.formOrigin
|
||||||
|
@ -104,14 +101,14 @@ private constructor(
|
||||||
AutofillPublisherChangedActivity.makePublisherChangedIntentSender(
|
AutofillPublisherChangedActivity.makePublisherChangedIntentSender(
|
||||||
context,
|
context,
|
||||||
publisherChangedException,
|
publisherChangedException,
|
||||||
fillResponseAfterReset
|
fillResponseAfterReset,
|
||||||
)
|
)
|
||||||
return makeIntentDataset(context, AutofillAction.Match, intentSender, metadata)
|
return makeIntentDataset(context, AutofillAction.Match, intentSender, metadata)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun makePublisherChangedResponse(
|
private fun makePublisherChangedResponse(
|
||||||
context: Context,
|
context: Context,
|
||||||
publisherChangedException: AutofillPublisherChangedException
|
publisherChangedException: AutofillPublisherChangedException,
|
||||||
): FillResponse {
|
): FillResponse {
|
||||||
return FillResponse.Builder().run {
|
return FillResponse.Builder().run {
|
||||||
addDataset(makePublisherChangedDataset(context, publisherChangedException))
|
addDataset(makePublisherChangedDataset(context, publisherChangedException))
|
||||||
|
@ -164,7 +161,7 @@ private constructor(
|
||||||
setHeader(
|
setHeader(
|
||||||
makeRemoteView(
|
makeRemoteView(
|
||||||
context,
|
context,
|
||||||
makeHeaderMetadata(formOrigin.getPrettyIdentifier(context, untrusted = true))
|
makeHeaderMetadata(formOrigin.getPrettyIdentifier(context, untrusted = true)),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -183,7 +180,7 @@ private constructor(
|
||||||
failure = { e ->
|
failure = { e ->
|
||||||
logcat(ERROR) { e.asLog() }
|
logcat(ERROR) { e.asLog() }
|
||||||
callback.onSuccess(makePublisherChangedResponse(context, e))
|
callback.onSuccess(makePublisherChangedResponse(context, e))
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,15 +33,12 @@ import logcat.logcat
|
||||||
|
|
||||||
/** Implements [AutofillResponseBuilder]'s methods for API 30 and above */
|
/** Implements [AutofillResponseBuilder]'s methods for API 30 and above */
|
||||||
@RequiresApi(Build.VERSION_CODES.R)
|
@RequiresApi(Build.VERSION_CODES.R)
|
||||||
class Api30AutofillResponseBuilder
|
class Api30AutofillResponseBuilder private constructor(form: FillableForm) :
|
||||||
private constructor(
|
AutofillResponseBuilder {
|
||||||
form: FillableForm,
|
|
||||||
) : AutofillResponseBuilder {
|
|
||||||
|
|
||||||
object Factory : AutofillResponseBuilder.Factory {
|
object Factory : AutofillResponseBuilder.Factory {
|
||||||
override fun create(
|
override fun create(form: FillableForm): AutofillResponseBuilder =
|
||||||
form: FillableForm,
|
Api30AutofillResponseBuilder(form)
|
||||||
): AutofillResponseBuilder = Api30AutofillResponseBuilder(form)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private val formOrigin = form.formOrigin
|
private val formOrigin = form.formOrigin
|
||||||
|
@ -127,7 +124,7 @@ private constructor(
|
||||||
private fun makeMatchDataset(
|
private fun makeMatchDataset(
|
||||||
context: Context,
|
context: Context,
|
||||||
file: File,
|
file: File,
|
||||||
imeSpec: InlinePresentationSpec?
|
imeSpec: InlinePresentationSpec?,
|
||||||
): Dataset? {
|
): Dataset? {
|
||||||
if (!scenario.hasFieldsToFillOn(AutofillAction.Match)) return null
|
if (!scenario.hasFieldsToFillOn(AutofillAction.Match)) return null
|
||||||
val metadata = makeFillMatchMetadata(context, file)
|
val metadata = makeFillMatchMetadata(context, file)
|
||||||
|
@ -151,7 +148,7 @@ private constructor(
|
||||||
|
|
||||||
private fun makeFillOtpFromSmsDataset(
|
private fun makeFillOtpFromSmsDataset(
|
||||||
context: Context,
|
context: Context,
|
||||||
imeSpec: InlinePresentationSpec?
|
imeSpec: InlinePresentationSpec?,
|
||||||
): Dataset? {
|
): Dataset? {
|
||||||
if (!scenario.hasFieldsToFillOn(AutofillAction.FillOtpFromSms)) return null
|
if (!scenario.hasFieldsToFillOn(AutofillAction.FillOtpFromSms)) return null
|
||||||
if (!AutofillSmsActivity.shouldOfferFillFromSms(context)) return null
|
if (!AutofillSmsActivity.shouldOfferFillFromSms(context)) return null
|
||||||
|
@ -162,14 +159,14 @@ private constructor(
|
||||||
AutofillAction.FillOtpFromSms,
|
AutofillAction.FillOtpFromSms,
|
||||||
intentSender,
|
intentSender,
|
||||||
metadata,
|
metadata,
|
||||||
imeSpec
|
imeSpec,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun makePublisherChangedDataset(
|
private fun makePublisherChangedDataset(
|
||||||
context: Context,
|
context: Context,
|
||||||
publisherChangedException: AutofillPublisherChangedException,
|
publisherChangedException: AutofillPublisherChangedException,
|
||||||
imeSpec: InlinePresentationSpec?
|
imeSpec: InlinePresentationSpec?,
|
||||||
): Dataset {
|
): Dataset {
|
||||||
val metadata = makeWarningMetadata(context)
|
val metadata = makeWarningMetadata(context)
|
||||||
// If the user decides to trust the new publisher, they can choose reset the list of
|
// If the user decides to trust the new publisher, they can choose reset the list of
|
||||||
|
@ -181,7 +178,7 @@ private constructor(
|
||||||
AutofillPublisherChangedActivity.makePublisherChangedIntentSender(
|
AutofillPublisherChangedActivity.makePublisherChangedIntentSender(
|
||||||
context,
|
context,
|
||||||
publisherChangedException,
|
publisherChangedException,
|
||||||
fillResponseAfterReset
|
fillResponseAfterReset,
|
||||||
)
|
)
|
||||||
return makeIntentDataset(context, AutofillAction.Match, intentSender, metadata, imeSpec)
|
return makeIntentDataset(context, AutofillAction.Match, intentSender, metadata, imeSpec)
|
||||||
}
|
}
|
||||||
|
@ -189,7 +186,7 @@ private constructor(
|
||||||
private fun makePublisherChangedResponse(
|
private fun makePublisherChangedResponse(
|
||||||
context: Context,
|
context: Context,
|
||||||
inlineSuggestionsRequest: InlineSuggestionsRequest?,
|
inlineSuggestionsRequest: InlineSuggestionsRequest?,
|
||||||
publisherChangedException: AutofillPublisherChangedException
|
publisherChangedException: AutofillPublisherChangedException,
|
||||||
): FillResponse {
|
): FillResponse {
|
||||||
val imeSpec = inlineSuggestionsRequest?.inlinePresentationSpecs?.firstOrNull()
|
val imeSpec = inlineSuggestionsRequest?.inlinePresentationSpecs?.firstOrNull()
|
||||||
return FillResponse.Builder().run {
|
return FillResponse.Builder().run {
|
||||||
|
@ -202,7 +199,7 @@ private constructor(
|
||||||
private fun makeFillResponse(
|
private fun makeFillResponse(
|
||||||
context: Context,
|
context: Context,
|
||||||
inlineSuggestionsRequest: InlineSuggestionsRequest?,
|
inlineSuggestionsRequest: InlineSuggestionsRequest?,
|
||||||
matchedFiles: List<File>
|
matchedFiles: List<File>,
|
||||||
): FillResponse? {
|
): FillResponse? {
|
||||||
var datasetCount = 0
|
var datasetCount = 0
|
||||||
val imeSpecs = inlineSuggestionsRequest?.inlinePresentationSpecs ?: emptyList()
|
val imeSpecs = inlineSuggestionsRequest?.inlinePresentationSpecs ?: emptyList()
|
||||||
|
@ -229,7 +226,7 @@ private constructor(
|
||||||
setHeader(
|
setHeader(
|
||||||
makeRemoteView(
|
makeRemoteView(
|
||||||
context,
|
context,
|
||||||
makeHeaderMetadata(formOrigin.getPrettyIdentifier(context, untrusted = true))
|
makeHeaderMetadata(formOrigin.getPrettyIdentifier(context, untrusted = true)),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
makeSaveInfo()?.let { setSaveInfo(it) }
|
makeSaveInfo()?.let { setSaveInfo(it) }
|
||||||
|
@ -271,7 +268,7 @@ private constructor(
|
||||||
callback.onSuccess(
|
callback.onSuccess(
|
||||||
makePublisherChangedResponse(context, fillRequest.inlineSuggestionsRequest, e)
|
makePublisherChangedResponse(context, fillRequest.inlineSuggestionsRequest, e)
|
||||||
)
|
)
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -102,7 +102,7 @@ class AutofillMatcher {
|
||||||
*/
|
*/
|
||||||
fun getMatchesFor(
|
fun getMatchesFor(
|
||||||
context: Context,
|
context: Context,
|
||||||
formOrigin: FormOrigin
|
formOrigin: FormOrigin,
|
||||||
): Result<List<File>, AutofillPublisherChangedException> {
|
): Result<List<File>, AutofillPublisherChangedException> {
|
||||||
if (hasFormOriginHashChanged(context, formOrigin)) {
|
if (hasFormOriginHashChanged(context, formOrigin)) {
|
||||||
return Err(AutofillPublisherChangedException(formOrigin))
|
return Err(AutofillPublisherChangedException(formOrigin))
|
||||||
|
@ -151,7 +151,7 @@ class AutofillMatcher {
|
||||||
Toast.makeText(
|
Toast.makeText(
|
||||||
context,
|
context,
|
||||||
context.getString(R.string.oreo_autofill_max_matches_reached, MAX_NUM_MATCHES),
|
context.getString(R.string.oreo_autofill_max_matches_reached, MAX_NUM_MATCHES),
|
||||||
Toast.LENGTH_LONG
|
Toast.LENGTH_LONG,
|
||||||
)
|
)
|
||||||
.show()
|
.show()
|
||||||
return
|
return
|
||||||
|
@ -170,7 +170,7 @@ class AutofillMatcher {
|
||||||
fun updateMatches(
|
fun updateMatches(
|
||||||
context: Context,
|
context: Context,
|
||||||
moveFromTo: Map<File, File> = emptyMap(),
|
moveFromTo: Map<File, File> = emptyMap(),
|
||||||
delete: Collection<File> = emptyList()
|
delete: Collection<File> = emptyList(),
|
||||||
) {
|
) {
|
||||||
val deletePathList = delete.map { it.absolutePath }
|
val deletePathList = delete.map { it.absolutePath }
|
||||||
val oldNewPathMap =
|
val oldNewPathMap =
|
||||||
|
|
|
@ -134,7 +134,7 @@ object AutofillPreferences {
|
||||||
context: Context,
|
context: Context,
|
||||||
file: File,
|
file: File,
|
||||||
entry: PasswordEntry,
|
entry: PasswordEntry,
|
||||||
directoryStructure: DirectoryStructure
|
directoryStructure: DirectoryStructure,
|
||||||
): Credentials {
|
): Credentials {
|
||||||
// Always give priority to a username stored in the encrypted extras
|
// Always give priority to a username stored in the encrypted extras
|
||||||
val username =
|
val username =
|
||||||
|
|
|
@ -26,7 +26,7 @@ interface AutofillResponseBuilder {
|
||||||
context: Context,
|
context: Context,
|
||||||
credentials: Credentials,
|
credentials: Credentials,
|
||||||
clientState: Bundle,
|
clientState: Bundle,
|
||||||
action: AutofillAction
|
action: AutofillAction,
|
||||||
): Dataset {
|
): Dataset {
|
||||||
val scenario = AutofillScenario.fromClientState(clientState)
|
val scenario = AutofillScenario.fromClientState(clientState)
|
||||||
// Before Android P, Datasets used for fill-in had to come with a RemoteViews, even
|
// Before Android P, Datasets used for fill-in had to come with a RemoteViews, even
|
||||||
|
|
|
@ -45,7 +45,7 @@ fun makeRemoteView(context: Context, metadata: DatasetMetadata): RemoteViews {
|
||||||
fun makeInlinePresentation(
|
fun makeInlinePresentation(
|
||||||
context: Context,
|
context: Context,
|
||||||
imeSpec: InlinePresentationSpec,
|
imeSpec: InlinePresentationSpec,
|
||||||
metadata: DatasetMetadata
|
metadata: DatasetMetadata,
|
||||||
): InlinePresentation? {
|
): InlinePresentation? {
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) return null
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) return null
|
||||||
|
|
||||||
|
@ -91,21 +91,21 @@ fun makeSearchAndFillMetadata(context: Context) =
|
||||||
DatasetMetadata(
|
DatasetMetadata(
|
||||||
context.getString(R.string.oreo_autofill_search_in_store),
|
context.getString(R.string.oreo_autofill_search_in_store),
|
||||||
null,
|
null,
|
||||||
R.drawable.ic_search_black_24dp
|
R.drawable.ic_search_black_24dp,
|
||||||
)
|
)
|
||||||
|
|
||||||
fun makeGenerateAndFillMetadata(context: Context) =
|
fun makeGenerateAndFillMetadata(context: Context) =
|
||||||
DatasetMetadata(
|
DatasetMetadata(
|
||||||
context.getString(R.string.oreo_autofill_generate_password),
|
context.getString(R.string.oreo_autofill_generate_password),
|
||||||
null,
|
null,
|
||||||
R.drawable.ic_autofill_new_password
|
R.drawable.ic_autofill_new_password,
|
||||||
)
|
)
|
||||||
|
|
||||||
fun makeFillOtpFromSmsMetadata(context: Context) =
|
fun makeFillOtpFromSmsMetadata(context: Context) =
|
||||||
DatasetMetadata(
|
DatasetMetadata(
|
||||||
context.getString(R.string.oreo_autofill_fill_otp_from_sms),
|
context.getString(R.string.oreo_autofill_fill_otp_from_sms),
|
||||||
null,
|
null,
|
||||||
R.drawable.ic_autofill_sms
|
R.drawable.ic_autofill_sms,
|
||||||
)
|
)
|
||||||
|
|
||||||
fun makeEmptyMetadata() = DatasetMetadata("PLACEHOLDER", "PLACEHOLDER", R.mipmap.ic_launcher)
|
fun makeEmptyMetadata() = DatasetMetadata("PLACEHOLDER", "PLACEHOLDER", R.mipmap.ic_launcher)
|
||||||
|
@ -114,7 +114,7 @@ fun makeWarningMetadata(context: Context) =
|
||||||
DatasetMetadata(
|
DatasetMetadata(
|
||||||
context.getString(R.string.oreo_autofill_warning_publisher_dataset_title),
|
context.getString(R.string.oreo_autofill_warning_publisher_dataset_title),
|
||||||
context.getString(R.string.oreo_autofill_warning_publisher_dataset_summary),
|
context.getString(R.string.oreo_autofill_warning_publisher_dataset_summary),
|
||||||
R.drawable.ic_warning_red_24dp
|
R.drawable.ic_warning_red_24dp,
|
||||||
)
|
)
|
||||||
|
|
||||||
fun makeHeaderMetadata(title: String) = DatasetMetadata(title, null, 0)
|
fun makeHeaderMetadata(title: String) = DatasetMetadata(title, null, 0)
|
||||||
|
|
|
@ -56,7 +56,7 @@ private fun Context.getEncryptedPrefs(fileName: String): SharedPreferences {
|
||||||
fileName,
|
fileName,
|
||||||
masterKeyAlias,
|
masterKeyAlias,
|
||||||
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
|
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
|
||||||
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
|
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -79,9 +79,7 @@ fun Context.resolveAttribute(attr: Int): Int {
|
||||||
* Commit changes to the store from a [FragmentActivity] using a custom implementation of
|
* Commit changes to the store from a [FragmentActivity] using a custom implementation of
|
||||||
* [GitOperation]
|
* [GitOperation]
|
||||||
*/
|
*/
|
||||||
suspend fun FragmentActivity.commitChange(
|
suspend fun FragmentActivity.commitChange(message: String): Result<Unit, Throwable> {
|
||||||
message: String,
|
|
||||||
): Result<Unit, Throwable> {
|
|
||||||
if (!PasswordRepository.isInitialized) {
|
if (!PasswordRepository.isInitialized) {
|
||||||
return Ok(Unit)
|
return Ok(Unit)
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,7 +25,7 @@ fun Fragment.finish() = requireActivity().finish()
|
||||||
*/
|
*/
|
||||||
fun FragmentManager.performTransactionWithBackStack(
|
fun FragmentManager.performTransactionWithBackStack(
|
||||||
destinationFragment: Fragment,
|
destinationFragment: Fragment,
|
||||||
@IdRes containerViewId: Int = android.R.id.content
|
@IdRes containerViewId: Int = android.R.id.content,
|
||||||
) {
|
) {
|
||||||
commit {
|
commit {
|
||||||
addToBackStack(destinationFragment.tag)
|
addToBackStack(destinationFragment.tag)
|
||||||
|
@ -33,7 +33,7 @@ fun FragmentManager.performTransactionWithBackStack(
|
||||||
R.animator.slide_in_left,
|
R.animator.slide_in_left,
|
||||||
R.animator.slide_out_left,
|
R.animator.slide_out_left,
|
||||||
R.animator.slide_in_right,
|
R.animator.slide_in_right,
|
||||||
R.animator.slide_out_right
|
R.animator.slide_out_right,
|
||||||
)
|
)
|
||||||
replace(containerViewId, destinationFragment)
|
replace(containerViewId, destinationFragment)
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,7 @@ import kotlin.reflect.KProperty
|
||||||
*/
|
*/
|
||||||
class FragmentViewBindingDelegate<T : ViewBinding>(
|
class FragmentViewBindingDelegate<T : ViewBinding>(
|
||||||
val fragment: Fragment,
|
val fragment: Fragment,
|
||||||
val viewBindingFactory: (View) -> T
|
val viewBindingFactory: (View) -> T,
|
||||||
) : ReadOnlyProperty<Fragment, T> {
|
) : ReadOnlyProperty<Fragment, T> {
|
||||||
|
|
||||||
private var binding: T? = null
|
private var binding: T? = null
|
||||||
|
|
|
@ -14,5 +14,5 @@ enum class Feature(
|
||||||
) {
|
) {
|
||||||
|
|
||||||
/** Opt into a cache layer for PGP passphrases. */
|
/** Opt into a cache layer for PGP passphrases. */
|
||||||
EnablePGPPassphraseCache(false, "enable_gpg_passphrase_cache"),
|
EnablePGPPassphraseCache(false, "enable_gpg_passphrase_cache")
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,9 +11,7 @@ import javax.inject.Inject
|
||||||
|
|
||||||
class Features
|
class Features
|
||||||
@Inject
|
@Inject
|
||||||
constructor(
|
constructor(@SettingsPreferences private val preferences: SharedPreferences) {
|
||||||
@SettingsPreferences private val preferences: SharedPreferences,
|
|
||||||
) {
|
|
||||||
|
|
||||||
fun isEnabled(feature: Feature): Boolean {
|
fun isEnabled(feature: Feature): Boolean {
|
||||||
return preferences.getBoolean(feature.configKey, feature.defaultValue)
|
return preferences.getBoolean(feature.configKey, feature.defaultValue)
|
||||||
|
|
|
@ -38,7 +38,7 @@ class GitCommandExecutor(
|
||||||
private val hiltEntryPoint by unsafeLazy {
|
private val hiltEntryPoint by unsafeLazy {
|
||||||
EntryPointAccessors.fromApplication(
|
EntryPointAccessors.fromApplication(
|
||||||
activity.applicationContext,
|
activity.applicationContext,
|
||||||
GitCommandExecutorEntryPoint::class.java
|
GitCommandExecutorEntryPoint::class.java,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -93,7 +93,7 @@ class GitCommandExecutor(
|
||||||
RemoteRefUpdate.Status.REJECTED_NODELETE,
|
RemoteRefUpdate.Status.REJECTED_NODELETE,
|
||||||
RemoteRefUpdate.Status.REJECTED_REMOTE_CHANGED,
|
RemoteRefUpdate.Status.REJECTED_REMOTE_CHANGED,
|
||||||
RemoteRefUpdate.Status.NON_EXISTING,
|
RemoteRefUpdate.Status.NON_EXISTING,
|
||||||
RemoteRefUpdate.Status.NOT_ATTEMPTED, ->
|
RemoteRefUpdate.Status.NOT_ATTEMPTED ->
|
||||||
throw PushException.Generic(rru.status.name)
|
throw PushException.Generic(rru.status.name)
|
||||||
RemoteRefUpdate.Status.REJECTED_OTHER_REASON -> {
|
RemoteRefUpdate.Status.REJECTED_OTHER_REASON -> {
|
||||||
throw if ("non-fast-forward" == rru.message) {
|
throw if ("non-fast-forward" == rru.message) {
|
||||||
|
@ -107,7 +107,7 @@ class GitCommandExecutor(
|
||||||
Toast.makeText(
|
Toast.makeText(
|
||||||
activity,
|
activity,
|
||||||
activity.applicationContext.getString(R.string.git_push_up_to_date),
|
activity.applicationContext.getString(R.string.git_push_up_to_date),
|
||||||
Toast.LENGTH_SHORT
|
Toast.LENGTH_SHORT,
|
||||||
)
|
)
|
||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,5 +19,5 @@ data class GitCommit(
|
||||||
val hash: String,
|
val hash: String,
|
||||||
val shortMessage: String,
|
val shortMessage: String,
|
||||||
val authorName: String,
|
val authorName: String,
|
||||||
val time: Instant
|
val time: Instant,
|
||||||
)
|
)
|
||||||
|
|
|
@ -18,7 +18,5 @@ class CloneOperation(callingActivity: AppCompatActivity, uri: String) :
|
||||||
GitOperation(callingActivity) {
|
GitOperation(callingActivity) {
|
||||||
|
|
||||||
override val commands: Array<GitCommand<out Any>> =
|
override val commands: Array<GitCommand<out Any>> =
|
||||||
arrayOf(
|
arrayOf(Git.cloneRepository().setDirectory(repository.workTree).setURI(uri))
|
||||||
Git.cloneRepository().setDirectory(repository.workTree).setURI(uri),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,9 +12,7 @@ import org.eclipse.jgit.api.GitCommand
|
||||||
* Run an aggressive garbage collection job on the repository, expiring every loose object to
|
* Run an aggressive garbage collection job on the repository, expiring every loose object to
|
||||||
* achieve the best compression.
|
* achieve the best compression.
|
||||||
*/
|
*/
|
||||||
class GcOperation(
|
class GcOperation(callingActivity: AppCompatActivity) : GitOperation(callingActivity) {
|
||||||
callingActivity: AppCompatActivity,
|
|
||||||
) : GitOperation(callingActivity) {
|
|
||||||
|
|
||||||
override val requiresAuth: Boolean = false
|
override val requiresAuth: Boolean = false
|
||||||
override val commands: Array<GitCommand<out Any>> =
|
override val commands: Array<GitCommand<out Any>> =
|
||||||
|
|
|
@ -116,7 +116,7 @@ abstract class GitOperation(protected val callingActivity: FragmentActivity) {
|
||||||
|
|
||||||
private fun registerAuthProviders(
|
private fun registerAuthProviders(
|
||||||
authMethod: SshAuthMethod,
|
authMethod: SshAuthMethod,
|
||||||
credentialsProvider: CredentialsProvider? = null
|
credentialsProvider: CredentialsProvider? = null,
|
||||||
) {
|
) {
|
||||||
sshSessionFactory =
|
sshSessionFactory =
|
||||||
SshjSessionFactory(authMethod, hostKeyFile, hiltEntryPoint.dispatcherProvider())
|
SshjSessionFactory(authMethod, hostKeyFile, hiltEntryPoint.dispatcherProvider())
|
||||||
|
@ -134,12 +134,7 @@ abstract class GitOperation(protected val callingActivity: FragmentActivity) {
|
||||||
if (!preExecute()) {
|
if (!preExecute()) {
|
||||||
return Ok(Unit)
|
return Ok(Unit)
|
||||||
}
|
}
|
||||||
val operationResult =
|
val operationResult = GitCommandExecutor(callingActivity, this).execute()
|
||||||
GitCommandExecutor(
|
|
||||||
callingActivity,
|
|
||||||
this,
|
|
||||||
)
|
|
||||||
.execute()
|
|
||||||
postExecute()
|
postExecute()
|
||||||
return operationResult
|
return operationResult
|
||||||
}
|
}
|
||||||
|
@ -174,7 +169,7 @@ abstract class GitOperation(protected val callingActivity: FragmentActivity) {
|
||||||
suspendCoroutine { cont ->
|
suspendCoroutine { cont ->
|
||||||
BiometricAuthenticator.authenticate(
|
BiometricAuthenticator.authenticate(
|
||||||
callingActivity,
|
callingActivity,
|
||||||
R.string.biometric_prompt_title_ssh_auth
|
R.string.biometric_prompt_title_ssh_auth,
|
||||||
) { result ->
|
) { result ->
|
||||||
if (result !is Failure && result !is Retry) cont.resume(result)
|
if (result !is Failure && result !is Retry) cont.resume(result)
|
||||||
}
|
}
|
||||||
|
@ -199,7 +194,7 @@ abstract class GitOperation(protected val callingActivity: FragmentActivity) {
|
||||||
Toast.makeText(
|
Toast.makeText(
|
||||||
callingActivity,
|
callingActivity,
|
||||||
R.string.biometric_auth_generic_failure,
|
R.string.biometric_auth_generic_failure,
|
||||||
Toast.LENGTH_LONG
|
Toast.LENGTH_LONG,
|
||||||
)
|
)
|
||||||
.show()
|
.show()
|
||||||
callingActivity.finish()
|
callingActivity.finish()
|
||||||
|
@ -220,7 +215,7 @@ abstract class GitOperation(protected val callingActivity: FragmentActivity) {
|
||||||
CredentialFinder(
|
CredentialFinder(
|
||||||
callingActivity,
|
callingActivity,
|
||||||
AuthMode.Password,
|
AuthMode.Password,
|
||||||
hiltEntryPoint.dispatcherProvider()
|
hiltEntryPoint.dispatcherProvider(),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
registerAuthProviders(SshAuthMethod.Password(authActivity), httpsCredentialProvider)
|
registerAuthProviders(SshAuthMethod.Password(authActivity), httpsCredentialProvider)
|
||||||
|
|
|
@ -7,10 +7,8 @@ package app.passwordstore.util.git.operation
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import org.eclipse.jgit.api.GitCommand
|
import org.eclipse.jgit.api.GitCommand
|
||||||
|
|
||||||
class PullOperation(
|
class PullOperation(callingActivity: AppCompatActivity, rebase: Boolean) :
|
||||||
callingActivity: AppCompatActivity,
|
GitOperation(callingActivity) {
|
||||||
rebase: Boolean,
|
|
||||||
) : GitOperation(callingActivity) {
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The story of why the pull operation is committing files goes like this: Once upon a time when
|
* The story of why the pull operation is committing files goes like this: Once upon a time when
|
||||||
|
|
|
@ -10,7 +10,5 @@ import org.eclipse.jgit.api.GitCommand
|
||||||
class PushOperation(callingActivity: AppCompatActivity) : GitOperation(callingActivity) {
|
class PushOperation(callingActivity: AppCompatActivity) : GitOperation(callingActivity) {
|
||||||
|
|
||||||
override val commands: Array<GitCommand<out Any>> =
|
override val commands: Array<GitCommand<out Any>> =
|
||||||
arrayOf(
|
arrayOf(git.push().setPushAll().setRemote("origin"))
|
||||||
git.push().setPushAll().setRemote("origin"),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,10 +7,8 @@ package app.passwordstore.util.git.operation
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import org.eclipse.jgit.api.GitCommand
|
import org.eclipse.jgit.api.GitCommand
|
||||||
|
|
||||||
class SyncOperation(
|
class SyncOperation(callingActivity: AppCompatActivity, rebase: Boolean) :
|
||||||
callingActivity: AppCompatActivity,
|
GitOperation(callingActivity) {
|
||||||
rebase: Boolean,
|
|
||||||
) : GitOperation(callingActivity) {
|
|
||||||
|
|
||||||
override val commands: Array<GitCommand<out Any>> =
|
override val commands: Array<GitCommand<out Any>> =
|
||||||
arrayOf(
|
arrayOf(
|
||||||
|
|
|
@ -135,8 +135,7 @@ object SshKey {
|
||||||
KeystoreWrappedEd25519("keystore_wrapped_ed25519"),
|
KeystoreWrappedEd25519("keystore_wrapped_ed25519"),
|
||||||
|
|
||||||
// Behaves like `Imported`, but allows to view the public key.
|
// Behaves like `Imported`, but allows to view the public key.
|
||||||
LegacyGenerated("legacy_generated"),
|
LegacyGenerated("legacy_generated");
|
||||||
;
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
|
@ -146,7 +145,7 @@ object SshKey {
|
||||||
|
|
||||||
enum class Algorithm(
|
enum class Algorithm(
|
||||||
val algorithm: String,
|
val algorithm: String,
|
||||||
val applyToSpec: KeyGenParameterSpec.Builder.() -> Unit
|
val applyToSpec: KeyGenParameterSpec.Builder.() -> Unit,
|
||||||
) {
|
) {
|
||||||
Rsa(
|
Rsa(
|
||||||
KeyProperties.KEY_ALGORITHM_RSA,
|
KeyProperties.KEY_ALGORITHM_RSA,
|
||||||
|
@ -156,9 +155,9 @@ object SshKey {
|
||||||
setDigests(
|
setDigests(
|
||||||
KeyProperties.DIGEST_SHA1,
|
KeyProperties.DIGEST_SHA1,
|
||||||
KeyProperties.DIGEST_SHA256,
|
KeyProperties.DIGEST_SHA256,
|
||||||
KeyProperties.DIGEST_SHA512
|
KeyProperties.DIGEST_SHA512,
|
||||||
)
|
)
|
||||||
}
|
},
|
||||||
),
|
),
|
||||||
Ecdsa(
|
Ecdsa(
|
||||||
KeyProperties.KEY_ALGORITHM_EC,
|
KeyProperties.KEY_ALGORITHM_EC,
|
||||||
|
@ -169,7 +168,7 @@ object SshKey {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||||
setIsStrongBoxBacked(isStrongBoxSupported)
|
setIsStrongBoxBacked(isStrongBoxSupported)
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -250,7 +249,7 @@ object SshKey {
|
||||||
context,
|
context,
|
||||||
privateKeyFile,
|
privateKeyFile,
|
||||||
getOrCreateWrappingMasterKey(requireAuthentication),
|
getOrCreateWrappingMasterKey(requireAuthentication),
|
||||||
EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB
|
EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB,
|
||||||
)
|
)
|
||||||
.setKeysetPrefName(ANDROIDX_SECURITY_KEYSET_PREF_NAME)
|
.setKeysetPrefName(ANDROIDX_SECURITY_KEYSET_PREF_NAME)
|
||||||
.build()
|
.build()
|
||||||
|
@ -319,7 +318,7 @@ object SshKey {
|
||||||
logcat { error.asLog() }
|
logcat { error.asLog() }
|
||||||
throw IOException(
|
throw IOException(
|
||||||
"Failed to get public key '$KEYSTORE_ALIAS' from Android Keystore",
|
"Failed to get public key '$KEYSTORE_ALIAS' from Android Keystore",
|
||||||
error
|
error,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -329,7 +328,7 @@ object SshKey {
|
||||||
logcat { error.asLog() }
|
logcat { error.asLog() }
|
||||||
throw IOException(
|
throw IOException(
|
||||||
"Failed to access private key '$KEYSTORE_ALIAS' from Android Keystore",
|
"Failed to access private key '$KEYSTORE_ALIAS' from Android Keystore",
|
||||||
error
|
error,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -140,18 +140,10 @@ class SshjConfig : ConfigImpl() {
|
||||||
|
|
||||||
private fun initMACFactories() {
|
private fun initMACFactories() {
|
||||||
macFactories =
|
macFactories =
|
||||||
listOf(
|
listOf(Macs.HMACSHA2512Etm(), Macs.HMACSHA2256Etm(), Macs.HMACSHA2512(), Macs.HMACSHA2256())
|
||||||
Macs.HMACSHA2512Etm(),
|
|
||||||
Macs.HMACSHA2256Etm(),
|
|
||||||
Macs.HMACSHA2512(),
|
|
||||||
Macs.HMACSHA2256(),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun initCompressionFactories() {
|
private fun initCompressionFactories() {
|
||||||
compressionFactories =
|
compressionFactories = listOf(NoneCompression.Factory())
|
||||||
listOf(
|
|
||||||
NoneCompression.Factory(),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -79,7 +79,7 @@ class SshjSessionFactory(
|
||||||
uri: URIish,
|
uri: URIish,
|
||||||
credentialsProvider: CredentialsProvider?,
|
credentialsProvider: CredentialsProvider?,
|
||||||
fs: FS?,
|
fs: FS?,
|
||||||
tms: Int
|
tms: Int,
|
||||||
): RemoteSession {
|
): RemoteSession {
|
||||||
return currentSession
|
return currentSession
|
||||||
?: SshjSession(uri, uri.user, authMethod, hostKeyFile, dispatcherProvider).connect().also {
|
?: SshjSession(uri, uri.user, authMethod, hostKeyFile, dispatcherProvider).connect().also {
|
||||||
|
@ -164,7 +164,7 @@ private class SshjSession(
|
||||||
AuthPublickey(
|
AuthPublickey(
|
||||||
SshKey.provide(
|
SshKey.provide(
|
||||||
ssh,
|
ssh,
|
||||||
CredentialFinder(authMethod.activity, AuthMode.SshKey, dispatcherProvider)
|
CredentialFinder(authMethod.activity, AuthMode.SshKey, dispatcherProvider),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
ssh.auth(username, pubkeyAuth, passwordAuth)
|
ssh.auth(username, pubkeyAuth, passwordAuth)
|
||||||
|
|
|
@ -125,7 +125,7 @@ class ClipboardService : Service() {
|
||||||
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE
|
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE
|
||||||
} else {
|
} else {
|
||||||
PendingIntent.FLAG_UPDATE_CURRENT
|
PendingIntent.FLAG_UPDATE_CURRENT
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
val notification = createNotification(pendingIntent, clearTimeMs)
|
val notification = createNotification(pendingIntent, clearTimeMs)
|
||||||
|
|
||||||
|
@ -152,7 +152,7 @@ class ClipboardService : Service() {
|
||||||
NotificationChannel(
|
NotificationChannel(
|
||||||
CHANNEL_ID,
|
CHANNEL_ID,
|
||||||
getString(R.string.app_name),
|
getString(R.string.app_name),
|
||||||
NotificationManager.IMPORTANCE_LOW
|
NotificationManager.IMPORTANCE_LOW,
|
||||||
)
|
)
|
||||||
val manager = getSystemService<NotificationManager>()
|
val manager = getSystemService<NotificationManager>()
|
||||||
if (manager != null) {
|
if (manager != null) {
|
||||||
|
|
|
@ -65,7 +65,7 @@ class OreoAutofillService : AutofillService() {
|
||||||
override fun onFillRequest(
|
override fun onFillRequest(
|
||||||
request: FillRequest,
|
request: FillRequest,
|
||||||
cancellationSignal: CancellationSignal,
|
cancellationSignal: CancellationSignal,
|
||||||
callback: FillCallback
|
callback: FillCallback,
|
||||||
) {
|
) {
|
||||||
val structure =
|
val structure =
|
||||||
request.fillContexts.lastOrNull()?.structure
|
request.fillContexts.lastOrNull()?.structure
|
||||||
|
@ -144,7 +144,7 @@ class OreoAutofillService : AutofillService() {
|
||||||
AutofillSaveActivity.makeSaveIntentSender(
|
AutofillSaveActivity.makeSaveIntentSender(
|
||||||
this,
|
this,
|
||||||
credentials = Credentials(username, password, null),
|
credentials = Credentials(username, password, null),
|
||||||
formOrigin = formOrigin
|
formOrigin = formOrigin,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -134,7 +134,7 @@ class PasswordExportService : Service() {
|
||||||
NotificationChannel(
|
NotificationChannel(
|
||||||
CHANNEL_ID,
|
CHANNEL_ID,
|
||||||
getString(R.string.app_name),
|
getString(R.string.app_name),
|
||||||
NotificationManager.IMPORTANCE_LOW
|
NotificationManager.IMPORTANCE_LOW,
|
||||||
)
|
)
|
||||||
val manager = getSystemService<NotificationManager>()
|
val manager = getSystemService<NotificationManager>()
|
||||||
if (manager != null) {
|
if (manager != null) {
|
||||||
|
|
|
@ -21,8 +21,7 @@ import org.eclipse.jgit.transport.URIish
|
||||||
|
|
||||||
enum class Protocol(val pref: String) {
|
enum class Protocol(val pref: String) {
|
||||||
Ssh("ssh://"),
|
Ssh("ssh://"),
|
||||||
Https("https://"),
|
Https("https://");
|
||||||
;
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
|
@ -36,8 +35,7 @@ enum class Protocol(val pref: String) {
|
||||||
enum class AuthMode(val pref: String) {
|
enum class AuthMode(val pref: String) {
|
||||||
SshKey("ssh-key"),
|
SshKey("ssh-key"),
|
||||||
Password("username/password"),
|
Password("username/password"),
|
||||||
None("None"),
|
None("None");
|
||||||
;
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
|
@ -140,7 +138,7 @@ constructor(
|
||||||
|
|
||||||
fun updateConnectionSettingsIfValid(
|
fun updateConnectionSettingsIfValid(
|
||||||
newAuthMode: AuthMode,
|
newAuthMode: AuthMode,
|
||||||
newUrl: String
|
newUrl: String,
|
||||||
): UpdateConnectionSettingsResult {
|
): UpdateConnectionSettingsResult {
|
||||||
val parsedUrl =
|
val parsedUrl =
|
||||||
runCatching { URIish(newUrl) }
|
runCatching { URIish(newUrl) }
|
||||||
|
|
|
@ -91,7 +91,7 @@ private fun migrateToGitUrlBasedConfig(sharedPrefs: SharedPreferences, gitSettin
|
||||||
url == null ||
|
url == null ||
|
||||||
gitSettings.updateConnectionSettingsIfValid(
|
gitSettings.updateConnectionSettingsIfValid(
|
||||||
newAuthMode = gitSettings.authMode,
|
newAuthMode = gitSettings.authMode,
|
||||||
newUrl = url
|
newUrl = url,
|
||||||
) != GitSettings.UpdateConnectionSettingsResult.Valid
|
) != GitSettings.UpdateConnectionSettingsResult.Valid
|
||||||
) {
|
) {
|
||||||
logcat(TAG, ERROR) { "Failed to migrate to URL-based Git config, generated URL is invalid" }
|
logcat(TAG, ERROR) { "Failed to migrate to URL-based Git config, generated URL is invalid" }
|
||||||
|
@ -128,7 +128,7 @@ private fun migrateToClipboardHistory(sharedPrefs: SharedPreferences) {
|
||||||
sharedPrefs.edit {
|
sharedPrefs.edit {
|
||||||
putBoolean(
|
putBoolean(
|
||||||
PreferenceKeys.CLEAR_CLIPBOARD_HISTORY,
|
PreferenceKeys.CLEAR_CLIPBOARD_HISTORY,
|
||||||
sharedPrefs.getBoolean(PreferenceKeys.CLEAR_CLIPBOARD_20X, false)
|
sharedPrefs.getBoolean(PreferenceKeys.CLEAR_CLIPBOARD_20X, false),
|
||||||
)
|
)
|
||||||
remove(PreferenceKeys.CLEAR_CLIPBOARD_20X)
|
remove(PreferenceKeys.CLEAR_CLIPBOARD_20X)
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,7 +64,7 @@ object PreferenceKeys {
|
||||||
|
|
||||||
@Deprecated(
|
@Deprecated(
|
||||||
message = "Use SHOW_HIDDEN_CONTENTS instead",
|
message = "Use SHOW_HIDDEN_CONTENTS instead",
|
||||||
replaceWith = ReplaceWith("PreferenceKeys.SHOW_HIDDEN_CONTENTS")
|
replaceWith = ReplaceWith("PreferenceKeys.SHOW_HIDDEN_CONTENTS"),
|
||||||
)
|
)
|
||||||
const val SHOW_HIDDEN_FOLDERS = "show_hidden_folders"
|
const val SHOW_HIDDEN_FOLDERS = "show_hidden_folders"
|
||||||
const val SHOW_HIDDEN_CONTENTS = "show_hidden_contents"
|
const val SHOW_HIDDEN_CONTENTS = "show_hidden_contents"
|
||||||
|
|
|
@ -19,11 +19,7 @@ import javax.inject.Inject
|
||||||
import logcat.logcat
|
import logcat.logcat
|
||||||
|
|
||||||
@Reusable
|
@Reusable
|
||||||
class ShortcutHandler
|
class ShortcutHandler @Inject constructor(@ApplicationContext val context: Context) {
|
||||||
@Inject
|
|
||||||
constructor(
|
|
||||||
@ApplicationContext val context: Context,
|
|
||||||
) {
|
|
||||||
|
|
||||||
private companion object {
|
private companion object {
|
||||||
|
|
||||||
|
|
|
@ -67,7 +67,7 @@ private val CaseInsensitiveComparator = Collator.getInstance().apply { strength
|
||||||
|
|
||||||
private fun PasswordItem.Companion.makeComparator(
|
private fun PasswordItem.Companion.makeComparator(
|
||||||
typeSortOrder: PasswordSortOrder,
|
typeSortOrder: PasswordSortOrder,
|
||||||
directoryStructure: DirectoryStructure
|
directoryStructure: DirectoryStructure,
|
||||||
): Comparator<PasswordItem> {
|
): Comparator<PasswordItem> {
|
||||||
return when (typeSortOrder) {
|
return when (typeSortOrder) {
|
||||||
PasswordSortOrder.FOLDER_FIRST -> compareBy { it.type }
|
PasswordSortOrder.FOLDER_FIRST -> compareBy { it.type }
|
||||||
|
@ -158,7 +158,7 @@ constructor(
|
||||||
val listMode: ListMode,
|
val listMode: ListMode,
|
||||||
// This counter can be increased to force a reexecution of the search action even if all
|
// This counter can be increased to force a reexecution of the search action even if all
|
||||||
// other arguments are left unchanged.
|
// other arguments are left unchanged.
|
||||||
val updateCounter: Int
|
val updateCounter: Int,
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun makeSearchAction(
|
private fun makeSearchAction(
|
||||||
|
@ -166,7 +166,7 @@ constructor(
|
||||||
filter: String,
|
filter: String,
|
||||||
filterMode: FilterMode,
|
filterMode: FilterMode,
|
||||||
searchMode: SearchMode,
|
searchMode: SearchMode,
|
||||||
listMode: ListMode
|
listMode: ListMode,
|
||||||
): SearchAction {
|
): SearchAction {
|
||||||
return SearchAction(
|
return SearchAction(
|
||||||
baseDirectory = baseDirectory,
|
baseDirectory = baseDirectory,
|
||||||
|
@ -174,7 +174,7 @@ constructor(
|
||||||
filterMode = filterMode,
|
filterMode = filterMode,
|
||||||
searchMode = searchMode,
|
searchMode = searchMode,
|
||||||
listMode = listMode,
|
listMode = listMode,
|
||||||
updateCounter = updateCounter
|
updateCounter = updateCounter,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -187,7 +187,7 @@ constructor(
|
||||||
filter = "",
|
filter = "",
|
||||||
filterMode = FilterMode.NoFilter,
|
filterMode = FilterMode.NoFilter,
|
||||||
searchMode = SearchMode.InCurrentDirectoryOnly,
|
searchMode = SearchMode.InCurrentDirectoryOnly,
|
||||||
listMode = ListMode.AllEntries
|
listMode = ListMode.AllEntries,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -310,7 +310,7 @@ constructor(
|
||||||
newDirectory: File = root,
|
newDirectory: File = root,
|
||||||
listMode: ListMode = ListMode.AllEntries,
|
listMode: ListMode = ListMode.AllEntries,
|
||||||
recyclerViewState: Parcelable? = null,
|
recyclerViewState: Parcelable? = null,
|
||||||
pushPreviousLocation: Boolean = true
|
pushPreviousLocation: Boolean = true,
|
||||||
) {
|
) {
|
||||||
if (!newDirectory.exists()) return
|
if (!newDirectory.exists()) return
|
||||||
require(newDirectory.isDirectory) { "Can only navigate to a directory" }
|
require(newDirectory.isDirectory) { "Can only navigate to a directory" }
|
||||||
|
@ -323,7 +323,7 @@ constructor(
|
||||||
baseDirectory = newDirectory,
|
baseDirectory = newDirectory,
|
||||||
filterMode = FilterMode.NoFilter,
|
filterMode = FilterMode.NoFilter,
|
||||||
searchMode = SearchMode.InCurrentDirectoryOnly,
|
searchMode = SearchMode.InCurrentDirectoryOnly,
|
||||||
listMode = listMode
|
listMode = listMode,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
_currentDir.update { newDirectory }
|
_currentDir.update { newDirectory }
|
||||||
|
@ -356,7 +356,7 @@ constructor(
|
||||||
baseDirectory: File? = null,
|
baseDirectory: File? = null,
|
||||||
filterMode: FilterMode = FilterMode.Fuzzy,
|
filterMode: FilterMode = FilterMode.Fuzzy,
|
||||||
searchMode: SearchMode? = null,
|
searchMode: SearchMode? = null,
|
||||||
listMode: ListMode = ListMode.AllEntries
|
listMode: ListMode = ListMode.AllEntries,
|
||||||
) {
|
) {
|
||||||
require(baseDirectory?.isDirectory != false) { "Can only search in a directory" }
|
require(baseDirectory?.isDirectory != false) { "Can only search in a directory" }
|
||||||
searchActionFlow.update {
|
searchActionFlow.update {
|
||||||
|
@ -365,7 +365,7 @@ constructor(
|
||||||
baseDirectory = baseDirectory ?: _currentDir.value,
|
baseDirectory = baseDirectory ?: _currentDir.value,
|
||||||
filterMode = filterMode,
|
filterMode = filterMode,
|
||||||
searchMode = searchMode ?: defaultSearchMode,
|
searchMode = searchMode ?: defaultSearchMode,
|
||||||
listMode = listMode
|
listMode = listMode,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -417,7 +417,7 @@ open class SearchableRepositoryAdapter<T : RecyclerView.ViewHolder>(
|
||||||
|
|
||||||
fun <T : ItemDetailsLookup<String>> makeSelectable(
|
fun <T : ItemDetailsLookup<String>> makeSelectable(
|
||||||
recyclerView: RecyclerView,
|
recyclerView: RecyclerView,
|
||||||
itemDetailsLookupCreator: (recyclerView: RecyclerView) -> T
|
itemDetailsLookupCreator: (recyclerView: RecyclerView) -> T,
|
||||||
) {
|
) {
|
||||||
selectionTracker =
|
selectionTracker =
|
||||||
SelectionTracker.Builder(
|
SelectionTracker.Builder(
|
||||||
|
@ -425,7 +425,7 @@ open class SearchableRepositoryAdapter<T : RecyclerView.ViewHolder>(
|
||||||
recyclerView,
|
recyclerView,
|
||||||
itemKeyProvider,
|
itemKeyProvider,
|
||||||
itemDetailsLookupCreator(recyclerView),
|
itemDetailsLookupCreator(recyclerView),
|
||||||
StorageStrategy.createStringStorage()
|
StorageStrategy.createStringStorage(),
|
||||||
)
|
)
|
||||||
.withSelectionPredicate(SelectionPredicates.createSelectAnything())
|
.withSelectionPredicate(SelectionPredicates.createSelectAnything())
|
||||||
.build()
|
.build()
|
||||||
|
|
|
@ -87,7 +87,7 @@ class AutofillSmsActivity : AppCompatActivity() {
|
||||||
context,
|
context,
|
||||||
fillOtpFromSmsRequestCode++,
|
fillOtpFromSmsRequestCode++,
|
||||||
intent,
|
intent,
|
||||||
PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_IMMUTABLE,
|
||||||
)
|
)
|
||||||
.intentSender
|
.intentSender
|
||||||
}
|
}
|
||||||
|
@ -159,11 +159,11 @@ class AutofillSmsActivity : AppCompatActivity() {
|
||||||
this@AutofillSmsActivity,
|
this@AutofillSmsActivity,
|
||||||
Credentials(null, null, smsCode),
|
Credentials(null, null, smsCode),
|
||||||
clientState,
|
clientState,
|
||||||
AutofillAction.FillOtpFromSms
|
AutofillAction.FillOtpFromSms,
|
||||||
)
|
)
|
||||||
setResult(
|
setResult(
|
||||||
RESULT_OK,
|
RESULT_OK,
|
||||||
Intent().apply { putExtra(AutofillManager.EXTRA_AUTHENTICATION_RESULT, fillInDataset) }
|
Intent().apply { putExtra(AutofillManager.EXTRA_AUTHENTICATION_RESULT, fillInDataset) },
|
||||||
)
|
)
|
||||||
finish()
|
finish()
|
||||||
}
|
}
|
||||||
|
|
|
@ -61,12 +61,12 @@ class MigrationsTest {
|
||||||
runMigrations(
|
runMigrations(
|
||||||
filesDir,
|
filesDir,
|
||||||
sharedPrefs,
|
sharedPrefs,
|
||||||
GitSettings(sharedPrefs, encryptedSharedPreferences, proxySharedPreferences, filesDir)
|
GitSettings(sharedPrefs, encryptedSharedPreferences, proxySharedPreferences, filesDir),
|
||||||
)
|
)
|
||||||
checkOldKeysAreRemoved()
|
checkOldKeysAreRemoved()
|
||||||
assertEquals(
|
assertEquals(
|
||||||
sharedPrefs.getString(PreferenceKeys.GIT_REMOTE_URL),
|
sharedPrefs.getString(PreferenceKeys.GIT_REMOTE_URL),
|
||||||
"ssh://msfjarvis@192.168.0.102:2200/mnt/disk3/pass-repo"
|
"ssh://msfjarvis@192.168.0.102:2200/mnt/disk3/pass-repo",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -82,12 +82,12 @@ class MigrationsTest {
|
||||||
runMigrations(
|
runMigrations(
|
||||||
filesDir,
|
filesDir,
|
||||||
sharedPrefs,
|
sharedPrefs,
|
||||||
GitSettings(sharedPrefs, encryptedSharedPreferences, proxySharedPreferences, filesDir)
|
GitSettings(sharedPrefs, encryptedSharedPreferences, proxySharedPreferences, filesDir),
|
||||||
)
|
)
|
||||||
checkOldKeysAreRemoved()
|
checkOldKeysAreRemoved()
|
||||||
assertEquals(
|
assertEquals(
|
||||||
sharedPrefs.getString(PreferenceKeys.GIT_REMOTE_URL),
|
sharedPrefs.getString(PreferenceKeys.GIT_REMOTE_URL),
|
||||||
"msfjarvis@192.168.0.102:/mnt/disk3/pass-repo"
|
"msfjarvis@192.168.0.102:/mnt/disk3/pass-repo",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -103,12 +103,12 @@ class MigrationsTest {
|
||||||
runMigrations(
|
runMigrations(
|
||||||
filesDir,
|
filesDir,
|
||||||
sharedPrefs,
|
sharedPrefs,
|
||||||
GitSettings(sharedPrefs, encryptedSharedPreferences, proxySharedPreferences, filesDir)
|
GitSettings(sharedPrefs, encryptedSharedPreferences, proxySharedPreferences, filesDir),
|
||||||
)
|
)
|
||||||
checkOldKeysAreRemoved()
|
checkOldKeysAreRemoved()
|
||||||
assertEquals(
|
assertEquals(
|
||||||
sharedPrefs.getString(PreferenceKeys.GIT_REMOTE_URL),
|
sharedPrefs.getString(PreferenceKeys.GIT_REMOTE_URL),
|
||||||
"https://github.com/Android-Password-Store/pass-test"
|
"https://github.com/Android-Password-Store/pass-test",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -117,7 +117,7 @@ class MigrationsTest {
|
||||||
runMigrations(
|
runMigrations(
|
||||||
filesDir,
|
filesDir,
|
||||||
sharedPrefs,
|
sharedPrefs,
|
||||||
GitSettings(sharedPrefs, encryptedSharedPreferences, proxySharedPreferences, filesDir)
|
GitSettings(sharedPrefs, encryptedSharedPreferences, proxySharedPreferences, filesDir),
|
||||||
)
|
)
|
||||||
assertEquals(true, sharedPrefs.getBoolean(PreferenceKeys.SHOW_HIDDEN_FOLDERS, true))
|
assertEquals(true, sharedPrefs.getBoolean(PreferenceKeys.SHOW_HIDDEN_FOLDERS, true))
|
||||||
assertEquals(false, sharedPrefs.getBoolean(PreferenceKeys.SHOW_HIDDEN_CONTENTS, false))
|
assertEquals(false, sharedPrefs.getBoolean(PreferenceKeys.SHOW_HIDDEN_CONTENTS, false))
|
||||||
|
@ -129,7 +129,7 @@ class MigrationsTest {
|
||||||
runMigrations(
|
runMigrations(
|
||||||
filesDir,
|
filesDir,
|
||||||
sharedPrefs,
|
sharedPrefs,
|
||||||
GitSettings(sharedPrefs, encryptedSharedPreferences, proxySharedPreferences, filesDir)
|
GitSettings(sharedPrefs, encryptedSharedPreferences, proxySharedPreferences, filesDir),
|
||||||
)
|
)
|
||||||
assertEquals(false, sharedPrefs.getBoolean(PreferenceKeys.SHOW_HIDDEN_FOLDERS, false))
|
assertEquals(false, sharedPrefs.getBoolean(PreferenceKeys.SHOW_HIDDEN_FOLDERS, false))
|
||||||
assertEquals(true, sharedPrefs.getBoolean(PreferenceKeys.SHOW_HIDDEN_CONTENTS, false))
|
assertEquals(true, sharedPrefs.getBoolean(PreferenceKeys.SHOW_HIDDEN_CONTENTS, false))
|
||||||
|
@ -141,7 +141,7 @@ class MigrationsTest {
|
||||||
runMigrations(
|
runMigrations(
|
||||||
filesDir,
|
filesDir,
|
||||||
sharedPrefs,
|
sharedPrefs,
|
||||||
GitSettings(sharedPrefs, encryptedSharedPreferences, proxySharedPreferences, filesDir)
|
GitSettings(sharedPrefs, encryptedSharedPreferences, proxySharedPreferences, filesDir),
|
||||||
)
|
)
|
||||||
assertEquals(true, sharedPrefs.getBoolean(PreferenceKeys.CLEAR_CLIPBOARD_HISTORY, false))
|
assertEquals(true, sharedPrefs.getBoolean(PreferenceKeys.CLEAR_CLIPBOARD_HISTORY, false))
|
||||||
assertFalse(sharedPrefs.contains(PreferenceKeys.CLEAR_CLIPBOARD_20X))
|
assertFalse(sharedPrefs.contains(PreferenceKeys.CLEAR_CLIPBOARD_20X))
|
||||||
|
|
|
@ -47,7 +47,7 @@ public sealed class FormOrigin(public open val identifier: String) {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
context.packageManager.getApplicationInfo(
|
context.packageManager.getApplicationInfo(
|
||||||
identifier,
|
identifier,
|
||||||
ApplicationInfoFlags.of(PackageManager.GET_META_DATA.toLong())
|
ApplicationInfoFlags.of(PackageManager.GET_META_DATA.toLong()),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
context.packageManager.getApplicationInfo(identifier, PackageManager.GET_META_DATA)
|
context.packageManager.getApplicationInfo(identifier, PackageManager.GET_META_DATA)
|
||||||
|
@ -74,7 +74,7 @@ private class AutofillFormParser(
|
||||||
context: Context,
|
context: Context,
|
||||||
structure: AssistStructure,
|
structure: AssistStructure,
|
||||||
isManualRequest: Boolean,
|
isManualRequest: Boolean,
|
||||||
private val customSuffixes: Sequence<String>
|
private val customSuffixes: Sequence<String>,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
@ -136,7 +136,7 @@ private class AutofillFormParser(
|
||||||
autofillStrategy.match(
|
autofillStrategy.match(
|
||||||
relevantFields,
|
relevantFields,
|
||||||
singleOriginMode = trustedBrowserInfo?.multiOriginMethod == BrowserMultiOriginMethod.None,
|
singleOriginMode = trustedBrowserInfo?.multiOriginMethod == BrowserMultiOriginMethod.None,
|
||||||
isManualRequest = isManualRequest
|
isManualRequest = isManualRequest,
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun trackOrigin(node: AssistStructure.ViewNode) {
|
private fun trackOrigin(node: AssistStructure.ViewNode) {
|
||||||
|
@ -187,7 +187,7 @@ private class AutofillFormParser(
|
||||||
// situation is uncertain and Autofill should not be offered.
|
// situation is uncertain and Autofill should not be offered.
|
||||||
webOriginToFormOrigin(
|
webOriginToFormOrigin(
|
||||||
context,
|
context,
|
||||||
scenario.allFields.map { it.webOrigin }.toSet().singleOrNull() ?: return null
|
scenario.allFields.map { it.webOrigin }.toSet().singleOrNull() ?: return null,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -206,7 +206,7 @@ private constructor(
|
||||||
public val formOrigin: FormOrigin,
|
public val formOrigin: FormOrigin,
|
||||||
public val scenario: AutofillScenario<AutofillId>,
|
public val scenario: AutofillScenario<AutofillId>,
|
||||||
public val ignoredIds: List<AutofillId>,
|
public val ignoredIds: List<AutofillId>,
|
||||||
public val saveFlags: Int?
|
public val saveFlags: Int?,
|
||||||
) {
|
) {
|
||||||
public companion object {
|
public companion object {
|
||||||
/** Returns a [FillableForm] if a login form could be detected in [structure]. */
|
/** Returns a [FillableForm] if a login form could be detected in [structure]. */
|
||||||
|
@ -222,7 +222,7 @@ private constructor(
|
||||||
form.formOrigin,
|
form.formOrigin,
|
||||||
form.scenario.map { it.autofillId },
|
form.scenario.map { it.autofillId },
|
||||||
form.ignoredIds,
|
form.ignoredIds,
|
||||||
form.saveFlags
|
form.saveFlags,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,7 +57,7 @@ public fun computeCertificatesHash(context: Context, appPackage: String): String
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
context.packageManager.getPackageInfo(
|
context.packageManager.getPackageInfo(
|
||||||
appPackage,
|
appPackage,
|
||||||
PackageManager.PackageInfoFlags.of(PackageManager.GET_SIGNING_CERTIFICATES.toLong())
|
PackageManager.PackageInfoFlags.of(PackageManager.GET_SIGNING_CERTIFICATES.toLong()),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
context.packageManager.getPackageInfo(appPackage, PackageManager.GET_SIGNING_CERTIFICATES)
|
context.packageManager.getPackageInfo(appPackage, PackageManager.GET_SIGNING_CERTIFICATES)
|
||||||
|
@ -121,7 +121,7 @@ private fun visitViewNodes(structure: AssistStructure, block: (AssistStructure.V
|
||||||
|
|
||||||
private fun visitViewNode(
|
private fun visitViewNode(
|
||||||
node: AssistStructure.ViewNode,
|
node: AssistStructure.ViewNode,
|
||||||
block: (AssistStructure.ViewNode) -> Unit
|
block: (AssistStructure.ViewNode) -> Unit,
|
||||||
) {
|
) {
|
||||||
block(node)
|
block(node)
|
||||||
for (i in 0 until node.childCount) {
|
for (i in 0 until node.childCount) {
|
||||||
|
|
|
@ -21,7 +21,7 @@ public enum class AutofillAction {
|
||||||
Match,
|
Match,
|
||||||
Search,
|
Search,
|
||||||
Generate,
|
Generate,
|
||||||
FillOtpFromSms
|
FillOtpFromSms,
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -46,8 +46,8 @@ public sealed class AutofillScenario<out T : Any> {
|
||||||
"Use `fromClientState` instead.",
|
"Use `fromClientState` instead.",
|
||||||
ReplaceWith(
|
ReplaceWith(
|
||||||
"fromClientState(clientState)",
|
"fromClientState(clientState)",
|
||||||
"com.github.androidpasswordstore.autofillparser.AutofillScenario.Companion.fromClientState"
|
"com.github.androidpasswordstore.autofillparser.AutofillScenario.Companion.fromClientState",
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
public fun fromBundle(clientState: Bundle): AutofillScenario<AutofillId>? {
|
public fun fromBundle(clientState: Bundle): AutofillScenario<AutofillId>? {
|
||||||
return fromClientState(clientState)
|
return fromClientState(clientState)
|
||||||
|
@ -61,7 +61,7 @@ public sealed class AutofillScenario<out T : Any> {
|
||||||
BundleCompat.getParcelable(
|
BundleCompat.getParcelable(
|
||||||
clientState,
|
clientState,
|
||||||
BUNDLE_KEY_USERNAME_ID,
|
BUNDLE_KEY_USERNAME_ID,
|
||||||
AutofillId::class.java
|
AutofillId::class.java,
|
||||||
)
|
)
|
||||||
fillUsername = clientState.getBoolean(BUNDLE_KEY_FILL_USERNAME)
|
fillUsername = clientState.getBoolean(BUNDLE_KEY_FILL_USERNAME)
|
||||||
otp = BundleCompat.getParcelable(clientState, BUNDLE_KEY_OTP_ID, AutofillId::class.java)
|
otp = BundleCompat.getParcelable(clientState, BUNDLE_KEY_OTP_ID, AutofillId::class.java)
|
||||||
|
@ -69,21 +69,21 @@ public sealed class AutofillScenario<out T : Any> {
|
||||||
BundleCompat.getParcelableArrayList(
|
BundleCompat.getParcelableArrayList(
|
||||||
clientState,
|
clientState,
|
||||||
BUNDLE_KEY_CURRENT_PASSWORD_IDS,
|
BUNDLE_KEY_CURRENT_PASSWORD_IDS,
|
||||||
AutofillId::class.java
|
AutofillId::class.java,
|
||||||
) ?: emptyList()
|
) ?: emptyList()
|
||||||
)
|
)
|
||||||
newPassword.addAll(
|
newPassword.addAll(
|
||||||
BundleCompat.getParcelableArrayList(
|
BundleCompat.getParcelableArrayList(
|
||||||
clientState,
|
clientState,
|
||||||
BUNDLE_KEY_NEW_PASSWORD_IDS,
|
BUNDLE_KEY_NEW_PASSWORD_IDS,
|
||||||
AutofillId::class.java
|
AutofillId::class.java,
|
||||||
) ?: emptyList()
|
) ?: emptyList()
|
||||||
)
|
)
|
||||||
genericPassword.addAll(
|
genericPassword.addAll(
|
||||||
BundleCompat.getParcelableArrayList(
|
BundleCompat.getParcelableArrayList(
|
||||||
clientState,
|
clientState,
|
||||||
BUNDLE_KEY_GENERIC_PASSWORD_IDS,
|
BUNDLE_KEY_GENERIC_PASSWORD_IDS,
|
||||||
AutofillId::class.java
|
AutofillId::class.java,
|
||||||
) ?: emptyList()
|
) ?: emptyList()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -114,14 +114,14 @@ public sealed class AutofillScenario<out T : Any> {
|
||||||
fillUsername = fillUsername,
|
fillUsername = fillUsername,
|
||||||
otp = otp,
|
otp = otp,
|
||||||
currentPassword = currentPassword,
|
currentPassword = currentPassword,
|
||||||
newPassword = newPassword
|
newPassword = newPassword,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
GenericAutofillScenario(
|
GenericAutofillScenario(
|
||||||
username = username,
|
username = username,
|
||||||
fillUsername = fillUsername,
|
fillUsername = fillUsername,
|
||||||
otp = otp,
|
otp = otp,
|
||||||
genericPassword = genericPassword
|
genericPassword = genericPassword,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -193,7 +193,7 @@ internal data class ClassifiedAutofillScenario<T : Any>(
|
||||||
override val fillUsername: Boolean,
|
override val fillUsername: Boolean,
|
||||||
override val otp: T?,
|
override val otp: T?,
|
||||||
val currentPassword: List<T>,
|
val currentPassword: List<T>,
|
||||||
val newPassword: List<T>
|
val newPassword: List<T>,
|
||||||
) : AutofillScenario<T>() {
|
) : AutofillScenario<T>() {
|
||||||
|
|
||||||
override val allPasswordFields
|
override val allPasswordFields
|
||||||
|
@ -217,7 +217,7 @@ internal data class GenericAutofillScenario<T : Any>(
|
||||||
override val username: T?,
|
override val username: T?,
|
||||||
override val fillUsername: Boolean,
|
override val fillUsername: Boolean,
|
||||||
override val otp: T?,
|
override val otp: T?,
|
||||||
val genericPassword: List<T>
|
val genericPassword: List<T>,
|
||||||
) : AutofillScenario<T>() {
|
) : AutofillScenario<T>() {
|
||||||
|
|
||||||
override val allPasswordFields
|
override val allPasswordFields
|
||||||
|
@ -254,7 +254,7 @@ internal fun AutofillScenario<FormField>.passesOriginCheck(singleOriginMode: Boo
|
||||||
public fun Dataset.Builder.fillWith(
|
public fun Dataset.Builder.fillWith(
|
||||||
scenario: AutofillScenario<AutofillId>,
|
scenario: AutofillScenario<AutofillId>,
|
||||||
action: AutofillAction,
|
action: AutofillAction,
|
||||||
credentials: Credentials?
|
credentials: Credentials?,
|
||||||
) {
|
) {
|
||||||
val credentialsToFill = credentials ?: Credentials("USERNAME", "PASSWORD", "OTP")
|
val credentialsToFill = credentials ?: Credentials("USERNAME", "PASSWORD", "OTP")
|
||||||
for (field in scenario.fieldsToFillOn(action)) {
|
for (field in scenario.fieldsToFillOn(action)) {
|
||||||
|
@ -303,7 +303,7 @@ internal fun AutofillScenario<AutofillId>.toBundle(): Bundle =
|
||||||
putParcelable(AutofillScenario.BUNDLE_KEY_OTP_ID, otp)
|
putParcelable(AutofillScenario.BUNDLE_KEY_OTP_ID, otp)
|
||||||
putParcelableArrayList(
|
putParcelableArrayList(
|
||||||
AutofillScenario.BUNDLE_KEY_CURRENT_PASSWORD_IDS,
|
AutofillScenario.BUNDLE_KEY_CURRENT_PASSWORD_IDS,
|
||||||
ArrayList(currentPassword)
|
ArrayList(currentPassword),
|
||||||
)
|
)
|
||||||
putParcelableArrayList(AutofillScenario.BUNDLE_KEY_NEW_PASSWORD_IDS, ArrayList(newPassword))
|
putParcelableArrayList(AutofillScenario.BUNDLE_KEY_NEW_PASSWORD_IDS, ArrayList(newPassword))
|
||||||
}
|
}
|
||||||
|
@ -315,7 +315,7 @@ internal fun AutofillScenario<AutofillId>.toBundle(): Bundle =
|
||||||
putParcelable(AutofillScenario.BUNDLE_KEY_OTP_ID, otp)
|
putParcelable(AutofillScenario.BUNDLE_KEY_OTP_ID, otp)
|
||||||
putParcelableArrayList(
|
putParcelableArrayList(
|
||||||
AutofillScenario.BUNDLE_KEY_GENERIC_PASSWORD_IDS,
|
AutofillScenario.BUNDLE_KEY_GENERIC_PASSWORD_IDS,
|
||||||
ArrayList(genericPassword)
|
ArrayList(genericPassword),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -75,7 +75,7 @@ internal interface FieldMatcher {
|
||||||
@RequiresApi(Build.VERSION_CODES.O)
|
@RequiresApi(Build.VERSION_CODES.O)
|
||||||
internal class SingleFieldMatcher(
|
internal class SingleFieldMatcher(
|
||||||
private val take: (FormField, List<FormField>) -> Boolean,
|
private val take: (FormField, List<FormField>) -> Boolean,
|
||||||
private val tieBreakers: List<(FormField, List<FormField>) -> Boolean>
|
private val tieBreakers: List<(FormField, List<FormField>) -> Boolean>,
|
||||||
) : FieldMatcher {
|
) : FieldMatcher {
|
||||||
|
|
||||||
@AutofillDsl
|
@AutofillDsl
|
||||||
|
@ -100,7 +100,7 @@ internal class SingleFieldMatcher(
|
||||||
fun build() =
|
fun build() =
|
||||||
SingleFieldMatcher(
|
SingleFieldMatcher(
|
||||||
takeSingle ?: throw IllegalArgumentException("Every block needs a take{Single,Pair} block"),
|
takeSingle ?: throw IllegalArgumentException("Every block needs a take{Single,Pair} block"),
|
||||||
tieBreakersSingle
|
tieBreakersSingle,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -139,7 +139,7 @@ internal class SingleFieldMatcher(
|
||||||
@RequiresApi(Build.VERSION_CODES.O)
|
@RequiresApi(Build.VERSION_CODES.O)
|
||||||
private class PairOfFieldsMatcher(
|
private class PairOfFieldsMatcher(
|
||||||
private val take: (Pair<FormField, FormField>, List<FormField>) -> Boolean,
|
private val take: (Pair<FormField, FormField>, List<FormField>) -> Boolean,
|
||||||
private val tieBreakers: List<(Pair<FormField, FormField>, List<FormField>) -> Boolean>
|
private val tieBreakers: List<(Pair<FormField, FormField>, List<FormField>) -> Boolean>,
|
||||||
) : FieldMatcher {
|
) : FieldMatcher {
|
||||||
|
|
||||||
override fun match(fields: List<FormField>, alreadyMatched: List<FormField>): List<FormField>? {
|
override fun match(fields: List<FormField>, alreadyMatched: List<FormField>): List<FormField>? {
|
||||||
|
@ -180,14 +180,14 @@ private constructor(
|
||||||
private val matchers: List<AutofillRuleMatcher>,
|
private val matchers: List<AutofillRuleMatcher>,
|
||||||
private val applyInSingleOriginMode: Boolean,
|
private val applyInSingleOriginMode: Boolean,
|
||||||
private val applyOnManualRequestOnly: Boolean,
|
private val applyOnManualRequestOnly: Boolean,
|
||||||
private val name: String
|
private val name: String,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
data class AutofillRuleMatcher(
|
data class AutofillRuleMatcher(
|
||||||
val type: FillableFieldType,
|
val type: FillableFieldType,
|
||||||
val matcher: FieldMatcher,
|
val matcher: FieldMatcher,
|
||||||
val optional: Boolean,
|
val optional: Boolean,
|
||||||
val matchHidden: Boolean
|
val matchHidden: Boolean,
|
||||||
)
|
)
|
||||||
|
|
||||||
enum class FillableFieldType {
|
enum class FillableFieldType {
|
||||||
|
@ -201,7 +201,7 @@ private constructor(
|
||||||
@AutofillDsl
|
@AutofillDsl
|
||||||
class Builder(
|
class Builder(
|
||||||
private val applyInSingleOriginMode: Boolean,
|
private val applyInSingleOriginMode: Boolean,
|
||||||
private val applyOnManualRequestOnly: Boolean
|
private val applyOnManualRequestOnly: Boolean,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
@ -215,7 +215,7 @@ private constructor(
|
||||||
fun username(
|
fun username(
|
||||||
optional: Boolean = false,
|
optional: Boolean = false,
|
||||||
matchHidden: Boolean = false,
|
matchHidden: Boolean = false,
|
||||||
block: SingleFieldMatcher.Builder.() -> Unit
|
block: SingleFieldMatcher.Builder.() -> Unit,
|
||||||
) {
|
) {
|
||||||
require(matchers.none { it.type == FillableFieldType.Username }) {
|
require(matchers.none { it.type == FillableFieldType.Username }) {
|
||||||
"Every rule block can only have at most one username block"
|
"Every rule block can only have at most one username block"
|
||||||
|
@ -225,7 +225,7 @@ private constructor(
|
||||||
type = FillableFieldType.Username,
|
type = FillableFieldType.Username,
|
||||||
matcher = SingleFieldMatcher.Builder().apply(block).build(),
|
matcher = SingleFieldMatcher.Builder().apply(block).build(),
|
||||||
optional = optional,
|
optional = optional,
|
||||||
matchHidden = matchHidden
|
matchHidden = matchHidden,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -239,7 +239,7 @@ private constructor(
|
||||||
type = FillableFieldType.Otp,
|
type = FillableFieldType.Otp,
|
||||||
matcher = SingleFieldMatcher.Builder().apply(block).build(),
|
matcher = SingleFieldMatcher.Builder().apply(block).build(),
|
||||||
optional = optional,
|
optional = optional,
|
||||||
matchHidden = false
|
matchHidden = false,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -247,7 +247,7 @@ private constructor(
|
||||||
fun currentPassword(
|
fun currentPassword(
|
||||||
optional: Boolean = false,
|
optional: Boolean = false,
|
||||||
matchHidden: Boolean = false,
|
matchHidden: Boolean = false,
|
||||||
block: FieldMatcher.Builder.() -> Unit
|
block: FieldMatcher.Builder.() -> Unit,
|
||||||
) {
|
) {
|
||||||
require(matchers.none { it.type == FillableFieldType.GenericPassword }) {
|
require(matchers.none { it.type == FillableFieldType.GenericPassword }) {
|
||||||
"Every rule block can only have either genericPassword or {current,new}Password blocks"
|
"Every rule block can only have either genericPassword or {current,new}Password blocks"
|
||||||
|
@ -257,7 +257,7 @@ private constructor(
|
||||||
type = FillableFieldType.CurrentPassword,
|
type = FillableFieldType.CurrentPassword,
|
||||||
matcher = FieldMatcher.Builder().apply(block).build(),
|
matcher = FieldMatcher.Builder().apply(block).build(),
|
||||||
optional = optional,
|
optional = optional,
|
||||||
matchHidden = matchHidden
|
matchHidden = matchHidden,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -271,7 +271,7 @@ private constructor(
|
||||||
type = FillableFieldType.NewPassword,
|
type = FillableFieldType.NewPassword,
|
||||||
matcher = FieldMatcher.Builder().apply(block).build(),
|
matcher = FieldMatcher.Builder().apply(block).build(),
|
||||||
optional = optional,
|
optional = optional,
|
||||||
matchHidden = false
|
matchHidden = false,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -279,11 +279,7 @@ private constructor(
|
||||||
fun genericPassword(optional: Boolean = false, block: FieldMatcher.Builder.() -> Unit) {
|
fun genericPassword(optional: Boolean = false, block: FieldMatcher.Builder.() -> Unit) {
|
||||||
require(
|
require(
|
||||||
matchers.none {
|
matchers.none {
|
||||||
it.type in
|
it.type in listOf(FillableFieldType.CurrentPassword, FillableFieldType.NewPassword)
|
||||||
listOf(
|
|
||||||
FillableFieldType.CurrentPassword,
|
|
||||||
FillableFieldType.NewPassword,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
"Every rule block can only have either genericPassword or {current,new}Password blocks"
|
"Every rule block can only have either genericPassword or {current,new}Password blocks"
|
||||||
|
@ -293,7 +289,7 @@ private constructor(
|
||||||
type = FillableFieldType.GenericPassword,
|
type = FillableFieldType.GenericPassword,
|
||||||
matcher = FieldMatcher.Builder().apply(block).build(),
|
matcher = FieldMatcher.Builder().apply(block).build(),
|
||||||
optional = optional,
|
optional = optional,
|
||||||
matchHidden = false
|
matchHidden = false,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -314,7 +310,7 @@ private constructor(
|
||||||
matchers,
|
matchers,
|
||||||
applyInSingleOriginMode,
|
applyInSingleOriginMode,
|
||||||
applyOnManualRequestOnly,
|
applyOnManualRequestOnly,
|
||||||
name ?: "Rule #$ruleId"
|
name ?: "Rule #$ruleId",
|
||||||
)
|
)
|
||||||
.also { ruleId++ }
|
.also { ruleId++ }
|
||||||
}
|
}
|
||||||
|
@ -325,7 +321,7 @@ private constructor(
|
||||||
allUsername: List<FormField>,
|
allUsername: List<FormField>,
|
||||||
allOtp: List<FormField>,
|
allOtp: List<FormField>,
|
||||||
singleOriginMode: Boolean,
|
singleOriginMode: Boolean,
|
||||||
isManualRequest: Boolean
|
isManualRequest: Boolean,
|
||||||
): AutofillScenario<FormField>? {
|
): AutofillScenario<FormField>? {
|
||||||
if (singleOriginMode && !applyInSingleOriginMode) {
|
if (singleOriginMode && !applyInSingleOriginMode) {
|
||||||
logcat { "$name: Skipped in single origin mode" }
|
logcat { "$name: Skipped in single origin mode" }
|
||||||
|
@ -399,12 +395,12 @@ internal class AutofillStrategy private constructor(private val rules: List<Auto
|
||||||
fun rule(
|
fun rule(
|
||||||
applyInSingleOriginMode: Boolean = false,
|
applyInSingleOriginMode: Boolean = false,
|
||||||
applyOnManualRequestOnly: Boolean = false,
|
applyOnManualRequestOnly: Boolean = false,
|
||||||
block: AutofillRule.Builder.() -> Unit
|
block: AutofillRule.Builder.() -> Unit,
|
||||||
) {
|
) {
|
||||||
rules.add(
|
rules.add(
|
||||||
AutofillRule.Builder(
|
AutofillRule.Builder(
|
||||||
applyInSingleOriginMode = applyInSingleOriginMode,
|
applyInSingleOriginMode = applyInSingleOriginMode,
|
||||||
applyOnManualRequestOnly = applyOnManualRequestOnly
|
applyOnManualRequestOnly = applyOnManualRequestOnly,
|
||||||
)
|
)
|
||||||
.apply(block)
|
.apply(block)
|
||||||
.build()
|
.build()
|
||||||
|
@ -417,7 +413,7 @@ internal class AutofillStrategy private constructor(private val rules: List<Auto
|
||||||
fun match(
|
fun match(
|
||||||
fields: List<FormField>,
|
fields: List<FormField>,
|
||||||
singleOriginMode: Boolean,
|
singleOriginMode: Boolean,
|
||||||
isManualRequest: Boolean
|
isManualRequest: Boolean,
|
||||||
): AutofillScenario<FormField>? {
|
): AutofillScenario<FormField>? {
|
||||||
val possiblePasswordFields = fields.filter { it.passwordCertainty >= CertaintyLevel.Possible }
|
val possiblePasswordFields = fields.filter { it.passwordCertainty >= CertaintyLevel.Possible }
|
||||||
logcat { "Possible password fields: ${possiblePasswordFields.size}" }
|
logcat { "Possible password fields: ${possiblePasswordFields.size}" }
|
||||||
|
@ -433,7 +429,7 @@ internal class AutofillStrategy private constructor(private val rules: List<Auto
|
||||||
possibleUsernameFields,
|
possibleUsernameFields,
|
||||||
possibleOtpFields,
|
possibleOtpFields,
|
||||||
singleOriginMode = singleOriginMode,
|
singleOriginMode = singleOriginMode,
|
||||||
isManualRequest = isManualRequest
|
isManualRequest = isManualRequest,
|
||||||
) ?: continue
|
) ?: continue
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
|
|
|
@ -59,7 +59,7 @@ private val TRUSTED_BROWSER_CERTIFICATE_HASH =
|
||||||
"com.duckduckgo.mobile.android" to
|
"com.duckduckgo.mobile.android" to
|
||||||
arrayOf(
|
arrayOf(
|
||||||
"u3uzHFc8RqHaf8XFKKas9DIQhFb+7FCBDH8zaU6z0tQ=",
|
"u3uzHFc8RqHaf8XFKKas9DIQhFb+7FCBDH8zaU6z0tQ=",
|
||||||
"8HB9AhwL8+b43MEbo/VwBCXVl9yjAaMeIQVWk067Gwo="
|
"8HB9AhwL8+b43MEbo/VwBCXVl9yjAaMeIQVWk067Gwo=",
|
||||||
),
|
),
|
||||||
"com.jamal2367.styx" to arrayOf("Lph3oaG1C8WLhLiK5PVxOp5+6wTU9ipJSBYlD2bA3VI="),
|
"com.jamal2367.styx" to arrayOf("Lph3oaG1C8WLhLiK5PVxOp5+6wTU9ipJSBYlD2bA3VI="),
|
||||||
"com.microsoft.emmx" to arrayOf("AeGZlxCoLCdJtNUMRF3IXWcLYTYInQp2anOCfIKh6sk="),
|
"com.microsoft.emmx" to arrayOf("AeGZlxCoLCdJtNUMRF3IXWcLYTYInQp2anOCfIKh6sk="),
|
||||||
|
@ -85,7 +85,7 @@ private val TRUSTED_BROWSER_CERTIFICATE_HASH =
|
||||||
"us.spotco.fennec_dos" to
|
"us.spotco.fennec_dos" to
|
||||||
arrayOf(
|
arrayOf(
|
||||||
"Jg4KSWeMeLcMAtZTet07bcChcXG73oznX9QCaoo+GNI=",
|
"Jg4KSWeMeLcMAtZTet07bcChcXG73oznX9QCaoo+GNI=",
|
||||||
"/4H1vlY5ZZTu5w/vKDIlbhUhQSLiupzt0mAF/9S8qqg="
|
"/4H1vlY5ZZTu5w/vKDIlbhUhQSLiupzt0mAF/9S8qqg=",
|
||||||
),
|
),
|
||||||
"com.vivaldi.browser" to arrayOf("6KeFRGVbqMCYF/cydo9WibFmLsSyvFoLwOwTjTPKPR4="),
|
"com.vivaldi.browser" to arrayOf("6KeFRGVbqMCYF/cydo9WibFmLsSyvFoLwOwTjTPKPR4="),
|
||||||
"app.vanadium.browser" to arrayOf("xq24uDxtTBfSkq/eVv1IilHTFv+PLBHFQQIjv/in27M="),
|
"app.vanadium.browser" to arrayOf("xq24uDxtTBfSkq/eVv1IilHTFv+PLBHFQQIjv/in27M="),
|
||||||
|
@ -100,7 +100,7 @@ private fun isTrustedBrowser(context: Context, appPackage: String): Boolean {
|
||||||
internal enum class BrowserMultiOriginMethod {
|
internal enum class BrowserMultiOriginMethod {
|
||||||
None,
|
None,
|
||||||
WebView,
|
WebView,
|
||||||
Field
|
Field,
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -173,7 +173,7 @@ private fun isNoAccessibilityServiceEnabled(context: Context): Boolean {
|
||||||
// See https://chromium.googlesource.com/chromium/src/+/447a31e977a65e2eb78804e4a09633699b4ede33
|
// See https://chromium.googlesource.com/chromium/src/+/447a31e977a65e2eb78804e4a09633699b4ede33
|
||||||
return Settings.Secure.getString(
|
return Settings.Secure.getString(
|
||||||
context.contentResolver,
|
context.contentResolver,
|
||||||
Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES
|
Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
|
||||||
)
|
)
|
||||||
.isNullOrEmpty()
|
.isNullOrEmpty()
|
||||||
}
|
}
|
||||||
|
@ -187,25 +187,22 @@ private fun getBrowserSaveFlag(context: Context, appPackage: String): Int? =
|
||||||
|
|
||||||
internal data class BrowserAutofillSupportInfo(
|
internal data class BrowserAutofillSupportInfo(
|
||||||
val multiOriginMethod: BrowserMultiOriginMethod,
|
val multiOriginMethod: BrowserMultiOriginMethod,
|
||||||
val saveFlags: Int?
|
val saveFlags: Int?,
|
||||||
)
|
)
|
||||||
|
|
||||||
@RequiresApi(Build.VERSION_CODES.O)
|
@RequiresApi(Build.VERSION_CODES.O)
|
||||||
internal fun getBrowserAutofillSupportInfoIfTrusted(
|
internal fun getBrowserAutofillSupportInfoIfTrusted(
|
||||||
context: Context,
|
context: Context,
|
||||||
appPackage: String
|
appPackage: String,
|
||||||
): BrowserAutofillSupportInfo? {
|
): BrowserAutofillSupportInfo? {
|
||||||
if (!isTrustedBrowser(context, appPackage)) return null
|
if (!isTrustedBrowser(context, appPackage)) return null
|
||||||
return BrowserAutofillSupportInfo(
|
return BrowserAutofillSupportInfo(
|
||||||
multiOriginMethod = getBrowserMultiOriginMethod(appPackage),
|
multiOriginMethod = getBrowserMultiOriginMethod(appPackage),
|
||||||
saveFlags = getBrowserSaveFlag(context, appPackage)
|
saveFlags = getBrowserSaveFlag(context, appPackage),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val FLAKY_BROWSERS =
|
private val FLAKY_BROWSERS = listOf("com.kiwibrowser.browser")
|
||||||
listOf(
|
|
||||||
"com.kiwibrowser.browser",
|
|
||||||
)
|
|
||||||
|
|
||||||
public enum class BrowserAutofillSupportLevel {
|
public enum class BrowserAutofillSupportLevel {
|
||||||
None,
|
None,
|
||||||
|
@ -219,7 +216,7 @@ public enum class BrowserAutofillSupportLevel {
|
||||||
@RequiresApi(Build.VERSION_CODES.O)
|
@RequiresApi(Build.VERSION_CODES.O)
|
||||||
private fun getBrowserAutofillSupportLevel(
|
private fun getBrowserAutofillSupportLevel(
|
||||||
context: Context,
|
context: Context,
|
||||||
appPackage: String
|
appPackage: String,
|
||||||
): BrowserAutofillSupportLevel {
|
): BrowserAutofillSupportLevel {
|
||||||
val browserInfo = getBrowserAutofillSupportInfoIfTrusted(context, appPackage)
|
val browserInfo = getBrowserAutofillSupportInfoIfTrusted(context, appPackage)
|
||||||
return when {
|
return when {
|
||||||
|
@ -252,7 +249,7 @@ public fun getInstalledBrowsersWithAutofillSupportLevel(
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
context.packageManager.queryIntentActivities(
|
context.packageManager.queryIntentActivities(
|
||||||
testWebIntent,
|
testWebIntent,
|
||||||
ResolveInfoFlags.of(PackageManager.MATCH_ALL.toLong())
|
ResolveInfoFlags.of(PackageManager.MATCH_ALL.toLong()),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
context.packageManager.queryIntentActivities(testWebIntent, PackageManager.MATCH_ALL)
|
context.packageManager.queryIntentActivities(testWebIntent, PackageManager.MATCH_ALL)
|
||||||
|
|
|
@ -17,7 +17,7 @@ internal enum class CertaintyLevel {
|
||||||
Impossible,
|
Impossible,
|
||||||
Possible,
|
Possible,
|
||||||
Likely,
|
Likely,
|
||||||
Certain
|
Certain,
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -29,31 +29,19 @@ internal class FormField(
|
||||||
node: AssistStructure.ViewNode,
|
node: AssistStructure.ViewNode,
|
||||||
private val index: Int,
|
private val index: Int,
|
||||||
passDownWebViewOrigins: Boolean,
|
passDownWebViewOrigins: Boolean,
|
||||||
passedDownWebOrigin: String? = null
|
passedDownWebOrigin: String? = null,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
private val HINTS_USERNAME =
|
private val HINTS_USERNAME =
|
||||||
listOf(
|
listOf(HintConstants.AUTOFILL_HINT_USERNAME, HintConstants.AUTOFILL_HINT_NEW_USERNAME)
|
||||||
HintConstants.AUTOFILL_HINT_USERNAME,
|
private val HINTS_NEW_PASSWORD = listOf(HintConstants.AUTOFILL_HINT_NEW_PASSWORD)
|
||||||
HintConstants.AUTOFILL_HINT_NEW_USERNAME,
|
|
||||||
)
|
|
||||||
private val HINTS_NEW_PASSWORD =
|
|
||||||
listOf(
|
|
||||||
HintConstants.AUTOFILL_HINT_NEW_PASSWORD,
|
|
||||||
)
|
|
||||||
private val HINTS_PASSWORD =
|
private val HINTS_PASSWORD =
|
||||||
HINTS_NEW_PASSWORD +
|
HINTS_NEW_PASSWORD +
|
||||||
listOf(
|
listOf(HintConstants.AUTOFILL_HINT_PASSWORD, HintConstants.AUTOFILL_HINT_WIFI_PASSWORD)
|
||||||
HintConstants.AUTOFILL_HINT_PASSWORD,
|
|
||||||
HintConstants.AUTOFILL_HINT_WIFI_PASSWORD,
|
|
||||||
)
|
|
||||||
private val HINTS_OTP =
|
private val HINTS_OTP =
|
||||||
listOf(
|
listOf(HintConstants.AUTOFILL_HINT_SMS_OTP, HintConstants.AUTOFILL_HINT_2FA_APP_OTP)
|
||||||
HintConstants.AUTOFILL_HINT_SMS_OTP,
|
|
||||||
HintConstants.AUTOFILL_HINT_2FA_APP_OTP,
|
|
||||||
)
|
|
||||||
|
|
||||||
@Suppress("DEPRECATION")
|
@Suppress("DEPRECATION")
|
||||||
private val HINTS_FILLABLE =
|
private val HINTS_FILLABLE =
|
||||||
|
@ -93,21 +81,9 @@ internal class FormField(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val HTML_INPUT_FIELD_TYPES_USERNAME =
|
private val HTML_INPUT_FIELD_TYPES_USERNAME = listOf("email", "tel", "text")
|
||||||
listOf(
|
private val HTML_INPUT_FIELD_TYPES_PASSWORD = listOf("password")
|
||||||
"email",
|
private val HTML_INPUT_FIELD_TYPES_OTP = listOf("tel", "text")
|
||||||
"tel",
|
|
||||||
"text",
|
|
||||||
)
|
|
||||||
private val HTML_INPUT_FIELD_TYPES_PASSWORD =
|
|
||||||
listOf(
|
|
||||||
"password",
|
|
||||||
)
|
|
||||||
private val HTML_INPUT_FIELD_TYPES_OTP =
|
|
||||||
listOf(
|
|
||||||
"tel",
|
|
||||||
"text",
|
|
||||||
)
|
|
||||||
private val HTML_INPUT_FIELD_TYPES_FILLABLE =
|
private val HTML_INPUT_FIELD_TYPES_FILLABLE =
|
||||||
(HTML_INPUT_FIELD_TYPES_USERNAME +
|
(HTML_INPUT_FIELD_TYPES_USERNAME +
|
||||||
HTML_INPUT_FIELD_TYPES_PASSWORD +
|
HTML_INPUT_FIELD_TYPES_PASSWORD +
|
||||||
|
@ -128,33 +104,11 @@ internal class FormField(
|
||||||
"captcha",
|
"captcha",
|
||||||
"postal", // Prevent postal code fields from being mistaken for OTP fields
|
"postal", // Prevent postal code fields from being mistaken for OTP fields
|
||||||
)
|
)
|
||||||
private val PASSWORD_HEURISTIC_TERMS =
|
private val PASSWORD_HEURISTIC_TERMS = listOf("pass", "pswd", "pwd")
|
||||||
listOf(
|
|
||||||
"pass",
|
|
||||||
"pswd",
|
|
||||||
"pwd",
|
|
||||||
)
|
|
||||||
private val USERNAME_HEURISTIC_TERMS =
|
private val USERNAME_HEURISTIC_TERMS =
|
||||||
listOf(
|
listOf("alias", "benutzername", "e-mail", "email", "mail", "login", "user")
|
||||||
"alias",
|
private val OTP_HEURISTIC_TERMS = listOf("einmal", "otp", "challenge", "verification")
|
||||||
"benutzername",
|
private val OTP_WEAK_HEURISTIC_TERMS = listOf("code")
|
||||||
"e-mail",
|
|
||||||
"email",
|
|
||||||
"mail",
|
|
||||||
"login",
|
|
||||||
"user",
|
|
||||||
)
|
|
||||||
private val OTP_HEURISTIC_TERMS =
|
|
||||||
listOf(
|
|
||||||
"einmal",
|
|
||||||
"otp",
|
|
||||||
"challenge",
|
|
||||||
"verification",
|
|
||||||
)
|
|
||||||
private val OTP_WEAK_HEURISTIC_TERMS =
|
|
||||||
listOf(
|
|
||||||
"code",
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private val List<String>.anyMatchesFieldInfo
|
private val List<String>.anyMatchesFieldInfo
|
||||||
|
|
|
@ -39,7 +39,7 @@ public fun cachePublicSuffixList(context: Context) {
|
||||||
internal fun getPublicSuffixPlusOne(
|
internal fun getPublicSuffixPlusOne(
|
||||||
context: Context,
|
context: Context,
|
||||||
domain: String,
|
domain: String,
|
||||||
customSuffixes: Sequence<String>
|
customSuffixes: Sequence<String>,
|
||||||
) = runBlocking {
|
) = runBlocking {
|
||||||
// We only feed valid domain names which are not IP addresses into getPublicSuffixPlusOne.
|
// We only feed valid domain names which are not IP addresses into getPublicSuffixPlusOne.
|
||||||
// We do not check whether the domain actually exists (actually, not even whether its TLD
|
// We do not check whether the domain actually exists (actually, not even whether its TLD
|
||||||
|
@ -77,7 +77,7 @@ private fun getSuffixPlusUpToOne(domain: String, suffix: String): String? {
|
||||||
private suspend fun getCanonicalSuffix(
|
private suspend fun getCanonicalSuffix(
|
||||||
context: Context,
|
context: Context,
|
||||||
domain: String,
|
domain: String,
|
||||||
customSuffixes: Sequence<String>
|
customSuffixes: Sequence<String>,
|
||||||
): String {
|
): String {
|
||||||
val publicSuffixList = PublicSuffixListCache.getOrCachePublicSuffixList(context)
|
val publicSuffixList = PublicSuffixListCache.getOrCachePublicSuffixList(context)
|
||||||
val publicSuffixPlusOne =
|
val publicSuffixPlusOne =
|
||||||
|
|
|
@ -29,7 +29,7 @@ import kotlinx.coroutines.async
|
||||||
internal class PublicSuffixList(
|
internal class PublicSuffixList(
|
||||||
context: Context,
|
context: Context,
|
||||||
dispatcher: CoroutineDispatcher = Dispatchers.IO,
|
dispatcher: CoroutineDispatcher = Dispatchers.IO,
|
||||||
private val scope: CoroutineScope = CoroutineScope(dispatcher)
|
private val scope: CoroutineScope = CoroutineScope(dispatcher),
|
||||||
) {
|
) {
|
||||||
|
|
||||||
private val data: PublicSuffixListData by
|
private val data: PublicSuffixListData by
|
||||||
|
|
|
@ -14,7 +14,7 @@ import mozilla.components.lib.publicsuffixlist.ext.binarySearch
|
||||||
/** Class wrapping the public suffix list data and offering methods for accessing rules in it. */
|
/** Class wrapping the public suffix list data and offering methods for accessing rules in it. */
|
||||||
internal class PublicSuffixListData(
|
internal class PublicSuffixListData(
|
||||||
private val rules: ByteArray,
|
private val rules: ByteArray,
|
||||||
private val exceptions: ByteArray
|
private val exceptions: ByteArray,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
private fun binarySearchRules(labels: List<ByteArray>, labelIndex: Int): String? {
|
private fun binarySearchRules(labels: List<ByteArray>, labelIndex: Int): String? {
|
||||||
|
|
|
@ -41,10 +41,7 @@ class KotlinCommonPlugin : Plugin<Project> {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val ADDITIONAL_COMPILER_ARGS =
|
private val ADDITIONAL_COMPILER_ARGS =
|
||||||
listOf(
|
listOf("-opt-in=kotlin.RequiresOptIn", "-Xsuppress-version-warnings")
|
||||||
"-opt-in=kotlin.RequiresOptIn",
|
|
||||||
"-Xsuppress-version-warnings",
|
|
||||||
)
|
|
||||||
|
|
||||||
val JVM_TOOLCHAIN_ACTION =
|
val JVM_TOOLCHAIN_ACTION =
|
||||||
Action<JavaToolchainSpec> { languageVersion.set(JavaLanguageVersion.of(17)) }
|
Action<JavaToolchainSpec> { languageVersion.set(JavaLanguageVersion.of(17)) }
|
||||||
|
|
|
@ -62,10 +62,7 @@ class CrowdinDownloadPlugin : Plugin<Project> {
|
||||||
if (extension.skipCleanup.getOrElse(false)) {
|
if (extension.skipCleanup.getOrElse(false)) {
|
||||||
emptySet()
|
emptySet()
|
||||||
} else {
|
} else {
|
||||||
setOf(
|
setOf(extractStrings.map { it.source }, downloadCrowdin.map { it.outputFiles })
|
||||||
extractStrings.map { it.source },
|
|
||||||
downloadCrowdin.map { it.outputFiles },
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,7 +57,7 @@ abstract class KtfmtCheckTask : SourceTask() {
|
||||||
maxWidth = 100,
|
maxWidth = 100,
|
||||||
continuationIndent = 2,
|
continuationIndent = 2,
|
||||||
),
|
),
|
||||||
originCode
|
originCode,
|
||||||
)
|
)
|
||||||
val pathNormalizer = { file: File -> file.toRelativeString(projectDirectory.asFile.get()) }
|
val pathNormalizer = { file: File -> file.toRelativeString(projectDirectory.asFile.get()) }
|
||||||
return (originCode != formattedCode) to
|
return (originCode != formattedCode) to
|
||||||
|
|
|
@ -10,7 +10,7 @@ object KtfmtDiffer {
|
||||||
fun computeDiff(
|
fun computeDiff(
|
||||||
inputFile: File,
|
inputFile: File,
|
||||||
formattedCode: String,
|
formattedCode: String,
|
||||||
pathNormalizer: (File) -> String
|
pathNormalizer: (File) -> String,
|
||||||
): List<KtfmtDiffEntry> {
|
): List<KtfmtDiffEntry> {
|
||||||
val originCode = inputFile.readText()
|
val originCode = inputFile.readText()
|
||||||
return DiffUtils.diff(originCode, formattedCode, null).deltas.map {
|
return DiffUtils.diff(originCode, formattedCode, null).deltas.map {
|
||||||
|
|
|
@ -11,10 +11,8 @@ import org.jetbrains.kotlin.utils.addToStdlib.ifNotEmpty
|
||||||
|
|
||||||
abstract class KtfmtFormatTask
|
abstract class KtfmtFormatTask
|
||||||
@Inject
|
@Inject
|
||||||
constructor(
|
constructor(private val workerExecutor: WorkerExecutor, private val projectLayout: ProjectLayout) :
|
||||||
private val workerExecutor: WorkerExecutor,
|
SourceTask() {
|
||||||
private val projectLayout: ProjectLayout,
|
|
||||||
) : SourceTask() {
|
|
||||||
|
|
||||||
@TaskAction
|
@TaskAction
|
||||||
fun execute() {
|
fun execute() {
|
||||||
|
|
|
@ -104,7 +104,7 @@ abstract class PSLUpdateTask : DefaultTask() {
|
||||||
var totalRuleBytes: Int = 0,
|
var totalRuleBytes: Int = 0,
|
||||||
var totalExceptionRuleBytes: Int = 0,
|
var totalExceptionRuleBytes: Int = 0,
|
||||||
val sortedRules: TreeSet<ByteString> = TreeSet(),
|
val sortedRules: TreeSet<ByteString> = TreeSet(),
|
||||||
val sortedExceptionRules: TreeSet<ByteString> = TreeSet()
|
val sortedExceptionRules: TreeSet<ByteString> = TreeSet(),
|
||||||
)
|
)
|
||||||
|
|
||||||
private companion object {
|
private companion object {
|
||||||
|
|
|
@ -48,7 +48,7 @@ abstract class GitHooks : DefaultTask() {
|
||||||
GROUP_EXECUTE,
|
GROUP_EXECUTE,
|
||||||
OTHERS_READ,
|
OTHERS_READ,
|
||||||
OTHERS_EXECUTE,
|
OTHERS_EXECUTE,
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,8 @@
|
||||||
package app.passwordstore.crypto
|
package app.passwordstore.crypto
|
||||||
|
|
||||||
/** [CryptoOptions] implementation for PGPainless decrypt operations. */
|
/** [CryptoOptions] implementation for PGPainless decrypt operations. */
|
||||||
public class PGPDecryptOptions
|
public class PGPDecryptOptions private constructor(private val values: Map<String, Boolean>) :
|
||||||
private constructor(
|
CryptoOptions {
|
||||||
private val values: Map<String, Boolean>,
|
|
||||||
) : CryptoOptions {
|
|
||||||
|
|
||||||
override fun isOptionEnabled(option: String): Boolean {
|
override fun isOptionEnabled(option: String): Boolean {
|
||||||
return values.getOrDefault(option, false)
|
return values.getOrDefault(option, false)
|
||||||
|
|
|
@ -1,10 +1,8 @@
|
||||||
package app.passwordstore.crypto
|
package app.passwordstore.crypto
|
||||||
|
|
||||||
/** [CryptoOptions] implementation for PGPainless encrypt operations. */
|
/** [CryptoOptions] implementation for PGPainless encrypt operations. */
|
||||||
public class PGPEncryptOptions
|
public class PGPEncryptOptions private constructor(private val values: Map<String, Boolean>) :
|
||||||
private constructor(
|
CryptoOptions {
|
||||||
private val values: Map<String, Boolean>,
|
|
||||||
) : CryptoOptions {
|
|
||||||
|
|
||||||
internal companion object {
|
internal companion object {
|
||||||
const val ASCII_ARMOR = "ASCII_ARMOR"
|
const val ASCII_ARMOR = "ASCII_ARMOR"
|
||||||
|
|
|
@ -31,10 +31,8 @@ import org.pgpainless.util.selection.userid.SelectUserId
|
||||||
|
|
||||||
public class PGPKeyManager
|
public class PGPKeyManager
|
||||||
@Inject
|
@Inject
|
||||||
constructor(
|
constructor(filesDir: String, private val dispatcher: CoroutineDispatcher) :
|
||||||
filesDir: String,
|
KeyManager<PGPKey, PGPIdentifier> {
|
||||||
private val dispatcher: CoroutineDispatcher,
|
|
||||||
) : KeyManager<PGPKey, PGPIdentifier> {
|
|
||||||
|
|
||||||
private val keyDir = File(filesDir, KEY_DIR_NAME)
|
private val keyDir = File(filesDir, KEY_DIR_NAME)
|
||||||
|
|
||||||
|
|
|
@ -217,10 +217,7 @@ class PGPKeyManagerTest {
|
||||||
KeyId(-961222705095032109), // Bobby
|
KeyId(-961222705095032109), // Bobby
|
||||||
)
|
)
|
||||||
val userIds =
|
val userIds =
|
||||||
arrayOf(
|
arrayOf(UserId("Alice <owner@example.com>"), UserId("Bobby <owner@example.com>"))
|
||||||
UserId("Alice <owner@example.com>"),
|
|
||||||
UserId("Bobby <owner@example.com>"),
|
|
||||||
)
|
|
||||||
|
|
||||||
for (idCollection in arrayOf(longKeyIds, userIds)) {
|
for (idCollection in arrayOf(longKeyIds, userIds)) {
|
||||||
val alice1 = keyManager.getKeyById(idCollection[0])
|
val alice1 = keyManager.getKeyById(idCollection[0])
|
||||||
|
|
|
@ -205,7 +205,7 @@ constructor(
|
||||||
millis / (THOUSAND_MILLIS * totpPeriod),
|
millis / (THOUSAND_MILLIS * totpPeriod),
|
||||||
totpAlgorithm,
|
totpAlgorithm,
|
||||||
digits,
|
digits,
|
||||||
issuer
|
issuer,
|
||||||
)
|
)
|
||||||
.mapBoth({ code -> Ok(Totp(code, remainingTime)) }, ::Err)
|
.mapBoth({ code -> Ok(Totp(code, remainingTime)) }, ::Err)
|
||||||
}
|
}
|
||||||
|
@ -233,12 +233,7 @@ constructor(
|
||||||
"identity:",
|
"identity:",
|
||||||
)
|
)
|
||||||
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
|
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
|
||||||
public val PASSWORD_FIELDS: Array<String> =
|
public val PASSWORD_FIELDS: Array<String> = arrayOf("password:", "secret:", "pass:")
|
||||||
arrayOf(
|
|
||||||
"password:",
|
|
||||||
"secret:",
|
|
||||||
"pass:",
|
|
||||||
)
|
|
||||||
private const val THOUSAND_MILLIS = 1000L
|
private const val THOUSAND_MILLIS = 1000L
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,11 +26,7 @@ class PasswordEntryTest {
|
||||||
private val totpFinder = UriTotpFinder()
|
private val totpFinder = UriTotpFinder()
|
||||||
|
|
||||||
private fun makeEntry(content: String, clock: UserClock = fakeClock) =
|
private fun makeEntry(content: String, clock: UserClock = fakeClock) =
|
||||||
PasswordEntry(
|
PasswordEntry(clock, totpFinder, content.encodeToByteArray())
|
||||||
clock,
|
|
||||||
totpFinder,
|
|
||||||
content.encodeToByteArray(),
|
|
||||||
)
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun getPassword() {
|
fun getPassword() {
|
||||||
|
@ -62,7 +58,7 @@ class PasswordEntryTest {
|
||||||
assertEquals("blubb", makeEntry("password: foo\nblubb").extraContentString)
|
assertEquals("blubb", makeEntry("password: foo\nblubb").extraContentString)
|
||||||
assertEquals(
|
assertEquals(
|
||||||
"blubb\nusername: bar",
|
"blubb\nusername: bar",
|
||||||
makeEntry("blubb\npassword: foo\nusername: bar").extraContentString
|
makeEntry("blubb\npassword: foo\nusername: bar").extraContentString,
|
||||||
)
|
)
|
||||||
assertEquals("", makeEntry("\n").extraContentString)
|
assertEquals("", makeEntry("\n").extraContentString)
|
||||||
assertEquals("", makeEntry("").extraContentString)
|
assertEquals("", makeEntry("").extraContentString)
|
||||||
|
@ -107,7 +103,7 @@ class PasswordEntryTest {
|
||||||
assertEquals("username", makeEntry("\n$field username").username)
|
assertEquals("username", makeEntry("\n$field username").username)
|
||||||
assertEquals(
|
assertEquals(
|
||||||
"username",
|
"username",
|
||||||
makeEntry("\n${field.uppercase(Locale.getDefault())} username").username
|
makeEntry("\n${field.uppercase(Locale.getDefault())} username").username,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
assertEquals("username", makeEntry("secret\nextra\nlogin: username\ncontent\n").username)
|
assertEquals("username", makeEntry("secret\nextra\nlogin: username\ncontent\n").username)
|
||||||
|
|
|
@ -25,103 +25,36 @@ class OtpTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun otpGeneration6Digits() {
|
fun otpGeneration6Digits() {
|
||||||
assertEquals(
|
assertEquals("953550", generateOtp(counter = 1593333298159 / (1000 * 30)))
|
||||||
"953550",
|
assertEquals("275379", generateOtp(counter = 1593333571918 / (1000 * 30)))
|
||||||
generateOtp(
|
assertEquals("867507", generateOtp(counter = 1593333600517 / (1000 * 57)))
|
||||||
counter = 1593333298159 / (1000 * 30),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
assertEquals(
|
|
||||||
"275379",
|
|
||||||
generateOtp(
|
|
||||||
counter = 1593333571918 / (1000 * 30),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
assertEquals(
|
|
||||||
"867507",
|
|
||||||
generateOtp(
|
|
||||||
counter = 1593333600517 / (1000 * 57),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun otpGeneration10Digits() {
|
fun otpGeneration10Digits() {
|
||||||
assertEquals(
|
assertEquals("0740900914", generateOtp(counter = 1593333655044 / (1000 * 30), digits = "10"))
|
||||||
"0740900914",
|
assertEquals("0070632029", generateOtp(counter = 1593333691405 / (1000 * 30), digits = "10"))
|
||||||
generateOtp(
|
assertEquals("1017265882", generateOtp(counter = 1593333728893 / (1000 * 83), digits = "10"))
|
||||||
counter = 1593333655044 / (1000 * 30),
|
|
||||||
digits = "10",
|
|
||||||
)
|
|
||||||
)
|
|
||||||
assertEquals(
|
|
||||||
"0070632029",
|
|
||||||
generateOtp(
|
|
||||||
counter = 1593333691405 / (1000 * 30),
|
|
||||||
digits = "10",
|
|
||||||
)
|
|
||||||
)
|
|
||||||
assertEquals(
|
|
||||||
"1017265882",
|
|
||||||
generateOtp(
|
|
||||||
counter = 1593333728893 / (1000 * 83),
|
|
||||||
digits = "10",
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun otpGenerationIllegalInput() {
|
fun otpGenerationIllegalInput() {
|
||||||
assertNull(
|
assertNull(generateOtp(counter = 10000, algorithm = "SHA0", digits = "10"))
|
||||||
generateOtp(
|
assertNull(generateOtp(counter = 10000, digits = "a"))
|
||||||
counter = 10000,
|
assertNull(generateOtp(counter = 10000, algorithm = "SHA1", digits = "5"))
|
||||||
algorithm = "SHA0",
|
assertNull(generateOtp(counter = 10000, digits = "11"))
|
||||||
digits = "10",
|
assertNull(generateOtp(counter = 10000, secret = "JBSWY3DPEHPK3PXPAAAAB", digits = "6"))
|
||||||
)
|
|
||||||
)
|
|
||||||
assertNull(
|
|
||||||
generateOtp(
|
|
||||||
counter = 10000,
|
|
||||||
digits = "a",
|
|
||||||
)
|
|
||||||
)
|
|
||||||
assertNull(
|
|
||||||
generateOtp(
|
|
||||||
counter = 10000,
|
|
||||||
algorithm = "SHA1",
|
|
||||||
digits = "5",
|
|
||||||
)
|
|
||||||
)
|
|
||||||
assertNull(
|
|
||||||
generateOtp(
|
|
||||||
counter = 10000,
|
|
||||||
digits = "11",
|
|
||||||
)
|
|
||||||
)
|
|
||||||
assertNull(
|
|
||||||
generateOtp(
|
|
||||||
counter = 10000,
|
|
||||||
secret = "JBSWY3DPEHPK3PXPAAAAB",
|
|
||||||
digits = "6",
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun otpGenerationUnusualSecrets() {
|
fun otpGenerationUnusualSecrets() {
|
||||||
assertEquals(
|
assertEquals(
|
||||||
"127764",
|
"127764",
|
||||||
generateOtp(
|
generateOtp(counter = 1593367111963 / (1000 * 30), secret = "JBSWY3DPEHPK3PXPAAAAAAAA"),
|
||||||
counter = 1593367111963 / (1000 * 30),
|
|
||||||
secret = "JBSWY3DPEHPK3PXPAAAAAAAA",
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
assertEquals(
|
assertEquals(
|
||||||
"047515",
|
"047515",
|
||||||
generateOtp(
|
generateOtp(counter = 1593367171420 / (1000 * 30), secret = "JBSWY3DPEHPK3PXPAAAAA"),
|
||||||
counter = 1593367171420 / (1000 * 30),
|
|
||||||
secret = "JBSWY3DPEHPK3PXPAAAAA",
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,7 @@ class UriTotpFinderTest {
|
||||||
assertEquals("HXDMVJECJJWSRB3HWIZR4IFUGFTMXBOZ", totpFinder.findSecret(TOTP_URI))
|
assertEquals("HXDMVJECJJWSRB3HWIZR4IFUGFTMXBOZ", totpFinder.findSecret(TOTP_URI))
|
||||||
assertEquals(
|
assertEquals(
|
||||||
"HXDMVJECJJWSRB3HWIZR4IFUGFTMXBOZ",
|
"HXDMVJECJJWSRB3HWIZR4IFUGFTMXBOZ",
|
||||||
totpFinder.findSecret("name\npassword\ntotp: HXDMVJECJJWSRB3HWIZR4IFUGFTMXBOZ")
|
totpFinder.findSecret("name\npassword\ntotp: HXDMVJECJJWSRB3HWIZR4IFUGFTMXBOZ"),
|
||||||
)
|
)
|
||||||
assertEquals("HXDMVJECJJWSRB3HWIZR4IFUGFTMXBOZ", totpFinder.findSecret(PASS_FILE_CONTENT))
|
assertEquals("HXDMVJECJJWSRB3HWIZR4IFUGFTMXBOZ", totpFinder.findSecret(PASS_FILE_CONTENT))
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,10 +14,7 @@ import javax.inject.Inject
|
||||||
*/
|
*/
|
||||||
public class DicewarePassphraseGenerator
|
public class DicewarePassphraseGenerator
|
||||||
@Inject
|
@Inject
|
||||||
constructor(
|
constructor(private val die: Die, wordList: InputStream) {
|
||||||
private val die: Die,
|
|
||||||
wordList: InputStream,
|
|
||||||
) {
|
|
||||||
|
|
||||||
private val wordMap = WordListParser.parse(wordList)
|
private val wordMap = WordListParser.parse(wordList)
|
||||||
|
|
||||||
|
|
|
@ -10,10 +10,7 @@ import javax.inject.Inject
|
||||||
/** Basic implementation of a die with configurable number of sides. */
|
/** Basic implementation of a die with configurable number of sides. */
|
||||||
public class Die
|
public class Die
|
||||||
@Inject
|
@Inject
|
||||||
constructor(
|
constructor(private val sides: Int, private val random: RandomIntGenerator) {
|
||||||
private val sides: Int,
|
|
||||||
private val random: RandomIntGenerator,
|
|
||||||
) {
|
|
||||||
|
|
||||||
/** Roll the die to return a single number. */
|
/** Roll the die to return a single number. */
|
||||||
public fun roll(): Int {
|
public fun roll(): Int {
|
||||||
|
|
|
@ -19,11 +19,7 @@ class DicewarePassphraseGeneratorTest {
|
||||||
fun generatePassphrase() {
|
fun generatePassphrase() {
|
||||||
val die = Die(6, intGenerator)
|
val die = Die(6, intGenerator)
|
||||||
|
|
||||||
val generator =
|
val generator = DicewarePassphraseGenerator(die, WordListParserTest.getDefaultWordList())
|
||||||
DicewarePassphraseGenerator(
|
|
||||||
die,
|
|
||||||
WordListParserTest.getDefaultWordList(),
|
|
||||||
)
|
|
||||||
|
|
||||||
assertEquals("salvation_cozily_croon_trustee_fidgety", generator.generatePassphrase(5, '_'))
|
assertEquals("salvation_cozily_croon_trustee_fidgety", generator.generatePassphrase(5, '_'))
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,5 +6,5 @@ public enum class PasswordOption(public val key: String) {
|
||||||
NoAmbiguousCharacters("B"),
|
NoAmbiguousCharacters("B"),
|
||||||
FullyRandom("s"),
|
FullyRandom("s"),
|
||||||
AtLeastOneSymbol("y"),
|
AtLeastOneSymbol("y"),
|
||||||
NoLowercaseLetters("L")
|
NoLowercaseLetters("L"),
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,7 +55,7 @@ internal object RandomPhonemesGenerator {
|
||||||
Element("w", CONSONANT),
|
Element("w", CONSONANT),
|
||||||
Element("x", CONSONANT),
|
Element("x", CONSONANT),
|
||||||
Element("y", CONSONANT),
|
Element("y", CONSONANT),
|
||||||
Element("z", CONSONANT)
|
Element("z", CONSONANT),
|
||||||
)
|
)
|
||||||
|
|
||||||
private class Element(str: String, val flags: Int) {
|
private class Element(str: String, val flags: Int) {
|
||||||
|
|
|
@ -39,10 +39,7 @@ public fun APSAppBar(
|
||||||
navigationIcon = {
|
navigationIcon = {
|
||||||
if (navigationIcon != null) {
|
if (navigationIcon != null) {
|
||||||
IconButton(onClick = { onNavigationIconClick.invoke() }) {
|
IconButton(onClick = { onNavigationIconClick.invoke() }) {
|
||||||
Icon(
|
Icon(painter = navigationIcon, contentDescription = null)
|
||||||
painter = navigationIcon,
|
|
||||||
contentDescription = null,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -68,7 +65,7 @@ private fun APSAppBarPreview() {
|
||||||
contentDescription = "Search items",
|
contentDescription = "Search items",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -56,10 +56,7 @@ private fun ToggleButton(
|
||||||
val icon =
|
val icon =
|
||||||
if (visible) painterResource(id = R.drawable.baseline_visibility_off_24)
|
if (visible) painterResource(id = R.drawable.baseline_visibility_off_24)
|
||||||
else painterResource(id = R.drawable.baseline_visibility_24)
|
else painterResource(id = R.drawable.baseline_visibility_24)
|
||||||
Icon(
|
Icon(painter = icon, contentDescription = contentDescription)
|
||||||
painter = icon,
|
|
||||||
contentDescription = contentDescription,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -70,10 +67,7 @@ public fun CopyButton(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
val clipboard = LocalClipboardManager.current
|
val clipboard = LocalClipboardManager.current
|
||||||
IconButton(
|
IconButton(onClick = { clipboard.setText(AnnotatedString(textToCopy)) }, modifier = modifier) {
|
||||||
onClick = { clipboard.setText(AnnotatedString(textToCopy)) },
|
|
||||||
modifier = modifier,
|
|
||||||
) {
|
|
||||||
Icon(
|
Icon(
|
||||||
painter = painterResource(R.drawable.ic_content_copy),
|
painter = painterResource(R.drawable.ic_content_copy),
|
||||||
contentDescription = stringResource(buttonLabelRes),
|
contentDescription = stringResource(buttonLabelRes),
|
||||||
|
|
|
@ -34,7 +34,7 @@ public fun PasswordItem(
|
||||||
label: String,
|
label: String,
|
||||||
type: ItemType,
|
type: ItemType,
|
||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier =
|
modifier =
|
||||||
|
|
Loading…
Reference in a new issue