diff options
108 files changed, 1386 insertions, 474 deletions
diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml index 79ee123c..0f7bc519 100644 --- a/.idea/codeStyles/codeStyleConfig.xml +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -2,4 +2,4 @@ <state> <option name="USE_PER_PROJECT_SETTINGS" value="true" /> </state> -</component>
\ No newline at end of file +</component> diff --git a/.idea/copyright/APS.xml b/.idea/copyright/APS.xml index 5c3add79..be4d0e65 100644 --- a/.idea/copyright/APS.xml +++ b/.idea/copyright/APS.xml @@ -3,4 +3,4 @@ <option name="notice" value="Copyright © 2014-&#36;today.year The Android Password Store Authors. All Rights Reserved. SPDX-License-Identifier: GPL-3.0-only" /> <option name="myName" value="APS" /> </copyright> -</component>
\ No newline at end of file +</component> diff --git a/.idea/gradle.xml b/.idea/gradle.xml index 723fff50..2af52cc4 100644 --- a/.idea/gradle.xml +++ b/.idea/gradle.xml @@ -22,4 +22,4 @@ </GradleProjectSettings> </option> </component> -</project>
\ No newline at end of file +</project> diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml index 730487eb..e9135e0d 100644 --- a/.idea/jarRepositories.xml +++ b/.idea/jarRepositories.xml @@ -37,4 +37,4 @@ <option name="url" value="file:$USER_HOME$/.m2/repository/" /> </remote-repository> </component> -</project>
\ No newline at end of file +</project> diff --git a/.idea/kotlinScripting.xml b/.idea/kotlinScripting.xml index bc444dea..78aa73da 100644 --- a/.idea/kotlinScripting.xml +++ b/.idea/kotlinScripting.xml @@ -3,4 +3,4 @@ <component name="KotlinScriptingSettings"> <option name="suppressDefinitionsCheck" value="true" /> </component> -</project>
\ No newline at end of file +</project> diff --git a/.idea/scopes/third_party.xml b/.idea/scopes/third_party.xml index 270b49b7..28c65850 100644 --- a/.idea/scopes/third_party.xml +++ b/.idea/scopes/third_party.xml @@ -1,3 +1,3 @@ <component name="DependencyValidationManager"> <scope name="third_party" pattern="src[Android-Password-Store.app]:mozilla.components.lib.publicsuffixlist..*" /> -</component>
\ No newline at end of file +</component> diff --git a/app/lint.xml b/app/lint.xml index b8589b9b..ca55e01c 100644 --- a/app/lint.xml +++ b/app/lint.xml @@ -6,4 +6,4 @@ <issue id="InvalidPackage"> <ignore regexp="X509LDAPCertStoreSpi" /> </issue> -</lint>
\ No newline at end of file +</lint> diff --git a/app/src/androidTest/java/dev/msfjarvis/aps/util/settings/MigrationsTest.kt b/app/src/androidTest/java/dev/msfjarvis/aps/util/settings/MigrationsTest.kt index 83a6afde..a5975be7 100644 --- a/app/src/androidTest/java/dev/msfjarvis/aps/util/settings/MigrationsTest.kt +++ b/app/src/androidTest/java/dev/msfjarvis/aps/util/settings/MigrationsTest.kt @@ -115,7 +115,10 @@ class MigrationsTest { putBoolean(PreferenceKeys.CLEAR_CLIPBOARD_20X, true) } runMigrations(context) - assertEquals(true, context.sharedPrefs.getBoolean(PreferenceKeys.CLEAR_CLIPBOARD_HISTORY, false)) + assertEquals( + true, + context.sharedPrefs.getBoolean(PreferenceKeys.CLEAR_CLIPBOARD_HISTORY, false) + ) assertFalse(context.sharedPrefs.contains(PreferenceKeys.CLEAR_CLIPBOARD_20X)) } } diff --git a/app/src/main/java/dev/msfjarvis/aps/Application.kt b/app/src/main/java/dev/msfjarvis/aps/Application.kt index 8194019c..d7ca97bc 100644 --- a/app/src/main/java/dev/msfjarvis/aps/Application.kt +++ b/app/src/main/java/dev/msfjarvis/aps/Application.kt @@ -32,7 +32,9 @@ class Application : android.app.Application(), SharedPreferences.OnSharedPrefere override fun onCreate() { super.onCreate() instance = this - if (BuildConfig.ENABLE_DEBUG_FEATURES || prefs.getBoolean(PreferenceKeys.ENABLE_DEBUG_LOGGING, false)) { + if (BuildConfig.ENABLE_DEBUG_FEATURES || + prefs.getBoolean(PreferenceKeys.ENABLE_DEBUG_LOGGING, false) + ) { plant(DebugTree()) StrictMode.setVmPolicy(VmPolicy.Builder().detectAll().penaltyLog().build()) StrictMode.setThreadPolicy(ThreadPolicy.Builder().detectAll().penaltyLog().build()) diff --git a/app/src/main/java/dev/msfjarvis/aps/data/repo/PasswordRepository.kt b/app/src/main/java/dev/msfjarvis/aps/data/repo/PasswordRepository.kt index 5aa63e76..79b34be9 100644 --- a/app/src/main/java/dev/msfjarvis/aps/data/repo/PasswordRepository.kt +++ b/app/src/main/java/dev/msfjarvis/aps/data/repo/PasswordRepository.kt @@ -199,8 +199,11 @@ object PasswordRepository { fun getFilesList(path: File?): ArrayList<File> { if (path == null || !path.exists()) return ArrayList() - val directories = (path.listFiles(FileFilter { pathname -> pathname.isDirectory }) ?: emptyArray()).toList() - val files = (path.listFiles(FileFilter { pathname -> pathname.extension == "gpg" }) ?: emptyArray()).toList() + val directories = + (path.listFiles(FileFilter { pathname -> pathname.isDirectory }) ?: emptyArray()).toList() + val files = + (path.listFiles(FileFilter { pathname -> pathname.extension == "gpg" }) ?: emptyArray()) + .toList() val items = ArrayList<File>() items.addAll(directories) @@ -216,7 +219,11 @@ object PasswordRepository { * @return a list of password items */ @JvmStatic - fun getPasswords(path: File, rootDir: File, sortOrder: PasswordSortOrder): ArrayList<PasswordItem> { + fun getPasswords( + path: File, + rootDir: File, + sortOrder: PasswordSortOrder + ): ArrayList<PasswordItem> { // We need to recover the passwords then parse the files val passList = getFilesList(path).also { it.sortBy { f -> f.name } } val passwordList = ArrayList<PasswordItem>() diff --git a/app/src/main/java/dev/msfjarvis/aps/ui/adapters/FieldItemAdapter.kt b/app/src/main/java/dev/msfjarvis/aps/ui/adapters/FieldItemAdapter.kt index afb2a130..030f6056 100644 --- a/app/src/main/java/dev/msfjarvis/aps/ui/adapters/FieldItemAdapter.kt +++ b/app/src/main/java/dev/msfjarvis/aps/ui/adapters/FieldItemAdapter.kt @@ -55,7 +55,8 @@ class FieldItemAdapter( notifyDataSetChanged() } - class FieldItemViewHolder(itemView: View, val binding: ItemFieldBinding) : RecyclerView.ViewHolder(itemView) { + class FieldItemViewHolder(itemView: View, val binding: ItemFieldBinding) : + RecyclerView.ViewHolder(itemView) { fun bind(fieldItem: FieldItem, showPassword: Boolean, copyTextToClipBoard: (String?) -> Unit) { with(binding) { @@ -66,7 +67,8 @@ class FieldItemAdapter( when (fieldItem.action) { FieldItem.ActionType.COPY -> { itemTextContainer.apply { - endIconDrawable = ContextCompat.getDrawable(itemView.context, R.drawable.ic_content_copy) + endIconDrawable = + ContextCompat.getDrawable(itemView.context, R.drawable.ic_content_copy) endIconMode = TextInputLayout.END_ICON_CUSTOM setEndIconOnClickListener { copyTextToClipBoard(itemText.text.toString()) } } diff --git a/app/src/main/java/dev/msfjarvis/aps/ui/adapters/PasswordItemRecyclerAdapter.kt b/app/src/main/java/dev/msfjarvis/aps/ui/adapters/PasswordItemRecyclerAdapter.kt index fc3b1a7e..a551a8b1 100644 --- a/app/src/main/java/dev/msfjarvis/aps/ui/adapters/PasswordItemRecyclerAdapter.kt +++ b/app/src/main/java/dev/msfjarvis/aps/ui/adapters/PasswordItemRecyclerAdapter.kt @@ -35,7 +35,9 @@ open class PasswordItemRecyclerAdapter : return super.onItemClicked(listener) as PasswordItemRecyclerAdapter } - override fun onSelectionChanged(listener: (selection: Selection<String>) -> Unit): PasswordItemRecyclerAdapter { + override fun onSelectionChanged( + listener: (selection: Selection<String>) -> Unit + ): PasswordItemRecyclerAdapter { return super.onSelectionChanged(listener) as PasswordItemRecyclerAdapter } @@ -59,7 +61,8 @@ open class PasswordItemRecyclerAdapter : name.text = spannable if (item.type == PasswordItem.TYPE_CATEGORY) { folderIndicator.visibility = View.VISIBLE - val count = item.file.listFiles { path -> path.isDirectory || path.extension == "gpg" }?.size ?: 0 + val count = + item.file.listFiles { path -> path.isDirectory || path.extension == "gpg" }?.size ?: 0 childCount.visibility = if (count > 0) View.VISIBLE else View.GONE childCount.text = "$count" } else { @@ -74,7 +77,8 @@ open class PasswordItemRecyclerAdapter : } } - class PasswordItemDetailsLookup(private val recyclerView: RecyclerView) : ItemDetailsLookup<String>() { + class PasswordItemDetailsLookup(private val recyclerView: RecyclerView) : + ItemDetailsLookup<String>() { override fun getItemDetails(event: MotionEvent): ItemDetails<String>? { val view = recyclerView.findChildViewUnder(event.x, event.y) ?: return null diff --git a/app/src/main/java/dev/msfjarvis/aps/ui/autofill/AutofillDecryptActivity.kt b/app/src/main/java/dev/msfjarvis/aps/ui/autofill/AutofillDecryptActivity.kt index 79fc44aa..07ae4c42 100644 --- a/app/src/main/java/dev/msfjarvis/aps/ui/autofill/AutofillDecryptActivity.kt +++ b/app/src/main/java/dev/msfjarvis/aps/ui/autofill/AutofillDecryptActivity.kt @@ -73,7 +73,12 @@ class AutofillDecryptActivity : AppCompatActivity() { putExtra(EXTRA_SEARCH_ACTION, false) putExtra(EXTRA_FILE_PATH, file.absolutePath) } - return PendingIntent.getActivity(context, decryptFileRequestCode++, intent, PendingIntent.FLAG_CANCEL_CURRENT) + return PendingIntent.getActivity( + context, + decryptFileRequestCode++, + intent, + PendingIntent.FLAG_CANCEL_CURRENT + ) .intentSender } } @@ -124,9 +129,17 @@ class AutofillDecryptActivity : AppCompatActivity() { setResult(RESULT_CANCELED) } else { val fillInDataset = - AutofillResponseBuilder.makeFillInDataset(this@AutofillDecryptActivity, credentials, clientState, action) + AutofillResponseBuilder.makeFillInDataset( + this@AutofillDecryptActivity, + credentials, + clientState, + action + ) withContext(Dispatchers.Main) { - setResult(RESULT_OK, Intent().apply { putExtra(AutofillManager.EXTRA_AUTHENTICATION_RESULT, fillInDataset) }) + setResult( + RESULT_OK, + Intent().apply { putExtra(AutofillManager.EXTRA_AUTHENTICATION_RESULT, fillInDataset) } + ) } } withContext(Dispatchers.Main) { finish() } @@ -137,7 +150,11 @@ class AutofillDecryptActivity : AppCompatActivity() { super.onDestroy() } - private suspend fun executeOpenPgpApi(data: Intent, input: InputStream, output: OutputStream): Intent? { + private suspend fun executeOpenPgpApi( + data: Intent, + input: InputStream, + output: OutputStream + ): Intent? { var openPgpServiceConnection: OpenPgpServiceConnection? = null val openPgpService = suspendCoroutine<IOpenPgpService2> { cont -> @@ -177,7 +194,9 @@ class AutofillDecryptActivity : AppCompatActivity() { return null } .onSuccess { result -> - return when (val resultCode = result?.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR)) { + return when (val resultCode = + result?.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR) + ) { OpenPgpApi.RESULT_CODE_SUCCESS -> { runCatching { val entry = @@ -185,7 +204,12 @@ class AutofillDecryptActivity : AppCompatActivity() { @Suppress("BlockingMethodInNonBlockingContext") passwordEntryFactory.create(lifecycleScope, decryptedOutput.toByteArray()) } - AutofillPreferences.credentialsFromStoreEntry(this, file, entry, directoryStructure) + AutofillPreferences.credentialsFromStoreEntry( + this, + file, + entry, + directoryStructure + ) } .getOrElse { e -> e(e) { "Failed to parse password entry" } @@ -193,7 +217,8 @@ class AutofillDecryptActivity : AppCompatActivity() { } } OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED -> { - val pendingIntent: PendingIntent = result.getParcelableExtra(OpenPgpApi.RESULT_INTENT)!! + val pendingIntent: PendingIntent = + result.getParcelableExtra(OpenPgpApi.RESULT_INTENT)!! runCatching { val intentToResume = withContext(Dispatchers.Main) { @@ -215,10 +240,16 @@ class AutofillDecryptActivity : AppCompatActivity() { val error = result.getParcelableExtra<OpenPgpError>(OpenPgpApi.RESULT_ERROR) if (error != null) { withContext(Dispatchers.Main) { - Toast.makeText(applicationContext, "Error from OpenKeyChain: ${error.message}", Toast.LENGTH_LONG) + Toast.makeText( + applicationContext, + "Error from OpenKeyChain: ${error.message}", + Toast.LENGTH_LONG + ) .show() } - e { "OpenPgpApi ACTION_DECRYPT_VERIFY failed (${error.errorId}): ${error.message}" } + e { + "OpenPgpApi ACTION_DECRYPT_VERIFY failed (${error.errorId}): ${error.message}" + } } null } diff --git a/app/src/main/java/dev/msfjarvis/aps/ui/autofill/AutofillFilterActivity.kt b/app/src/main/java/dev/msfjarvis/aps/ui/autofill/AutofillFilterActivity.kt index cf833a19..aac7008d 100644 --- a/app/src/main/java/dev/msfjarvis/aps/ui/autofill/AutofillFilterActivity.kt +++ b/app/src/main/java/dev/msfjarvis/aps/ui/autofill/AutofillFilterActivity.kt @@ -46,11 +46,16 @@ class AutofillFilterView : AppCompatActivity() { private const val HEIGHT_PERCENTAGE = 0.9 private const val WIDTH_PERCENTAGE = 0.75 - private const val EXTRA_FORM_ORIGIN_WEB = "dev.msfjarvis.aps.autofill.oreo.ui.EXTRA_FORM_ORIGIN_WEB" - private const val EXTRA_FORM_ORIGIN_APP = "dev.msfjarvis.aps.autofill.oreo.ui.EXTRA_FORM_ORIGIN_APP" + private const val EXTRA_FORM_ORIGIN_WEB = + "dev.msfjarvis.aps.autofill.oreo.ui.EXTRA_FORM_ORIGIN_WEB" + private const val EXTRA_FORM_ORIGIN_APP = + "dev.msfjarvis.aps.autofill.oreo.ui.EXTRA_FORM_ORIGIN_APP" private var matchAndDecryptFileRequestCode = 1 - fun makeMatchAndDecryptFileIntentSender(context: Context, formOrigin: FormOrigin): IntentSender { + fun makeMatchAndDecryptFileIntentSender( + context: Context, + formOrigin: FormOrigin + ): IntentSender { val intent = Intent(context, AutofillFilterView::class.java).apply { when (formOrigin) { @@ -108,7 +113,9 @@ class AutofillFilterView : AppCompatActivity() { FormOrigin.App(intent!!.getStringExtra(EXTRA_FORM_ORIGIN_APP)!!) } else -> { - e { "AutofillFilterActivity started without EXTRA_FORM_ORIGIN_WEB or EXTRA_FORM_ORIGIN_APP" } + e { + "AutofillFilterActivity started without EXTRA_FORM_ORIGIN_WEB or EXTRA_FORM_ORIGIN_APP" + } finish() return } @@ -125,7 +132,8 @@ class AutofillFilterView : AppCompatActivity() { with(binding) { rvPassword.apply { adapter = - SearchableRepositoryAdapter(R.layout.oreo_autofill_filter_row, ::PasswordViewHolder) { item -> + SearchableRepositoryAdapter(R.layout.oreo_autofill_filter_row, ::PasswordViewHolder) { + item -> val file = item.file.relativeTo(item.rootDir) val pathToIdentifier = directoryStructure.getPathToIdentifierFor(file) val identifier = directoryStructure.getIdentifierFor(file) @@ -171,10 +179,15 @@ class AutofillFilterView : AppCompatActivity() { setOnCheckedChangeListener { _, _ -> updateSearch() } } shouldMatch.text = - getString(R.string.oreo_autofill_match_with, formOrigin.getPrettyIdentifier(applicationContext)) + getString( + R.string.oreo_autofill_match_with, + formOrigin.getPrettyIdentifier(applicationContext) + ) model.searchResult.observe(this@AutofillFilterView) { result -> val list = result.passwordItems - (rvPassword.adapter as SearchableRepositoryAdapter).submitList(list) { rvPassword.scrollToPosition(0) } + (rvPassword.adapter as SearchableRepositoryAdapter).submitList(list) { + rvPassword.scrollToPosition(0) + } // Switch RecyclerView out for a "no results" message if the new list is empty and // the message is not yet shown (and vice versa). if ((list.isEmpty() && rvPasswordSwitcher.nextView.id == rvPasswordEmpty.id) || @@ -189,16 +202,21 @@ class AutofillFilterView : AppCompatActivity() { private fun updateSearch() { model.search( binding.search.text.toString().trim(), - filterMode = if (binding.strictDomainSearch.isChecked) FilterMode.StrictDomain else FilterMode.Fuzzy, + filterMode = + if (binding.strictDomainSearch.isChecked) FilterMode.StrictDomain else FilterMode.Fuzzy, searchMode = SearchMode.RecursivelyInSubdirectories, listMode = ListMode.FilesOnly ) } private fun decryptAndFill(item: PasswordItem) { - if (binding.shouldClear.isChecked) AutofillMatcher.clearMatchesFor(applicationContext, formOrigin) - if (binding.shouldMatch.isChecked) AutofillMatcher.addMatchFor(applicationContext, formOrigin, item.file) + if (binding.shouldClear.isChecked) + AutofillMatcher.clearMatchesFor(applicationContext, formOrigin) + if (binding.shouldMatch.isChecked) + AutofillMatcher.addMatchFor(applicationContext, formOrigin, item.file) // intent?.extras? is checked to be non-null in onCreate - decryptAction.launch(AutofillDecryptActivity.makeDecryptFileIntent(item.file, intent!!.extras!!, this)) + decryptAction.launch( + AutofillDecryptActivity.makeDecryptFileIntent(item.file, intent!!.extras!!, this) + ) } } diff --git a/app/src/main/java/dev/msfjarvis/aps/ui/autofill/AutofillPublisherChangedActivity.kt b/app/src/main/java/dev/msfjarvis/aps/ui/autofill/AutofillPublisherChangedActivity.kt index bea2bcd3..6ea47ea1 100644 --- a/app/src/main/java/dev/msfjarvis/aps/ui/autofill/AutofillPublisherChangedActivity.kt +++ b/app/src/main/java/dev/msfjarvis/aps/ui/autofill/AutofillPublisherChangedActivity.kt @@ -83,9 +83,15 @@ class AutofillPublisherChangedActivity : AppCompatActivity() { resetButton.visibility = View.VISIBLE } resetButton.setOnClickListener { - AutofillMatcher.clearMatchesFor(this@AutofillPublisherChangedActivity, FormOrigin.App(appPackage)) + AutofillMatcher.clearMatchesFor( + this@AutofillPublisherChangedActivity, + FormOrigin.App(appPackage) + ) val fillResponse = intent.getParcelableExtra<FillResponse>(EXTRA_FILL_RESPONSE_AFTER_RESET) - setResult(RESULT_OK, Intent().apply { putExtra(AutofillManager.EXTRA_AUTHENTICATION_RESULT, fillResponse) }) + setResult( + RESULT_OK, + Intent().apply { putExtra(AutofillManager.EXTRA_AUTHENTICATION_RESULT, fillResponse) } + ) finish() } } @@ -96,13 +102,18 @@ class AutofillPublisherChangedActivity : AppCompatActivity() { with(binding) { val packageInfo = packageManager.getPackageInfo(appPackage, PackageManager.GET_META_DATA) val installTime = DateUtils.getRelativeTimeSpanString(packageInfo.firstInstallTime) - warningAppInstallDate.text = getString(R.string.oreo_autofill_warning_publisher_install_time, installTime) + warningAppInstallDate.text = + getString(R.string.oreo_autofill_warning_publisher_install_time, installTime) val appInfo = packageManager.getApplicationInfo(appPackage, PackageManager.GET_META_DATA) warningAppName.text = "“${packageManager.getApplicationLabel(appInfo)}”" val currentHash = computeCertificatesHash(this@AutofillPublisherChangedActivity, appPackage) warningAppAdvancedInfo.text = - getString(R.string.oreo_autofill_warning_publisher_advanced_info_template, appPackage, currentHash) + getString( + R.string.oreo_autofill_warning_publisher_advanced_info_template, + appPackage, + currentHash + ) } } .onFailure { e -> diff --git a/app/src/main/java/dev/msfjarvis/aps/ui/autofill/AutofillSaveActivity.kt b/app/src/main/java/dev/msfjarvis/aps/ui/autofill/AutofillSaveActivity.kt index dfec29be..ee3cf752 100644 --- a/app/src/main/java/dev/msfjarvis/aps/ui/autofill/AutofillSaveActivity.kt +++ b/app/src/main/java/dev/msfjarvis/aps/ui/autofill/AutofillSaveActivity.kt @@ -34,13 +34,20 @@ class AutofillSaveActivity : AppCompatActivity() { private const val EXTRA_FOLDER_NAME = "dev.msfjarvis.aps.autofill.oreo.ui.EXTRA_FOLDER_NAME" private const val EXTRA_PASSWORD = "dev.msfjarvis.aps.autofill.oreo.ui.EXTRA_PASSWORD" private const val EXTRA_NAME = "dev.msfjarvis.aps.autofill.oreo.ui.EXTRA_NAME" - private const val EXTRA_SHOULD_MATCH_APP = "dev.msfjarvis.aps.autofill.oreo.ui.EXTRA_SHOULD_MATCH_APP" - private const val EXTRA_SHOULD_MATCH_WEB = "dev.msfjarvis.aps.autofill.oreo.ui.EXTRA_SHOULD_MATCH_WEB" - private const val EXTRA_GENERATE_PASSWORD = "dev.msfjarvis.aps.autofill.oreo.ui.EXTRA_GENERATE_PASSWORD" + private const val EXTRA_SHOULD_MATCH_APP = + "dev.msfjarvis.aps.autofill.oreo.ui.EXTRA_SHOULD_MATCH_APP" + private const val EXTRA_SHOULD_MATCH_WEB = + "dev.msfjarvis.aps.autofill.oreo.ui.EXTRA_SHOULD_MATCH_WEB" + private const val EXTRA_GENERATE_PASSWORD = + "dev.msfjarvis.aps.autofill.oreo.ui.EXTRA_GENERATE_PASSWORD" private var saveRequestCode = 1 - fun makeSaveIntentSender(context: Context, credentials: Credentials?, formOrigin: FormOrigin): IntentSender { + fun makeSaveIntentSender( + context: Context, + credentials: Credentials?, + formOrigin: FormOrigin + ): IntentSender { val identifier = formOrigin.getPrettyIdentifier(context, untrusted = false) // Prevent directory traversals val sanitizedIdentifier = @@ -52,7 +59,11 @@ class AutofillSaveActivity : AppCompatActivity() { sanitizedIdentifier = sanitizedIdentifier, username = credentials?.username ) - val fileName = directoryStructure.getSaveFileName(username = credentials?.username, identifier = identifier) + val fileName = + directoryStructure.getSaveFileName( + username = credentials?.username, + identifier = identifier + ) val intent = Intent(context, AutofillSaveActivity::class.java).apply { putExtras( @@ -60,13 +71,20 @@ class AutofillSaveActivity : AppCompatActivity() { EXTRA_FOLDER_NAME to folderName, EXTRA_NAME to fileName, EXTRA_PASSWORD to credentials?.password, - EXTRA_SHOULD_MATCH_APP to formOrigin.identifier.takeIf { formOrigin is FormOrigin.App }, - EXTRA_SHOULD_MATCH_WEB to formOrigin.identifier.takeIf { formOrigin is FormOrigin.Web }, + EXTRA_SHOULD_MATCH_APP to + formOrigin.identifier.takeIf { formOrigin is FormOrigin.App }, + EXTRA_SHOULD_MATCH_WEB to + formOrigin.identifier.takeIf { formOrigin is FormOrigin.Web }, EXTRA_GENERATE_PASSWORD to (credentials == null) ) ) } - return PendingIntent.getActivity(context, saveRequestCode++, intent, PendingIntent.FLAG_CANCEL_CURRENT) + return PendingIntent.getActivity( + context, + saveRequestCode++, + intent, + PendingIntent.FLAG_CANCEL_CURRENT + ) .intentSender } } @@ -94,7 +112,8 @@ class AutofillSaveActivity : AppCompatActivity() { "FILE_PATH" to repo.resolve(intent.getStringExtra(EXTRA_FOLDER_NAME)!!).absolutePath, PasswordCreationActivity.EXTRA_FILE_NAME to intent.getStringExtra(EXTRA_NAME), PasswordCreationActivity.EXTRA_PASSWORD to intent.getStringExtra(EXTRA_PASSWORD), - PasswordCreationActivity.EXTRA_GENERATE_PASSWORD to intent.getBooleanExtra(EXTRA_GENERATE_PASSWORD, false) + PasswordCreationActivity.EXTRA_GENERATE_PASSWORD to + intent.getBooleanExtra(EXTRA_GENERATE_PASSWORD, false) ) ) } @@ -117,8 +136,15 @@ class AutofillSaveActivity : AppCompatActivity() { } val credentials = Credentials(username, password, null) val fillInDataset = - AutofillResponseBuilder.makeFillInDataset(this, credentials, clientState, AutofillAction.Generate) - Intent().apply { putExtra(AutofillManager.EXTRA_AUTHENTICATION_RESULT, fillInDataset) } + AutofillResponseBuilder.makeFillInDataset( + this, + credentials, + clientState, + AutofillAction.Generate + ) + Intent().apply { + putExtra(AutofillManager.EXTRA_AUTHENTICATION_RESULT, fillInDataset) + } } else { // Password was extracted from a form, there is nothing to fill. Intent() diff --git a/app/src/main/java/dev/msfjarvis/aps/ui/crypto/BasePgpActivity.kt b/app/src/main/java/dev/msfjarvis/aps/ui/crypto/BasePgpActivity.kt index eea7f79a..f17ca72b 100644 --- a/app/src/main/java/dev/msfjarvis/aps/ui/crypto/BasePgpActivity.kt +++ b/app/src/main/java/dev/msfjarvis/aps/ui/crypto/BasePgpActivity.kt @@ -157,7 +157,10 @@ open class BasePgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBou return } else { previousListener = null - serviceConnection = OpenPgpServiceConnection(this, OPENPGP_PROVIDER, onBoundListener).also { it.bindToService() } + serviceConnection = + OpenPgpServiceConnection(this, OPENPGP_PROVIDER, onBoundListener).also { + it.bindToService() + } } } @@ -250,7 +253,10 @@ open class BasePgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBou fun getParentPath(fullPath: String, repositoryPath: String): String { val relativePath = getRelativePath(fullPath, repositoryPath) val index = relativePath.lastIndexOf("/") - return "/${relativePath.substring(startIndex = 0, endIndex = index + 1)}/".replace("/+".toRegex(), "/") + return "/${relativePath.substring(startIndex = 0, endIndex = index + 1)}/".replace( + "/+".toRegex(), + "/" + ) } /** /path/to/store/social/facebook.gpg -> social/facebook */ diff --git a/app/src/main/java/dev/msfjarvis/aps/ui/crypto/DecryptActivity.kt b/app/src/main/java/dev/msfjarvis/aps/ui/crypto/DecryptActivity.kt index 24859852..e17ce612 100644 --- a/app/src/main/java/dev/msfjarvis/aps/ui/crypto/DecryptActivity.kt +++ b/app/src/main/java/dev/msfjarvis/aps/ui/crypto/DecryptActivity.kt @@ -44,7 +44,9 @@ class DecryptActivity : BasePgpActivity(), OpenPgpServiceConnection.OnBound { private val binding by viewBinding(DecryptLayoutBinding::inflate) @Inject lateinit var passwordEntryFactory: PasswordEntryFactory - private val relativeParentPath by lazy(LazyThreadSafetyMode.NONE) { getParentPath(fullPath, repoPath) } + private val relativeParentPath by lazy(LazyThreadSafetyMode.NONE) { + getParentPath(fullPath, repoPath) + } private var passwordEntry: PasswordEntry? = null private val userInteractionRequiredResult = @@ -136,7 +138,10 @@ class DecryptActivity : BasePgpActivity(), OpenPgpServiceConnection.OnBound { intent.putExtra("REPO_PATH", repoPath) intent.putExtra(PasswordCreationActivity.EXTRA_FILE_NAME, name) intent.putExtra(PasswordCreationActivity.EXTRA_PASSWORD, passwordEntry?.password) - intent.putExtra(PasswordCreationActivity.EXTRA_EXTRA_CONTENT, passwordEntry?.extraContentWithoutAuthData) + intent.putExtra( + PasswordCreationActivity.EXTRA_EXTRA_CONTENT, + passwordEntry?.extraContentWithoutAuthData + ) intent.putExtra(PasswordCreationActivity.EXTRA_EDITING, true) startActivity(intent) finish() @@ -150,7 +155,9 @@ class DecryptActivity : BasePgpActivity(), OpenPgpServiceConnection.OnBound { type = "text/plain" } // Always show a picker to give the user a chance to cancel - startActivity(Intent.createChooser(sendIntent, resources.getText(R.string.send_plaintext_password_to))) + startActivity( + Intent.createChooser(sendIntent, resources.getText(R.string.send_plaintext_password_to)) + ) } @OptIn(ExperimentalTime::class) @@ -166,7 +173,10 @@ class DecryptActivity : BasePgpActivity(), OpenPgpServiceConnection.OnBound { val outputStream = ByteArrayOutputStream() lifecycleScope.launch(Dispatchers.Main) { - val result = withContext(Dispatchers.IO) { checkNotNull(api).executeApi(data, inputStream, outputStream) } + val result = + withContext(Dispatchers.IO) { + checkNotNull(api).executeApi(data, inputStream, outputStream) + } when (result.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR)) { OpenPgpApi.RESULT_CODE_SUCCESS -> { startAutoDismissTimer() @@ -174,7 +184,8 @@ class DecryptActivity : BasePgpActivity(), OpenPgpServiceConnection.OnBound { val showPassword = settings.getBoolean(PreferenceKeys.SHOW_PASSWORD, true) val entry = passwordEntryFactory.create(lifecycleScope, outputStream.toByteArray()) val items = arrayListOf<FieldItem>() - val adapter = FieldItemAdapter(emptyList(), showPassword) { text -> copyTextToClipboard(text) } + val adapter = + FieldItemAdapter(emptyList(), showPassword) { text -> copyTextToClipboard(text) } if (settings.getBoolean(PreferenceKeys.COPY_ON_DECRYPT, false)) { copyPasswordToClipboard(entry.password) @@ -190,7 +201,9 @@ class DecryptActivity : BasePgpActivity(), OpenPgpServiceConnection.OnBound { if (entry.hasTotp()) { launch { items.add(FieldItem.createOtpField(entry.totp.value)) - entry.totp.collect { code -> withContext(Dispatchers.Main) { adapter.updateOTPCode(code) } } + entry.totp.collect { code -> + withContext(Dispatchers.Main) { adapter.updateOTPCode(code) } + } } } @@ -198,7 +211,9 @@ class DecryptActivity : BasePgpActivity(), OpenPgpServiceConnection.OnBound { items.add(FieldItem.createUsernameField(entry.username!!)) } - entry.extraContent.forEach { (key, value) -> items.add(FieldItem(key, value, FieldItem.ActionType.COPY)) } + entry.extraContent.forEach { (key, value) -> + items.add(FieldItem(key, value, FieldItem.ActionType.COPY)) + } binding.recyclerView.adapter = adapter adapter.updateItems(items) diff --git a/app/src/main/java/dev/msfjarvis/aps/ui/crypto/GetKeyIdsActivity.kt b/app/src/main/java/dev/msfjarvis/aps/ui/crypto/GetKeyIdsActivity.kt index 120ccaab..c98ea877 100644 --- a/app/src/main/java/dev/msfjarvis/aps/ui/crypto/GetKeyIdsActivity.kt +++ b/app/src/main/java/dev/msfjarvis/aps/ui/crypto/GetKeyIdsActivity.kt @@ -55,7 +55,9 @@ class GetKeyIdsActivity : BasePgpActivity() { OpenPgpApi.RESULT_CODE_SUCCESS -> { runCatching { val ids = - result.getLongArrayExtra(OpenPgpApi.RESULT_KEY_IDS)?.map { OpenPgpUtils.convertKeyIdToHex(it) } + result.getLongArrayExtra(OpenPgpApi.RESULT_KEY_IDS)?.map { + OpenPgpUtils.convertKeyIdToHex(it) + } ?: emptyList() val keyResult = Intent().putExtra(OpenPgpApi.EXTRA_KEY_IDS, ids.toTypedArray()) setResult(RESULT_OK, keyResult) diff --git a/app/src/main/java/dev/msfjarvis/aps/ui/crypto/PasswordCreationActivity.kt b/app/src/main/java/dev/msfjarvis/aps/ui/crypto/PasswordCreationActivity.kt index b72b3b86..c9ae348a 100644 --- a/app/src/main/java/dev/msfjarvis/aps/ui/crypto/PasswordCreationActivity.kt +++ b/app/src/main/java/dev/msfjarvis/aps/ui/crypto/PasswordCreationActivity.kt @@ -63,14 +63,24 @@ class PasswordCreationActivity : BasePgpActivity(), OpenPgpServiceConnection.OnB private val binding by viewBinding(PasswordCreationActivityBinding::inflate) @Inject lateinit var passwordEntryFactory: PasswordEntryFactory - private val suggestedName by lazy(LazyThreadSafetyMode.NONE) { intent.getStringExtra(EXTRA_FILE_NAME) } - private val suggestedPass by lazy(LazyThreadSafetyMode.NONE) { intent.getStringExtra(EXTRA_PASSWORD) } - private val suggestedExtra by lazy(LazyThreadSafetyMode.NONE) { intent.getStringExtra(EXTRA_EXTRA_CONTENT) } + private val suggestedName by lazy(LazyThreadSafetyMode.NONE) { + intent.getStringExtra(EXTRA_FILE_NAME) + } + private val suggestedPass by lazy(LazyThreadSafetyMode.NONE) { + intent.getStringExtra(EXTRA_PASSWORD) + } + private val suggestedExtra by lazy(LazyThreadSafetyMode.NONE) { + intent.getStringExtra(EXTRA_EXTRA_CONTENT) + } private val shouldGeneratePassword by lazy(LazyThreadSafetyMode.NONE) { intent.getBooleanExtra(EXTRA_GENERATE_PASSWORD, false) } - private val editing by lazy(LazyThreadSafetyMode.NONE) { intent.getBooleanExtra(EXTRA_EDITING, false) } - private val oldFileName by lazy(LazyThreadSafetyMode.NONE) { intent.getStringExtra(EXTRA_FILE_NAME) } + private val editing by lazy(LazyThreadSafetyMode.NONE) { + intent.getBooleanExtra(EXTRA_EDITING, false) + } + private val oldFileName by lazy(LazyThreadSafetyMode.NONE) { + intent.getStringExtra(EXTRA_FILE_NAME) + } private var oldCategory: String? = null private var copy: Boolean = false private var encryptionIntent: Intent = Intent() @@ -99,7 +109,8 @@ class PasswordCreationActivity : BasePgpActivity(), OpenPgpServiceConnection.OnB val intentResult = IntentIntegrator.parseActivityResult(RESULT_OK, result.data) val contents = "${intentResult.contents}\n" val currentExtras = binding.extraContent.text.toString() - if (currentExtras.isNotEmpty() && currentExtras.last() != '\n') binding.extraContent.append("\n$contents") + if (currentExtras.isNotEmpty() && currentExtras.last() != '\n') + binding.extraContent.append("\n$contents") else binding.extraContent.append(contents) snackbar(message = getString(R.string.otp_import_success)) } else { @@ -113,18 +124,27 @@ class PasswordCreationActivity : BasePgpActivity(), OpenPgpServiceConnection.OnB result.data?.getStringArrayExtra(OpenPgpApi.EXTRA_KEY_IDS)?.let { keyIds -> lifecycleScope.launch { val gpgIdentifierFile = File(PasswordRepository.getRepositoryDirectory(), ".gpg-id") - withContext(Dispatchers.IO) { gpgIdentifierFile.writeText((keyIds + "").joinToString("\n")) } + withContext(Dispatchers.IO) { + gpgIdentifierFile.writeText((keyIds + "").joinToString("\n")) + } commitChange( getString( R.string.git_commit_gpg_id, - getLongName(gpgIdentifierFile.parentFile!!.absolutePath, repoPath, gpgIdentifierFile.name) + getLongName( + gpgIdentifierFile.parentFile!!.absolutePath, + repoPath, + gpgIdentifierFile.name + ) ) ) .onSuccess { encrypt(encryptionIntent) } } } } else { - snackbar(message = getString(R.string.gpg_key_select_mandatory), length = Snackbar.LENGTH_LONG) + snackbar( + message = getString(R.string.gpg_key_select_mandatory), + length = Snackbar.LENGTH_LONG + ) } } @@ -148,24 +168,31 @@ class PasswordCreationActivity : BasePgpActivity(), OpenPgpServiceConnection.OnB super.onCreate(savedInstanceState) supportActionBar?.setDisplayHomeAsUpEnabled(true) bindToOpenKeychain(this) - title = if (editing) getString(R.string.edit_password) else getString(R.string.new_password_title) + title = + if (editing) getString(R.string.edit_password) else getString(R.string.new_password_title) with(binding) { setContentView(root) generatePassword.setOnClickListener { generatePassword() } otpImportButton.setOnClickListener { - supportFragmentManager.setFragmentResultListener(OTP_RESULT_REQUEST_KEY, this@PasswordCreationActivity) { - requestKey, - bundle -> + supportFragmentManager.setFragmentResultListener( + OTP_RESULT_REQUEST_KEY, + this@PasswordCreationActivity + ) { requestKey, bundle -> if (requestKey == OTP_RESULT_REQUEST_KEY) { val contents = bundle.getString(RESULT) val currentExtras = binding.extraContent.text.toString() - if (currentExtras.isNotEmpty() && currentExtras.last() != '\n') binding.extraContent.append("\n$contents") + if (currentExtras.isNotEmpty() && currentExtras.last() != '\n') + binding.extraContent.append("\n$contents") else binding.extraContent.append(contents) } } val hasCamera = packageManager?.hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY) == true if (hasCamera) { - val items = arrayOf(getString(R.string.otp_import_qr_code), getString(R.string.otp_import_manual_entry)) + val items = + arrayOf( + getString(R.string.otp_import_qr_code), + getString(R.string.otp_import_manual_entry) + ) MaterialAlertDialogBuilder(this@PasswordCreationActivity) .setItems(items) { _, index -> when (index) { @@ -209,7 +236,8 @@ class PasswordCreationActivity : BasePgpActivity(), OpenPgpServiceConnection.OnB // in the encrypted extras. This only makes sense if the directory structure is // FileBased. if (suggestedName == null && - AutofillPreferences.directoryStructure(this@PasswordCreationActivity) == DirectoryStructure.FileBased + AutofillPreferences.directoryStructure(this@PasswordCreationActivity) == + DirectoryStructure.FileBased ) { encryptUsername.apply { visibility = View.VISIBLE @@ -226,7 +254,10 @@ class PasswordCreationActivity : BasePgpActivity(), OpenPgpServiceConnection.OnB // User wants to disable username encryption, so we extract the // username from the encrypted extras and use it as the filename. val entry = - passwordEntryFactory.create(lifecycleScope, "PASSWORD\n${extraContent.text}".encodeToByteArray()) + passwordEntryFactory.create( + lifecycleScope, + "PASSWORD\n${extraContent.text}".encodeToByteArray() + ) val username = entry.username // username should not be null here by the logic in @@ -239,7 +270,9 @@ class PasswordCreationActivity : BasePgpActivity(), OpenPgpServiceConnection.OnB } } } - listOf(filename, extraContent).forEach { it.doOnTextChanged { _, _, _, _ -> updateViewState() } } + listOf(filename, extraContent).forEach { + it.doOnTextChanged { _, _, _, _ -> updateViewState() } + } } suggestedPass?.let { password.setText(it) @@ -279,21 +312,29 @@ class PasswordCreationActivity : BasePgpActivity(), OpenPgpServiceConnection.OnB } private fun generatePassword() { - supportFragmentManager.setFragmentResultListener(PASSWORD_RESULT_REQUEST_KEY, this) { requestKey, bundle -> + supportFragmentManager.setFragmentResultListener(PASSWORD_RESULT_REQUEST_KEY, this) { + requestKey, + bundle -> if (requestKey == PASSWORD_RESULT_REQUEST_KEY) { binding.password.setText(bundle.getString(RESULT)) } } when (settings.getString(PreferenceKeys.PREF_KEY_PWGEN_TYPE) ?: KEY_PWGEN_TYPE_CLASSIC) { - KEY_PWGEN_TYPE_CLASSIC -> PasswordGeneratorDialogFragment().show(supportFragmentManager, "generator") - KEY_PWGEN_TYPE_XKPASSWD -> XkPasswordGeneratorDialogFragment().show(supportFragmentManager, "xkpwgenerator") + KEY_PWGEN_TYPE_CLASSIC -> + PasswordGeneratorDialogFragment().show(supportFragmentManager, "generator") + KEY_PWGEN_TYPE_XKPASSWD -> + XkPasswordGeneratorDialogFragment().show(supportFragmentManager, "xkpwgenerator") } } private fun updateViewState() = with(binding) { // Use PasswordEntry to parse extras for username - val entry = passwordEntryFactory.create(lifecycleScope, "PLACEHOLDER\n${extraContent.text}".encodeToByteArray()) + val entry = + passwordEntryFactory.create( + lifecycleScope, + "PLACEHOLDER\n${extraContent.text}".encodeToByteArray() + ) encryptUsername.apply { if (visibility != View.VISIBLE) return@apply val hasUsernameInFileName = filename.text.toString().isNotBlank() @@ -354,14 +395,18 @@ class PasswordCreationActivity : BasePgpActivity(), OpenPgpServiceConnection.OnB } } if (gpgIdentifiers.isEmpty()) { - gpgKeySelectAction.launch(Intent(this@PasswordCreationActivity, GetKeyIdsActivity::class.java)) + gpgKeySelectAction.launch( + Intent(this@PasswordCreationActivity, GetKeyIdsActivity::class.java) + ) return@with } - val keyIds = gpgIdentifiers.filterIsInstance<GpgIdentifier.KeyId>().map { it.id }.toLongArray() + val keyIds = + gpgIdentifiers.filterIsInstance<GpgIdentifier.KeyId>().map { it.id }.toLongArray() if (keyIds.isNotEmpty()) { encryptionIntent.putExtra(OpenPgpApi.EXTRA_KEY_IDS, keyIds) } - val userIds = gpgIdentifiers.filterIsInstance<GpgIdentifier.UserId>().map { it.email }.toTypedArray() + val userIds = + gpgIdentifiers.filterIsInstance<GpgIdentifier.UserId>().map { it.email }.toTypedArray() if (userIds.isNotEmpty()) { encryptionIntent.putExtra(OpenPgpApi.EXTRA_USER_IDS, userIds) } @@ -396,7 +441,9 @@ class PasswordCreationActivity : BasePgpActivity(), OpenPgpServiceConnection.OnB lifecycleScope.launch(Dispatchers.Main) { val result = - withContext(Dispatchers.IO) { checkNotNull(api).executeApi(encryptionIntent, inputStream, outputStream) } + withContext(Dispatchers.IO) { + checkNotNull(api).executeApi(encryptionIntent, inputStream, outputStream) + } when (result.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR)) { OpenPgpApi.RESULT_CODE_SUCCESS -> { runCatching { @@ -405,7 +452,9 @@ class PasswordCreationActivity : BasePgpActivity(), OpenPgpServiceConnection.OnB // Additionally, if we were editing and the incoming and outgoing // filenames differ, it means we renamed. Ensure that the target // doesn't already exist to prevent an accidental overwrite. - if ((!editing || (editing && suggestedName != file.nameWithoutExtension)) && file.exists()) { + if ((!editing || (editing && suggestedName != file.nameWithoutExtension)) && + file.exists() + ) { snackbar(message = getString(R.string.password_creation_duplicate_error)) return@runCatching } @@ -415,7 +464,9 @@ class PasswordCreationActivity : BasePgpActivity(), OpenPgpServiceConnection.OnB return@runCatching } - withContext(Dispatchers.IO) { file.outputStream().use { it.write(outputStream.toByteArray()) } } + withContext(Dispatchers.IO) { + file.outputStream().use { it.write(outputStream.toByteArray()) } + } // associate the new password name with the last name's timestamp in // history @@ -432,7 +483,10 @@ class PasswordCreationActivity : BasePgpActivity(), OpenPgpServiceConnection.OnB val returnIntent = Intent() returnIntent.putExtra(RETURN_EXTRA_CREATED_FILE, path) returnIntent.putExtra(RETURN_EXTRA_NAME, editName) - returnIntent.putExtra(RETURN_EXTRA_LONG_NAME, getLongName(fullPath, repoPath, editName)) + returnIntent.putExtra( + RETURN_EXTRA_LONG_NAME, + getLongName(fullPath, repoPath, editName) + ) if (shouldGeneratePassword) { val directoryStructure = AutofillPreferences.directoryStructure(applicationContext) @@ -442,13 +496,18 @@ class PasswordCreationActivity : BasePgpActivity(), OpenPgpServiceConnection.OnB returnIntent.putExtra(RETURN_EXTRA_USERNAME, username) } - if (directoryInputLayout.isVisible && directoryInputLayout.isEnabled && oldFileName != null) { + if (directoryInputLayout.isVisible && + directoryInputLayout.isEnabled && + oldFileName != null + ) { val oldFile = File("$repoPath/${oldCategory?.trim('/')}/$oldFileName.gpg") if (oldFile.path != file.path && !oldFile.delete()) { setResult(RESULT_CANCELED) MaterialAlertDialogBuilder(this@PasswordCreationActivity) .setTitle(R.string.password_creation_file_fail_title) - .setMessage(getString(R.string.password_creation_file_delete_fail_message, oldFileName)) + .setMessage( + getString(R.string.password_creation_file_delete_fail_message, oldFileName) + ) .setCancelable(false) .setPositiveButton(android.R.string.ok) { _, _ -> finish() } .show() @@ -456,9 +515,12 @@ class PasswordCreationActivity : BasePgpActivity(), OpenPgpServiceConnection.OnB } } - val commitMessageRes = if (editing) R.string.git_commit_edit_text else R.string.git_commit_add_text + val commitMessageRes = + if (editing) R.string.git_commit_edit_text else R.string.git_commit_add_text lifecycleScope.launch { - commitChange(resources.getString(commitMessageRes, getLongName(fullPath, repoPath, editName))) + commitChange( + resources.getString(commitMessageRes, getLongName(fullPath, repoPath, editName)) + ) .onSuccess { setResult(RESULT_OK, returnIntent) finish() diff --git a/app/src/main/java/dev/msfjarvis/aps/ui/dialogs/BasicBottomSheet.kt b/app/src/main/java/dev/msfjarvis/aps/ui/dialogs/BasicBottomSheet.kt index fa188a22..dad86479 100644 --- a/app/src/main/java/dev/msfjarvis/aps/ui/dialogs/BasicBottomSheet.kt +++ b/app/src/main/java/dev/msfjarvis/aps/ui/dialogs/BasicBottomSheet.kt @@ -52,7 +52,11 @@ private constructor( } } - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { if (savedInstanceState != null) dismiss() return layoutInflater.inflate(R.layout.basic_bottom_sheet, container, false) } @@ -85,7 +89,9 @@ private constructor( } if (negativeButtonClickListener != null) { binding.bottomSheetCancelButton.isVisible = true - negativeButtonLabel?.let { buttonLbl -> binding.bottomSheetCancelButton.text = buttonLbl } + negativeButtonLabel?.let { buttonLbl -> + binding.bottomSheetCancelButton.text = buttonLbl + } binding.bottomSheetCancelButton.setOnClickListener { negativeButtonClickListener.onClick(it) dismiss() @@ -95,7 +101,9 @@ private constructor( } ) val gradientDrawable = - GradientDrawable().apply { setColor(requireContext().resolveAttribute(android.R.attr.windowBackground)) } + GradientDrawable().apply { + setColor(requireContext().resolveAttribute(android.R.attr.windowBackground)) + } view.background = gradientDrawable } @@ -133,13 +141,19 @@ private constructor( return this } - fun setPositiveButtonClickListener(buttonLabel: String? = null, listener: View.OnClickListener): Builder { + fun setPositiveButtonClickListener( + buttonLabel: String? = null, + listener: View.OnClickListener + ): Builder { this.positiveButtonClickListener = listener this.positiveButtonLabel = buttonLabel return this } - fun setNegativeButtonClickListener(buttonLabel: String? = null, listener: View.OnClickListener): Builder { + fun setNegativeButtonClickListener( + buttonLabel: String? = null, + listener: View.OnClickListener + ): Builder { this.negativeButtonClickListener = listener this.negativeButtonLabel = buttonLabel return this diff --git a/app/src/main/java/dev/msfjarvis/aps/ui/dialogs/ItemCreationBottomSheet.kt b/app/src/main/java/dev/msfjarvis/aps/ui/dialogs/ItemCreationBottomSheet.kt index fcf7b372..cb693d28 100644 --- a/app/src/main/java/dev/msfjarvis/aps/ui/dialogs/ItemCreationBottomSheet.kt +++ b/app/src/main/java/dev/msfjarvis/aps/ui/dialogs/ItemCreationBottomSheet.kt @@ -37,7 +37,11 @@ class ItemCreationBottomSheet : BottomSheetDialogFragment() { } } - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { if (savedInstanceState != null) dismiss() return inflater.inflate(R.layout.item_create_sheet, container, false) } @@ -67,7 +71,9 @@ class ItemCreationBottomSheet : BottomSheetDialogFragment() { } ) val gradientDrawable = - GradientDrawable().apply { setColor(requireContext().resolveAttribute(android.R.attr.windowBackground)) } + GradientDrawable().apply { + setColor(requireContext().resolveAttribute(android.R.attr.windowBackground)) + } view.background = gradientDrawable } diff --git a/app/src/main/java/dev/msfjarvis/aps/ui/dialogs/PasswordGeneratorDialogFragment.kt b/app/src/main/java/dev/msfjarvis/aps/ui/dialogs/PasswordGeneratorDialogFragment.kt index 16a9eefb..4820521f 100644 --- a/app/src/main/java/dev/msfjarvis/aps/ui/dialogs/PasswordGeneratorDialogFragment.kt +++ b/app/src/main/java/dev/msfjarvis/aps/ui/dialogs/PasswordGeneratorDialogFragment.kt @@ -36,7 +36,10 @@ class PasswordGeneratorDialogFragment : DialogFragment() { val callingActivity = requireActivity() val binding = FragmentPwgenBinding.inflate(layoutInflater) val monoTypeface = Typeface.createFromAsset(callingActivity.assets, "fonts/sourcecodepro.ttf") - val prefs = requireActivity().applicationContext.getSharedPreferences("PasswordGenerator", Context.MODE_PRIVATE) + val prefs = + requireActivity() + .applicationContext + .getSharedPreferences("PasswordGenerator", Context.MODE_PRIVATE) builder.setView(binding.root) @@ -65,7 +68,9 @@ class PasswordGeneratorDialogFragment : DialogFragment() { .apply { setOnShowListener { generate(binding.passwordText) - getButton(AlertDialog.BUTTON_NEGATIVE).setOnClickListener { generate(binding.passwordText) } + getButton(AlertDialog.BUTTON_NEGATIVE).setOnClickListener { + generate(binding.passwordText) + } } } } diff --git a/app/src/main/java/dev/msfjarvis/aps/ui/dialogs/XkPasswordGeneratorDialogFragment.kt b/app/src/main/java/dev/msfjarvis/aps/ui/dialogs/XkPasswordGeneratorDialogFragment.kt index e2e7426b..ad252975 100644 --- a/app/src/main/java/dev/msfjarvis/aps/ui/dialogs/XkPasswordGeneratorDialogFragment.kt +++ b/app/src/main/java/dev/msfjarvis/aps/ui/dialogs/XkPasswordGeneratorDialogFragment.kt @@ -49,7 +49,9 @@ class XkPasswordGeneratorDialogFragment : DialogFragment() { binding.xkNumWords.setText(prefs.getString(PREF_KEY_NUM_WORDS, DEFAULT_NUMBER_OF_WORDS)) binding.xkSeparator.setText(prefs.getString(PREF_KEY_SEPARATOR, DEFAULT_WORD_SEPARATOR)) - binding.xkNumberSymbolMask.setText(prefs.getString(PREF_KEY_EXTRA_SYMBOLS_MASK, DEFAULT_EXTRA_SYMBOLS_MASK)) + binding.xkNumberSymbolMask.setText( + prefs.getString(PREF_KEY_EXTRA_SYMBOLS_MASK, DEFAULT_EXTRA_SYMBOLS_MASK) + ) binding.xkPasswordText.typeface = monoTypeface @@ -85,8 +87,12 @@ class XkPasswordGeneratorDialogFragment : DialogFragment() { .setMinimumWordLength(DEFAULT_MIN_WORD_LENGTH) .setMaximumWordLength(DEFAULT_MAX_WORD_LENGTH) .setSeparator(binding.xkSeparator.text.toString()) - .appendNumbers(binding.xkNumberSymbolMask.text!!.count { c -> c == EXTRA_CHAR_PLACEHOLDER_DIGIT }) - .appendSymbols(binding.xkNumberSymbolMask.text!!.count { c -> c == EXTRA_CHAR_PLACEHOLDER_SYMBOL }) + .appendNumbers( + binding.xkNumberSymbolMask.text!!.count { c -> c == EXTRA_CHAR_PLACEHOLDER_DIGIT } + ) + .appendSymbols( + binding.xkNumberSymbolMask.text!!.count { c -> c == EXTRA_CHAR_PLACEHOLDER_SYMBOL } + ) .setCapitalization(CapsType.valueOf(binding.xkCapType.selectedItem.toString())) .create() .fold( diff --git a/app/src/main/java/dev/msfjarvis/aps/ui/folderselect/SelectFolderActivity.kt b/app/src/main/java/dev/msfjarvis/aps/ui/folderselect/SelectFolderActivity.kt index 1c6a236e..48ed5b79 100644 --- a/app/src/main/java/dev/msfjarvis/aps/ui/folderselect/SelectFolderActivity.kt +++ b/app/src/main/java/dev/msfjarvis/aps/ui/folderselect/SelectFolderActivity.kt @@ -24,7 +24,10 @@ class SelectFolderActivity : AppCompatActivity(R.layout.select_folder_layout) { passwordList = SelectFolderFragment() val args = Bundle() - args.putString(PasswordStore.REQUEST_ARG_PATH, PasswordRepository.getRepositoryDirectory().absolutePath) + args.putString( + PasswordStore.REQUEST_ARG_PATH, + PasswordRepository.getRepositoryDirectory().absolutePath + ) passwordList.arguments = args @@ -32,7 +35,9 @@ class SelectFolderActivity : AppCompatActivity(R.layout.select_folder_layout) { supportFragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE) - supportFragmentManager.commit { replace(R.id.pgp_handler_linearlayout, passwordList, PASSWORD_FRAGMENT_TAG) } + supportFragmentManager.commit { + replace(R.id.pgp_handler_linearlayout, passwordList, PASSWORD_FRAGMENT_TAG) + } } override fun onCreateOptionsMenu(menu: Menu): Boolean { diff --git a/app/src/main/java/dev/msfjarvis/aps/ui/folderselect/SelectFolderFragment.kt b/app/src/main/java/dev/msfjarvis/aps/ui/folderselect/SelectFolderFragment.kt index 1905aab0..8c9d5801 100644 --- a/app/src/main/java/dev/msfjarvis/aps/ui/folderselect/SelectFolderFragment.kt +++ b/app/src/main/java/dev/msfjarvis/aps/ui/folderselect/SelectFolderFragment.kt @@ -35,7 +35,10 @@ class SelectFolderFragment : Fragment(R.layout.password_recycler_view) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) binding.fab.hide() - recyclerAdapter = PasswordItemRecyclerAdapter().onItemClicked { _, item -> listener.onFragmentInteraction(item) } + recyclerAdapter = + PasswordItemRecyclerAdapter().onItemClicked { _, item -> + listener.onFragmentInteraction(item) + } binding.passRecycler.apply { layoutManager = LinearLayoutManager(requireContext()) itemAnimator = null @@ -47,7 +50,9 @@ class SelectFolderFragment : Fragment(R.layout.password_recycler_view) { val path = requireNotNull(requireArguments().getString(PasswordStore.REQUEST_ARG_PATH)) model.navigateTo(File(path), listMode = ListMode.DirectoriesOnly, pushPreviousLocation = false) - model.searchResult.observe(viewLifecycleOwner) { result -> recyclerAdapter.submitList(result.passwordItems) } + model.searchResult.observe(viewLifecycleOwner) { result -> + recyclerAdapter.submitList(result.passwordItems) + } } override fun onAttach(context: Context) { @@ -58,12 +63,16 @@ class SelectFolderFragment : Fragment(R.layout.password_recycler_view) { override fun onFragmentInteraction(item: PasswordItem) { if (item.type == PasswordItem.TYPE_CATEGORY) { model.navigateTo(item.file, listMode = ListMode.DirectoriesOnly) - (requireActivity() as AppCompatActivity).supportActionBar?.setDisplayHomeAsUpEnabled(true) + (requireActivity() as AppCompatActivity).supportActionBar?.setDisplayHomeAsUpEnabled( + true + ) } } } } - .onFailure { throw ClassCastException("$context must implement OnFragmentInteractionListener") } + .onFailure { + throw ClassCastException("$context must implement OnFragmentInteractionListener") + } } val currentDir: File diff --git a/app/src/main/java/dev/msfjarvis/aps/ui/git/base/BaseGitActivity.kt b/app/src/main/java/dev/msfjarvis/aps/ui/git/base/BaseGitActivity.kt index ac4c400d..b9bfd911 100644 --- a/app/src/main/java/dev/msfjarvis/aps/ui/git/base/BaseGitActivity.kt +++ b/app/src/main/java/dev/msfjarvis/aps/ui/git/base/BaseGitActivity.kt @@ -116,7 +116,8 @@ abstract class BaseGitActivity : ContinuationContainerActivity() { "Your local repository appears to be an incomplete Git clone, please delete and re-clone from settings" ) } - err is TransportException && err.disconnectReason == DisconnectReason.HOST_KEY_NOT_VERIFIABLE -> { + err is TransportException && + err.disconnectReason == DisconnectReason.HOST_KEY_NOT_VERIFIABLE -> { SSHException( 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." @@ -135,7 +136,9 @@ abstract class BaseGitActivity : ContinuationContainerActivity() { private fun isExplicitlyUserInitiatedError(throwable: Throwable): Boolean { var cause: Throwable? = throwable while (cause != null) { - if (cause is SSHException && cause.disconnectReason == DisconnectReason.AUTH_CANCELLED_BY_USER) return true + if (cause is SSHException && cause.disconnectReason == DisconnectReason.AUTH_CANCELLED_BY_USER + ) + return true cause = cause.cause } return false @@ -154,7 +157,8 @@ abstract class BaseGitActivity : ContinuationContainerActivity() { while ((rootCause is org.eclipse.jgit.errors.TransportException || rootCause is org.eclipse.jgit.api.errors.TransportException || rootCause is org.eclipse.jgit.api.errors.InvalidRemoteException || - (rootCause is UserAuthException && rootCause.message == "Exhausted available authentication methods"))) { + (rootCause is UserAuthException && + rootCause.message == "Exhausted available authentication methods"))) { rootCause = rootCause.cause ?: break } return rootCause diff --git a/app/src/main/java/dev/msfjarvis/aps/ui/git/config/GitConfigActivity.kt b/app/src/main/java/dev/msfjarvis/aps/ui/git/config/GitConfigActivity.kt index a1125ef0..faaf426f 100644 --- a/app/src/main/java/dev/msfjarvis/aps/ui/git/config/GitConfigActivity.kt +++ b/app/src/main/java/dev/msfjarvis/aps/ui/git/config/GitConfigActivity.kt @@ -55,7 +55,12 @@ class GitConfigActivity : BaseGitActivity() { } else { GitSettings.authorEmail = email GitSettings.authorName = name - Snackbar.make(binding.root, getString(R.string.git_server_config_save_success), Snackbar.LENGTH_SHORT).show() + Snackbar.make( + binding.root, + getString(R.string.git_server_config_save_success), + Snackbar.LENGTH_SHORT + ) + .show() Handler(Looper.getMainLooper()).postDelayed(500) { finish() } } } @@ -77,7 +82,8 @@ class GitConfigActivity : BaseGitActivity() { if (repo != null) { binding.gitHeadStatus.text = headStatusMsg(repo) // enable the abort button only if we're rebasing or merging - val needsAbort = repo.repositoryState.isRebasing || repo.repositoryState == RepositoryState.MERGING + val needsAbort = + repo.repositoryState.isRebasing || repo.repositoryState == RepositoryState.MERGING binding.gitAbortRebase.isEnabled = needsAbort binding.gitAbortRebase.alpha = if (needsAbort) 1.0f else 0.5f } diff --git a/app/src/main/java/dev/msfjarvis/aps/ui/git/config/GitServerConfigActivity.kt b/app/src/main/java/dev/msfjarvis/aps/ui/git/config/GitServerConfigActivity.kt index 372d5863..cab8d3ae 100644 --- a/app/src/main/java/dev/msfjarvis/aps/ui/git/config/GitServerConfigActivity.kt +++ b/app/src/main/java/dev/msfjarvis/aps/ui/git/config/GitServerConfigActivity.kt @@ -89,7 +89,12 @@ class GitServerConfigActivity : BaseGitActivity() { binding.clearHostKeyButton.isVisible = GitSettings.hasSavedHostKey() binding.clearHostKeyButton.setOnClickListener { GitSettings.clearSavedHostKey() - Snackbar.make(binding.root, getString(R.string.clear_saved_host_key_success), Snackbar.LENGTH_LONG).show() + Snackbar.make( + binding.root, + getString(R.string.clear_saved_host_key_success), + Snackbar.LENGTH_LONG + ) + .show() it.isVisible = false } binding.saveButton.setOnClickListener { @@ -102,7 +107,9 @@ class GitServerConfigActivity : BaseGitActivity() { BasicBottomSheet.Builder(this) .setTitleRes(R.string.https_scheme_with_port_title) .setMessageRes(R.string.https_scheme_with_port_message) - .setPositiveButtonClickListener { binding.serverUrl.setText(newUrl.replace(PORT_REGEX, "/")) } + .setPositiveButtonClickListener { + binding.serverUrl.setText(newUrl.replace(PORT_REGEX, "/")) + } .build() .show(supportFragmentManager, "SSH_SCHEME_WARNING") return@setOnClickListener @@ -110,7 +117,9 @@ class GitServerConfigActivity : BaseGitActivity() { BasicBottomSheet.Builder(this) .setTitleRes(R.string.ssh_scheme_needed_title) .setMessageRes(R.string.ssh_scheme_needed_message) - .setPositiveButtonClickListener { @Suppress("SetTextI18n") binding.serverUrl.setText("ssh://$newUrl") } + .setPositiveButtonClickListener { + @Suppress("SetTextI18n") binding.serverUrl.setText("ssh://$newUrl") + } .build() .show(supportFragmentManager, "SSH_SCHEME_WARNING") return@setOnClickListener @@ -133,7 +142,12 @@ class GitServerConfigActivity : BaseGitActivity() { ) ) { GitSettings.UpdateConnectionSettingsResult.FailedToParseUrl -> { - Snackbar.make(binding.root, getString(R.string.git_server_config_save_error), Snackbar.LENGTH_LONG).show() + Snackbar.make( + binding.root, + getString(R.string.git_server_config_save_error), + Snackbar.LENGTH_LONG + ) + .show() } is GitSettings.UpdateConnectionSettingsResult.MissingUsername -> { when (updateResult.newProtocol) { @@ -154,9 +168,14 @@ class GitServerConfigActivity : BaseGitActivity() { } } GitSettings.UpdateConnectionSettingsResult.Valid -> { - if (isClone && PasswordRepository.getRepository(null) == null) PasswordRepository.initialize() + if (isClone && PasswordRepository.getRepository(null) == null) + PasswordRepository.initialize() if (!isClone) { - Snackbar.make(binding.root, getString(R.string.git_server_config_save_success), Snackbar.LENGTH_SHORT) + Snackbar.make( + binding.root, + getString(R.string.git_server_config_save_success), + Snackbar.LENGTH_SHORT + ) .show() Handler(Looper.getMainLooper()).postDelayed(500) { finish() } } else { @@ -206,7 +225,9 @@ class GitServerConfigActivity : BaseGitActivity() { val localDir = requireNotNull(PasswordRepository.getRepositoryDirectory()) val localDirFiles = localDir.listFiles() ?: emptyArray() // Warn if non-empty folder unless it's a just-initialized store that has just a .git folder - if (localDir.exists() && localDirFiles.isNotEmpty() && !(localDirFiles.size == 1 && localDirFiles[0].name == ".git") + if (localDir.exists() && + localDirFiles.isNotEmpty() && + !(localDirFiles.size == 1 && localDirFiles[0].name == ".git") ) { MaterialAlertDialogBuilder(this) .setTitle(R.string.dialog_delete_title) @@ -269,7 +290,9 @@ class GitServerConfigActivity : BaseGitActivity() { private val PORT_REGEX = ":[0-9]{1,5}/".toRegex() fun createCloneIntent(context: Context): Intent { - return Intent(context, GitServerConfigActivity::class.java).apply { putExtra("cloning", true) } + return Intent(context, GitServerConfigActivity::class.java).apply { + putExtra("cloning", true) + } } } } diff --git a/app/src/main/java/dev/msfjarvis/aps/ui/git/log/GitLogAdapter.kt b/app/src/main/java/dev/msfjarvis/aps/ui/git/log/GitLogAdapter.kt index 4b29542e..1470d642 100644 --- a/app/src/main/java/dev/msfjarvis/aps/ui/git/log/GitLogAdapter.kt +++ b/app/src/main/java/dev/msfjarvis/aps/ui/git/log/GitLogAdapter.kt @@ -45,7 +45,8 @@ class GitLogAdapter : RecyclerView.Adapter<GitLogAdapter.ViewHolder>() { override fun getItemCount() = model.size - class ViewHolder(private val binding: GitLogRowLayoutBinding) : RecyclerView.ViewHolder(binding.root) { + class ViewHolder(private val binding: GitLogRowLayoutBinding) : + RecyclerView.ViewHolder(binding.root) { fun bind(commit: GitCommit) = with(binding) { diff --git a/app/src/main/java/dev/msfjarvis/aps/ui/onboarding/fragments/CloneFragment.kt b/app/src/main/java/dev/msfjarvis/aps/ui/onboarding/fragments/CloneFragment.kt index 12a97d45..407da1d1 100644 --- a/app/src/main/java/dev/msfjarvis/aps/ui/onboarding/fragments/CloneFragment.kt +++ b/app/src/main/java/dev/msfjarvis/aps/ui/onboarding/fragments/CloneFragment.kt @@ -24,7 +24,9 @@ class CloneFragment : Fragment(R.layout.fragment_clone) { private val binding by viewBinding(FragmentCloneBinding::bind) - private val settings by lazy(LazyThreadSafetyMode.NONE) { requireActivity().applicationContext.sharedPrefs } + private val settings by lazy(LazyThreadSafetyMode.NONE) { + requireActivity().applicationContext.sharedPrefs + } private val cloneAction = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> diff --git a/app/src/main/java/dev/msfjarvis/aps/ui/onboarding/fragments/KeySelectionFragment.kt b/app/src/main/java/dev/msfjarvis/aps/ui/onboarding/fragments/KeySelectionFragment.kt index 3d7a66b3..4123f031 100644 --- a/app/src/main/java/dev/msfjarvis/aps/ui/onboarding/fragments/KeySelectionFragment.kt +++ b/app/src/main/java/dev/msfjarvis/aps/ui/onboarding/fragments/KeySelectionFragment.kt @@ -32,7 +32,9 @@ import me.msfjarvis.openpgpktx.util.OpenPgpApi class KeySelectionFragment : Fragment(R.layout.fragment_key_selection) { - private val settings by lazy(LazyThreadSafetyMode.NONE) { requireActivity().applicationContext.sharedPrefs } + private val settings by lazy(LazyThreadSafetyMode.NONE) { + requireActivity().applicationContext.sharedPrefs + } private val binding by viewBinding(FragmentKeySelectionBinding::bind) private val gpgKeySelectAction = @@ -45,13 +47,17 @@ class KeySelectionFragment : Fragment(R.layout.fragment_key_selection) { gpgIdentifierFile.writeText((keyIds + "").joinToString("\n")) } settings.edit { putBoolean(PreferenceKeys.REPOSITORY_INITIALIZED, true) } - requireActivity().commitChange(getString(R.string.git_commit_gpg_id, getString(R.string.app_name))) + requireActivity() + .commitChange(getString(R.string.git_commit_gpg_id, getString(R.string.app_name))) } } finish() } else { requireActivity() - .snackbar(message = getString(R.string.gpg_key_select_mandatory), length = Snackbar.LENGTH_LONG) + .snackbar( + message = getString(R.string.gpg_key_select_mandatory), + length = Snackbar.LENGTH_LONG + ) } } diff --git a/app/src/main/java/dev/msfjarvis/aps/ui/onboarding/fragments/RepoLocationFragment.kt b/app/src/main/java/dev/msfjarvis/aps/ui/onboarding/fragments/RepoLocationFragment.kt index 9adbc3e8..2fdb82e8 100644 --- a/app/src/main/java/dev/msfjarvis/aps/ui/onboarding/fragments/RepoLocationFragment.kt +++ b/app/src/main/java/dev/msfjarvis/aps/ui/onboarding/fragments/RepoLocationFragment.kt @@ -35,7 +35,9 @@ import java.io.File class RepoLocationFragment : Fragment(R.layout.fragment_repo_location) { - private val settings by lazy(LazyThreadSafetyMode.NONE) { requireActivity().applicationContext.sharedPrefs } + private val settings by lazy(LazyThreadSafetyMode.NONE) { + requireActivity().applicationContext.sharedPrefs + } private val directorySelectIntent by lazy(LazyThreadSafetyMode.NONE) { Intent(requireContext(), DirectorySelectionActivity::class.java) } @@ -65,7 +67,9 @@ class RepoLocationFragment : Fragment(R.layout.fragment_repo_location) { externalDirectorySelectAction.launch(directorySelectIntent) } - private val repositoryUsePermGrantedAction = createPermGrantedAction { initializeRepositoryInfo() } + private val repositoryUsePermGrantedAction = createPermGrantedAction { + initializeRepositoryInfo() + } private val repositoryChangePermGrantedAction = createPermGrantedAction { repositoryInitAction.launch(directorySelectIntent) @@ -132,7 +136,12 @@ class RepoLocationFragment : Fragment(R.layout.fragment_repo_location) { dir.isDirectory && // The directory, is really a directory dir.listFilesRecursively().isNotEmpty() && // The directory contains files // The directory contains a non-zero number of password files - PasswordRepository.getPasswords(dir, PasswordRepository.getRepositoryDirectory(), sortOrder).isNotEmpty() + PasswordRepository.getPasswords( + dir, + PasswordRepository.getRepositoryDirectory(), + sortOrder + ) + .isNotEmpty() ) { PasswordRepository.closeRepository() return true diff --git a/app/src/main/java/dev/msfjarvis/aps/ui/onboarding/fragments/WelcomeFragment.kt b/app/src/main/java/dev/msfjarvis/aps/ui/onboarding/fragments/WelcomeFragment.kt index 7c3788c7..e47eeffc 100644 --- a/app/src/main/java/dev/msfjarvis/aps/ui/onboarding/fragments/WelcomeFragment.kt +++ b/app/src/main/java/dev/msfjarvis/aps/ui/onboarding/fragments/WelcomeFragment.kt @@ -27,6 +27,8 @@ class WelcomeFragment : Fragment(R.layout.fragment_welcome) { binding.letsGo.setOnClickListener { parentFragmentManager.performTransactionWithBackStack(CloneFragment.newInstance()) } - binding.settingsButton.setOnClickListener { startActivity(Intent(requireContext(), SettingsActivity::class.java)) } + binding.settingsButton.setOnClickListener { + startActivity(Intent(requireContext(), SettingsActivity::class.java)) + } } } diff --git a/app/src/main/java/dev/msfjarvis/aps/ui/passwords/PasswordFragment.kt b/app/src/main/java/dev/msfjarvis/aps/ui/passwords/PasswordFragment.kt index 3740404a..541a3f9e 100644 --- a/app/src/main/java/dev/msfjarvis/aps/ui/passwords/PasswordFragment.kt +++ b/app/src/main/java/dev/msfjarvis/aps/ui/passwords/PasswordFragment.kt @@ -77,8 +77,12 @@ class PasswordFragment : Fragment(R.layout.password_recycler_view) { super.onViewCreated(view, savedInstanceState) settings = requireContext().sharedPrefs initializePasswordList() - binding.fab.setOnClickListener { ItemCreationBottomSheet().show(childFragmentManager, "BOTTOM_SHEET") } - childFragmentManager.setFragmentResultListener(ITEM_CREATION_REQUEST_KEY, viewLifecycleOwner) { _, bundle -> + binding.fab.setOnClickListener { + ItemCreationBottomSheet().show(childFragmentManager, "BOTTOM_SHEET") + } + childFragmentManager.setFragmentResultListener(ITEM_CREATION_REQUEST_KEY, viewLifecycleOwner) { + _, + bundle -> when (bundle.getString(ACTION_KEY)) { ACTION_FOLDER -> requireStore().createFolder() ACTION_PASSWORD -> requireStore().createPassword() @@ -88,7 +92,8 @@ class PasswordFragment : Fragment(R.layout.password_recycler_view) { private fun initializePasswordList() { val gitDir = File(PasswordRepository.getRepositoryDirectory(), ".git") - val hasGitDir = gitDir.exists() && gitDir.isDirectory && (gitDir.listFiles()?.isNotEmpty() == true) + val hasGitDir = + gitDir.exists() && gitDir.isDirectory && (gitDir.listFiles()?.isNotEmpty() == true) binding.swipeRefresher.setOnRefreshListener { if (!hasGitDir) { requireStore().refreshPasswordList() @@ -118,7 +123,9 @@ class PasswordFragment : Fragment(R.layout.password_recycler_view) { binding.swipeRefresher.isRefreshing = false refreshPasswordList() }, - failure = { err -> promptOnErrorHandler(err) { binding.swipeRefresher.isRefreshing = false } }, + failure = { err -> + promptOnErrorHandler(err) { binding.swipeRefresher.isRefreshing = false } + }, ) } } @@ -135,10 +142,16 @@ class PasswordFragment : Fragment(R.layout.password_recycler_view) { binding.swipeRefresher.isEnabled = selection.isEmpty if (actionMode == null) - actionMode = requireStore().startSupportActionMode(actionModeCallback) ?: return@onSelectionChanged + actionMode = + requireStore().startSupportActionMode(actionModeCallback) ?: return@onSelectionChanged if (!selection.isEmpty) { - actionMode!!.title = resources.getQuantityString(R.plurals.delete_title, selection.size(), selection.size()) + actionMode!!.title = + resources.getQuantityString( + R.plurals.delete_title, + selection.size(), + selection.size() + ) actionMode!!.invalidate() } else { actionMode!!.finish() @@ -171,14 +184,18 @@ class PasswordFragment : Fragment(R.layout.password_recycler_view) { recyclerView.scrollToPosition(0) } scrollTarget != null -> { - scrollTarget?.let { recyclerView.scrollToPosition(recyclerAdapter.getPositionForFile(it)) } + scrollTarget?.let { + recyclerView.scrollToPosition(recyclerAdapter.getPositionForFile(it)) + } scrollTarget = null } else -> { // When the result is not filtered and there is a saved scroll position for // it, // we try to restore it. - recyclerViewStateToRestore?.let { recyclerView.layoutManager!!.onRestoreInstanceState(it) } + recyclerViewStateToRestore?.let { + recyclerView.layoutManager!!.onRestoreInstanceState(it) + } recyclerViewStateToRestore = null } } @@ -201,7 +218,8 @@ class PasswordFragment : Fragment(R.layout.password_recycler_view) { // but may be called multiple times if the mode is invalidated. override fun onPrepareActionMode(mode: ActionMode, menu: Menu): Boolean { val selectedItems = recyclerAdapter.getSelectedItems() - menu.findItem(R.id.menu_edit_password).isVisible = selectedItems.all { it.type == PasswordItem.TYPE_CATEGORY } + menu.findItem(R.id.menu_edit_password).isVisible = + selectedItems.all { it.type == PasswordItem.TYPE_CATEGORY } menu.findItem(R.id.menu_pin_password).isVisible = selectedItems.size == 1 && selectedItems[0].type == PasswordItem.TYPE_PASSWORD return true @@ -227,7 +245,10 @@ class PasswordFragment : Fragment(R.layout.password_recycler_view) { } R.id.menu_pin_password -> { val passwordItem = recyclerAdapter.getSelectedItems()[0] - shortcutHandler.addPinnedShortcut(passwordItem, passwordItem.createAuthEnabledIntent(requireContext())) + shortcutHandler.addPinnedShortcut( + passwordItem, + passwordItem.createAuthEnabledIntent(requireContext()) + ) false } else -> false @@ -244,7 +265,8 @@ class PasswordFragment : Fragment(R.layout.password_recycler_view) { private fun animateFab(show: Boolean) = with(binding.fab) { - val animation = AnimationUtils.loadAnimation(context, if (show) R.anim.scale_up else R.anim.scale_down) + val animation = + AnimationUtils.loadAnimation(context, if (show) R.anim.scale_up else R.anim.scale_down) animation.setAnimationListener( object : Animation.AnimationListener { override fun onAnimationRepeat(animation: Animation?) {} @@ -258,7 +280,11 @@ class PasswordFragment : Fragment(R.layout.password_recycler_view) { } } ) - animate().rotationBy(if (show) -90f else 90f).setStartDelay(if (show) 100 else 0).setDuration(100).start() + animate() + .rotationBy(if (show) -90f else 90f) + .setStartDelay(if (show) 100 else 0) + .setDuration(100) + .start() startAnimation(animation) } } @@ -269,10 +295,15 @@ class PasswordFragment : Fragment(R.layout.password_recycler_view) { listener = object : OnFragmentInteractionListener { override fun onFragmentInteraction(item: PasswordItem) { - if (settings.getString(PreferenceKeys.SORT_ORDER) == PasswordSortOrder.RECENTLY_USED.name) { + if (settings.getString(PreferenceKeys.SORT_ORDER) == + PasswordSortOrder.RECENTLY_USED.name + ) { // save the time when password was used - val preferences = context.getSharedPreferences("recent_password_history", Context.MODE_PRIVATE) - preferences.edit { putString(item.file.absolutePath.base64(), System.currentTimeMillis().toString()) } + val preferences = + context.getSharedPreferences("recent_password_history", Context.MODE_PRIVATE) + preferences.edit { + putString(item.file.absolutePath.base64(), System.currentTimeMillis().toString()) + } } if (item.type == PasswordItem.TYPE_CATEGORY) { @@ -287,7 +318,9 @@ class PasswordFragment : Fragment(R.layout.password_recycler_view) { } } } - .onFailure { throw ClassCastException("$context must implement OnFragmentInteractionListener") } + .onFailure { + throw ClassCastException("$context must implement OnFragmentInteractionListener") + } } private fun requireStore() = requireActivity() as PasswordStore @@ -322,7 +355,10 @@ class PasswordFragment : Fragment(R.layout.password_recycler_view) { fun navigateTo(file: File) { requireStore().clearSearch() - model.navigateTo(file, recyclerViewState = binding.passRecycler.layoutManager!!.onSaveInstanceState()) + model.navigateTo( + file, + recyclerViewState = binding.passRecycler.layoutManager!!.onSaveInstanceState() + ) requireStore().supportActionBar?.setDisplayHomeAsUpEnabled(true) } diff --git a/app/src/main/java/dev/msfjarvis/aps/ui/passwords/PasswordStore.kt b/app/src/main/java/dev/msfjarvis/aps/ui/passwords/PasswordStore.kt index 8ab69552..b2591a92 100644 --- a/app/src/main/java/dev/msfjarvis/aps/ui/passwords/PasswordStore.kt +++ b/app/src/main/java/dev/msfjarvis/aps/ui/passwords/PasswordStore.kt @@ -82,7 +82,9 @@ class PasswordStore : BaseGitActivity() { } private val storagePermissionRequest = - registerForActivityResult(RequestPermission()) { granted -> if (granted) checkLocalRepository() } + registerForActivityResult(RequestPermission()) { granted -> + if (granted) checkLocalRepository() + } private val directorySelectAction = registerForActivityResult(StartActivityForResult()) { result -> @@ -128,7 +130,13 @@ class PasswordStore : BaseGitActivity() { withContext(Dispatchers.Main) { MaterialAlertDialogBuilder(this@PasswordStore) .setTitle(resources.getString(R.string.password_exists_title)) - .setMessage(resources.getString(R.string.password_exists_message, destinationLongName, sourceLongName)) + .setMessage( + resources.getString( + R.string.password_exists_message, + destinationLongName, + sourceLongName + ) + ) .setPositiveButton(R.string.dialog_ok) { _, _ -> launch(Dispatchers.IO) { moveFile(source, destinationFile) } } @@ -143,11 +151,16 @@ class PasswordStore : BaseGitActivity() { 1 -> { val source = File(filesToMove[0]) val basename = source.nameWithoutExtension - val sourceLongName = getLongName(requireNotNull(source.parent), repositoryPath, basename) + val sourceLongName = + getLongName(requireNotNull(source.parent), repositoryPath, basename) val destinationLongName = getLongName(target.absolutePath, repositoryPath, basename) withContext(Dispatchers.Main) { commitChange( - resources.getString(R.string.git_commit_move_text, sourceLongName, destinationLongName), + resources.getString( + R.string.git_commit_move_text, + sourceLongName, + destinationLongName + ), ) } } @@ -168,8 +181,8 @@ class PasswordStore : BaseGitActivity() { override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean { // open search view on search key, or Ctr+F - if ((keyCode == KeyEvent.KEYCODE_SEARCH || keyCode == KeyEvent.KEYCODE_F && event.isCtrlPressed) && - !searchItem.isActionViewExpanded + if ((keyCode == KeyEvent.KEYCODE_SEARCH || + keyCode == KeyEvent.KEYCODE_F && event.isCtrlPressed) && !searchItem.isActionViewExpanded ) { searchItem.expandActionView() return true @@ -202,7 +215,9 @@ class PasswordStore : BaseGitActivity() { model.currentDir.observe(this) { dir -> val basePath = PasswordRepository.getRepositoryDirectory().absoluteFile - supportActionBar!!.apply { if (dir != basePath) title = dir.name else setTitle(R.string.app_name) } + supportActionBar!!.apply { + if (dir != basePath) title = dir.name else setTitle(R.string.app_name) + } } } @@ -253,7 +268,8 @@ class PasswordStore : BaseGitActivity() { val filter = s.trim() // List the contents of the current directory if the user enters a blank // search term. - if (filter.isEmpty()) model.navigateTo(newDirectory = model.currentDir.value!!, pushPreviousLocation = false) + if (filter.isEmpty()) + model.navigateTo(newDirectory = model.currentDir.value!!, pushPreviousLocation = false) else model.search(filter) return true } @@ -288,7 +304,9 @@ class PasswordStore : BaseGitActivity() { .setPositiveButton(resources.getString(R.string.dialog_ok), null) when (id) { R.id.user_pref -> { - runCatching { startActivity(Intent(this, SettingsActivity::class.java)) }.onFailure { e -> e.printStackTrace() } + runCatching { startActivity(Intent(this, SettingsActivity::class.java)) }.onFailure { e -> + e.printStackTrace() + } } R.id.git_push -> { if (!PasswordRepository.isInitialized) { @@ -372,7 +390,8 @@ class PasswordStore : BaseGitActivity() { if (localDir != null && settings.getBoolean(PreferenceKeys.REPOSITORY_INITIALIZED, false)) { d { "Check, dir: ${localDir.absolutePath}" } // do not push the fragment if we already have it - if (getPasswordFragment() == null || settings.getBoolean(PreferenceKeys.REPO_CHANGED, false)) { + if (getPasswordFragment() == null || settings.getBoolean(PreferenceKeys.REPO_CHANGED, false) + ) { settings.edit { putBoolean(PreferenceKeys.REPO_CHANGED, false) } val args = Bundle() args.putString(REQUEST_ARG_PATH, PasswordRepository.getRepositoryDirectory().absolutePath) @@ -403,7 +422,9 @@ class PasswordStore : BaseGitActivity() { fun decryptPassword(item: PasswordItem) { val authDecryptIntent = item.createAuthEnabledIntent(this) val decryptIntent = - (authDecryptIntent.clone() as Intent).setComponent(ComponentName(this, DecryptActivity::class.java)) + (authDecryptIntent.clone() as Intent).setComponent( + ComponentName(this, DecryptActivity::class.java) + ) startActivity(decryptIntent) @@ -439,7 +460,9 @@ class PasswordStore : BaseGitActivity() { fun deletePasswords(selectedItems: List<PasswordItem>) { var size = 0 - selectedItems.forEach { if (it.file.isFile) size++ else size += it.file.listFilesRecursively().size } + selectedItems.forEach { + if (it.file.isFile) size++ else size += it.file.listFilesRecursively().size + } if (size == 0) { selectedItems.map { item -> item.file.deleteRecursively() } refreshPasswordList() @@ -497,7 +520,10 @@ class PasswordStore : BaseGitActivity() { * @see [CategoryRenameError] * @see [isInsideRepository] */ - private fun renameCategory(oldCategory: PasswordItem, error: CategoryRenameError = CategoryRenameError.None) { + private fun renameCategory( + oldCategory: PasswordItem, + error: CategoryRenameError = CategoryRenameError.None + ) { val view = layoutInflater.inflate(R.layout.folder_dialog_fragment, null) val newCategoryEditText = view.findViewById<TextInputEditText>(R.id.folder_name_text) @@ -513,16 +539,19 @@ class PasswordStore : BaseGitActivity() { .setPositiveButton(R.string.dialog_ok) { _, _ -> val newCategory = File("${oldCategory.file.parent}/${newCategoryEditText.text}") when { - newCategoryEditText.text.isNullOrBlank() -> renameCategory(oldCategory, CategoryRenameError.EmptyField) + newCategoryEditText.text.isNullOrBlank() -> + renameCategory(oldCategory, CategoryRenameError.EmptyField) newCategory.exists() -> renameCategory(oldCategory, CategoryRenameError.CategoryExists) - !newCategory.isInsideRepository() -> renameCategory(oldCategory, CategoryRenameError.DestinationOutsideRepo) + !newCategory.isInsideRepository() -> + renameCategory(oldCategory, CategoryRenameError.DestinationOutsideRepo) else -> lifecycleScope.launch(Dispatchers.IO) { moveFile(oldCategory.file, newCategory) // associate the new category with the last category's timestamp in // history - val preference = getSharedPreferences("recent_password_history", Context.MODE_PRIVATE) + val preference = + getSharedPreferences("recent_password_history", Context.MODE_PRIVATE) val timestamp = preference.getString(oldCategory.file.absolutePath.base64()) if (timestamp != null) { preference.edit { @@ -533,7 +562,11 @@ class PasswordStore : BaseGitActivity() { withContext(Dispatchers.Main) { commitChange( - resources.getString(R.string.git_commit_move_text, oldCategory.name, newCategory.name), + resources.getString( + R.string.git_commit_move_text, + oldCategory.name, + newCategory.name + ), ) } } @@ -584,7 +617,9 @@ class PasswordStore : BaseGitActivity() { // Recursively list all files (not directories) below `source`, then // obtain the corresponding target file by resolving the relative path // starting at the destination folder. - source.listFilesRecursively().associateWith { destinationFile.resolve(it.relativeTo(source)) } + source.listFilesRecursively().associateWith { + destinationFile.resolve(it.relativeTo(source)) + } } else { mapOf(source to destinationFile) } diff --git a/app/src/main/java/dev/msfjarvis/aps/ui/proxy/ProxySelectorActivity.kt b/app/src/main/java/dev/msfjarvis/aps/ui/proxy/ProxySelectorActivity.kt index 3841438e..e3761292 100644 --- a/app/src/main/java/dev/msfjarvis/aps/ui/proxy/ProxySelectorActivity.kt +++ b/app/src/main/java/dev/msfjarvis/aps/ui/proxy/ProxySelectorActivity.kt @@ -28,7 +28,9 @@ private val WEB_ADDRESS_REGEX = Patterns.WEB_URL.toRegex() class ProxySelectorActivity : AppCompatActivity() { private val binding by viewBinding(ActivityProxySelectorBinding::inflate) - private val proxyPrefs by lazy(LazyThreadSafetyMode.NONE) { applicationContext.getEncryptedProxyPrefs() } + private val proxyPrefs by lazy(LazyThreadSafetyMode.NONE) { + applicationContext.getEncryptedProxyPrefs() + } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -36,7 +38,9 @@ class ProxySelectorActivity : AppCompatActivity() { with(binding) { proxyHost.setText(proxyPrefs.getString(PreferenceKeys.PROXY_HOST)) proxyUser.setText(proxyPrefs.getString(PreferenceKeys.PROXY_USERNAME)) - proxyPrefs.getInt(PreferenceKeys.PROXY_PORT, -1).takeIf { it != -1 }?.let { proxyPort.setText("$it") } + proxyPrefs.getInt(PreferenceKeys.PROXY_PORT, -1).takeIf { it != -1 }?.let { + proxyPort.setText("$it") + } proxyPassword.setText(proxyPrefs.getString(PreferenceKeys.PROXY_PASSWORD)) save.setOnClickListener { saveSettings() } proxyHost.doOnTextChanged { text, _, _, _ -> @@ -54,10 +58,18 @@ class ProxySelectorActivity : AppCompatActivity() { private fun saveSettings() { proxyPrefs.edit { - binding.proxyHost.text?.toString()?.takeIf { it.isNotEmpty() }.let { GitSettings.proxyHost = it } - binding.proxyUser.text?.toString()?.takeIf { it.isNotEmpty() }.let { GitSettings.proxyUsername = it } - binding.proxyPort.text?.toString()?.takeIf { it.isNotEmpty() }?.let { GitSettings.proxyPort = it.toInt() } - binding.proxyPassword.text?.toString()?.takeIf { it.isNotEmpty() }.let { GitSettings.proxyPassword = it } + binding.proxyHost.text?.toString()?.takeIf { it.isNotEmpty() }.let { + GitSettings.proxyHost = it + } + binding.proxyUser.text?.toString()?.takeIf { it.isNotEmpty() }.let { + GitSettings.proxyUsername = it + } + binding.proxyPort.text?.toString()?.takeIf { it.isNotEmpty() }?.let { + GitSettings.proxyPort = it.toInt() + } + binding.proxyPassword.text?.toString()?.takeIf { it.isNotEmpty() }.let { + GitSettings.proxyPassword = it + } } ProxyUtils.setDefaultProxy() Handler(Looper.getMainLooper()).postDelayed(500) { finish() } diff --git a/app/src/main/java/dev/msfjarvis/aps/ui/settings/AutofillSettings.kt b/app/src/main/java/dev/msfjarvis/aps/ui/settings/AutofillSettings.kt index ee6b468b..10dae205 100644 --- a/app/src/main/java/dev/msfjarvis/aps/ui/settings/AutofillSettings.kt +++ b/app/src/main/java/dev/msfjarvis/aps/ui/settings/AutofillSettings.kt @@ -59,13 +59,18 @@ class AutofillSettings(private val activity: FragmentActivity) : SettingsProvide val appLabel = it.first val supportDescription = when (it.second) { - BrowserAutofillSupportLevel.None -> activity.getString(R.string.oreo_autofill_no_support) - BrowserAutofillSupportLevel.FlakyFill -> activity.getString(R.string.oreo_autofill_flaky_fill_support) + BrowserAutofillSupportLevel.None -> + activity.getString(R.string.oreo_autofill_no_support) + BrowserAutofillSupportLevel.FlakyFill -> + activity.getString(R.string.oreo_autofill_flaky_fill_support) BrowserAutofillSupportLevel.PasswordFill -> activity.getString(R.string.oreo_autofill_password_fill_support) BrowserAutofillSupportLevel.PasswordFillAndSaveIfNoAccessibility -> - activity.getString(R.string.oreo_autofill_password_fill_and_conditional_save_support) - BrowserAutofillSupportLevel.GeneralFill -> activity.getString(R.string.oreo_autofill_general_fill_support) + activity.getString( + R.string.oreo_autofill_password_fill_and_conditional_save_support + ) + BrowserAutofillSupportLevel.GeneralFill -> + activity.getString(R.string.oreo_autofill_general_fill_support) BrowserAutofillSupportLevel.GeneralFillAndSave -> activity.getString(R.string.oreo_autofill_general_fill_and_save_support) } @@ -102,8 +107,10 @@ class AutofillSettings(private val activity: FragmentActivity) : SettingsProvide false } } - val values = activity.resources.getStringArray(R.array.oreo_autofill_directory_structure_values) - val titles = activity.resources.getStringArray(R.array.oreo_autofill_directory_structure_entries) + val values = + activity.resources.getStringArray(R.array.oreo_autofill_directory_structure_values) + val titles = + activity.resources.getStringArray(R.array.oreo_autofill_directory_structure_entries) val items = values.zip(titles).map { SelectionItem(it.first, it.second, null) } singleChoice(PreferenceKeys.OREO_AUTOFILL_DIRECTORY_STRUCTURE, items) { initialSelection = DirectoryStructure.DEFAULT.value diff --git a/app/src/main/java/dev/msfjarvis/aps/ui/settings/DirectorySelectionActivity.kt b/app/src/main/java/dev/msfjarvis/aps/ui/settings/DirectorySelectionActivity.kt index 475d3b5e..039b7a59 100644 --- a/app/src/main/java/dev/msfjarvis/aps/ui/settings/DirectorySelectionActivity.kt +++ b/app/src/main/java/dev/msfjarvis/aps/ui/settings/DirectorySelectionActivity.kt @@ -39,7 +39,9 @@ class DirectorySelectionActivity : AppCompatActivity() { MaterialAlertDialogBuilder(this) .setTitle(resources.getString(R.string.sdcard_root_warning_title)) .setMessage(resources.getString(R.string.sdcard_root_warning_message)) - .setPositiveButton(resources.getString(R.string.sdcard_root_warning_remove_everything)) { _, _ -> + .setPositiveButton(resources.getString(R.string.sdcard_root_warning_remove_everything)) { + _, + _ -> prefs.edit { putString(PreferenceKeys.GIT_EXTERNAL_REPO, uri.path) } } .setNegativeButton(R.string.dialog_cancel, null) diff --git a/app/src/main/java/dev/msfjarvis/aps/ui/settings/GeneralSettings.kt b/app/src/main/java/dev/msfjarvis/aps/ui/settings/GeneralSettings.kt index da93c3fe..1e67a2b5 100644 --- a/app/src/main/java/dev/msfjarvis/aps/ui/settings/GeneralSettings.kt +++ b/app/src/main/java/dev/msfjarvis/aps/ui/settings/GeneralSettings.kt @@ -26,7 +26,8 @@ class GeneralSettings(private val activity: FragmentActivity) : SettingsProvider builder.apply { val themeValues = activity.resources.getStringArray(R.array.app_theme_values) val themeOptions = activity.resources.getStringArray(R.array.app_theme_options) - val themeItems = themeValues.zip(themeOptions).map { SelectionItem(it.first, it.second, null) } + val themeItems = + themeValues.zip(themeOptions).map { SelectionItem(it.first, it.second, null) } singleChoice(PreferenceKeys.APP_THEME, themeItems) { initialSelection = activity.resources.getString(R.string.app_theme_def) titleRes = R.string.pref_app_theme_title @@ -64,7 +65,8 @@ class GeneralSettings(private val activity: FragmentActivity) : SettingsProvider defaultValue = false enabled = canAuthenticate summaryRes = - if (canAuthenticate) R.string.pref_biometric_auth_summary else R.string.pref_biometric_auth_summary_error + if (canAuthenticate) R.string.pref_biometric_auth_summary + else R.string.pref_biometric_auth_summary_error onClick { enabled = false val isChecked = checked diff --git a/app/src/main/java/dev/msfjarvis/aps/ui/settings/RepositorySettings.kt b/app/src/main/java/dev/msfjarvis/aps/ui/settings/RepositorySettings.kt index 3e99c890..ba871f91 100644 --- a/app/src/main/java/dev/msfjarvis/aps/ui/settings/RepositorySettings.kt +++ b/app/src/main/java/dev/msfjarvis/aps/ui/settings/RepositorySettings.kt @@ -37,7 +37,9 @@ import dev.msfjarvis.aps.util.settings.PreferenceKeys class RepositorySettings(private val activity: FragmentActivity) : SettingsProvider { - private val encryptedPreferences by lazy(LazyThreadSafetyMode.NONE) { activity.getEncryptedGitPrefs() } + private val encryptedPreferences by lazy(LazyThreadSafetyMode.NONE) { + activity.getEncryptedGitPrefs() + } private fun <T : FragmentActivity> launchActivity(clazz: Class<T>) { activity.startActivity(Intent(activity, clazz)) @@ -47,7 +49,9 @@ class RepositorySettings(private val activity: FragmentActivity) : SettingsProvi MaterialAlertDialogBuilder(activity) .setTitle(activity.resources.getString(R.string.external_repository_dialog_title)) .setMessage(activity.resources.getString(R.string.external_repository_dialog_text)) - .setPositiveButton(R.string.dialog_ok) { _, _ -> launchActivity(DirectorySelectionActivity::class.java) } + .setPositiveButton(R.string.dialog_ok) { _, _ -> + launchActivity(DirectorySelectionActivity::class.java) + } .setNegativeButton(R.string.dialog_cancel, null) .show() } @@ -130,7 +134,9 @@ class RepositorySettings(private val activity: FragmentActivity) : SettingsProvi } pref(PreferenceKeys.SSH_OPENKEYSTORE_CLEAR_KEY_ID) { titleRes = R.string.pref_title_openkeystore_clear_keyid - visible = activity.sharedPrefs.getString(PreferenceKeys.SSH_OPENKEYSTORE_KEYID)?.isNotEmpty() ?: false + visible = + activity.sharedPrefs.getString(PreferenceKeys.SSH_OPENKEYSTORE_KEYID)?.isNotEmpty() + ?: false onClick { activity.sharedPrefs.edit { putString(PreferenceKeys.SSH_OPENKEYSTORE_KEYID, null) } visible = false @@ -160,7 +166,9 @@ class RepositorySettings(private val activity: FragmentActivity) : SettingsProvi removeDynamicShortcuts(dynamicShortcuts.map { it.id }.toMutableList()) } } - activity.sharedPrefs.edit { putBoolean(PreferenceKeys.REPOSITORY_INITIALIZED, false) } + activity.sharedPrefs.edit { + putBoolean(PreferenceKeys.REPOSITORY_INITIALIZED, false) + } dialogInterface.cancel() activity.finish() } diff --git a/app/src/main/java/dev/msfjarvis/aps/ui/settings/SettingsActivity.kt b/app/src/main/java/dev/msfjarvis/aps/ui/settings/SettingsActivity.kt index ceb6599b..91dd4908 100644 --- a/app/src/main/java/dev/msfjarvis/aps/ui/settings/SettingsActivity.kt +++ b/app/src/main/java/dev/msfjarvis/aps/ui/settings/SettingsActivity.kt @@ -68,7 +68,9 @@ class SettingsActivity : AppCompatActivity() { getString(subScreen.titleRes) } } - savedInstanceState?.getParcelable<PreferencesAdapter.SavedState>("adapter")?.let(adapter::loadSavedState) + savedInstanceState + ?.getParcelable<PreferencesAdapter.SavedState>("adapter") + ?.let(adapter::loadSavedState) binding.preferenceRecyclerView.adapter = adapter } diff --git a/app/src/main/java/dev/msfjarvis/aps/ui/sshkeygen/ShowSshKeyFragment.kt b/app/src/main/java/dev/msfjarvis/aps/ui/sshkeygen/ShowSshKeyFragment.kt index ee54febe..73cd2ba1 100644 --- a/app/src/main/java/dev/msfjarvis/aps/ui/sshkeygen/ShowSshKeyFragment.kt +++ b/app/src/main/java/dev/msfjarvis/aps/ui/sshkeygen/ShowSshKeyFragment.kt @@ -20,7 +20,9 @@ class ShowSshKeyFragment : DialogFragment() { return MaterialAlertDialogBuilder(requireActivity()).run { setMessage(getString(R.string.ssh_keygen_message, publicKey)) setTitle(R.string.your_public_key) - setNegativeButton(R.string.ssh_keygen_later) { _, _ -> (activity as? SshKeyGenActivity)?.finish() } + setNegativeButton(R.string.ssh_keygen_later) { _, _ -> + (activity as? SshKeyGenActivity)?.finish() + } setPositiveButton(R.string.ssh_keygen_share) { _, _ -> val sendIntent = Intent().apply { diff --git a/app/src/main/java/dev/msfjarvis/aps/ui/sshkeygen/SshKeyGenActivity.kt b/app/src/main/java/dev/msfjarvis/aps/ui/sshkeygen/SshKeyGenActivity.kt index ae420813..cc2cd4c7 100644 --- a/app/src/main/java/dev/msfjarvis/aps/ui/sshkeygen/SshKeyGenActivity.kt +++ b/app/src/main/java/dev/msfjarvis/aps/ui/sshkeygen/SshKeyGenActivity.kt @@ -30,9 +30,15 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.withContext private enum class KeyGenType(val generateKey: suspend (requireAuthentication: Boolean) -> Unit) { - Rsa({ requireAuthentication -> SshKey.generateKeystoreNativeKey(SshKey.Algorithm.Rsa, requireAuthentication) }), - Ecdsa({ requireAuthentication -> SshKey.generateKeystoreNativeKey(SshKey.Algorithm.Ecdsa, requireAuthentication) }), - Ed25519({ requireAuthentication -> SshKey.generateKeystoreWrappedEd25519Key(requireAuthentication) }), + Rsa({ requireAuthentication -> + SshKey.generateKeystoreNativeKey(SshKey.Algorithm.Rsa, requireAuthentication) + }), + Ecdsa({ requireAuthentication -> + SshKey.generateKeystoreNativeKey(SshKey.Algorithm.Ecdsa, requireAuthentication) + }), + Ed25519({ requireAuthentication -> + SshKey.generateKeystoreWrappedEd25519Key(requireAuthentication) + }), } class SshKeyGenActivity : AppCompatActivity() { @@ -50,7 +56,9 @@ class SshKeyGenActivity : AppCompatActivity() { MaterialAlertDialogBuilder(this@SshKeyGenActivity).run { setTitle(R.string.ssh_keygen_existing_title) setMessage(R.string.ssh_keygen_existing_message) - setPositiveButton(R.string.ssh_keygen_existing_replace) { _, _ -> lifecycleScope.launch { generate() } } + setPositiveButton(R.string.ssh_keygen_existing_replace) { _, _ -> + lifecycleScope.launch { generate() } + } setNegativeButton(R.string.ssh_keygen_existing_keep) { _, _ -> finish() } show() } diff --git a/app/src/main/java/dev/msfjarvis/aps/ui/sshkeygen/SshKeyImportActivity.kt b/app/src/main/java/dev/msfjarvis/aps/ui/sshkeygen/SshKeyImportActivity.kt index bf9f6eda..af00e3df 100644 --- a/app/src/main/java/dev/msfjarvis/aps/ui/sshkeygen/SshKeyImportActivity.kt +++ b/app/src/main/java/dev/msfjarvis/aps/ui/sshkeygen/SshKeyImportActivity.kt @@ -26,7 +26,12 @@ class SshKeyImportActivity : AppCompatActivity() { } runCatching { SshKey.import(uri) - Toast.makeText(this, resources.getString(R.string.ssh_key_success_dialog_title), Toast.LENGTH_LONG).show() + Toast.makeText( + this, + resources.getString(R.string.ssh_key_success_dialog_title), + Toast.LENGTH_LONG + ) + .show() setResult(RESULT_OK) finish() } diff --git a/app/src/main/java/dev/msfjarvis/aps/util/auth/BiometricAuthenticator.kt b/app/src/main/java/dev/msfjarvis/aps/util/auth/BiometricAuthenticator.kt index c3655298..7236ac7c 100644 --- a/app/src/main/java/dev/msfjarvis/aps/util/auth/BiometricAuthenticator.kt +++ b/app/src/main/java/dev/msfjarvis/aps/util/auth/BiometricAuthenticator.kt @@ -19,7 +19,8 @@ import dev.msfjarvis.aps.R object BiometricAuthenticator { private const val TAG = "BiometricAuthenticator" - private const val validAuthenticators = Authenticators.DEVICE_CREDENTIAL or Authenticators.BIOMETRIC_WEAK + private const val validAuthenticators = + Authenticators.DEVICE_CREDENTIAL or Authenticators.BIOMETRIC_WEAK sealed class Result { data class Success(val cryptoObject: BiometricPrompt.CryptoObject?) : Result() @@ -29,7 +30,8 @@ object BiometricAuthenticator { } fun canAuthenticate(activity: FragmentActivity): Boolean { - return BiometricManager.from(activity).canAuthenticate(validAuthenticators) == BiometricManager.BIOMETRIC_SUCCESS + return BiometricManager.from(activity).canAuthenticate(validAuthenticators) == + BiometricManager.BIOMETRIC_SUCCESS } fun authenticate( @@ -55,7 +57,11 @@ object BiometricAuthenticator { BiometricPrompt.ERROR_NO_DEVICE_CREDENTIAL -> { Result.HardwareUnavailableOrDisabled } - else -> Result.Failure(errorCode, activity.getString(R.string.biometric_auth_error_reason, errString)) + else -> + Result.Failure( + errorCode, + activity.getString(R.string.biometric_auth_error_reason, errString) + ) } ) } @@ -77,7 +83,11 @@ object BiometricAuthenticator { .setTitle(activity.getString(dialogTitleRes)) .setAllowedAuthenticators(validAuthenticators) .build() - BiometricPrompt(activity, ContextCompat.getMainExecutor(activity.applicationContext), authCallback) + BiometricPrompt( + activity, + ContextCompat.getMainExecutor(activity.applicationContext), + authCallback + ) .authenticate(promptInfo) } else { callback(Result.HardwareUnavailableOrDisabled) diff --git a/app/src/main/java/dev/msfjarvis/aps/util/autofill/Api30AutofillResponseBuilder.kt b/app/src/main/java/dev/msfjarvis/aps/util/autofill/Api30AutofillResponseBuilder.kt index a39db31d..ea03bb23 100644 --- a/app/src/main/java/dev/msfjarvis/aps/util/autofill/Api30AutofillResponseBuilder.kt +++ b/app/src/main/java/dev/msfjarvis/aps/util/autofill/Api30AutofillResponseBuilder.kt @@ -61,7 +61,11 @@ class Api30AutofillResponseBuilder(form: FillableForm) { } } - private fun makeMatchDataset(context: Context, file: File, imeSpec: InlinePresentationSpec?): Dataset? { + private fun makeMatchDataset( + context: Context, + file: File, + imeSpec: InlinePresentationSpec? + ): Dataset? { if (!scenario.hasFieldsToFillOn(AutofillAction.Match)) return null val metadata = makeFillMatchMetadata(context, file) val intentSender = AutofillDecryptActivity.makeDecryptFileIntentSender(file, context) @@ -82,12 +86,21 @@ class Api30AutofillResponseBuilder(form: FillableForm) { return makeIntentDataset(context, AutofillAction.Generate, intentSender, metadata, imeSpec) } - private fun makeFillOtpFromSmsDataset(context: Context, imeSpec: InlinePresentationSpec?): Dataset? { + private fun makeFillOtpFromSmsDataset( + context: Context, + imeSpec: InlinePresentationSpec? + ): Dataset? { if (!scenario.hasFieldsToFillOn(AutofillAction.FillOtpFromSms)) return null if (!AutofillSmsActivity.shouldOfferFillFromSms(context)) return null val metadata = makeFillOtpFromSmsMetadata(context) val intentSender = AutofillSmsActivity.makeFillOtpFromSmsIntentSender(context) - return makeIntentDataset(context, AutofillAction.FillOtpFromSms, intentSender, metadata, imeSpec) + return makeIntentDataset( + context, + AutofillAction.FillOtpFromSms, + intentSender, + metadata, + imeSpec + ) } private fun makePublisherChangedDataset( @@ -150,7 +163,12 @@ class Api30AutofillResponseBuilder(form: FillableForm) { addDataset(it) } if (datasetCount == 0) return null - setHeader(makeRemoteView(context, makeHeaderMetadata(formOrigin.getPrettyIdentifier(context, untrusted = true)))) + setHeader( + makeRemoteView( + context, + makeHeaderMetadata(formOrigin.getPrettyIdentifier(context, untrusted = true)) + ) + ) makeSaveInfo()?.let { setSaveInfo(it) } setClientState(clientState) setIgnoredIds(*ignoredIds.toTypedArray()) @@ -177,7 +195,11 @@ class Api30AutofillResponseBuilder(form: FillableForm) { } /** Creates and returns a suitable [FillResponse] to the Autofill framework. */ - fun fillCredentials(context: Context, inlineSuggestionsRequest: InlineSuggestionsRequest?, callback: FillCallback) { + fun fillCredentials( + context: Context, + inlineSuggestionsRequest: InlineSuggestionsRequest?, + callback: FillCallback + ) { AutofillMatcher.getMatchesFor(context, formOrigin) .fold( success = { matchedFiles -> diff --git a/app/src/main/java/dev/msfjarvis/aps/util/autofill/AutofillMatcher.kt b/app/src/main/java/dev/msfjarvis/aps/util/autofill/AutofillMatcher.kt index 418843f6..ea10c1ac 100644 --- a/app/src/main/java/dev/msfjarvis/aps/util/autofill/AutofillMatcher.kt +++ b/app/src/main/java/dev/msfjarvis/aps/util/autofill/AutofillMatcher.kt @@ -35,7 +35,9 @@ private fun Context.matchPreferences(formOrigin: FormOrigin): SharedPreferences } class AutofillPublisherChangedException(val formOrigin: FormOrigin) : - Exception("The publisher of '${formOrigin.identifier}' changed since an entry was first matched with this app") { + Exception( + "The publisher of '${formOrigin.identifier}' changed since an entry was first matched with this app" + ) { init { require(formOrigin is FormOrigin.App) @@ -50,10 +52,12 @@ class AutofillMatcher { private const val MAX_NUM_MATCHES = 10 private const val PREFERENCE_PREFIX_TOKEN = "token;" - private fun tokenKey(formOrigin: FormOrigin.App) = "$PREFERENCE_PREFIX_TOKEN${formOrigin.identifier}" + private fun tokenKey(formOrigin: FormOrigin.App) = + "$PREFERENCE_PREFIX_TOKEN${formOrigin.identifier}" private const val PREFERENCE_PREFIX_MATCHES = "matches;" - private fun matchesKey(formOrigin: FormOrigin) = "$PREFERENCE_PREFIX_MATCHES${formOrigin.identifier}" + private fun matchesKey(formOrigin: FormOrigin) = + "$PREFERENCE_PREFIX_MATCHES${formOrigin.identifier}" private fun hasFormOriginHashChanged(context: Context, formOrigin: FormOrigin): Boolean { return when (formOrigin) { @@ -61,7 +65,8 @@ class AutofillMatcher { is FormOrigin.App -> { val packageName = formOrigin.identifier val certificatesHash = computeCertificatesHash(context, packageName) - val storedCertificatesHash = context.autofillAppMatches.getString(tokenKey(formOrigin), null) ?: return false + val storedCertificatesHash = + context.autofillAppMatches.getString(tokenKey(formOrigin), null) ?: return false val hashHasChanged = certificatesHash != storedCertificatesHash if (hashHasChanged) { e { "$packageName: stored=$storedCertificatesHash, new=$certificatesHash" } @@ -91,15 +96,21 @@ class AutofillMatcher { * time the user associated an entry with it, an [AutofillPublisherChangedException] will be * thrown. */ - fun getMatchesFor(context: Context, formOrigin: FormOrigin): Result<List<File>, AutofillPublisherChangedException> { + fun getMatchesFor( + context: Context, + formOrigin: FormOrigin + ): Result<List<File>, AutofillPublisherChangedException> { if (hasFormOriginHashChanged(context, formOrigin)) { return Err(AutofillPublisherChangedException(formOrigin)) } val matchPreferences = context.matchPreferences(formOrigin) - val matchedFiles = matchPreferences.getStringSet(matchesKey(formOrigin), emptySet())!!.map { File(it) } + val matchedFiles = + matchPreferences.getStringSet(matchesKey(formOrigin), emptySet())!!.map { File(it) } return Ok( matchedFiles.filter { it.exists() }.also { validFiles -> - matchPreferences.edit { putStringSet(matchesKey(formOrigin), validFiles.map { it.absolutePath }.toSet()) } + matchPreferences.edit { + putStringSet(matchesKey(formOrigin), validFiles.map { it.absolutePath }.toSet()) + } } ) } @@ -127,7 +138,8 @@ class AutofillMatcher { throw AutofillPublisherChangedException(formOrigin) } val matchPreferences = context.matchPreferences(formOrigin) - val matchedFiles = matchPreferences.getStringSet(matchesKey(formOrigin), emptySet())!!.map { File(it) } + val matchedFiles = + matchPreferences.getStringSet(matchesKey(formOrigin), emptySet())!!.map { File(it) } val newFiles = setOf(file.absoluteFile).union(matchedFiles) if (newFiles.size > MAX_NUM_MATCHES) { Toast.makeText( @@ -138,7 +150,9 @@ class AutofillMatcher { .show() return } - matchPreferences.edit { putStringSet(matchesKey(formOrigin), newFiles.map { it.absolutePath }.toSet()) } + matchPreferences.edit { + putStringSet(matchesKey(formOrigin), newFiles.map { it.absolutePath }.toSet()) + } storeFormOriginHash(context, formOrigin) d { "Stored match for $formOrigin" } } @@ -153,7 +167,8 @@ class AutofillMatcher { delete: Collection<File> = emptyList() ) { val deletePathList = delete.map { it.absolutePath } - val oldNewPathMap = moveFromTo.mapValues { it.value.absolutePath }.mapKeys { it.key.absolutePath } + val oldNewPathMap = + moveFromTo.mapValues { it.value.absolutePath }.mapKeys { it.key.absolutePath } for (prefs in listOf(context.autofillAppMatches, context.autofillWebMatches)) { for ((key, value) in prefs.all) { if (!key.startsWith(PREFERENCE_PREFIX_MATCHES)) continue diff --git a/app/src/main/java/dev/msfjarvis/aps/util/autofill/AutofillPreferences.kt b/app/src/main/java/dev/msfjarvis/aps/util/autofill/AutofillPreferences.kt index c6cdffed..fdbd15af 100644 --- a/app/src/main/java/dev/msfjarvis/aps/util/autofill/AutofillPreferences.kt +++ b/app/src/main/java/dev/msfjarvis/aps/util/autofill/AutofillPreferences.kt @@ -96,7 +96,8 @@ enum class DirectoryStructure(val value: String) { when (this) { EncryptedUsername -> null FileBased -> file.nameWithoutExtension.takeIf { file.parentFile != null } - DirectoryBased -> file.parentFile?.let { parentFile -> "${parentFile.name}/${file.nameWithoutExtension}" } + DirectoryBased -> + file.parentFile?.let { parentFile -> "${parentFile.name}/${file.nameWithoutExtension}" } ?: file.nameWithoutExtension } @@ -138,7 +139,8 @@ object AutofillPreferences { directoryStructure: DirectoryStructure ): Credentials { // Always give priority to a username stored in the encrypted extras - val username = entry.username ?: directoryStructure.getUsernameFor(file) ?: context.getDefaultUsername() + val username = + entry.username ?: directoryStructure.getUsernameFor(file) ?: context.getDefaultUsername() return Credentials(username, entry.password, entry.totp.value) } } diff --git a/app/src/main/java/dev/msfjarvis/aps/util/autofill/AutofillResponseBuilder.kt b/app/src/main/java/dev/msfjarvis/aps/util/autofill/AutofillResponseBuilder.kt index d8126438..9dabf914 100644 --- a/app/src/main/java/dev/msfjarvis/aps/util/autofill/AutofillResponseBuilder.kt +++ b/app/src/main/java/dev/msfjarvis/aps/util/autofill/AutofillResponseBuilder.kt @@ -154,7 +154,10 @@ class AutofillResponseBuilder(form: FillableForm) { if (datasetCount == 0) return null if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { setHeader( - makeRemoteView(context, makeHeaderMetadata(formOrigin.getPrettyIdentifier(context, untrusted = true))) + makeRemoteView( + context, + makeHeaderMetadata(formOrigin.getPrettyIdentifier(context, untrusted = true)) + ) ) } makeSaveInfo()?.let { setSaveInfo(it) } diff --git a/app/src/main/java/dev/msfjarvis/aps/util/autofill/AutofillViewUtils.kt b/app/src/main/java/dev/msfjarvis/aps/util/autofill/AutofillViewUtils.kt index 46f7d821..1d09d001 100644 --- a/app/src/main/java/dev/msfjarvis/aps/util/autofill/AutofillViewUtils.kt +++ b/app/src/main/java/dev/msfjarvis/aps/util/autofill/AutofillViewUtils.kt @@ -51,13 +51,15 @@ fun makeInlinePresentation( if (UiVersions.INLINE_UI_VERSION_1 !in UiVersions.getVersions(imeSpec.style)) return null - val launchIntent = PendingIntent.getActivity(context, 0, Intent(context, PasswordStore::class.java), 0) + val launchIntent = + PendingIntent.getActivity(context, 0, Intent(context, PasswordStore::class.java), 0) val slice = InlineSuggestionUi.newContentBuilder(launchIntent).run { setTitle(metadata.title) if (metadata.subtitle != null) setSubtitle(metadata.subtitle) setContentDescription( - if (metadata.subtitle != null) "${metadata.title} - ${metadata.subtitle}" else metadata.title + if (metadata.subtitle != null) "${metadata.title} - ${metadata.subtitle}" + else metadata.title ) setStartIcon(Icon.createWithResource(context, metadata.iconRes)) build().slice @@ -69,13 +71,19 @@ fun makeInlinePresentation( fun makeFillMatchMetadata(context: Context, file: File): DatasetMetadata { val directoryStructure = AutofillPreferences.directoryStructure(context) val relativeFile = file.relativeTo(PasswordRepository.getRepositoryDirectory()) - val title = directoryStructure.getIdentifierFor(relativeFile) ?: directoryStructure.getAccountPartFor(relativeFile)!! + val title = + directoryStructure.getIdentifierFor(relativeFile) + ?: directoryStructure.getAccountPartFor(relativeFile)!! val subtitle = directoryStructure.getAccountPartFor(relativeFile) return DatasetMetadata(title, subtitle, R.drawable.ic_person_black_24dp) } fun makeSearchAndFillMetadata(context: Context) = - DatasetMetadata(context.getString(R.string.oreo_autofill_search_in_store), null, R.drawable.ic_search_black_24dp) + DatasetMetadata( + context.getString(R.string.oreo_autofill_search_in_store), + null, + R.drawable.ic_search_black_24dp + ) fun makeGenerateAndFillMetadata(context: Context) = DatasetMetadata( @@ -85,7 +93,11 @@ fun makeGenerateAndFillMetadata(context: Context) = ) fun makeFillOtpFromSmsMetadata(context: Context) = - DatasetMetadata(context.getString(R.string.oreo_autofill_fill_otp_from_sms), null, R.drawable.ic_autofill_sms) + DatasetMetadata( + context.getString(R.string.oreo_autofill_fill_otp_from_sms), + null, + R.drawable.ic_autofill_sms + ) fun makeEmptyMetadata() = DatasetMetadata("PLACEHOLDER", "PLACEHOLDER", R.mipmap.ic_launcher) diff --git a/app/src/main/java/dev/msfjarvis/aps/util/crypto/GpgIdentifier.kt b/app/src/main/java/dev/msfjarvis/aps/util/crypto/GpgIdentifier.kt index cc759e9a..f99b623c 100644 --- a/app/src/main/java/dev/msfjarvis/aps/util/crypto/GpgIdentifier.kt +++ b/app/src/main/java/dev/msfjarvis/aps/util/crypto/GpgIdentifier.kt @@ -17,7 +17,8 @@ sealed class GpgIdentifier { if (identifier.isEmpty()) return null // Match long key IDs: // FF22334455667788 or 0xFF22334455667788 - val maybeLongKeyId = identifier.removePrefix("0x").takeIf { it.matches("[a-fA-F0-9]{16}".toRegex()) } + val maybeLongKeyId = + identifier.removePrefix("0x").takeIf { it.matches("[a-fA-F0-9]{16}".toRegex()) } if (maybeLongKeyId != null) { val keyId = maybeLongKeyId.toULong(16) return KeyId(keyId.toLong()) @@ -25,7 +26,8 @@ sealed class GpgIdentifier { // Match fingerprints: // FF223344556677889900112233445566778899 or 0xFF223344556677889900112233445566778899 - val maybeFingerprint = identifier.removePrefix("0x").takeIf { it.matches("[a-fA-F0-9]{40}".toRegex()) } + val maybeFingerprint = + identifier.removePrefix("0x").takeIf { it.matches("[a-fA-F0-9]{40}".toRegex()) } if (maybeFingerprint != null) { // Truncating to the long key ID is not a security issue since OpenKeychain only // accepts diff --git a/app/src/main/java/dev/msfjarvis/aps/util/extensions/AndroidExtensions.kt b/app/src/main/java/dev/msfjarvis/aps/util/extensions/AndroidExtensions.kt index 94103118..21cc30bc 100644 --- a/app/src/main/java/dev/msfjarvis/aps/util/extensions/AndroidExtensions.kt +++ b/app/src/main/java/dev/msfjarvis/aps/util/extensions/AndroidExtensions.kt @@ -41,7 +41,11 @@ fun <T : View> AlertDialog.requestInputFocusOnView(@IdRes id: Int) { setOnShowListener { findViewById<T>(id)?.apply { setOnFocusChangeListener { v, _ -> - v.post { context.getSystemService<InputMethodManager>()?.showSoftInput(v, InputMethodManager.SHOW_IMPLICIT) } + v.post { + context + .getSystemService<InputMethodManager>() + ?.showSoftInput(v, InputMethodManager.SHOW_IMPLICIT) + } } requestFocus() } @@ -64,7 +68,8 @@ fun Context.getEncryptedProxyPrefs() = getEncryptedPrefs("http_proxy") /** Get an instance of [EncryptedSharedPreferences] with the given [fileName] */ private fun Context.getEncryptedPrefs(fileName: String): SharedPreferences { - val masterKeyAlias = MasterKey.Builder(applicationContext).setKeyScheme(MasterKey.KeyScheme.AES256_GCM).build() + val masterKeyAlias = + MasterKey.Builder(applicationContext).setKeyScheme(MasterKey.KeyScheme.AES256_GCM).build() return EncryptedSharedPreferences.create( applicationContext, fileName, diff --git a/app/src/main/java/dev/msfjarvis/aps/util/extensions/FragmentViewBindingDelegate.kt b/app/src/main/java/dev/msfjarvis/aps/util/extensions/FragmentViewBindingDelegate.kt index 5a1b554f..de21a359 100644 --- a/app/src/main/java/dev/msfjarvis/aps/util/extensions/FragmentViewBindingDelegate.kt +++ b/app/src/main/java/dev/msfjarvis/aps/util/extensions/FragmentViewBindingDelegate.kt @@ -20,8 +20,10 @@ import kotlin.reflect.KProperty * Imported from * https://medium.com/@Zhuinden/simple-one-liner-viewbinding-in-fragments-and-activities-with-kotlin-961430c6c07c */ -class FragmentViewBindingDelegate<T : ViewBinding>(val fragment: Fragment, val viewBindingFactory: (View) -> T) : - ReadOnlyProperty<Fragment, T> { +class FragmentViewBindingDelegate<T : ViewBinding>( + val fragment: Fragment, + val viewBindingFactory: (View) -> T +) : ReadOnlyProperty<Fragment, T> { private var binding: T? = null @@ -51,7 +53,9 @@ class FragmentViewBindingDelegate<T : ViewBinding>(val fragment: Fragment, val v val lifecycle = fragment.viewLifecycleOwner.lifecycle if (!lifecycle.currentState.isAtLeast(Lifecycle.State.INITIALIZED)) { - throw IllegalStateException("Should not attempt to get bindings when Fragment views are destroyed.") + throw IllegalStateException( + "Should not attempt to get bindings when Fragment views are destroyed." + ) } return viewBindingFactory(thisRef.requireView()).also { this.binding = it } @@ -61,5 +65,6 @@ class FragmentViewBindingDelegate<T : ViewBinding>(val fragment: Fragment, val v fun <T : ViewBinding> Fragment.viewBinding(viewBindingFactory: (View) -> T) = FragmentViewBindingDelegate(this, viewBindingFactory) -inline fun <T : ViewBinding> AppCompatActivity.viewBinding(crossinline bindingInflater: (LayoutInflater) -> T) = - lazy(LazyThreadSafetyMode.NONE) { bindingInflater.invoke(layoutInflater) } +inline fun <T : ViewBinding> AppCompatActivity.viewBinding( + crossinline bindingInflater: (LayoutInflater) -> T +) = lazy(LazyThreadSafetyMode.NONE) { bindingInflater.invoke(layoutInflater) } diff --git a/app/src/main/java/dev/msfjarvis/aps/util/git/ErrorMessages.kt b/app/src/main/java/dev/msfjarvis/aps/util/git/ErrorMessages.kt index 04809fdd..36dc445f 100644 --- a/app/src/main/java/dev/msfjarvis/aps/util/git/ErrorMessages.kt +++ b/app/src/main/java/dev/msfjarvis/aps/util/git/ErrorMessages.kt @@ -14,7 +14,8 @@ import java.net.UnknownHostException /** * Supertype for all Git-related [Exception] s that can be thrown by [GitCommandExecutor.execute]. */ -sealed class GitException(@StringRes res: Int, vararg fmt: String) : Exception(buildMessage(res, *fmt)) { +sealed class GitException(@StringRes res: Int, vararg fmt: String) : + Exception(buildMessage(res, *fmt)) { override val message = super.message!! diff --git a/app/src/main/java/dev/msfjarvis/aps/util/git/GitCommandExecutor.kt b/app/src/main/java/dev/msfjarvis/aps/util/git/GitCommandExecutor.kt index 4546aee1..7be06385 100644 --- a/app/src/main/java/dev/msfjarvis/aps/util/git/GitCommandExecutor.kt +++ b/app/src/main/java/dev/msfjarvis/aps/util/git/GitCommandExecutor.kt @@ -74,11 +74,13 @@ class GitCommandExecutor( // Code imported (modified) from Gerrit PushOp, license Apache v2 for (rru in result.remoteUpdates) { when (rru.status) { - RemoteRefUpdate.Status.REJECTED_NONFASTFORWARD -> throw PushException.NonFastForward + RemoteRefUpdate.Status.REJECTED_NONFASTFORWARD -> + throw PushException.NonFastForward RemoteRefUpdate.Status.REJECTED_NODELETE, RemoteRefUpdate.Status.REJECTED_REMOTE_CHANGED, RemoteRefUpdate.Status.NON_EXISTING, - RemoteRefUpdate.Status.NOT_ATTEMPTED, -> throw PushException.Generic(rru.status.name) + RemoteRefUpdate.Status.NOT_ATTEMPTED, -> + throw PushException.Generic(rru.status.name) RemoteRefUpdate.Status.REJECTED_OTHER_REASON -> { throw if ("non-fast-forward" == rru.message) { PushException.RemoteRejected diff --git a/app/src/main/java/dev/msfjarvis/aps/util/git/GitCommit.kt b/app/src/main/java/dev/msfjarvis/aps/util/git/GitCommit.kt index 68b1c167..490f5e2c 100644 --- a/app/src/main/java/dev/msfjarvis/aps/util/git/GitCommit.kt +++ b/app/src/main/java/dev/msfjarvis/aps/util/git/GitCommit.kt @@ -15,4 +15,9 @@ import java.util.Date * @property authorName name of the commit's author without email address. * @property time time when the commit was created. */ -data class GitCommit(val hash: String, val shortMessage: String, val authorName: String, val time: Date) +data class GitCommit( + val hash: String, + val shortMessage: String, + val authorName: String, + val time: Date +) diff --git a/app/src/main/java/dev/msfjarvis/aps/util/git/GitLogModel.kt b/app/src/main/java/dev/msfjarvis/aps/util/git/GitLogModel.kt index 1ba65086..bfef2571 100644 --- a/app/src/main/java/dev/msfjarvis/aps/util/git/GitLogModel.kt +++ b/app/src/main/java/dev/msfjarvis/aps/util/git/GitLogModel.kt @@ -40,7 +40,9 @@ class GitLogModel { // Additionally, tests with 1000 commits in the log have not produced a significant delay in the // user experience. private val cache: MutableList<GitCommit> by lazy(LazyThreadSafetyMode.NONE) { - commits().map { GitCommit(it.hash, it.shortMessage, it.authorIdent.name, it.time) }.toMutableList() + commits() + .map { GitCommit(it.hash, it.shortMessage, it.authorIdent.name, it.time) } + .toMutableList() } val size = cache.size diff --git a/app/src/main/java/dev/msfjarvis/aps/util/git/operation/BreakOutOfDetached.kt b/app/src/main/java/dev/msfjarvis/aps/util/git/operation/BreakOutOfDetached.kt index 196d6d48..fd0aedf7 100644 --- a/app/src/main/java/dev/msfjarvis/aps/util/git/operation/BreakOutOfDetached.kt +++ b/app/src/main/java/dev/msfjarvis/aps/util/git/operation/BreakOutOfDetached.kt @@ -11,13 +11,17 @@ import org.eclipse.jgit.api.RebaseCommand import org.eclipse.jgit.api.ResetCommand import org.eclipse.jgit.lib.RepositoryState -class BreakOutOfDetached(callingActivity: ContinuationContainerActivity) : GitOperation(callingActivity) { +class BreakOutOfDetached(callingActivity: ContinuationContainerActivity) : + GitOperation(callingActivity) { private val merging = repository.repositoryState == RepositoryState.MERGING private val resetCommands = arrayOf( // git checkout -b conflict-branch - git.checkout().setCreateBranch(true).setName("conflicting-$remoteBranch-${System.currentTimeMillis()}"), + git + .checkout() + .setCreateBranch(true) + .setName("conflicting-$remoteBranch-${System.currentTimeMillis()}"), // push the changes git.push().setRemote("origin"), // switch back to ${gitBranch} @@ -47,8 +51,12 @@ class BreakOutOfDetached(callingActivity: ContinuationContainerActivity) : GitOp if (!git.repository.repositoryState.isRebasing && !merging) { MaterialAlertDialogBuilder(callingActivity) .setTitle(callingActivity.resources.getString(R.string.git_abort_and_push_title)) - .setMessage(callingActivity.resources.getString(R.string.git_break_out_of_detached_unneeded)) - .setPositiveButton(callingActivity.resources.getString(R.string.dialog_ok)) { _, _ -> callingActivity.finish() } + .setMessage( + callingActivity.resources.getString(R.string.git_break_out_of_detached_unneeded) + ) + .setPositiveButton(callingActivity.resources.getString(R.string.dialog_ok)) { _, _ -> + callingActivity.finish() + } .show() false } else { diff --git a/app/src/main/java/dev/msfjarvis/aps/util/git/operation/CloneOperation.kt b/app/src/main/java/dev/msfjarvis/aps/util/git/operation/CloneOperation.kt index a5a4f5d0..75db2ea5 100644 --- a/app/src/main/java/dev/msfjarvis/aps/util/git/operation/CloneOperation.kt +++ b/app/src/main/java/dev/msfjarvis/aps/util/git/operation/CloneOperation.kt @@ -14,7 +14,8 @@ import org.eclipse.jgit.api.GitCommand * @param uri URL to clone the repository from * @param callingActivity the calling activity */ -class CloneOperation(callingActivity: ContinuationContainerActivity, uri: String) : GitOperation(callingActivity) { +class CloneOperation(callingActivity: ContinuationContainerActivity, uri: String) : + GitOperation(callingActivity) { override val commands: Array<GitCommand<out Any>> = arrayOf( diff --git a/app/src/main/java/dev/msfjarvis/aps/util/git/operation/CredentialFinder.kt b/app/src/main/java/dev/msfjarvis/aps/util/git/operation/CredentialFinder.kt index 40869cf2..e06cc996 100644 --- a/app/src/main/java/dev/msfjarvis/aps/util/git/operation/CredentialFinder.kt +++ b/app/src/main/java/dev/msfjarvis/aps/util/git/operation/CredentialFinder.kt @@ -24,7 +24,8 @@ import dev.msfjarvis.aps.util.settings.PreferenceKeys import kotlin.coroutines.Continuation import kotlin.coroutines.resume -class CredentialFinder(val callingActivity: FragmentActivity, val authMode: AuthMode) : InteractivePasswordFinder() { +class CredentialFinder(val callingActivity: FragmentActivity, val authMode: AuthMode) : + InteractivePasswordFinder() { override fun askForPassword(cont: Continuation<String?>, isRetry: Boolean) { val gitOperationPrefs = callingActivity.getEncryptedGitPrefs() @@ -49,18 +50,22 @@ class CredentialFinder(val callingActivity: FragmentActivity, val authMode: Auth rememberRes = R.string.git_operation_remember_password errorRes = R.string.git_operation_wrong_password } - else -> throw IllegalStateException("Only SshKey and Password connection mode ask for passwords") + else -> + throw IllegalStateException("Only SshKey and Password connection mode ask for passwords") } if (isRetry) gitOperationPrefs.edit { remove(credentialPref) } val storedCredential = gitOperationPrefs.getString(credentialPref, null) if (storedCredential == null) { val layoutInflater = LayoutInflater.from(callingActivity) - @SuppressLint("InflateParams") val dialogView = layoutInflater.inflate(R.layout.git_credential_layout, null) - val credentialLayout = dialogView.findViewById<TextInputLayout>(R.id.git_auth_passphrase_layout) + @SuppressLint("InflateParams") + val dialogView = layoutInflater.inflate(R.layout.git_credential_layout, null) + val credentialLayout = + dialogView.findViewById<TextInputLayout>(R.id.git_auth_passphrase_layout) val editCredential = dialogView.findViewById<TextInputEditText>(R.id.git_auth_credential) editCredential.setHint(hintRes) - val rememberCredential = dialogView.findViewById<MaterialCheckBox>(R.id.git_auth_remember_credential) + val rememberCredential = + dialogView.findViewById<MaterialCheckBox>(R.id.git_auth_remember_credential) rememberCredential.setText(rememberRes) if (isRetry) { credentialLayout.error = callingActivity.resources.getString(errorRes) diff --git a/app/src/main/java/dev/msfjarvis/aps/util/git/operation/GitOperation.kt b/app/src/main/java/dev/msfjarvis/aps/util/git/operation/GitOperation.kt index 0d0dac9c..dbea37e8 100644 --- a/app/src/main/java/dev/msfjarvis/aps/util/git/operation/GitOperation.kt +++ b/app/src/main/java/dev/msfjarvis/aps/util/git/operation/GitOperation.kt @@ -60,7 +60,8 @@ abstract class GitOperation(protected val callingActivity: FragmentActivity) { private val authActivity get() = callingActivity as ContinuationContainerActivity - private class HttpsCredentialsProvider(private val passwordFinder: PasswordFinder) : CredentialsProvider() { + private class HttpsCredentialsProvider(private val passwordFinder: PasswordFinder) : + CredentialsProvider() { private var cachedPassword: CharArray? = null @@ -72,7 +73,8 @@ abstract class GitOperation(protected val callingActivity: FragmentActivity) { is CredentialItem.Username -> item.value = uri?.user is CredentialItem.Password -> { item.value = - cachedPassword?.clone() ?: passwordFinder.reqPassword(null).also { cachedPassword = it.clone() } + cachedPassword?.clone() + ?: passwordFinder.reqPassword(null).also { cachedPassword = it.clone() } } else -> UnsupportedCredentialItem(uri, item.javaClass.name) } @@ -102,7 +104,10 @@ abstract class GitOperation(protected val callingActivity: FragmentActivity) { .onFailure { e -> e(e) } } - private fun registerAuthProviders(authMethod: SshAuthMethod, credentialsProvider: CredentialsProvider? = null) { + private fun registerAuthProviders( + authMethod: SshAuthMethod, + credentialsProvider: CredentialsProvider? = null + ) { sshSessionFactory = SshjSessionFactory(authMethod, hostKeyFile) commands.filterIsInstance<TransportCommand<*, *>>().forEach { command -> command.setTransportConfigCallback { transport: Transport -> @@ -132,12 +137,12 @@ abstract class GitOperation(protected val callingActivity: FragmentActivity) { MaterialAlertDialogBuilder(callingActivity) .setMessage(callingActivity.resources.getString(R.string.ssh_preferences_dialog_text)) .setTitle(callingActivity.resources.getString(R.string.ssh_preferences_dialog_title)) - .setPositiveButton(callingActivity.resources.getString(R.string.ssh_preferences_dialog_import)) { _, _ -> - getSshKey(false) - } - .setNegativeButton(callingActivity.resources.getString(R.string.ssh_preferences_dialog_generate)) { _, _ -> - getSshKey(true) - } + .setPositiveButton( + callingActivity.resources.getString(R.string.ssh_preferences_dialog_import) + ) { _, _ -> getSshKey(false) } + .setNegativeButton( + callingActivity.resources.getString(R.string.ssh_preferences_dialog_generate) + ) { _, _ -> getSshKey(true) } .setNeutralButton(callingActivity.resources.getString(R.string.dialog_cancel)) { _, _ -> // Finish the blank GitActivity so user doesn't have to press back callingActivity.finish() @@ -153,9 +158,10 @@ abstract class GitOperation(protected val callingActivity: FragmentActivity) { val result = withContext(Dispatchers.Main) { suspendCoroutine<BiometricAuthenticator.Result> { cont -> - BiometricAuthenticator.authenticate(callingActivity, R.string.biometric_prompt_title_ssh_auth) { - if (it !is BiometricAuthenticator.Result.Failure) cont.resume(it) - } + BiometricAuthenticator.authenticate( + callingActivity, + R.string.biometric_prompt_title_ssh_auth + ) { if (it !is BiometricAuthenticator.Result.Failure) cont.resume(it) } } } when (result) { @@ -193,7 +199,8 @@ abstract class GitOperation(protected val callingActivity: FragmentActivity) { } AuthMode.OpenKeychain -> registerAuthProviders(SshAuthMethod.OpenKeychain(authActivity)) AuthMode.Password -> { - val httpsCredentialProvider = HttpsCredentialsProvider(CredentialFinder(callingActivity, AuthMode.Password)) + val httpsCredentialProvider = + HttpsCredentialsProvider(CredentialFinder(callingActivity, AuthMode.Password)) registerAuthProviders(SshAuthMethod.Password(authActivity), httpsCredentialProvider) } AuthMode.None -> {} diff --git a/app/src/main/java/dev/msfjarvis/aps/util/git/operation/PushOperation.kt b/app/src/main/java/dev/msfjarvis/aps/util/git/operation/PushOperation.kt index a9f168ad..14f16164 100644 --- a/app/src/main/java/dev/msfjarvis/aps/util/git/operation/PushOperation.kt +++ b/app/src/main/java/dev/msfjarvis/aps/util/git/operation/PushOperation.kt @@ -7,7 +7,8 @@ package dev.msfjarvis.aps.util.git.operation import dev.msfjarvis.aps.util.git.sshj.ContinuationContainerActivity import org.eclipse.jgit.api.GitCommand -class PushOperation(callingActivity: ContinuationContainerActivity) : GitOperation(callingActivity) { +class PushOperation(callingActivity: ContinuationContainerActivity) : + GitOperation(callingActivity) { override val commands: Array<GitCommand<out Any>> = arrayOf( diff --git a/app/src/main/java/dev/msfjarvis/aps/util/git/operation/ResetToRemoteOperation.kt b/app/src/main/java/dev/msfjarvis/aps/util/git/operation/ResetToRemoteOperation.kt index dccd69b0..16114f65 100644 --- a/app/src/main/java/dev/msfjarvis/aps/util/git/operation/ResetToRemoteOperation.kt +++ b/app/src/main/java/dev/msfjarvis/aps/util/git/operation/ResetToRemoteOperation.kt @@ -7,7 +7,8 @@ package dev.msfjarvis.aps.util.git.operation import dev.msfjarvis.aps.util.git.sshj.ContinuationContainerActivity import org.eclipse.jgit.api.ResetCommand -class ResetToRemoteOperation(callingActivity: ContinuationContainerActivity) : GitOperation(callingActivity) { +class ResetToRemoteOperation(callingActivity: ContinuationContainerActivity) : + GitOperation(callingActivity) { override val commands = arrayOf( diff --git a/app/src/main/java/dev/msfjarvis/aps/util/git/sshj/OpenKeychainKeyProvider.kt b/app/src/main/java/dev/msfjarvis/aps/util/git/sshj/OpenKeychainKeyProvider.kt index acb7d8d7..d726c353 100644 --- a/app/src/main/java/dev/msfjarvis/aps/util/git/sshj/OpenKeychainKeyProvider.kt +++ b/app/src/main/java/dev/msfjarvis/aps/util/git/sshj/OpenKeychainKeyProvider.kt @@ -106,7 +106,8 @@ class OpenKeychainKeyProvider private constructor(val activity: ContinuationCont val response = sshPublicKeyResponse.response as SshPublicKeyResponse val sshPublicKey = response.sshPublicKey!! publicKey = - parseSshPublicKey(sshPublicKey) ?: throw IllegalStateException("OpenKeychain API returned invalid SSH key") + parseSshPublicKey(sshPublicKey) + ?: throw IllegalStateException("OpenKeychain API returned invalid SSH key") } is ApiResponse.NoSuchKey -> if (isRetry) { @@ -122,13 +123,17 @@ class OpenKeychainKeyProvider private constructor(val activity: ContinuationCont private suspend fun selectKey() { when (val keySelectionResponse = executeApiRequest(KeySelectionRequest())) { - is ApiResponse.Success -> keyId = (keySelectionResponse.response as KeySelectionResponse).keyId + is ApiResponse.Success -> + keyId = (keySelectionResponse.response as KeySelectionResponse).keyId is ApiResponse.GeneralError -> throw keySelectionResponse.exception is ApiResponse.NoSuchKey -> throw keySelectionResponse.exception } } - private suspend fun executeApiRequest(request: Request, resultOfUserInteraction: Intent? = null): ApiResponse { + private suspend fun executeApiRequest( + request: Request, + resultOfUserInteraction: Intent? = null + ): ApiResponse { d { "executeRequest($request) called" } val result = withContext(Dispatchers.Main) { @@ -141,7 +146,11 @@ class OpenKeychainKeyProvider private constructor(val activity: ContinuationCont } private suspend fun parseResult(request: Request, result: Intent): ApiResponse { - return when (result.getIntExtra(SshAuthenticationApi.EXTRA_RESULT_CODE, SshAuthenticationApi.RESULT_CODE_ERROR)) { + return when (result.getIntExtra( + SshAuthenticationApi.EXTRA_RESULT_CODE, + SshAuthenticationApi.RESULT_CODE_ERROR + ) + ) { SshAuthenticationApi.RESULT_CODE_SUCCESS -> { ApiResponse.Success( when (request) { @@ -153,20 +162,27 @@ class OpenKeychainKeyProvider private constructor(val activity: ContinuationCont ) } SshAuthenticationApi.RESULT_CODE_USER_INTERACTION_REQUIRED -> { - val pendingIntent: PendingIntent = result.getParcelableExtra(SshAuthenticationApi.EXTRA_PENDING_INTENT)!! + val pendingIntent: PendingIntent = + result.getParcelableExtra(SshAuthenticationApi.EXTRA_PENDING_INTENT)!! val resultOfUserInteraction: Intent = withContext(Dispatchers.Main) { suspendCoroutine { cont -> activity.stashedCont = cont - activity.continueAfterUserInteraction.launch(IntentSenderRequest.Builder(pendingIntent).build()) + activity.continueAfterUserInteraction.launch( + IntentSenderRequest.Builder(pendingIntent).build() + ) } } executeApiRequest(request, resultOfUserInteraction) } else -> { - val error = result.getParcelableExtra<SshAuthenticationApiError>(SshAuthenticationApi.EXTRA_ERROR) + val error = + result.getParcelableExtra<SshAuthenticationApiError>(SshAuthenticationApi.EXTRA_ERROR) val exception = - UserAuthException(DisconnectReason.UNKNOWN, "Request ${request::class.simpleName} failed: ${error?.message}") + UserAuthException( + DisconnectReason.UNKNOWN, + "Request ${request::class.simpleName} failed: ${error?.message}" + ) when (error?.error) { SshAuthenticationApiError.NO_AUTH_KEY, SshAuthenticationApiError.NO_SUCH_KEY -> ApiResponse.NoSuchKey(exception) @@ -181,7 +197,9 @@ class OpenKeychainKeyProvider private constructor(val activity: ContinuationCont privateKey = object : OpenKeychainPrivateKey { override suspend fun sign(challenge: ByteArray, hashAlgorithm: Int) = - when (val signingResponse = executeApiRequest(SigningRequest(challenge, keyId, hashAlgorithm))) { + when (val signingResponse = + executeApiRequest(SigningRequest(challenge, keyId, hashAlgorithm)) + ) { is ApiResponse.Success -> (signingResponse.response as SigningResponse).signature is ApiResponse.GeneralError -> throw signingResponse.exception is ApiResponse.NoSuchKey -> throw signingResponse.exception diff --git a/app/src/main/java/dev/msfjarvis/aps/util/git/sshj/OpenKeychainWrappedKeyAlgorithmFactory.kt b/app/src/main/java/dev/msfjarvis/aps/util/git/sshj/OpenKeychainWrappedKeyAlgorithmFactory.kt index 0ab01ba5..56831b1d 100644 --- a/app/src/main/java/dev/msfjarvis/aps/util/git/sshj/OpenKeychainWrappedKeyAlgorithmFactory.kt +++ b/app/src/main/java/dev/msfjarvis/aps/util/git/sshj/OpenKeychainWrappedKeyAlgorithmFactory.kt @@ -28,7 +28,8 @@ class OpenKeychainWrappedKeyAlgorithmFactory(private val factory: Factory.Named< override fun create() = OpenKeychainWrappedKeyAlgorithm(factory.create()) } -class OpenKeychainWrappedKeyAlgorithm(private val keyAlgorithm: KeyAlgorithm) : KeyAlgorithm by keyAlgorithm { +class OpenKeychainWrappedKeyAlgorithm(private val keyAlgorithm: KeyAlgorithm) : + KeyAlgorithm by keyAlgorithm { private val hashAlgorithm = when (keyAlgorithm.keyAlgorithm) { @@ -39,11 +40,14 @@ class OpenKeychainWrappedKeyAlgorithm(private val keyAlgorithm: KeyAlgorithm) : else -> SshAuthenticationApi.SHA512 } - override fun newSignature() = OpenKeychainWrappedSignature(keyAlgorithm.newSignature(), hashAlgorithm) + override fun newSignature() = + OpenKeychainWrappedSignature(keyAlgorithm.newSignature(), hashAlgorithm) } -class OpenKeychainWrappedSignature(private val wrappedSignature: Signature, private val hashAlgorithm: Int) : - Signature by wrappedSignature { +class OpenKeychainWrappedSignature( + private val wrappedSignature: Signature, + private val hashAlgorithm: Int +) : Signature by wrappedSignature { private val data = ByteArrayOutputStream() diff --git a/app/src/main/java/dev/msfjarvis/aps/util/git/sshj/SshKey.kt b/app/src/main/java/dev/msfjarvis/aps/util/git/sshj/SshKey.kt index 37414707..3d23658e 100644 --- a/app/src/main/java/dev/msfjarvis/aps/util/git/sshj/SshKey.kt +++ b/app/src/main/java/dev/msfjarvis/aps/util/git/sshj/SshKey.kt @@ -115,7 +115,8 @@ object SshKey { private var type: Type? get() = Type.fromValue(context.sharedPrefs.getString(PreferenceKeys.GIT_REMOTE_KEY_TYPE)) - set(value) = context.sharedPrefs.edit { putString(PreferenceKeys.GIT_REMOTE_KEY_TYPE, value?.value) } + set(value) = + context.sharedPrefs.edit { putString(PreferenceKeys.GIT_REMOTE_KEY_TYPE, value?.value) } private val isStrongBoxSupported by lazy(LazyThreadSafetyMode.NONE) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) @@ -138,13 +139,20 @@ object SshKey { } } - enum class Algorithm(val algorithm: String, val applyToSpec: KeyGenParameterSpec.Builder.() -> Unit) { + enum class Algorithm( + val algorithm: String, + val applyToSpec: KeyGenParameterSpec.Builder.() -> Unit + ) { Rsa( KeyProperties.KEY_ALGORITHM_RSA, { setKeySize(3072) setSignaturePaddings(KeyProperties.SIGNATURE_PADDING_RSA_PKCS1) - setDigests(KeyProperties.DIGEST_SHA1, KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA512) + setDigests( + KeyProperties.DIGEST_SHA1, + KeyProperties.DIGEST_SHA256, + KeyProperties.DIGEST_SHA512 + ) } ), Ecdsa( @@ -163,7 +171,9 @@ object SshKey { private fun delete() { androidKeystore.deleteEntry(KEYSTORE_ALIAS) // Remove Tink key set used by AndroidX's EncryptedFile. - context.getSharedPreferences(ANDROIDX_SECURITY_KEYSET_PREF_NAME, Context.MODE_PRIVATE).edit { clear() } + context.getSharedPreferences(ANDROIDX_SECURITY_KEYSET_PREF_NAME, Context.MODE_PRIVATE).edit { + clear() + } if (privateKeyFile.isFile) { privateKeyFile.delete() } @@ -177,7 +187,8 @@ object SshKey { fun import(uri: Uri) { // First check whether the content at uri is likely an SSH private key. val fileSize = - context.contentResolver.query(uri, arrayOf(OpenableColumns.SIZE), null, null, null)?.use { cursor -> + context.contentResolver.query(uri, arrayOf(OpenableColumns.SIZE), null, null, null)?.use { + cursor -> // Cursor returns only a single row. cursor.moveToFirst() cursor.getInt(0) @@ -186,7 +197,9 @@ object SshKey { // We assume that an SSH key's ideal size is > 0 bytes && < 100 kilobytes. if (fileSize > 100_000 || fileSize == 0) - throw IllegalArgumentException(context.getString(R.string.ssh_key_import_error_not_an_ssh_key_message)) + throw IllegalArgumentException( + context.getString(R.string.ssh_key_import_error_not_an_ssh_key_message) + ) val sshKeyInputStream = context.contentResolver.openInputStream(uri) @@ -199,7 +212,9 @@ object SshKey { !Regex("BEGIN .* PRIVATE KEY").containsMatchIn(lines.first()) || !Regex("END .* PRIVATE KEY").containsMatchIn(lines.last()) ) - throw IllegalArgumentException(context.getString(R.string.ssh_key_import_error_not_an_ssh_key_message)) + throw IllegalArgumentException( + context.getString(R.string.ssh_key_import_error_not_an_ssh_key_message) + ) // At this point, we are reasonably confident that we have actually been provided a private // key and delete the old key. @@ -249,7 +264,9 @@ object SshKey { val encryptedPrivateKeyFile = getOrCreateWrappedPrivateKeyFile(requireAuthentication) // Generate the ed25519 key pair and encrypt the private key. val keyPair = net.i2p.crypto.eddsa.KeyPairGenerator().generateKeyPair() - encryptedPrivateKeyFile.openFileOutput().use { os -> os.write((keyPair.private as EdDSAPrivateKey).seed) } + encryptedPrivateKeyFile.openFileOutput().use { os -> + os.write((keyPair.private as EdDSAPrivateKey).seed) + } // Write public key in SSH format to .ssh_key.pub. publicKeyFile.writeText(toSshPublicKey(keyPair.public)) @@ -288,7 +305,8 @@ object SshKey { fun provide(client: SSHClient, passphraseFinder: InteractivePasswordFinder): KeyProvider? = when (type) { - Type.LegacyGenerated, Type.Imported -> client.loadKeys(privateKeyFile.absolutePath, passphraseFinder) + Type.LegacyGenerated, Type.Imported -> + client.loadKeys(privateKeyFile.absolutePath, passphraseFinder) Type.KeystoreNative -> KeystoreNativeKeyProvider Type.KeystoreWrappedEd25519 -> KeystoreWrappedEd25519KeyProvider null -> null @@ -305,7 +323,10 @@ object SshKey { override fun getPrivate(): PrivateKey = runCatching { androidKeystore.sshPrivateKey!! }.getOrElse { error -> e(error) - throw IOException("Failed to access private key '$KEYSTORE_ALIAS' from Android Keystore", error) + throw IOException( + "Failed to access private key '$KEYSTORE_ALIAS' from Android Keystore", + error + ) } override fun getType(): KeyType = KeyType.fromKey(public) @@ -326,7 +347,9 @@ object SshKey { // for `requireAuthentication` is not used as the key already exists at this point. val encryptedPrivateKeyFile = runBlocking { getOrCreateWrappedPrivateKeyFile(false) } val rawPrivateKey = encryptedPrivateKeyFile.openFileInput().use { it.readBytes() } - EdDSAPrivateKey(EdDSAPrivateKeySpec(rawPrivateKey, EdDSANamedCurveTable.ED_25519_CURVE_SPEC)) + EdDSAPrivateKey( + EdDSAPrivateKeySpec(rawPrivateKey, EdDSANamedCurveTable.ED_25519_CURVE_SPEC) + ) } .getOrElse { error -> e(error) diff --git a/app/src/main/java/dev/msfjarvis/aps/util/git/sshj/SshjConfig.kt b/app/src/main/java/dev/msfjarvis/aps/util/git/sshj/SshjConfig.kt index e93787f4..f8053ef3 100644 --- a/app/src/main/java/dev/msfjarvis/aps/util/git/sshj/SshjConfig.kt +++ b/app/src/main/java/dev/msfjarvis/aps/util/git/sshj/SshjConfig.kt @@ -37,7 +37,8 @@ fun setUpBouncyCastleForSshj() { // Replace the Android BC provider with the Java BouncyCastle provider since the former does // not include all the required algorithms. // Note: This may affect crypto operations in other parts of the application. - val bcIndex = Security.getProviders().indexOfFirst { it.name == BouncyCastleProvider.PROVIDER_NAME } + val bcIndex = + Security.getProviders().indexOfFirst { it.name == BouncyCastleProvider.PROVIDER_NAME } if (bcIndex == -1) { // No Android BC found, install Java BC at lowest priority. Security.addProvider(BouncyCastleProvider()) @@ -77,9 +78,11 @@ private abstract class AbstractLogger(private val name: String) : Logger { override fun trace(msg: String, t: Throwable?) = t(msg, t) override fun trace(marker: Marker, msg: String) = trace(msg) override fun trace(marker: Marker?, format: String, arg: Any?) = trace(format, arg) - override fun trace(marker: Marker?, format: String, arg1: Any?, arg2: Any?) = trace(format, arg1, arg2) + override fun trace(marker: Marker?, format: String, arg1: Any?, arg2: Any?) = + trace(format, arg1, arg2) - override fun trace(marker: Marker?, format: String, vararg arguments: Any?) = trace(format, *arguments) + override fun trace(marker: Marker?, format: String, vararg arguments: Any?) = + trace(format, *arguments) override fun trace(marker: Marker?, msg: String, t: Throwable?) = trace(msg, t) @@ -90,9 +93,11 @@ private abstract class AbstractLogger(private val name: String) : Logger { override fun debug(msg: String, t: Throwable?) = d(msg, t) override fun debug(marker: Marker, msg: String) = debug(msg) override fun debug(marker: Marker?, format: String, arg: Any?) = debug(format, arg) - override fun debug(marker: Marker?, format: String, arg1: Any?, arg2: Any?) = debug(format, arg1, arg2) + override fun debug(marker: Marker?, format: String, arg1: Any?, arg2: Any?) = + debug(format, arg1, arg2) - override fun debug(marker: Marker?, format: String, vararg arguments: Any?) = debug(format, *arguments) + override fun debug(marker: Marker?, format: String, vararg arguments: Any?) = + debug(format, *arguments) override fun debug(marker: Marker?, msg: String, t: Throwable?) = debug(msg, t) @@ -103,9 +108,11 @@ private abstract class AbstractLogger(private val name: String) : Logger { override fun info(msg: String, t: Throwable?) = i(msg, t) override fun info(marker: Marker, msg: String) = info(msg) override fun info(marker: Marker?, format: String, arg: Any?) = info(format, arg) - override fun info(marker: Marker?, format: String, arg1: Any?, arg2: Any?) = info(format, arg1, arg2) + override fun info(marker: Marker?, format: String, arg1: Any?, arg2: Any?) = + info(format, arg1, arg2) - override fun info(marker: Marker?, format: String, vararg arguments: Any?) = info(format, *arguments) + override fun info(marker: Marker?, format: String, vararg arguments: Any?) = + info(format, *arguments) override fun info(marker: Marker?, msg: String, t: Throwable?) = info(msg, t) @@ -116,9 +123,11 @@ private abstract class AbstractLogger(private val name: String) : Logger { override fun warn(msg: String, t: Throwable?) = w(msg, t) override fun warn(marker: Marker, msg: String) = warn(msg) override fun warn(marker: Marker?, format: String, arg: Any?) = warn(format, arg) - override fun warn(marker: Marker?, format: String, arg1: Any?, arg2: Any?) = warn(format, arg1, arg2) + override fun warn(marker: Marker?, format: String, arg1: Any?, arg2: Any?) = + warn(format, arg1, arg2) - override fun warn(marker: Marker?, format: String, vararg arguments: Any?) = warn(format, *arguments) + override fun warn(marker: Marker?, format: String, vararg arguments: Any?) = + warn(format, *arguments) override fun warn(marker: Marker?, msg: String, t: Throwable?) = warn(msg, t) @@ -129,9 +138,11 @@ private abstract class AbstractLogger(private val name: String) : Logger { override fun error(msg: String, t: Throwable?) = e(msg, t) override fun error(marker: Marker, msg: String) = error(msg) override fun error(marker: Marker?, format: String, arg: Any?) = error(format, arg) - override fun error(marker: Marker?, format: String, arg1: Any?, arg2: Any?) = error(format, arg1, arg2) + override fun error(marker: Marker?, format: String, arg1: Any?, arg2: Any?) = + error(format, arg1, arg2) - override fun error(marker: Marker?, format: String, vararg arguments: Any?) = error(format, *arguments) + override fun error(marker: Marker?, format: String, vararg arguments: Any?) = + error(format, *arguments) override fun error(marker: Marker?, msg: String, t: Throwable?) = error(msg, t) } @@ -148,7 +159,8 @@ object TimberLoggerFactory : LoggerFactory { // Replace slf4j's "{}" format string style with standard Java's "%s". // The supposedly redundant escape on the } is not redundant. - @Suppress("RegExpRedundantEscape") private fun String.fix() = replace("""(?!<\\)\{\}""".toRegex(), "%s") + @Suppress("RegExpRedundantEscape") + private fun String.fix() = replace("""(?!<\\)\{\}""".toRegex(), "%s") override fun t(message: String, t: Throwable?, vararg args: Any?) { Timber.tag(name).v(t, message.fix(), *args) diff --git a/app/src/main/java/dev/msfjarvis/aps/util/git/sshj/SshjSessionFactory.kt b/app/src/main/java/dev/msfjarvis/aps/util/git/sshj/SshjSessionFactory.kt index 95676d7f..bc068f52 100644 --- a/app/src/main/java/dev/msfjarvis/aps/util/git/sshj/SshjSessionFactory.kt +++ b/app/src/main/java/dev/msfjarvis/aps/util/git/sshj/SshjSessionFactory.kt @@ -52,7 +52,10 @@ abstract class InteractivePasswordFinder : PasswordFinder { abstract fun askForPassword(cont: Continuation<String?>, isRetry: Boolean) final override fun reqPassword(resource: Resource<*>?): CharArray { - val password = runBlocking(Dispatchers.Main) { suspendCoroutine<String?> { cont -> askForPassword(cont, isRetry) } } + val password = + runBlocking(Dispatchers.Main) { + suspendCoroutine<String?> { cont -> askForPassword(cont, isRetry) } + } isRetry = true return password?.toCharArray() ?: throw SSHException(DisconnectReason.AUTH_CANCELLED_BY_USER) } @@ -60,11 +63,17 @@ abstract class InteractivePasswordFinder : PasswordFinder { final override fun shouldRetry(resource: Resource<*>?) = true } -class SshjSessionFactory(private val authMethod: SshAuthMethod, private val hostKeyFile: File) : SshSessionFactory() { +class SshjSessionFactory(private val authMethod: SshAuthMethod, private val hostKeyFile: File) : + SshSessionFactory() { private var currentSession: SshjSession? = null - override fun getSession(uri: URIish, credentialsProvider: CredentialsProvider?, fs: FS?, tms: Int): RemoteSession { + override fun getSession( + uri: URIish, + credentialsProvider: CredentialsProvider?, + fs: FS?, + tms: Int + ): RemoteSession { return currentSession ?: SshjSession(uri, uri.user, authMethod, hostKeyFile).connect().also { d { "New SSH connection created" } @@ -81,7 +90,9 @@ private fun makeTofuHostKeyVerifier(hostKeyFile: File): HostKeyVerifier { if (!hostKeyFile.exists()) { return HostKeyVerifier { _, _, key -> val digest = - runCatching { SecurityUtils.getMessageDigest("SHA-256") }.getOrElse { e -> throw SSHRuntimeException(e) } + runCatching { SecurityUtils.getMessageDigest("SHA-256") }.getOrElse { e -> + throw SSHRuntimeException(e) + } digest.update(PlainBuffer().putPublicKey(key).compactData) val digestData = digest.digest() val hostKeyEntry = "SHA256:${Base64.encodeToString(digestData, Base64.NO_WRAP)}" @@ -115,7 +126,9 @@ private class SshjSession( val userPlusHost = "${uri.user}@${uri.host}" val realUser = userPlusHost.substringBeforeLast('@') val realHost = userPlusHost.substringAfterLast('@') - uri.setUser(realUser).setHost(realHost).also { d { "After fixup: user=${it.user}, host=${it.host}" } } + uri.setUser(realUser).setHost(realHost).also { + d { "After fixup: user=${it.user}, host=${it.host}" } + } } else { uri } @@ -131,7 +144,8 @@ private class SshjSession( ssh.auth(username, passwordAuth) } is SshAuthMethod.SshKey -> { - val pubkeyAuth = AuthPublickey(SshKey.provide(ssh, CredentialFinder(authMethod.activity, AuthMode.SshKey))) + val pubkeyAuth = + AuthPublickey(SshKey.provide(ssh, CredentialFinder(authMethod.activity, AuthMode.SshKey))) ssh.auth(username, pubkeyAuth, passwordAuth) } is SshAuthMethod.OpenKeychain -> { @@ -174,7 +188,8 @@ private class SshjSession( } } -private class SshjProcess(private val command: Session.Command, private val timeout: Long) : Process() { +private class SshjProcess(private val command: Session.Command, private val timeout: Long) : + Process() { override fun waitFor(): Int { command.join(timeout, TimeUnit.SECONDS) diff --git a/app/src/main/java/dev/msfjarvis/aps/util/pwgen/PasswordGenerator.kt b/app/src/main/java/dev/msfjarvis/aps/util/pwgen/PasswordGenerator.kt index 8199ebfc..bd21ea0a 100644 --- a/app/src/main/java/dev/msfjarvis/aps/util/pwgen/PasswordGenerator.kt +++ b/app/src/main/java/dev/msfjarvis/aps/util/pwgen/PasswordGenerator.kt @@ -42,7 +42,10 @@ object PasswordGenerator { */ fun setPrefs(ctx: Context, options: List<PasswordOption>, targetLength: Int): Boolean { ctx.getSharedPreferences("PasswordGenerator", Context.MODE_PRIVATE).edit { - for (possibleOption in PasswordOption.values()) putBoolean(possibleOption.key, possibleOption in options) + for (possibleOption in PasswordOption.values()) putBoolean( + possibleOption.key, + possibleOption in options + ) putInt("length", targetLength) } return true @@ -82,7 +85,9 @@ object PasswordGenerator { } else { // The No* options are false, so the respective character category will be included. when (option) { - PasswordOption.NoDigits, PasswordOption.NoUppercaseLetters, PasswordOption.NoLowercaseLetters -> { + PasswordOption.NoDigits, + PasswordOption.NoUppercaseLetters, + PasswordOption.NoLowercaseLetters -> { numCharacterCategories++ } PasswordOption.NoAmbiguousCharacters, @@ -98,7 +103,9 @@ object PasswordGenerator { throw PasswordGeneratorException(ctx.resources.getString(R.string.pwgen_no_chars_error)) } if (length < numCharacterCategories) { - throw PasswordGeneratorException(ctx.resources.getString(R.string.pwgen_length_too_short_error)) + throw PasswordGeneratorException( + ctx.resources.getString(R.string.pwgen_length_too_short_error) + ) } if (!(pwgenFlags hasFlag UPPERS) && !(pwgenFlags hasFlag LOWERS)) { phonemes = false @@ -114,7 +121,9 @@ object PasswordGenerator { var iterations = 0 do { if (iterations++ > 1000) - throw PasswordGeneratorException(ctx.resources.getString(R.string.pwgen_max_iterations_exceeded)) + throw PasswordGeneratorException( + ctx.resources.getString(R.string.pwgen_max_iterations_exceeded) + ) password = if (phonemes) { RandomPhonemesGenerator.generate(length, pwgenFlags) diff --git a/app/src/main/java/dev/msfjarvis/aps/util/pwgen/RandomPasswordGenerator.kt b/app/src/main/java/dev/msfjarvis/aps/util/pwgen/RandomPasswordGenerator.kt index e92a753b..c1a8aeb1 100644 --- a/app/src/main/java/dev/msfjarvis/aps/util/pwgen/RandomPasswordGenerator.kt +++ b/app/src/main/java/dev/msfjarvis/aps/util/pwgen/RandomPasswordGenerator.kt @@ -36,7 +36,9 @@ object RandomPasswordGenerator { var password = "" while (password.length < targetLength) { val candidate = bank.secureRandomCharacter() - if (pwFlags hasFlag PasswordGenerator.NO_AMBIGUOUS && candidate in PasswordGenerator.AMBIGUOUS_STR) { + if (pwFlags hasFlag PasswordGenerator.NO_AMBIGUOUS && + candidate in PasswordGenerator.AMBIGUOUS_STR + ) { continue } password += candidate diff --git a/app/src/main/java/dev/msfjarvis/aps/util/pwgen/RandomPhonemesGenerator.kt b/app/src/main/java/dev/msfjarvis/aps/util/pwgen/RandomPhonemesGenerator.kt index 0b8ca872..5a5f5f21 100644 --- a/app/src/main/java/dev/msfjarvis/aps/util/pwgen/RandomPhonemesGenerator.kt +++ b/app/src/main/java/dev/msfjarvis/aps/util/pwgen/RandomPhonemesGenerator.kt @@ -100,7 +100,9 @@ object RandomPhonemesGenerator { if (!candidate.flags.hasFlag(nextBasicType) || (isStartOfPart && candidate.flags hasFlag NOT_FIRST) || // Don't let a diphthong that starts with a vowel follow a vowel. - (previousFlags hasFlag VOWEL && candidate.flags hasFlag VOWEL && candidate.flags hasFlag DIPHTHONG) || + (previousFlags hasFlag VOWEL && + candidate.flags hasFlag VOWEL && + candidate.flags hasFlag DIPHTHONG) || // Don't add multi-character candidates if we would go over the targetLength. (password.length + candidate.length > targetLength) || (pwFlags hasFlag PasswordGenerator.NO_AMBIGUOUS && candidate.isAmbiguous) @@ -129,11 +131,15 @@ object RandomPhonemesGenerator { // Second part: Add digits and symbols with a certain probability (if requested) if // they would not directly follow the first character in a pronounceable part. - if (!isStartOfPart && pwFlags hasFlag PasswordGenerator.DIGITS && secureRandomBiasedBoolean(30)) { + if (!isStartOfPart && + pwFlags hasFlag PasswordGenerator.DIGITS && + secureRandomBiasedBoolean(30) + ) { var randomDigit: Char do { randomDigit = secureRandomNumber(10).toString(10).first() - } while (pwFlags hasFlag PasswordGenerator.NO_AMBIGUOUS && randomDigit in PasswordGenerator.AMBIGUOUS_STR) + } while (pwFlags hasFlag PasswordGenerator.NO_AMBIGUOUS && + randomDigit in PasswordGenerator.AMBIGUOUS_STR) password += randomDigit // Begin a new pronounceable part after every digit. @@ -143,11 +149,15 @@ object RandomPhonemesGenerator { continue } - if (!isStartOfPart && pwFlags hasFlag PasswordGenerator.SYMBOLS && secureRandomBiasedBoolean(20)) { + if (!isStartOfPart && + pwFlags hasFlag PasswordGenerator.SYMBOLS && + secureRandomBiasedBoolean(20) + ) { var randomSymbol: Char do { randomSymbol = PasswordGenerator.SYMBOLS_STR.secureRandomCharacter() - } while (pwFlags hasFlag PasswordGenerator.NO_AMBIGUOUS && randomSymbol in PasswordGenerator.AMBIGUOUS_STR) + } while (pwFlags hasFlag PasswordGenerator.NO_AMBIGUOUS && + randomSymbol in PasswordGenerator.AMBIGUOUS_STR) password += randomSymbol // Continue the password generation as if nothing was added. } @@ -157,8 +167,9 @@ object RandomPhonemesGenerator { nextBasicType = when { candidate.flags.hasFlag(CONSONANT) -> VOWEL - previousFlags.hasFlag(VOWEL) || candidate.flags.hasFlag(DIPHTHONG) || secureRandomBiasedBoolean(60) -> - CONSONANT + previousFlags.hasFlag(VOWEL) || + candidate.flags.hasFlag(DIPHTHONG) || + secureRandomBiasedBoolean(60) -> CONSONANT else -> VOWEL } previousFlags = candidate.flags diff --git a/app/src/main/java/dev/msfjarvis/aps/util/pwgenxkpwd/PasswordBuilder.kt b/app/src/main/java/dev/msfjarvis/aps/util/pwgenxkpwd/PasswordBuilder.kt index 3eb2cce7..af8ac5c2 100644 --- a/app/src/main/java/dev/msfjarvis/aps/util/pwgenxkpwd/PasswordBuilder.kt +++ b/app/src/main/java/dev/msfjarvis/aps/util/pwgenxkpwd/PasswordBuilder.kt @@ -109,7 +109,9 @@ class PasswordBuilder(ctx: Context) { } else candidate CapsType.TitleCase -> - candidate.replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString() } + candidate.replaceFirstChar { + if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString() + } CapsType.lowercase -> candidate.lowercase(Locale.getDefault()) CapsType.As_iS -> candidate } diff --git a/app/src/main/java/dev/msfjarvis/aps/util/pwgenxkpwd/XkpwdDictionary.kt b/app/src/main/java/dev/msfjarvis/aps/util/pwgenxkpwd/XkpwdDictionary.kt index ab31892f..25162378 100644 --- a/app/src/main/java/dev/msfjarvis/aps/util/pwgenxkpwd/XkpwdDictionary.kt +++ b/app/src/main/java/dev/msfjarvis/aps/util/pwgenxkpwd/XkpwdDictionary.kt @@ -30,7 +30,10 @@ class XkpwdDictionary(context: Context) { context.resources.openRawResource(R.raw.xkpwdict).bufferedReader().readLines() } - words = lines.asSequence().map { it.trim() }.filter { it.isNotEmpty() && !it.contains(' ') }.groupBy { it.length } + words = + lines.asSequence().map { it.trim() }.filter { it.isNotEmpty() && !it.contains(' ') }.groupBy { + it.length + } } companion object { diff --git a/app/src/main/java/dev/msfjarvis/aps/util/services/ClipboardService.kt b/app/src/main/java/dev/msfjarvis/aps/util/services/ClipboardService.kt index d25a110e..de7d727c 100644 --- a/app/src/main/java/dev/msfjarvis/aps/util/services/ClipboardService.kt +++ b/app/src/main/java/dev/msfjarvis/aps/util/services/ClipboardService.kt @@ -140,7 +140,10 @@ class ClipboardService : Service() { } @RequiresApi(Build.VERSION_CODES.N) - private fun createNotificationApi24(pendingIntent: PendingIntent, clearTimeMs: Long): Notification { + private fun createNotificationApi24( + pendingIntent: PendingIntent, + clearTimeMs: Long + ): Notification { return NotificationCompat.Builder(this, CHANNEL_ID) .setContentTitle(getString(R.string.app_name)) .setContentText(getString(R.string.tap_clear_clipboard)) @@ -157,7 +160,11 @@ class ClipboardService : Service() { private fun createNotificationChannel() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { val serviceChannel = - NotificationChannel(CHANNEL_ID, getString(R.string.app_name), NotificationManager.IMPORTANCE_LOW) + NotificationChannel( + CHANNEL_ID, + getString(R.string.app_name), + NotificationManager.IMPORTANCE_LOW + ) val manager = getSystemService<NotificationManager>() if (manager != null) { manager.createNotificationChannel(serviceChannel) diff --git a/app/src/main/java/dev/msfjarvis/aps/util/services/OreoAutofillService.kt b/app/src/main/java/dev/msfjarvis/aps/util/services/OreoAutofillService.kt index ce55fe7b..4c75a619 100644 --- a/app/src/main/java/dev/msfjarvis/aps/util/services/OreoAutofillService.kt +++ b/app/src/main/java/dev/msfjarvis/aps/util/services/OreoAutofillService.kt @@ -60,7 +60,11 @@ class OreoAutofillService : AutofillService() { cachePublicSuffixList(applicationContext) } - override fun onFillRequest(request: FillRequest, cancellationSignal: CancellationSignal, callback: FillCallback) { + override fun onFillRequest( + request: FillRequest, + cancellationSignal: CancellationSignal, + callback: FillCallback + ) { val structure = request.fillContexts.lastOrNull()?.structure ?: run { @@ -93,7 +97,8 @@ class OreoAutofillService : AutofillService() { return } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { - Api30AutofillResponseBuilder(formToFill).fillCredentials(this, request.inlineSuggestionsRequest, callback) + Api30AutofillResponseBuilder(formToFill) + .fillCredentials(this, request.inlineSuggestionsRequest, callback) } else { AutofillResponseBuilder(formToFill).fillCredentials(this, callback) } @@ -148,11 +153,13 @@ class OreoAutofillService : AutofillService() { } } -fun Context.getDefaultUsername() = sharedPrefs.getString(PreferenceKeys.OREO_AUTOFILL_DEFAULT_USERNAME) +fun Context.getDefaultUsername() = + sharedPrefs.getString(PreferenceKeys.OREO_AUTOFILL_DEFAULT_USERNAME) fun Context.getCustomSuffixes(): Sequence<String> { - return sharedPrefs.getString(PreferenceKeys.OREO_AUTOFILL_CUSTOM_PUBLIC_SUFFIXES)?.splitToSequence('\n')?.filter { - it.isNotBlank() && it.first() != '.' && it.last() != '.' - } + return sharedPrefs + .getString(PreferenceKeys.OREO_AUTOFILL_CUSTOM_PUBLIC_SUFFIXES) + ?.splitToSequence('\n') + ?.filter { it.isNotBlank() && it.first() != '.' && it.last() != '.' } ?: emptySequence() } diff --git a/app/src/main/java/dev/msfjarvis/aps/util/services/PasswordExportService.kt b/app/src/main/java/dev/msfjarvis/aps/util/services/PasswordExportService.kt index 2ecd2287..72f14144 100644 --- a/app/src/main/java/dev/msfjarvis/aps/util/services/PasswordExportService.kt +++ b/app/src/main/java/dev/msfjarvis/aps/util/services/PasswordExportService.kt @@ -137,7 +137,11 @@ class PasswordExportService : Service() { private fun createNotificationChannel() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { val serviceChannel = - NotificationChannel(CHANNEL_ID, getString(R.string.app_name), NotificationManager.IMPORTANCE_LOW) + NotificationChannel( + CHANNEL_ID, + getString(R.string.app_name), + NotificationManager.IMPORTANCE_LOW + ) val manager = getSystemService<NotificationManager>() if (manager != null) { manager.createNotificationChannel(serviceChannel) diff --git a/app/src/main/java/dev/msfjarvis/aps/util/settings/GitSettings.kt b/app/src/main/java/dev/msfjarvis/aps/util/settings/GitSettings.kt index 3a508e45..d3c85fa7 100644 --- a/app/src/main/java/dev/msfjarvis/aps/util/settings/GitSettings.kt +++ b/app/src/main/java/dev/msfjarvis/aps/util/settings/GitSettings.kt @@ -25,7 +25,8 @@ enum class Protocol(val pref: String) { private val map = values().associateBy(Protocol::pref) fun fromString(type: String?): Protocol { - return map[type ?: return Ssh] ?: throw IllegalArgumentException("$type is not a valid Protocol") + return map[type ?: return Ssh] + ?: throw IllegalArgumentException("$type is not a valid Protocol") } } } @@ -41,7 +42,8 @@ enum class AuthMode(val pref: String) { private val map = values().associateBy(AuthMode::pref) fun fromString(type: String?): AuthMode { - return map[type ?: return SshKey] ?: throw IllegalArgumentException("$type is not a valid AuthMode") + return map[type ?: return SshKey] + ?: throw IllegalArgumentException("$type is not a valid AuthMode") } } } @@ -50,12 +52,18 @@ object GitSettings { private const val DEFAULT_BRANCH = "master" - private val settings by lazy(LazyThreadSafetyMode.PUBLICATION) { Application.instance.sharedPrefs } + private val settings by lazy(LazyThreadSafetyMode.PUBLICATION) { + Application.instance.sharedPrefs + } private val encryptedSettings by lazy(LazyThreadSafetyMode.PUBLICATION) { Application.instance.getEncryptedGitPrefs() } - private val proxySettings by lazy(LazyThreadSafetyMode.PUBLICATION) { Application.instance.getEncryptedProxyPrefs() } - private val hostKeyPath by lazy(LazyThreadSafetyMode.NONE) { "${Application.instance.filesDir}/.host_key" } + private val proxySettings by lazy(LazyThreadSafetyMode.PUBLICATION) { + Application.instance.getEncryptedProxyPrefs() + } + private val hostKeyPath by lazy(LazyThreadSafetyMode.NONE) { + "${Application.instance.filesDir}/.host_key" + } var authMode get() = AuthMode.fromString(settings.getString(PreferenceKeys.GIT_REMOTE_AUTH)) diff --git a/app/src/main/java/dev/msfjarvis/aps/util/settings/Migrations.kt b/app/src/main/java/dev/msfjarvis/aps/util/settings/Migrations.kt index a5612603..b9333617 100644 --- a/app/src/main/java/dev/msfjarvis/aps/util/settings/Migrations.kt +++ b/app/src/main/java/dev/msfjarvis/aps/util/settings/Migrations.kt @@ -60,7 +60,8 @@ private fun migrateToGitUrlBasedConfig(sharedPrefs: SharedPreferences) { val url = when { urlWithFreeEntryScheme.startsWith("https://") -> urlWithFreeEntryScheme - urlWithFreeEntryScheme.startsWith("http://") -> urlWithFreeEntryScheme.replaceFirst("http", "https") + urlWithFreeEntryScheme.startsWith("http://") -> + urlWithFreeEntryScheme.replaceFirst("http", "https") else -> "https://$urlWithFreeEntryScheme" } runCatching { if (URI(url).rawAuthority != null) url else null }.get() @@ -96,7 +97,10 @@ private fun migrateToHideAll(sharedPrefs: SharedPreferences) { private fun migrateToSshKey(context: Context, sharedPrefs: SharedPreferences) { val privateKeyFile = File(context.filesDir, ".ssh_key") - if (sharedPrefs.contains(PreferenceKeys.USE_GENERATED_KEY) && !SshKey.exists && privateKeyFile.exists()) { + if (sharedPrefs.contains(PreferenceKeys.USE_GENERATED_KEY) && + !SshKey.exists && + privateKeyFile.exists() + ) { // Currently uses a private key imported or generated with an old version of Password Store. // Generated keys come with a public key which the user should still be able to view after // the migration (not possible for regular imported keys), hence the special case. diff --git a/app/src/main/java/dev/msfjarvis/aps/util/settings/PasswordSortOrder.kt b/app/src/main/java/dev/msfjarvis/aps/util/settings/PasswordSortOrder.kt index ee678de2..6b48b6a9 100644 --- a/app/src/main/java/dev/msfjarvis/aps/util/settings/PasswordSortOrder.kt +++ b/app/src/main/java/dev/msfjarvis/aps/util/settings/PasswordSortOrder.kt @@ -18,10 +18,15 @@ enum class PasswordSortOrder(val comparator: java.util.Comparator<PasswordItem>) (p1.type + p1.name).compareTo(p2.type + p2.name, ignoreCase = true) } ), - INDEPENDENT(Comparator { p1: PasswordItem, p2: PasswordItem -> p1.name.compareTo(p2.name, ignoreCase = true) }), + INDEPENDENT( + Comparator { p1: PasswordItem, p2: PasswordItem -> + p1.name.compareTo(p2.name, ignoreCase = true) + } + ), RECENTLY_USED( Comparator { p1: PasswordItem, p2: PasswordItem -> - val recentHistory = Application.instance.getSharedPreferences("recent_password_history", Context.MODE_PRIVATE) + val recentHistory = + Application.instance.getSharedPreferences("recent_password_history", Context.MODE_PRIVATE) val timeP1 = recentHistory.getString(p1.file.absolutePath.base64()) val timeP2 = recentHistory.getString(p2.file.absolutePath.base64()) when { diff --git a/app/src/main/java/dev/msfjarvis/aps/util/totp/UriTotpFinder.kt b/app/src/main/java/dev/msfjarvis/aps/util/totp/UriTotpFinder.kt index 2e954804..fa8481a9 100644 --- a/app/src/main/java/dev/msfjarvis/aps/util/totp/UriTotpFinder.kt +++ b/app/src/main/java/dev/msfjarvis/aps/util/totp/UriTotpFinder.kt @@ -44,7 +44,8 @@ class UriTotpFinder @Inject constructor() : TotpFinder { override fun findAlgorithm(content: String): String { content.split("\n".toRegex()).forEach { line -> - if (line.startsWith(TOTP_FIELDS[0]) && Uri.parse(line).getQueryParameter("algorithm") != null) { + if (line.startsWith(TOTP_FIELDS[0]) && Uri.parse(line).getQueryParameter("algorithm") != null + ) { return Uri.parse(line).getQueryParameter("algorithm")!! } } diff --git a/app/src/main/java/dev/msfjarvis/aps/util/viewmodel/SearchableRepositoryViewModel.kt b/app/src/main/java/dev/msfjarvis/aps/util/viewmodel/SearchableRepositoryViewModel.kt index f0488537..7106dbe3 100644 --- a/app/src/main/java/dev/msfjarvis/aps/util/viewmodel/SearchableRepositoryViewModel.kt +++ b/app/src/main/java/dev/msfjarvis/aps/util/viewmodel/SearchableRepositoryViewModel.kt @@ -75,8 +75,14 @@ private fun PasswordItem.Companion.makeComparator( PasswordSortOrder.FILE_FIRST -> compareByDescending { it.type } PasswordSortOrder.RECENTLY_USED -> PasswordSortOrder.RECENTLY_USED.comparator } - .then(compareBy(nullsLast(CaseInsensitiveComparator)) { directoryStructure.getIdentifierFor(it.file) }) - .then(compareBy(nullsLast(CaseInsensitiveComparator)) { directoryStructure.getUsernameFor(it.file) }) + .then( + compareBy(nullsLast(CaseInsensitiveComparator)) { + directoryStructure.getIdentifierFor(it.file) + } + ) + .then( + compareBy(nullsLast(CaseInsensitiveComparator)) { directoryStructure.getUsernameFor(it.file) } + ) } val PasswordItem.stableId: String @@ -179,7 +185,8 @@ class SearchableRepositoryViewModel(application: Application) : AndroidViewModel .mapLatest { searchAction -> val listResultFlow = when (searchAction.searchMode) { - SearchMode.RecursivelyInSubdirectories -> listFilesRecursively(searchAction.baseDirectory) + SearchMode.RecursivelyInSubdirectories -> + listFilesRecursively(searchAction.baseDirectory) SearchMode.InCurrentDirectoryOnly -> listFiles(searchAction.baseDirectory) } val prefilteredResultFlow = @@ -188,9 +195,8 @@ class SearchableRepositoryViewModel(application: Application) : AndroidViewModel ListMode.DirectoriesOnly -> listResultFlow.filter { it.isDirectory } ListMode.AllEntries -> listResultFlow } - val filterModeToUse = if (searchAction.filter == "") FilterMode.NoFilter else searchAction.filterMode val passwordList = - when (filterModeToUse) { + when (if (searchAction.filter == "") FilterMode.NoFilter else searchAction.filterMode) { FilterMode.NoFilter -> { prefilteredResultFlow.map { it.toPasswordItem() }.toList().sortedWith(itemComparator) } @@ -201,7 +207,9 @@ class SearchableRepositoryViewModel(application: Application) : AndroidViewModel val regex = generateStrictDomainRegex(searchAction.filter) if (regex != null) { prefilteredResultFlow - .filter { absoluteFile -> regex.containsMatchIn(absoluteFile.relativeTo(root).path) } + .filter { absoluteFile -> + regex.containsMatchIn(absoluteFile.relativeTo(root).path) + } .map { it.toPasswordItem() } .toList() .sortedWith(itemComparator) @@ -218,7 +226,9 @@ class SearchableRepositoryViewModel(application: Application) : AndroidViewModel .filter { it.first > 0 } .toList() .sortedWith( - compareByDescending<Pair<Int, PasswordItem>> { it.first }.thenBy(itemComparator) { it.second } + compareByDescending<Pair<Int, PasswordItem>> { it.first }.thenBy(itemComparator) { + it.second + } ) .map { it.second } } @@ -387,7 +397,9 @@ open class SearchableRepositoryAdapter<T : RecyclerView.ViewHolder>( addObserver( object : SelectionTracker.SelectionObserver<String>() { override fun onSelectionChanged() { - this@SearchableRepositoryAdapter.onSelectionChangedListener?.invoke(requireSelectionTracker().selection) + this@SearchableRepositoryAdapter.onSelectionChangedListener?.invoke( + requireSelectionTracker().selection + ) } } ) @@ -395,15 +407,23 @@ open class SearchableRepositoryAdapter<T : RecyclerView.ViewHolder>( } private var onItemClickedListener: ((holder: T, item: PasswordItem) -> Unit)? = null - open fun onItemClicked(listener: (holder: T, item: PasswordItem) -> Unit): SearchableRepositoryAdapter<T> { - check(onItemClickedListener == null) { "Only a single listener can be registered for onItemClicked" } + open fun onItemClicked( + listener: (holder: T, item: PasswordItem) -> Unit + ): SearchableRepositoryAdapter<T> { + check(onItemClickedListener == null) { + "Only a single listener can be registered for onItemClicked" + } onItemClickedListener = listener return this } private var onSelectionChangedListener: ((selection: Selection<String>) -> Unit)? = null - open fun onSelectionChanged(listener: (selection: Selection<String>) -> Unit): SearchableRepositoryAdapter<T> { - check(onSelectionChangedListener == null) { "Only a single listener can be registered for onSelectionChanged" } + open fun onSelectionChanged( + listener: (selection: Selection<String>) -> Unit + ): SearchableRepositoryAdapter<T> { + check(onSelectionChangedListener == null) { + "Only a single listener can be registered for onSelectionChanged" + } onSelectionChangedListener = listener return this } diff --git a/app/src/main/res/layout/fragment_repo_location.xml b/app/src/main/res/layout/fragment_repo_location.xml index b2adc0e6..4bdaefe8 100644 --- a/app/src/main/res/layout/fragment_repo_location.xml +++ b/app/src/main/res/layout/fragment_repo_location.xml @@ -96,4 +96,3 @@ </androidx.constraintlayout.widget.ConstraintLayout> </ScrollView> - diff --git a/app/src/nonFree/java/dev/msfjarvis/aps/autofill/oreo/ui/AutofillSmsActivity.kt b/app/src/nonFree/java/dev/msfjarvis/aps/autofill/oreo/ui/AutofillSmsActivity.kt index ab9f3d12..5e53bf3e 100644 --- a/app/src/nonFree/java/dev/msfjarvis/aps/autofill/oreo/ui/AutofillSmsActivity.kt +++ b/app/src/nonFree/java/dev/msfjarvis/aps/autofill/oreo/ui/AutofillSmsActivity.kt @@ -77,7 +77,12 @@ class AutofillSmsActivity : AppCompatActivity() { fun makeFillOtpFromSmsIntentSender(context: Context): IntentSender { val intent = Intent(context, AutofillSmsActivity::class.java) - return PendingIntent.getActivity(context, fillOtpFromSmsRequestCode++, intent, PendingIntent.FLAG_CANCEL_CURRENT) + return PendingIntent.getActivity( + context, + fillOtpFromSmsRequestCode++, + intent, + PendingIntent.FLAG_CANCEL_CURRENT + ) .intentSender } } @@ -122,15 +127,17 @@ class AutofillSmsActivity : AppCompatActivity() { private suspend fun waitForSms() { val smsClient = SmsCodeRetriever.getAutofillClient(this@AutofillSmsActivity) - runCatching { withContext(Dispatchers.IO) { smsClient.startSmsCodeRetriever().suspendableAwait() } }.onFailure { e - -> - if (e is ResolvableApiException) { - e.startResolutionForResult(this@AutofillSmsActivity, 1) - } else { - e(e) - withContext(Dispatchers.Main) { finish() } - } + runCatching { + withContext(Dispatchers.IO) { smsClient.startSmsCodeRetriever().suspendableAwait() } } + .onFailure { e -> + if (e is ResolvableApiException) { + e.startResolutionForResult(this@AutofillSmsActivity, 1) + } else { + e(e) + withContext(Dispatchers.Main) { finish() } + } + } } private val smsCodeRetrievedReceiver = @@ -144,7 +151,10 @@ class AutofillSmsActivity : AppCompatActivity() { clientState, AutofillAction.FillOtpFromSms ) - setResult(RESULT_OK, Intent().apply { putExtra(AutofillManager.EXTRA_AUTHENTICATION_RESULT, fillInDataset) }) + setResult( + RESULT_OK, + Intent().apply { putExtra(AutofillManager.EXTRA_AUTHENTICATION_RESULT, fillInDataset) } + ) finish() } } diff --git a/autofill-parser/src/main/java/com/github/androidpasswordstore/autofillparser/AutofillFormParser.kt b/autofill-parser/src/main/java/com/github/androidpasswordstore/autofillparser/AutofillFormParser.kt index e51ab69e..85381254 100644 --- a/autofill-parser/src/main/java/com/github/androidpasswordstore/autofillparser/AutofillFormParser.kt +++ b/autofill-parser/src/main/java/com/github/androidpasswordstore/autofillparser/AutofillFormParser.kt @@ -41,7 +41,8 @@ public sealed class FormOrigin(public open val identifier: String) { when (this) { is Web -> identifier is App -> { - val info = context.packageManager.getApplicationInfo(identifier, PackageManager.GET_META_DATA) + val info = + context.packageManager.getApplicationInfo(identifier, PackageManager.GET_META_DATA) val label = context.packageManager.getApplicationLabel(info) if (untrusted) "“$label”" else "$label" } @@ -174,7 +175,10 @@ private class AutofillFormParser( // the single origin among the detected fillable or saveable fields. If this origin // is null, but we encountered web origins elsewhere in the AssistStructure, the // situation is uncertain and Autofill should not be offered. - webOriginToFormOrigin(context, scenario.allFields.map { it.webOrigin }.toSet().singleOrNull() ?: return null) + webOriginToFormOrigin( + context, + scenario.allFields.map { it.webOrigin }.toSet().singleOrNull() ?: return null + ) } } } @@ -204,7 +208,12 @@ private constructor( ): FillableForm? { val form = AutofillFormParser(context, structure, isManualRequest, customSuffixes) if (form.formOrigin == null || form.scenario == null) return null - return FillableForm(form.formOrigin, form.scenario.map { it.autofillId }, form.ignoredIds, form.saveFlags) + return FillableForm( + form.formOrigin, + form.scenario.map { it.autofillId }, + form.ignoredIds, + form.saveFlags + ) } } diff --git a/autofill-parser/src/main/java/com/github/androidpasswordstore/autofillparser/AutofillHelper.kt b/autofill-parser/src/main/java/com/github/androidpasswordstore/autofillparser/AutofillHelper.kt index 6c8ae0dd..2de929b9 100644 --- a/autofill-parser/src/main/java/com/github/androidpasswordstore/autofillparser/AutofillHelper.kt +++ b/autofill-parser/src/main/java/com/github/androidpasswordstore/autofillparser/AutofillHelper.kt @@ -49,14 +49,19 @@ public fun computeCertificatesHash(context: Context, appPackage: String): String // hashes comparable between versions and hence default to using the deprecated API. @SuppressLint("PackageManagerGetSignatures") @Suppress("DEPRECATION") - val signaturesOld = context.packageManager.getPackageInfo(appPackage, PackageManager.GET_SIGNATURES).signatures + val signaturesOld = + context.packageManager.getPackageInfo(appPackage, PackageManager.GET_SIGNATURES).signatures val stableHashOld = stableHash(signaturesOld.map { it.toByteArray() }) if (Build.VERSION.SDK_INT >= 28) { - val info = context.packageManager.getPackageInfo(appPackage, PackageManager.GET_SIGNING_CERTIFICATES) - val signaturesNew = info.signingInfo.signingCertificateHistory ?: info.signingInfo.apkContentsSigners + val info = + context.packageManager.getPackageInfo(appPackage, PackageManager.GET_SIGNING_CERTIFICATES) + val signaturesNew = + info.signingInfo.signingCertificateHistory ?: info.signingInfo.apkContentsSigners val stableHashNew = stableHash(signaturesNew.map { it.toByteArray() }) if (stableHashNew != stableHashOld) - tag("CertificatesHash").e { "Mismatch between old and new hash: $stableHashNew != $stableHashOld" } + tag("CertificatesHash").e { + "Mismatch between old and new hash: $stableHashNew != $stableHashOld" + } } return stableHashOld } @@ -106,7 +111,10 @@ private fun visitViewNodes(structure: AssistStructure, block: (AssistStructure.V } } -private fun visitViewNode(node: AssistStructure.ViewNode, block: (AssistStructure.ViewNode) -> Unit) { +private fun visitViewNode( + node: AssistStructure.ViewNode, + block: (AssistStructure.ViewNode) -> Unit +) { block(node) for (i in 0 until node.childCount) { visitViewNode(node.getChildAt(i), block) @@ -114,7 +122,9 @@ private fun visitViewNode(node: AssistStructure.ViewNode, block: (AssistStructur } @RequiresApi(Build.VERSION_CODES.O) -internal fun AssistStructure.findNodeByAutofillId(autofillId: AutofillId): AssistStructure.ViewNode? { +internal fun AssistStructure.findNodeByAutofillId( + autofillId: AutofillId +): AssistStructure.ViewNode? { var node: AssistStructure.ViewNode? = null visitViewNodes(this) { if (it.autofillId == autofillId) node = it } return node diff --git a/autofill-parser/src/main/java/com/github/androidpasswordstore/autofillparser/AutofillScenario.kt b/autofill-parser/src/main/java/com/github/androidpasswordstore/autofillparser/AutofillScenario.kt index 3583d705..11b85b66 100644 --- a/autofill-parser/src/main/java/com/github/androidpasswordstore/autofillparser/AutofillScenario.kt +++ b/autofill-parser/src/main/java/com/github/androidpasswordstore/autofillparser/AutofillScenario.kt @@ -56,9 +56,15 @@ public sealed class AutofillScenario<out T : Any> { username = clientState.getParcelable(BUNDLE_KEY_USERNAME_ID) fillUsername = clientState.getBoolean(BUNDLE_KEY_FILL_USERNAME) otp = clientState.getParcelable(BUNDLE_KEY_OTP_ID) - currentPassword.addAll(clientState.getParcelableArrayList(BUNDLE_KEY_CURRENT_PASSWORD_IDS) ?: emptyList()) - newPassword.addAll(clientState.getParcelableArrayList(BUNDLE_KEY_NEW_PASSWORD_IDS) ?: emptyList()) - genericPassword.addAll(clientState.getParcelableArrayList(BUNDLE_KEY_GENERIC_PASSWORD_IDS) ?: emptyList()) + currentPassword.addAll( + clientState.getParcelableArrayList(BUNDLE_KEY_CURRENT_PASSWORD_IDS) ?: emptyList() + ) + newPassword.addAll( + clientState.getParcelableArrayList(BUNDLE_KEY_NEW_PASSWORD_IDS) ?: emptyList() + ) + genericPassword.addAll( + clientState.getParcelableArrayList(BUNDLE_KEY_GENERIC_PASSWORD_IDS) ?: emptyList() + ) } .build() } catch (e: Throwable) { @@ -227,7 +233,9 @@ public fun Dataset.Builder.fillWith( } } -internal inline fun <T : Any, S : Any> AutofillScenario<T>.map(transform: (T) -> S): AutofillScenario<S> { +internal inline fun <T : Any, S : Any> AutofillScenario<T>.map( + transform: (T) -> S +): AutofillScenario<S> { val builder = AutofillScenario.Builder<S>() builder.username = username?.let(transform) builder.fillUsername = fillUsername @@ -253,7 +261,10 @@ internal fun AutofillScenario<AutofillId>.toBundle(): Bundle = putParcelable(AutofillScenario.BUNDLE_KEY_USERNAME_ID, username) putBoolean(AutofillScenario.BUNDLE_KEY_FILL_USERNAME, fillUsername) putParcelable(AutofillScenario.BUNDLE_KEY_OTP_ID, otp) - putParcelableArrayList(AutofillScenario.BUNDLE_KEY_CURRENT_PASSWORD_IDS, ArrayList(currentPassword)) + putParcelableArrayList( + AutofillScenario.BUNDLE_KEY_CURRENT_PASSWORD_IDS, + ArrayList(currentPassword) + ) putParcelableArrayList(AutofillScenario.BUNDLE_KEY_NEW_PASSWORD_IDS, ArrayList(newPassword)) } } @@ -262,7 +273,10 @@ internal fun AutofillScenario<AutofillId>.toBundle(): Bundle = putParcelable(AutofillScenario.BUNDLE_KEY_USERNAME_ID, username) putBoolean(AutofillScenario.BUNDLE_KEY_FILL_USERNAME, fillUsername) putParcelable(AutofillScenario.BUNDLE_KEY_OTP_ID, otp) - putParcelableArrayList(AutofillScenario.BUNDLE_KEY_GENERIC_PASSWORD_IDS, ArrayList(genericPassword)) + putParcelableArrayList( + AutofillScenario.BUNDLE_KEY_GENERIC_PASSWORD_IDS, + ArrayList(genericPassword) + ) } } } diff --git a/autofill-parser/src/main/java/com/github/androidpasswordstore/autofillparser/AutofillStrategy.kt b/autofill-parser/src/main/java/com/github/androidpasswordstore/autofillparser/AutofillStrategy.kt index ed264233..7303efc5 100644 --- a/autofill-parser/src/main/java/com/github/androidpasswordstore/autofillparser/AutofillStrategy.kt +++ b/autofill-parser/src/main/java/com/github/androidpasswordstore/autofillparser/AutofillStrategy.kt @@ -9,11 +9,14 @@ import androidx.annotation.RequiresApi import com.github.androidpasswordstore.autofillparser.CertaintyLevel.Certain import com.github.androidpasswordstore.autofillparser.CertaintyLevel.Likely -private inline fun <T> Pair<T, T>.all(predicate: T.() -> Boolean) = predicate(first) && predicate(second) +private inline fun <T> Pair<T, T>.all(predicate: T.() -> Boolean) = + predicate(first) && predicate(second) -private inline fun <T> Pair<T, T>.any(predicate: T.() -> Boolean) = predicate(first) || predicate(second) +private inline fun <T> Pair<T, T>.any(predicate: T.() -> Boolean) = + predicate(first) || predicate(second) -private inline fun <T> Pair<T, T>.none(predicate: T.() -> Boolean) = !predicate(first) && !predicate(second) +private inline fun <T> Pair<T, T>.none(predicate: T.() -> Boolean) = + !predicate(first) && !predicate(second) /** * The strategy used to detect [AutofillScenario] s; expressed using the DSL implemented in @@ -32,7 +35,8 @@ internal val autofillStrategy = strategy { } currentPassword(optional = true) { takeSingle { alreadyMatched -> - val adjacentToNewPasswords = directlyPrecedes(alreadyMatched) || directlyFollows(alreadyMatched) + val adjacentToNewPasswords = + directlyPrecedes(alreadyMatched) || directlyFollows(alreadyMatched) // The Autofill framework has not hint that applies to current passwords only. // In this scenario, we have already matched fields a pair of fields with a specific // new password hint, so we take a generic Autofill password hint to mean a current @@ -109,7 +113,9 @@ internal val autofillStrategy = strategy { rule(applyInSingleOriginMode = true) { newPassword { takeSingle { hasHintNewPassword && isFocused } } username(optional = true) { - takeSingle { alreadyMatched -> usernameCertainty >= Likely && directlyPrecedes(alreadyMatched.singleOrNull()) } + takeSingle { alreadyMatched -> + usernameCertainty >= Likely && directlyPrecedes(alreadyMatched.singleOrNull()) + } } } @@ -119,7 +125,9 @@ internal val autofillStrategy = strategy { rule(applyInSingleOriginMode = true) { currentPassword { takeSingle { hasAutocompleteHintCurrentPassword && isFocused } } username(optional = true) { - takeSingle { alreadyMatched -> usernameCertainty >= Likely && directlyPrecedes(alreadyMatched.singleOrNull()) } + takeSingle { alreadyMatched -> + usernameCertainty >= Likely && directlyPrecedes(alreadyMatched.singleOrNull()) + } } } @@ -129,7 +137,9 @@ internal val autofillStrategy = strategy { rule(applyInSingleOriginMode = true) { genericPassword { takeSingle { passwordCertainty >= Likely && isFocused } } username(optional = true) { - takeSingle { alreadyMatched -> usernameCertainty >= Likely && directlyPrecedes(alreadyMatched.singleOrNull()) } + takeSingle { alreadyMatched -> + usernameCertainty >= Likely && directlyPrecedes(alreadyMatched.singleOrNull()) + } } } @@ -139,12 +149,16 @@ internal val autofillStrategy = strategy { rule { username { takeSingle { hasHintUsername && isFocused } } currentPassword(matchHidden = true) { - takeSingle { alreadyMatched -> directlyFollows(alreadyMatched.singleOrNull()) && couldBeTwoStepHiddenPassword } + takeSingle { alreadyMatched -> + directlyFollows(alreadyMatched.singleOrNull()) && couldBeTwoStepHiddenPassword + } } } // Match a single focused OTP field. - rule(applyInSingleOriginMode = true) { otp { takeSingle { otpCertainty >= Likely && isFocused } } } + rule(applyInSingleOriginMode = true) { + otp { takeSingle { otpCertainty >= Likely && isFocused } } + } // Match a single focused username field without a password field. rule(applyInSingleOriginMode = true) { @@ -162,7 +176,9 @@ internal val autofillStrategy = strategy { // This rule can apply in single origin mode since even though the password field may not be // focused at the time the rule runs, the fill suggestion will only show if it ever receives // focus. - rule(applyInSingleOriginMode = true) { currentPassword { takeSingle { hasAutocompleteHintCurrentPassword } } } + rule(applyInSingleOriginMode = true) { + currentPassword { takeSingle { hasAutocompleteHintCurrentPassword } } + } // See above. rule(applyInSingleOriginMode = true) { genericPassword { takeSingle { true } } } @@ -171,10 +187,14 @@ internal val autofillStrategy = strategy { rule(applyInSingleOriginMode = true, applyOnManualRequestOnly = true) { genericPassword { takeSingle { isFocused } } username(optional = true) { - takeSingle { alreadyMatched -> usernameCertainty >= Likely && directlyPrecedes(alreadyMatched.singleOrNull()) } + takeSingle { alreadyMatched -> + usernameCertainty >= Likely && directlyPrecedes(alreadyMatched.singleOrNull()) + } } } // Match any focused username field on manual request. - rule(applyInSingleOriginMode = true, applyOnManualRequestOnly = true) { username { takeSingle { isFocused } } } + rule(applyInSingleOriginMode = true, applyOnManualRequestOnly = true) { + username { takeSingle { isFocused } } + } } diff --git a/autofill-parser/src/main/java/com/github/androidpasswordstore/autofillparser/AutofillStrategyDsl.kt b/autofill-parser/src/main/java/com/github/androidpasswordstore/autofillparser/AutofillStrategyDsl.kt index c6886d15..3d7f9849 100644 --- a/autofill-parser/src/main/java/com/github/androidpasswordstore/autofillparser/AutofillStrategyDsl.kt +++ b/autofill-parser/src/main/java/com/github/androidpasswordstore/autofillparser/AutofillStrategyDsl.kt @@ -20,28 +20,41 @@ internal interface FieldMatcher { class Builder { private var takeSingle: (FormField.(List<FormField>) -> Boolean)? = null - private val tieBreakersSingle: MutableList<FormField.(List<FormField>) -> Boolean> = mutableListOf() + private val tieBreakersSingle: MutableList<FormField.(List<FormField>) -> Boolean> = + mutableListOf() private var takePair: (Pair<FormField, FormField>.(List<FormField>) -> Boolean)? = null - private var tieBreakersPair: MutableList<Pair<FormField, FormField>.(List<FormField>) -> Boolean> = mutableListOf() + private var tieBreakersPair: + MutableList<Pair<FormField, FormField>.(List<FormField>) -> Boolean> = + mutableListOf() fun takeSingle(block: FormField.(alreadyMatched: List<FormField>) -> Boolean = { true }) { - check(takeSingle == null && takePair == null) { "Every block can only have at most one take{Single,Pair} block" } + check(takeSingle == null && takePair == null) { + "Every block can only have at most one take{Single,Pair} block" + } takeSingle = block } fun breakTieOnSingle(block: FormField.(alreadyMatched: List<FormField>) -> Boolean) { - check(takeSingle != null) { "Every block needs a takeSingle block before a breakTieOnSingle block" } + check(takeSingle != null) { + "Every block needs a takeSingle block before a breakTieOnSingle block" + } check(takePair == null) { "takePair cannot be mixed with breakTieOnSingle" } tieBreakersSingle.add(block) } - fun takePair(block: Pair<FormField, FormField>.(alreadyMatched: List<FormField>) -> Boolean = { true }) { - check(takeSingle == null && takePair == null) { "Every block can only have at most one take{Single,Pair} block" } + fun takePair( + block: Pair<FormField, FormField>.(alreadyMatched: List<FormField>) -> Boolean = { true } + ) { + check(takeSingle == null && takePair == null) { + "Every block can only have at most one take{Single,Pair} block" + } takePair = block } - fun breakTieOnPair(block: Pair<FormField, FormField>.(alreadyMatched: List<FormField>) -> Boolean) { + fun breakTieOnPair( + block: Pair<FormField, FormField>.(alreadyMatched: List<FormField>) -> Boolean + ) { check(takePair != null) { "Every block needs a takePair block before a breakTieOnPair block" } check(takeSingle == null) { "takeSingle cannot be mixed with breakTieOnPair" } tieBreakersPair.add(block) @@ -69,7 +82,8 @@ internal class SingleFieldMatcher( class Builder { private var takeSingle: (FormField.(List<FormField>) -> Boolean)? = null - private val tieBreakersSingle: MutableList<FormField.(List<FormField>) -> Boolean> = mutableListOf() + private val tieBreakersSingle: MutableList<FormField.(List<FormField>) -> Boolean> = + mutableListOf() fun takeSingle(block: FormField.(alreadyMatched: List<FormField>) -> Boolean = { true }) { check(takeSingle == null) { "Every block can only have at most one takeSingle block" } @@ -77,7 +91,9 @@ internal class SingleFieldMatcher( } fun breakTieOnSingle(block: FormField.(alreadyMatched: List<FormField>) -> Boolean) { - check(takeSingle != null) { "Every block needs a takeSingle block before a breakTieOnSingle block" } + check(takeSingle != null) { + "Every block needs a takeSingle block before a breakTieOnSingle block" + } tieBreakersSingle.add(block) } @@ -180,7 +196,10 @@ private constructor( } @AutofillDsl - class Builder(private val applyInSingleOriginMode: Boolean, private val applyOnManualRequestOnly: Boolean) { + class Builder( + private val applyInSingleOriginMode: Boolean, + private val applyOnManualRequestOnly: Boolean + ) { companion object { @@ -286,9 +305,13 @@ private constructor( "Rules with applyInSingleOriginMode set to true must not fill into hidden fields" } } - return AutofillRule(matchers, applyInSingleOriginMode, applyOnManualRequestOnly, name ?: "Rule #$ruleId").also { - ruleId++ - } + return AutofillRule( + matchers, + applyInSingleOriginMode, + applyOnManualRequestOnly, + name ?: "Rule #$ruleId" + ) + .also { ruleId++ } } } @@ -409,4 +432,5 @@ internal class AutofillStrategy private constructor(private val rules: List<Auto } } -internal fun strategy(block: AutofillStrategy.Builder.() -> Unit) = AutofillStrategy.Builder().apply(block).build() +internal fun strategy(block: AutofillStrategy.Builder.() -> Unit) = + AutofillStrategy.Builder().apply(block).build() diff --git a/autofill-parser/src/main/java/com/github/androidpasswordstore/autofillparser/FeatureAndTrustDetection.kt b/autofill-parser/src/main/java/com/github/androidpasswordstore/autofillparser/FeatureAndTrustDetection.kt index c418df79..6e3e129a 100644 --- a/autofill-parser/src/main/java/com/github/androidpasswordstore/autofillparser/FeatureAndTrustDetection.kt +++ b/autofill-parser/src/main/java/com/github/androidpasswordstore/autofillparser/FeatureAndTrustDetection.kt @@ -63,7 +63,10 @@ private val TRUSTED_BROWSER_CERTIFICATE_HASH = "com.chrome.canary" to arrayOf("IBnfofsj779wxbzRRDxb6rBPPy/0Nm6aweNFdjmiTPw="), "com.chrome.dev" to arrayOf("kETuX+5LvF4h3URmVDHE6x8fcaMnFqC8knvLs5Izyr8="), "com.duckduckgo.mobile.android" to - arrayOf("u3uzHFc8RqHaf8XFKKas9DIQhFb+7FCBDH8zaU6z0tQ=", "8HB9AhwL8+b43MEbo/VwBCXVl9yjAaMeIQVWk067Gwo="), + arrayOf( + "u3uzHFc8RqHaf8XFKKas9DIQhFb+7FCBDH8zaU6z0tQ=", + "8HB9AhwL8+b43MEbo/VwBCXVl9yjAaMeIQVWk067Gwo=" + ), "com.microsoft.emmx" to arrayOf("AeGZlxCoLCdJtNUMRF3IXWcLYTYInQp2anOCfIKh6sk="), "com.opera.mini.native" to arrayOf("V6y8Ul8bLr0ZGWzW8BQ5fMkQ/RiEHgroUP68Ph5ZP/I="), "com.opera.mini.native.beta" to arrayOf("V6y8Ul8bLr0ZGWzW8BQ5fMkQ/RiEHgroUP68Ph5ZP/I="), @@ -80,7 +83,8 @@ private val TRUSTED_BROWSER_CERTIFICATE_HASH = "org.mozilla.klar" to arrayOf("YgOkc7421k7jf4f6UA7bx56rkwYQq5ufpMp9XB8bT/w="), "org.torproject.torbrowser" to arrayOf("IAYfBF5zfGc3XBd5TP7bQ2oDzsa6y3y5+WZCIFyizsg="), "org.ungoogled.chromium.stable" to arrayOf("29UOO5cXoxO/e/hH3hOu6bbtg1My4tK6Eik2Ym5Krtk="), - "org.ungoogled.chromium.extensions.stable" to arrayOf("29UOO5cXoxO/e/hH3hOu6bbtg1My4tK6Eik2Ym5Krtk="), + "org.ungoogled.chromium.extensions.stable" to + arrayOf("29UOO5cXoxO/e/hH3hOu6bbtg1My4tK6Eik2Ym5Krtk="), "com.kiwibrowser.browser" to arrayOf("wGnqlmMy6R4KDDzFd+b1Cf49ndr3AVrQxcXvj9o/hig="), ) @@ -162,19 +166,30 @@ private val BROWSER_SAVE_FLAG_IF_NO_ACCESSIBILITY = private fun isNoAccessibilityServiceEnabled(context: Context): Boolean { // See https://chromium.googlesource.com/chromium/src/+/447a31e977a65e2eb78804e4a09633699b4ede33 - return Settings.Secure.getString(context.contentResolver, Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES) + return Settings.Secure.getString( + context.contentResolver, + Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES + ) .isNullOrEmpty() } @RequiresApi(Build.VERSION_CODES.O) private fun getBrowserSaveFlag(context: Context, appPackage: String): Int? = BROWSER_SAVE_FLAG[appPackage] - ?: BROWSER_SAVE_FLAG_IF_NO_ACCESSIBILITY[appPackage]?.takeIf { isNoAccessibilityServiceEnabled(context) } + ?: BROWSER_SAVE_FLAG_IF_NO_ACCESSIBILITY[appPackage]?.takeIf { + isNoAccessibilityServiceEnabled(context) + } -internal data class BrowserAutofillSupportInfo(val multiOriginMethod: BrowserMultiOriginMethod, val saveFlags: Int?) +internal data class BrowserAutofillSupportInfo( + val multiOriginMethod: BrowserMultiOriginMethod, + val saveFlags: Int? +) @RequiresApi(Build.VERSION_CODES.O) -internal fun getBrowserAutofillSupportInfoIfTrusted(context: Context, appPackage: String): BrowserAutofillSupportInfo? { +internal fun getBrowserAutofillSupportInfoIfTrusted( + context: Context, + appPackage: String +): BrowserAutofillSupportInfo? { if (!isTrustedBrowser(context, appPackage)) return null return BrowserAutofillSupportInfo( multiOriginMethod = getBrowserMultiOriginMethod(appPackage), @@ -197,14 +212,18 @@ public enum class BrowserAutofillSupportLevel { } @RequiresApi(Build.VERSION_CODES.O) -private fun getBrowserAutofillSupportLevel(context: Context, appPackage: String): BrowserAutofillSupportLevel { +private fun getBrowserAutofillSupportLevel( + context: Context, + appPackage: String +): BrowserAutofillSupportLevel { val browserInfo = getBrowserAutofillSupportInfoIfTrusted(context, appPackage) return when { browserInfo == null -> BrowserAutofillSupportLevel.None appPackage in FLAKY_BROWSERS -> BrowserAutofillSupportLevel.FlakyFill appPackage in BROWSER_SAVE_FLAG_IF_NO_ACCESSIBILITY -> BrowserAutofillSupportLevel.PasswordFillAndSaveIfNoAccessibility - browserInfo.multiOriginMethod == BrowserMultiOriginMethod.None -> BrowserAutofillSupportLevel.PasswordFill + browserInfo.multiOriginMethod == BrowserMultiOriginMethod.None -> + BrowserAutofillSupportLevel.PasswordFill browserInfo.saveFlags == null -> BrowserAutofillSupportLevel.GeneralFill else -> BrowserAutofillSupportLevel.GeneralFillAndSave }.takeUnless { supportLevel -> @@ -212,7 +231,8 @@ private fun getBrowserAutofillSupportLevel(context: Context, appPackage: String) // (compatibility mode is only available on Android Pie and higher). Since all known browsers // with native Autofill support offer full save support as well, we reuse the list of those // browsers here. - supportLevel != BrowserAutofillSupportLevel.GeneralFillAndSave && Build.VERSION.SDK_INT < Build.VERSION_CODES.P + supportLevel != BrowserAutofillSupportLevel.GeneralFillAndSave && + Build.VERSION.SDK_INT < Build.VERSION_CODES.P } ?: BrowserAutofillSupportLevel.None } @@ -222,9 +242,15 @@ public fun getInstalledBrowsersWithAutofillSupportLevel( context: Context ): List<Pair<String, BrowserAutofillSupportLevel>> { val testWebIntent = Intent(Intent.ACTION_VIEW).apply { data = Uri.parse("http://example.org") } - val installedBrowsers = context.packageManager.queryIntentActivities(testWebIntent, PackageManager.MATCH_ALL) + val installedBrowsers = + context.packageManager.queryIntentActivities(testWebIntent, PackageManager.MATCH_ALL) return installedBrowsers .map { it to getBrowserAutofillSupportLevel(context, it.activityInfo.packageName) } .filter { it.first.isDefault || it.second != BrowserAutofillSupportLevel.None } - .map { context.packageManager.getApplicationLabel(it.first.activityInfo.applicationInfo).toString() to it.second } + .map { + context + .packageManager + .getApplicationLabel(it.first.activityInfo.applicationInfo) + .toString() to it.second + } } diff --git a/autofill-parser/src/main/java/com/github/androidpasswordstore/autofillparser/FormField.kt b/autofill-parser/src/main/java/com/github/androidpasswordstore/autofillparser/FormField.kt index 0bd3d404..719af425 100644 --- a/autofill-parser/src/main/java/com/github/androidpasswordstore/autofillparser/FormField.kt +++ b/autofill-parser/src/main/java/com/github/androidpasswordstore/autofillparser/FormField.kt @@ -108,9 +108,14 @@ internal class FormField( "text", ) private val HTML_INPUT_FIELD_TYPES_FILLABLE = - (HTML_INPUT_FIELD_TYPES_USERNAME + HTML_INPUT_FIELD_TYPES_PASSWORD + HTML_INPUT_FIELD_TYPES_OTP).toSet().toList() - - @RequiresApi(Build.VERSION_CODES.O) private fun isSupportedHint(hint: String) = hint in HINTS_FILLABLE + (HTML_INPUT_FIELD_TYPES_USERNAME + + HTML_INPUT_FIELD_TYPES_PASSWORD + + HTML_INPUT_FIELD_TYPES_OTP) + .toSet() + .toList() + + @RequiresApi(Build.VERSION_CODES.O) + private fun isSupportedHint(hint: String) = hint in HINTS_FILLABLE private val EXCLUDED_TERMS = listOf( "url_bar", // Chrome/Edge/Firefox address bar @@ -214,7 +219,8 @@ internal class FormField( private val hasAutocompleteHintUsername = htmlAutocomplete == "username" val hasAutocompleteHintCurrentPassword = htmlAutocomplete == "current-password" private val hasAutocompleteHintNewPassword = htmlAutocomplete == "new-password" - private val hasAutocompleteHintPassword = hasAutocompleteHintCurrentPassword || hasAutocompleteHintNewPassword + private val hasAutocompleteHintPassword = + hasAutocompleteHintCurrentPassword || hasAutocompleteHintNewPassword private val hasAutocompleteHintOtp = htmlAutocomplete == "one-time-code" // Results of hint-based field type detection @@ -238,7 +244,9 @@ internal class FormField( // fields to the fill rules and only exclude those fields that have incompatible autocomplete // hint. val couldBeTwoStepHiddenPassword = - !isVisible && isHtmlPasswordField && (hasAutocompleteHintCurrentPassword || htmlAutocomplete == null) + !isVisible && + isHtmlPasswordField && + (hasAutocompleteHintCurrentPassword || htmlAutocomplete == null) // Since many site put autocomplete=off on login forms for compliance reasons or since they are // worried of the user's browser automatically (i.e., without any user interaction) filling @@ -247,7 +255,8 @@ internal class FormField( private val excludedByHints = excludedByAutofillHints // Only offer to fill into custom views if they explicitly opted into Autofill. - val relevantField = hasAutofillTypeText && (isTextField || autofillHints.isNotEmpty()) && !excludedByHints + val relevantField = + hasAutofillTypeText && (isTextField || autofillHints.isNotEmpty()) && !excludedByHints // Exclude fields based on hint, resource ID or HTML name. // Note: We still report excluded fields as relevant since they count for adjacency heuristics, @@ -260,7 +269,8 @@ internal class FormField( notExcluded && (isAndroidPasswordField || isHtmlPasswordField || hasHintPassword) private val isCertainPasswordField = isPossiblePasswordField && hasHintPassword private val isLikelyPasswordField = - isPossiblePasswordField && (isCertainPasswordField || PASSWORD_HEURISTIC_TERMS.anyMatchesFieldInfo) + isPossiblePasswordField && + (isCertainPasswordField || PASSWORD_HEURISTIC_TERMS.anyMatchesFieldInfo) val passwordCertainty = if (isCertainPasswordField) CertaintyLevel.Certain else if (isLikelyPasswordField) CertaintyLevel.Likely @@ -273,17 +283,20 @@ internal class FormField( isPossibleOtpField && (isCertainOtpField || OTP_HEURISTIC_TERMS.anyMatchesFieldInfo || - ((htmlMaxLength == null || htmlMaxLength in 6..8) && OTP_WEAK_HEURISTIC_TERMS.anyMatchesFieldInfo)) + ((htmlMaxLength == null || htmlMaxLength in 6..8) && + OTP_WEAK_HEURISTIC_TERMS.anyMatchesFieldInfo)) val otpCertainty = if (isCertainOtpField) CertaintyLevel.Certain else if (isLikelyOtpField) CertaintyLevel.Likely else if (isPossibleOtpField) CertaintyLevel.Possible else CertaintyLevel.Impossible // Username field heuristics (based only on the current field) - private val isPossibleUsernameField = notExcluded && !isPossiblePasswordField && !isCertainOtpField + private val isPossibleUsernameField = + notExcluded && !isPossiblePasswordField && !isCertainOtpField private val isCertainUsernameField = isPossibleUsernameField && hasHintUsername private val isLikelyUsernameField = - isPossibleUsernameField && (isCertainUsernameField || (USERNAME_HEURISTIC_TERMS.anyMatchesFieldInfo)) + isPossibleUsernameField && + (isCertainUsernameField || (USERNAME_HEURISTIC_TERMS.anyMatchesFieldInfo)) val usernameCertainty = if (isCertainUsernameField) CertaintyLevel.Certain else if (isLikelyUsernameField) CertaintyLevel.Likely diff --git a/autofill-parser/src/main/java/com/github/androidpasswordstore/autofillparser/PublicSuffixListCache.kt b/autofill-parser/src/main/java/com/github/androidpasswordstore/autofillparser/PublicSuffixListCache.kt index c62e74ab..be3cbe66 100644 --- a/autofill-parser/src/main/java/com/github/androidpasswordstore/autofillparser/PublicSuffixListCache.kt +++ b/autofill-parser/src/main/java/com/github/androidpasswordstore/autofillparser/PublicSuffixListCache.kt @@ -34,12 +34,18 @@ public fun cachePublicSuffixList(context: Context) { * Note: Invalid domains, such as IP addresses, are returned unchanged and thus never collide with * the return value for valid domains. */ -internal fun getPublicSuffixPlusOne(context: Context, domain: String, customSuffixes: Sequence<String>) = runBlocking { +internal fun getPublicSuffixPlusOne( + context: Context, + domain: String, + customSuffixes: Sequence<String> +) = runBlocking { // 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 // exists). As long as we restrict ourselves to syntactically valid domain names, // getPublicSuffixPlusOne will return non-colliding results. - if (!Patterns.DOMAIN_NAME.matcher(domain).matches() || Patterns.IP_ADDRESS.matcher(domain).matches()) { + if (!Patterns.DOMAIN_NAME.matcher(domain).matches() || + Patterns.IP_ADDRESS.matcher(domain).matches() + ) { domain } else { getCanonicalSuffix(context, domain, customSuffixes) @@ -60,7 +66,11 @@ private fun getSuffixPlusUpToOne(domain: String, suffix: String): String? { return "$lastPrefixPart.$suffix" } -private suspend fun getCanonicalSuffix(context: Context, domain: String, customSuffixes: Sequence<String>): String { +private suspend fun getCanonicalSuffix( + context: Context, + domain: String, + customSuffixes: Sequence<String> +): String { val publicSuffixList = PublicSuffixListCache.getOrCachePublicSuffixList(context) val publicSuffixPlusOne = publicSuffixList.getPublicSuffixPlusOne(domain).await() ?: return domain var longestSuffix = publicSuffixPlusOne diff --git a/autofill-parser/src/main/java/mozilla/components/lib/publicsuffixlist/PublicSuffixList.kt b/autofill-parser/src/main/java/mozilla/components/lib/publicsuffixlist/PublicSuffixList.kt index 8976242e..6f8c6d80 100644 --- a/autofill-parser/src/main/java/mozilla/components/lib/publicsuffixlist/PublicSuffixList.kt +++ b/autofill-parser/src/main/java/mozilla/components/lib/publicsuffixlist/PublicSuffixList.kt @@ -58,7 +58,8 @@ internal class PublicSuffixList( fun getPublicSuffixPlusOne(domain: String): Deferred<String?> = scope.async { when (val offset = data.getPublicSuffixOffset(domain)) { - is PublicSuffixOffset.Offset -> domain.split('.').drop(offset.value).joinToString(separator = ".") + is PublicSuffixOffset.Offset -> + domain.split('.').drop(offset.value).joinToString(separator = ".") else -> null } } diff --git a/autofill-parser/src/main/java/mozilla/components/lib/publicsuffixlist/PublicSuffixListData.kt b/autofill-parser/src/main/java/mozilla/components/lib/publicsuffixlist/PublicSuffixListData.kt index 7a17a80f..2bdf5f70 100644 --- a/autofill-parser/src/main/java/mozilla/components/lib/publicsuffixlist/PublicSuffixListData.kt +++ b/autofill-parser/src/main/java/mozilla/components/lib/publicsuffixlist/PublicSuffixListData.kt @@ -12,7 +12,10 @@ import java.net.IDN import mozilla.components.lib.publicsuffixlist.ext.binarySearch /** Class wrapping the public suffix list data and offering methods for accessing rules in it. */ -internal class PublicSuffixListData(private val rules: ByteArray, private val exceptions: ByteArray) { +internal class PublicSuffixListData( + private val rules: ByteArray, + private val exceptions: ByteArray +) { private fun binarySearchRules(labels: List<ByteArray>, labelIndex: Int): String? { return rules.binarySearch(labels, labelIndex) diff --git a/autofill-parser/src/main/java/mozilla/components/lib/publicsuffixlist/PublicSuffixListLoader.kt b/autofill-parser/src/main/java/mozilla/components/lib/publicsuffixlist/PublicSuffixListLoader.kt index 0cf4c918..5f3fc296 100644 --- a/autofill-parser/src/main/java/mozilla/components/lib/publicsuffixlist/PublicSuffixListLoader.kt +++ b/autofill-parser/src/main/java/mozilla/components/lib/publicsuffixlist/PublicSuffixListLoader.kt @@ -30,7 +30,12 @@ internal object PublicSuffixListLoader { @Suppress("MagicNumber") private fun BufferedInputStream.readInt(): Int { - return (read() and 0xff shl 24 or (read() and 0xff shl 16) or (read() and 0xff shl 8) or (read() and 0xff)) + return (read() and + 0xff shl + 24 or + (read() and 0xff shl 16) or + (read() and 0xff shl 8) or + (read() and 0xff)) } private fun BufferedInputStream.readFully(size: Int): ByteArray { diff --git a/buildSrc/settings.gradle.kts b/buildSrc/settings.gradle.kts deleted file mode 100644 index fae32dd6..00000000 --- a/buildSrc/settings.gradle.kts +++ /dev/null @@ -1,11 +0,0 @@ -/** - * IDEs don't support this very well for buildSrc, so we use the regular dependency format - * until that changes. -dependencyResolutionManagement { - versionCatalogs { - create("libs") { - from(files("../gradle/libs.versions.toml")) - } - } -} -*/ diff --git a/format-common/src/main/kotlin/dev/msfjarvis/aps/data/passfile/PasswordEntry.kt b/format-common/src/main/kotlin/dev/msfjarvis/aps/data/passfile/PasswordEntry.kt index a65e3494..96337bf7 100644 --- a/format-common/src/main/kotlin/dev/msfjarvis/aps/data/passfile/PasswordEntry.kt +++ b/format-common/src/main/kotlin/dev/msfjarvis/aps/data/passfile/PasswordEntry.kt @@ -115,11 +115,13 @@ constructor( .lineSequence() .filter { line -> return@filter when { - USERNAME_FIELDS.any { prefix -> line.startsWith(prefix, ignoreCase = true) } && !foundUsername -> { + USERNAME_FIELDS.any { prefix -> line.startsWith(prefix, ignoreCase = true) } && + !foundUsername -> { foundUsername = true false } - line.startsWith("otpauth://", ignoreCase = true) || line.startsWith("totp:", ignoreCase = true) -> { + line.startsWith("otpauth://", ignoreCase = true) || + line.startsWith("totp:", ignoreCase = true) -> { false } else -> { @@ -174,7 +176,8 @@ constructor( private fun findUsername(): String? { extraContentString.splitToSequence("\n").forEach { line -> for (prefix in USERNAME_FIELDS) { - if (line.startsWith(prefix, ignoreCase = true)) return line.substring(prefix.length).trimStart() + if (line.startsWith(prefix, ignoreCase = true)) + return line.substring(prefix.length).trimStart() } } return null diff --git a/format-common/src/main/kotlin/dev/msfjarvis/aps/util/totp/Otp.kt b/format-common/src/main/kotlin/dev/msfjarvis/aps/util/totp/Otp.kt index 832529e9..65284441 100644 --- a/format-common/src/main/kotlin/dev/msfjarvis/aps/util/totp/Otp.kt +++ b/format-common/src/main/kotlin/dev/msfjarvis/aps/util/totp/Otp.kt @@ -23,7 +23,8 @@ internal object Otp { check(STEAM_ALPHABET.size == 26) } - fun calculateCode(secret: String, counter: Long, algorithm: String, digits: String) = runCatching { + fun calculateCode(secret: String, counter: Long, algorithm: String, digits: String) = + runCatching { val algo = "Hmac${algorithm.uppercase(Locale.ROOT)}" val decodedSecret = BASE_32.decode(secret) val secretKey = SecretKeySpec(decodedSecret, algo) diff --git a/format-common/src/test/kotlin/dev/msfjarvis/aps/data/passfile/PasswordEntryTest.kt b/format-common/src/test/kotlin/dev/msfjarvis/aps/data/passfile/PasswordEntryTest.kt index 2520853c..fad58be7 100644 --- a/format-common/src/test/kotlin/dev/msfjarvis/aps/data/passfile/PasswordEntryTest.kt +++ b/format-common/src/test/kotlin/dev/msfjarvis/aps/data/passfile/PasswordEntryTest.kt @@ -19,7 +19,8 @@ import org.junit.Test @OptIn(ExperimentalCoroutinesApi::class, ExperimentalTime::class) internal class PasswordEntryTest { - private fun makeEntry(content: String) = PasswordEntry(fakeClock, testFinder, testScope, content.encodeToByteArray()) + private fun makeEntry(content: String) = + PasswordEntry(fakeClock, testFinder, testScope, content.encodeToByteArray()) @Test fun testGetPassword() { @@ -49,7 +50,10 @@ internal class PasswordEntryTest { assertEquals("blubb", makeEntry("\nblubb").extraContentString) assertEquals("blubb", makeEntry("blubb\npassword: foo").extraContentString) assertEquals("blubb", makeEntry("password: foo\nblubb").extraContentString) - assertEquals("blubb\nusername: bar", makeEntry("blubb\npassword: foo\nusername: bar").extraContentString) + assertEquals( + "blubb\nusername: bar", + makeEntry("blubb\npassword: foo\nusername: bar").extraContentString + ) assertEquals("", makeEntry("\n").extraContentString) assertEquals("", makeEntry("").extraContentString) } diff --git a/format-common/src/test/kotlin/dev/msfjarvis/aps/util/totp/OtpTest.kt b/format-common/src/test/kotlin/dev/msfjarvis/aps/util/totp/OtpTest.kt index e6dc372d..4c01ac74 100644 --- a/format-common/src/test/kotlin/dev/msfjarvis/aps/util/totp/OtpTest.kt +++ b/format-common/src/test/kotlin/dev/msfjarvis/aps/util/totp/OtpTest.kt @@ -15,16 +15,34 @@ internal class OtpTest { @Test fun testOtpGeneration6Digits() { - assertEquals("953550", Otp.calculateCode("JBSWY3DPEHPK3PXP", 1593333298159 / (1000 * 30), "SHA1", "6").get()) - assertEquals("275379", Otp.calculateCode("JBSWY3DPEHPK3PXP", 1593333571918 / (1000 * 30), "SHA1", "6").get()) - assertEquals("867507", Otp.calculateCode("JBSWY3DPEHPK3PXP", 1593333600517 / (1000 * 57), "SHA1", "6").get()) + assertEquals( + "953550", + Otp.calculateCode("JBSWY3DPEHPK3PXP", 1593333298159 / (1000 * 30), "SHA1", "6").get() + ) + assertEquals( + "275379", + Otp.calculateCode("JBSWY3DPEHPK3PXP", 1593333571918 / (1000 * 30), "SHA1", "6").get() + ) + assertEquals( + "867507", + Otp.calculateCode("JBSWY3DPEHPK3PXP", 1593333600517 / (1000 * 57), "SHA1", "6").get() + ) } @Test fun testOtpGeneration10Digits() { - assertEquals("0740900914", Otp.calculateCode("JBSWY3DPEHPK3PXP", 1593333655044 / (1000 * 30), "SHA1", "10").get()) - assertEquals("0070632029", Otp.calculateCode("JBSWY3DPEHPK3PXP", 1593333691405 / (1000 * 30), "SHA1", "10").get()) - assertEquals("1017265882", Otp.calculateCode("JBSWY3DPEHPK3PXP", 1593333728893 / (1000 * 83), "SHA1", "10").get()) + assertEquals( + "0740900914", + Otp.calculateCode("JBSWY3DPEHPK3PXP", 1593333655044 / (1000 * 30), "SHA1", "10").get() + ) + assertEquals( + "0070632029", + Otp.calculateCode("JBSWY3DPEHPK3PXP", 1593333691405 / (1000 * 30), "SHA1", "10").get() + ) + assertEquals( + "1017265882", + Otp.calculateCode("JBSWY3DPEHPK3PXP", 1593333728893 / (1000 * 83), "SHA1", "10").get() + ) } @Test @@ -42,7 +60,10 @@ internal class OtpTest { "127764", Otp.calculateCode("JBSWY3DPEHPK3PXPAAAAAAAA", 1593367111963 / (1000 * 30), "SHA1", "6").get() ) - assertEquals("047515", Otp.calculateCode("JBSWY3DPEHPK3PXPAAAAA", 1593367171420 / (1000 * 30), "SHA1", "6").get()) + assertEquals( + "047515", + Otp.calculateCode("JBSWY3DPEHPK3PXPAAAAA", 1593367171420 / (1000 * 30), "SHA1", "6").get() + ) } @Test diff --git a/openpgp-ktx/src/main/java/me/msfjarvis/openpgpktx/AutocryptPeerUpdate.kt b/openpgp-ktx/src/main/java/me/msfjarvis/openpgpktx/AutocryptPeerUpdate.kt index f81b72a4..45ee5755 100644 --- a/openpgp-ktx/src/main/java/me/msfjarvis/openpgpktx/AutocryptPeerUpdate.kt +++ b/openpgp-ktx/src/main/java/me/msfjarvis/openpgpktx/AutocryptPeerUpdate.kt @@ -15,7 +15,11 @@ public class AutocryptPeerUpdate() : Parcelable { private var effectiveDate: Date? = null private lateinit var preferEncrypt: PreferEncrypt - internal constructor(keyData: ByteArray?, effectiveDate: Date?, preferEncrypt: PreferEncrypt) : this() { + internal constructor( + keyData: ByteArray?, + effectiveDate: Date?, + preferEncrypt: PreferEncrypt + ) : this() { this.keyData = keyData this.effectiveDate = effectiveDate this.preferEncrypt = preferEncrypt diff --git a/openpgp-ktx/src/main/java/me/msfjarvis/openpgpktx/util/OpenPgpApi.kt b/openpgp-ktx/src/main/java/me/msfjarvis/openpgpktx/util/OpenPgpApi.kt index c855f619..91523a54 100644 --- a/openpgp-ktx/src/main/java/me/msfjarvis/openpgpktx/util/OpenPgpApi.kt +++ b/openpgp-ktx/src/main/java/me/msfjarvis/openpgpktx/util/OpenPgpApi.kt @@ -21,7 +21,11 @@ public class OpenPgpApi(private val context: Context, private val service: IOpen private val pipeIdGen: AtomicInteger = AtomicInteger() - public suspend fun executeApi(data: Intent, inputStream: InputStream?, outputStream: OutputStream?): Intent { + public suspend fun executeApi( + data: Intent, + inputStream: InputStream?, + outputStream: OutputStream? + ): Intent { var input: ParcelFileDescriptor? = null return try { if (inputStream != null) { @@ -124,7 +128,8 @@ public class OpenPgpApi(private val context: Context, private val service: IOpen * * This action uses no extras. */ - public const val ACTION_CHECK_PERMISSION: String = "org.openintents.openpgp.action.CHECK_PERMISSION" + public const val ACTION_CHECK_PERMISSION: String = + "org.openintents.openpgp.action.CHECK_PERMISSION" /** * Sign text resulting in a cleartext signature Some magic pre-processing of the text is done to @@ -178,9 +183,11 @@ public class OpenPgpApi(private val context: Context, private val service: IOpen * passphrase) String EXTRA_ORIGINAL_FILENAME (original filename to be encrypted as metadata) * boolean EXTRA_ENABLE_COMPRESSION (enable ZLIB compression, default ist true) */ - public const val ACTION_SIGN_AND_ENCRYPT: String = "org.openintents.openpgp.action.SIGN_AND_ENCRYPT" + public const val ACTION_SIGN_AND_ENCRYPT: String = + "org.openintents.openpgp.action.SIGN_AND_ENCRYPT" - public const val ACTION_QUERY_AUTOCRYPT_STATUS: String = "org.openintents.openpgp.action.QUERY_AUTOCRYPT_STATUS" + public const val ACTION_QUERY_AUTOCRYPT_STATUS: String = + "org.openintents.openpgp.action.QUERY_AUTOCRYPT_STATUS" /** * Decrypts and verifies given input stream. This methods handles encrypted-only, @@ -208,7 +215,8 @@ public class OpenPgpApi(private val context: Context, private val service: IOpen * returned extras: OpenPgpDecryptMetadata RESULT_METADATA String RESULT_CHARSET (charset which * was specified in the headers of ascii armored input, if any) */ - public const val ACTION_DECRYPT_METADATA: String = "org.openintents.openpgp.action.DECRYPT_METADATA" + public const val ACTION_DECRYPT_METADATA: String = + "org.openintents.openpgp.action.DECRYPT_METADATA" /** * Select key id for signing @@ -217,7 +225,8 @@ public class OpenPgpApi(private val context: Context, private val service: IOpen * * returned extras: long EXTRA_SIGN_KEY_ID */ - public const val ACTION_GET_SIGN_KEY_ID: String = "org.openintents.openpgp.action.GET_SIGN_KEY_ID" + public const val ACTION_GET_SIGN_KEY_ID: String = + "org.openintents.openpgp.action.GET_SIGN_KEY_ID" /** * Get key ids based on given user ids (=emails) @@ -254,7 +263,8 @@ public class OpenPgpApi(private val context: Context, private val service: IOpen */ public const val ACTION_BACKUP: String = "org.openintents.openpgp.action.BACKUP" - public const val ACTION_UPDATE_AUTOCRYPT_PEER: String = "org.openintents.openpgp.action.UPDATE_AUTOCRYPT_PEER" + public const val ACTION_UPDATE_AUTOCRYPT_PEER: String = + "org.openintents.openpgp.action.UPDATE_AUTOCRYPT_PEER" /* Intent extras */ public const val EXTRA_API_VERSION: String = "api_version" @@ -323,7 +333,8 @@ public class OpenPgpApi(private val context: Context, private val service: IOpen public const val EXTRA_DATA_LENGTH: String = "data_length" public const val EXTRA_DECRYPTION_RESULT: String = "decryption_result" public const val EXTRA_SENDER_ADDRESS: String = "sender_address" - public const val EXTRA_SUPPORT_OVERRIDE_CRYPTO_WARNING: String = "support_override_crpto_warning" + public const val EXTRA_SUPPORT_OVERRIDE_CRYPTO_WARNING: String = + "support_override_crpto_warning" public const val EXTRA_AUTOCRYPT_PEER_ID: String = "autocrypt_peer_id" public const val EXTRA_AUTOCRYPT_PEER_UPDATE: String = "autocrypt_peer_update" public const val EXTRA_AUTOCRYPT_PEER_GOSSIP_UPDATES: String = "autocrypt_peer_gossip_updates" diff --git a/openpgp-ktx/src/main/java/me/msfjarvis/openpgpktx/util/OpenPgpServiceConnection.kt b/openpgp-ktx/src/main/java/me/msfjarvis/openpgpktx/util/OpenPgpServiceConnection.kt index 528c0127..67796c26 100644 --- a/openpgp-ktx/src/main/java/me/msfjarvis/openpgpktx/util/OpenPgpServiceConnection.kt +++ b/openpgp-ktx/src/main/java/me/msfjarvis/openpgpktx/util/OpenPgpServiceConnection.kt @@ -65,7 +65,12 @@ public class OpenPgpServiceConnection(context: Context, providerPackageName: Str val serviceIntent = Intent(OpenPgpApi.SERVICE_INTENT_2) // NOTE: setPackage is very important to restrict the intent to this provider only! serviceIntent.setPackage(mProviderPackageName) - val connect = mApplicationContext.bindService(serviceIntent, mServiceConnection, Context.BIND_AUTO_CREATE) + val connect = + mApplicationContext.bindService( + serviceIntent, + mServiceConnection, + Context.BIND_AUTO_CREATE + ) if (!connect) { throw Exception("bindService() returned false!") } diff --git a/openpgp-ktx/src/main/java/me/msfjarvis/openpgpktx/util/OpenPgpUtils.kt b/openpgp-ktx/src/main/java/me/msfjarvis/openpgpktx/util/OpenPgpUtils.kt index ca7e3d3d..c70d9c3c 100644 --- a/openpgp-ktx/src/main/java/me/msfjarvis/openpgpktx/util/OpenPgpUtils.kt +++ b/openpgp-ktx/src/main/java/me/msfjarvis/openpgpktx/util/OpenPgpUtils.kt @@ -13,7 +13,10 @@ import java.util.regex.Pattern public object OpenPgpUtils { private val PGP_MESSAGE: Pattern = - Pattern.compile(".*?(-----BEGIN PGP MESSAGE-----.*?-----END PGP MESSAGE-----).*", Pattern.DOTALL) + Pattern.compile( + ".*?(-----BEGIN PGP MESSAGE-----.*?-----END PGP MESSAGE-----).*", + Pattern.DOTALL + ) private val PGP_SIGNED_MESSAGE: Pattern = Pattern.compile( ".*?(-----BEGIN PGP SIGNED MESSAGE-----.*?-----BEGIN PGP SIGNATURE-----.*?-----END PGP SIGNATURE-----).*", @@ -103,5 +106,9 @@ public object OpenPgpUtils { return if (userIdBuilder.isEmpty()) null else userIdBuilder.toString() } - public class UserId(public val name: String?, public val email: String?, public val comment: String?) : Serializable + public class UserId( + public val name: String?, + public val email: String?, + public val comment: String? + ) : Serializable } diff --git a/openpgp-ktx/src/main/java/org/openintents/openpgp/OpenPgpDecryptionResult.kt b/openpgp-ktx/src/main/java/org/openintents/openpgp/OpenPgpDecryptionResult.kt index 18f8cd0c..72bc1b39 100644 --- a/openpgp-ktx/src/main/java/org/openintents/openpgp/OpenPgpDecryptionResult.kt +++ b/openpgp-ktx/src/main/java/org/openintents/openpgp/OpenPgpDecryptionResult.kt @@ -22,7 +22,11 @@ public class OpenPgpDecryptionResult() : Parcelable { decryptedSessionKey = null } - private constructor(result: Int, sessionKey: ByteArray?, decryptedSessionKey: ByteArray?) : this() { + private constructor( + result: Int, + sessionKey: ByteArray?, + decryptedSessionKey: ByteArray? + ) : this() { this.result = result if (sessionKey == null != (decryptedSessionKey == null)) { throw AssertionError("sessionkey must be null iff decryptedSessionKey is null") diff --git a/openpgp-ktx/src/main/java/org/openintents/openpgp/OpenPgpMetadata.kt b/openpgp-ktx/src/main/java/org/openintents/openpgp/OpenPgpMetadata.kt index a73c77c1..a22c8af0 100644 --- a/openpgp-ktx/src/main/java/org/openintents/openpgp/OpenPgpMetadata.kt +++ b/openpgp-ktx/src/main/java/org/openintents/openpgp/OpenPgpMetadata.kt @@ -32,7 +32,12 @@ public class OpenPgpMetadata() : Parcelable { this.charset = charset } - private constructor(filename: String?, mimeType: String?, modificationTime: Long, originalSize: Long) : this() { + private constructor( + filename: String?, + mimeType: String?, + modificationTime: Long, + originalSize: Long + ) : this() { this.filename = filename this.mimeType = mimeType this.modificationTime = modificationTime diff --git a/openpgp-ktx/src/main/java/org/openintents/openpgp/OpenPgpSignatureResult.kt b/openpgp-ktx/src/main/java/org/openintents/openpgp/OpenPgpSignatureResult.kt index 45f10abd..68a542a8 100644 --- a/openpgp-ktx/src/main/java/org/openintents/openpgp/OpenPgpSignatureResult.kt +++ b/openpgp-ktx/src/main/java/org/openintents/openpgp/OpenPgpSignatureResult.kt @@ -58,7 +58,8 @@ public class OpenPgpSignatureResult : Parcelable { } // backward compatibility for this exact version if (version > 2) { - senderStatusResult = readEnumWithNullAndFallback(source, SenderStatusResult.values(), SenderStatusResult.UNKNOWN) + senderStatusResult = + readEnumWithNullAndFallback(source, SenderStatusResult.values(), SenderStatusResult.UNKNOWN) confirmedUserIds = source.createStringArrayList() } else { senderStatusResult = SenderStatusResult.UNKNOWN @@ -151,7 +152,9 @@ public class OpenPgpSignatureResult : Parcelable { ) } - public fun withAutocryptPeerResult(autocryptPeerentityResult: AutocryptPeerResult?): OpenPgpSignatureResult { + public fun withAutocryptPeerResult( + autocryptPeerentityResult: AutocryptPeerResult? + ): OpenPgpSignatureResult { return OpenPgpSignatureResult( result, primaryUserId, @@ -253,18 +256,55 @@ public class OpenPgpSignatureResult : Parcelable { } public fun createWithNoSignature(): OpenPgpSignatureResult { - return OpenPgpSignatureResult(RESULT_NO_SIGNATURE, null, 0L, null, null, null, null, null, null) + return OpenPgpSignatureResult( + RESULT_NO_SIGNATURE, + null, + 0L, + null, + null, + null, + null, + null, + null + ) } - public fun createWithKeyMissing(keyId: Long, signatureTimestamp: Date?): OpenPgpSignatureResult { - return OpenPgpSignatureResult(RESULT_KEY_MISSING, null, keyId, null, null, null, null, signatureTimestamp, null) + public fun createWithKeyMissing( + keyId: Long, + signatureTimestamp: Date? + ): OpenPgpSignatureResult { + return OpenPgpSignatureResult( + RESULT_KEY_MISSING, + null, + keyId, + null, + null, + null, + null, + signatureTimestamp, + null + ) } public fun createWithInvalidSignature(): OpenPgpSignatureResult { - return OpenPgpSignatureResult(RESULT_INVALID_SIGNATURE, null, 0L, null, null, null, null, null, null) + return OpenPgpSignatureResult( + RESULT_INVALID_SIGNATURE, + null, + 0L, + null, + null, + null, + null, + null, + null + ) } - private fun <T : Enum<T>?> readEnumWithNullAndFallback(source: Parcel, enumValues: Array<T>, fallback: T?): T? { + private fun <T : Enum<T>?> readEnumWithNullAndFallback( + source: Parcel, + enumValues: Array<T>, + fallback: T? + ): T? { val valueOrdinal = source.readInt() if (valueOrdinal == -1) { return null diff --git a/settings.gradle.kts b/settings.gradle.kts index d2d24bb5..37ce4d98 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -5,12 +5,16 @@ // Modules include(":app") + include(":autofill-parser") + include(":format-common") + include(":openpgp-ktx") // Experimental features enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") + enableFeaturePreview("VERSION_CATALOGS") // Plugin repositories |