mirror of
https://github.com/android-password-store/Android-Password-Store
synced 2025-08-30 13:57:47 +00:00
Remove support for external storage and raise target SDK to 31 (#1863)
This commit is contained in:
@@ -40,6 +40,7 @@ All notable changes to this project will be documented in this file.
|
|||||||
- Using HTTPS without authentication is now fully supported, and no longer asks for a username
|
- Using HTTPS without authentication is now fully supported, and no longer asks for a username
|
||||||
- Enabling 'Show hidden files and folders' no longer shows Git-related files and folders
|
- Enabling 'Show hidden files and folders' no longer shows Git-related files and folders
|
||||||
- XkPasswd password generator has been removed in favor of one backed by [Diceware](https://theworld.com/~reinhold/diceware.html)
|
- XkPasswd password generator has been removed in favor of one backed by [Diceware](https://theworld.com/~reinhold/diceware.html)
|
||||||
|
- Support for stores outside the hidden app directory has been removed due to technical restrictions, see [this issue](https://msfjarvis.dev/aps/issue/1849) for details.
|
||||||
|
|
||||||
## [1.13.5] - 2021-07-28
|
## [1.13.5] - 2021-07-28
|
||||||
|
|
||||||
|
@@ -8,10 +8,11 @@
|
|||||||
android:installLocation="auto">
|
android:installLocation="auto">
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
|
||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
|
||||||
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
|
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||||
|
<!-- Required by Autofill to verify the certificate hashes of packages -->
|
||||||
|
<uses-permission
|
||||||
|
android:name="android.permission.QUERY_ALL_PACKAGES"
|
||||||
|
tools:ignore="QueryAllPackagesPermission" />
|
||||||
|
|
||||||
<uses-feature
|
<uses-feature
|
||||||
android:name="android.hardware.touchscreen"
|
android:name="android.hardware.touchscreen"
|
||||||
@@ -23,23 +24,28 @@
|
|||||||
<application
|
<application
|
||||||
android:name="dev.msfjarvis.aps.Application"
|
android:name="dev.msfjarvis.aps.Application"
|
||||||
android:allowBackup="false"
|
android:allowBackup="false"
|
||||||
|
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||||
|
android:fullBackupContent="@xml/backup_content"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:requestLegacyExternalStorage="true"
|
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/AppThemeM3"
|
android:theme="@style/AppThemeM3"
|
||||||
tools:ignore="GoogleAppIndexingWarning">
|
tools:ignore="GoogleAppIndexingWarning"
|
||||||
|
tools:targetApi="s">
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name="dev.msfjarvis.aps.ui.passwords.PasswordStore"
|
android:name="dev.msfjarvis.aps.ui.passwords.PasswordStore"
|
||||||
android:configChanges="orientation|screenSize" />
|
android:configChanges="orientation|screenSize"
|
||||||
|
android:exported="false" />
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name="dev.msfjarvis.aps.ui.onboarding.activity.OnboardingActivity"
|
android:name="dev.msfjarvis.aps.ui.onboarding.activity.OnboardingActivity"
|
||||||
android:configChanges="orientation|screenSize" />
|
android:configChanges="orientation|screenSize"
|
||||||
|
android:exported="false" />
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name="dev.msfjarvis.aps.ui.proxy.ProxySelectorActivity"
|
android:name="dev.msfjarvis.aps.ui.proxy.ProxySelectorActivity"
|
||||||
|
android:exported="false"
|
||||||
android:windowSoftInputMode="adjustResize" />
|
android:windowSoftInputMode="adjustResize" />
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
@@ -60,6 +66,7 @@
|
|||||||
<activity
|
<activity
|
||||||
android:name="com.journeyapps.barcodescanner.CaptureActivity"
|
android:name="com.journeyapps.barcodescanner.CaptureActivity"
|
||||||
android:clearTaskOnLaunch="true"
|
android:clearTaskOnLaunch="true"
|
||||||
|
android:exported="false"
|
||||||
android:stateNotNeeded="true"
|
android:stateNotNeeded="true"
|
||||||
android:theme="@style/zxing_CaptureTheme"
|
android:theme="@style/zxing_CaptureTheme"
|
||||||
android:windowSoftInputMode="stateAlwaysHidden"
|
android:windowSoftInputMode="stateAlwaysHidden"
|
||||||
@@ -67,50 +74,56 @@
|
|||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name="dev.msfjarvis.aps.ui.git.config.GitServerConfigActivity"
|
android:name="dev.msfjarvis.aps.ui.git.config.GitServerConfigActivity"
|
||||||
|
android:exported="false"
|
||||||
android:label="@string/title_activity_git_clone"
|
android:label="@string/title_activity_git_clone"
|
||||||
android:windowSoftInputMode="adjustResize" />
|
android:windowSoftInputMode="adjustResize" />
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name="dev.msfjarvis.aps.ui.git.config.GitConfigActivity"
|
android:name="dev.msfjarvis.aps.ui.git.config.GitConfigActivity"
|
||||||
|
android:exported="false"
|
||||||
android:label="@string/title_activity_git_config"
|
android:label="@string/title_activity_git_config"
|
||||||
android:windowSoftInputMode="adjustResize" />
|
android:windowSoftInputMode="adjustResize" />
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name="dev.msfjarvis.aps.ui.git.log.GitLogActivity"
|
android:name="dev.msfjarvis.aps.ui.git.log.GitLogActivity"
|
||||||
|
android:exported="false"
|
||||||
android:label="@string/title_activity_git_log" />
|
android:label="@string/title_activity_git_log" />
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name="dev.msfjarvis.aps.ui.settings.SettingsActivity"
|
android:name="dev.msfjarvis.aps.ui.settings.SettingsActivity"
|
||||||
|
android:exported="false"
|
||||||
android:label="@string/action_settings"
|
android:label="@string/action_settings"
|
||||||
android:parentActivityName=".ui.passwords.PasswordStore" />
|
android:parentActivityName=".ui.passwords.PasswordStore" />
|
||||||
|
|
||||||
<activity
|
|
||||||
android:name="dev.msfjarvis.aps.ui.settings.DirectorySelectionActivity"
|
|
||||||
android:theme="@style/NoBackgroundThemeM3" />
|
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name="dev.msfjarvis.aps.ui.crypto.PasswordCreationActivity"
|
android:name="dev.msfjarvis.aps.ui.crypto.PasswordCreationActivity"
|
||||||
|
android:exported="false"
|
||||||
android:label="@string/new_password_title"
|
android:label="@string/new_password_title"
|
||||||
android:windowSoftInputMode="adjustResize" />
|
android:windowSoftInputMode="adjustResize" />
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name="dev.msfjarvis.aps.ui.crypto.PasswordCreationActivityV2"
|
android:name="dev.msfjarvis.aps.ui.crypto.PasswordCreationActivityV2"
|
||||||
|
android:exported="false"
|
||||||
android:label="@string/new_password_title"
|
android:label="@string/new_password_title"
|
||||||
android:windowSoftInputMode="adjustResize" />
|
android:windowSoftInputMode="adjustResize" />
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name="dev.msfjarvis.aps.ui.crypto.DecryptActivity"
|
android:name="dev.msfjarvis.aps.ui.crypto.DecryptActivity"
|
||||||
|
android:exported="false"
|
||||||
android:windowSoftInputMode="adjustResize" />
|
android:windowSoftInputMode="adjustResize" />
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name="dev.msfjarvis.aps.ui.crypto.GetKeyIdsActivity"
|
android:name="dev.msfjarvis.aps.ui.crypto.GetKeyIdsActivity"
|
||||||
|
android:exported="false"
|
||||||
android:theme="@style/NoBackgroundThemeM3" />
|
android:theme="@style/NoBackgroundThemeM3" />
|
||||||
|
|
||||||
<service
|
<service
|
||||||
android:name="dev.msfjarvis.aps.util.services.ClipboardService"
|
android:name="dev.msfjarvis.aps.util.services.ClipboardService"
|
||||||
|
android:exported="false"
|
||||||
android:process=":clipboard_service_process" />
|
android:process=":clipboard_service_process" />
|
||||||
<service
|
<service
|
||||||
android:name="dev.msfjarvis.aps.util.services.PasswordExportService"
|
android:name="dev.msfjarvis.aps.util.services.PasswordExportService"
|
||||||
|
android:exported="false"
|
||||||
android:process=":password_export_service_process" />
|
android:process=":password_export_service_process" />
|
||||||
<service
|
<service
|
||||||
android:name="dev.msfjarvis.aps.util.services.OreoAutofillService"
|
android:name="dev.msfjarvis.aps.util.services.OreoAutofillService"
|
||||||
@@ -124,41 +137,66 @@
|
|||||||
android:resource="@xml/oreo_autofill_service" />
|
android:resource="@xml/oreo_autofill_service" />
|
||||||
</service>
|
</service>
|
||||||
|
|
||||||
<activity android:name="dev.msfjarvis.aps.ui.folderselect.SelectFolderActivity" />
|
<activity
|
||||||
|
android:name="dev.msfjarvis.aps.ui.folderselect.SelectFolderActivity"
|
||||||
|
android:exported="false" />
|
||||||
<activity
|
<activity
|
||||||
android:name="dev.msfjarvis.aps.ui.sshkeygen.SshKeyImportActivity"
|
android:name="dev.msfjarvis.aps.ui.sshkeygen.SshKeyImportActivity"
|
||||||
|
android:exported="false"
|
||||||
android:theme="@style/NoBackgroundThemeM3"
|
android:theme="@style/NoBackgroundThemeM3"
|
||||||
android:windowSoftInputMode="adjustResize" />
|
android:windowSoftInputMode="adjustResize" />
|
||||||
<activity
|
<activity
|
||||||
android:name="dev.msfjarvis.aps.ui.sshkeygen.SshKeyGenActivity"
|
android:name="dev.msfjarvis.aps.ui.sshkeygen.SshKeyGenActivity"
|
||||||
|
android:exported="false"
|
||||||
android:label="@string/pref_ssh_keygen_title"
|
android:label="@string/pref_ssh_keygen_title"
|
||||||
android:windowSoftInputMode="adjustResize" />
|
android:windowSoftInputMode="adjustResize" />
|
||||||
<activity
|
<activity
|
||||||
android:name="dev.msfjarvis.aps.ui.autofill.AutofillDecryptActivity"
|
android:name="dev.msfjarvis.aps.ui.autofill.AutofillDecryptActivity"
|
||||||
|
android:exported="false"
|
||||||
android:theme="@style/NoBackgroundThemeM3" />
|
android:theme="@style/NoBackgroundThemeM3" />
|
||||||
<activity
|
<activity
|
||||||
android:name="dev.msfjarvis.aps.ui.autofill.AutofillDecryptActivityV2"
|
android:name="dev.msfjarvis.aps.ui.autofill.AutofillDecryptActivityV2"
|
||||||
|
android:exported="false"
|
||||||
android:theme="@style/NoBackgroundThemeM3" />
|
android:theme="@style/NoBackgroundThemeM3" />
|
||||||
<activity
|
<activity
|
||||||
android:name="dev.msfjarvis.aps.ui.autofill.AutofillFilterView"
|
android:name="dev.msfjarvis.aps.ui.autofill.AutofillFilterView"
|
||||||
android:configChanges="orientation|keyboardHidden"
|
android:configChanges="orientation|keyboardHidden"
|
||||||
|
android:exported="false"
|
||||||
android:theme="@style/DialogLikeThemeM3"
|
android:theme="@style/DialogLikeThemeM3"
|
||||||
android:windowSoftInputMode="adjustNothing" />
|
android:windowSoftInputMode="adjustNothing" />
|
||||||
<activity
|
<activity
|
||||||
android:name="dev.msfjarvis.aps.ui.autofill.AutofillSaveActivity"
|
android:name="dev.msfjarvis.aps.ui.autofill.AutofillSaveActivity"
|
||||||
|
android:exported="false"
|
||||||
android:theme="@style/NoBackgroundThemeM3" />
|
android:theme="@style/NoBackgroundThemeM3" />
|
||||||
<activity
|
<activity
|
||||||
android:name="dev.msfjarvis.aps.autofill.oreo.ui.AutofillSmsActivity"
|
android:name="dev.msfjarvis.aps.autofill.oreo.ui.AutofillSmsActivity"
|
||||||
android:configChanges="orientation"
|
android:configChanges="orientation"
|
||||||
|
android:exported="false"
|
||||||
android:theme="@style/DialogLikeThemeM3"
|
android:theme="@style/DialogLikeThemeM3"
|
||||||
android:windowSoftInputMode="adjustNothing" />
|
android:windowSoftInputMode="adjustNothing" />
|
||||||
<activity
|
<activity
|
||||||
android:name="dev.msfjarvis.aps.ui.autofill.AutofillPublisherChangedActivity"
|
android:name="dev.msfjarvis.aps.ui.autofill.AutofillPublisherChangedActivity"
|
||||||
android:configChanges="orientation|keyboardHidden"
|
android:configChanges="orientation|keyboardHidden"
|
||||||
|
android:exported="false"
|
||||||
android:theme="@style/DialogLikeThemeM3"
|
android:theme="@style/DialogLikeThemeM3"
|
||||||
android:windowSoftInputMode="adjustNothing" />
|
android:windowSoftInputMode="adjustNothing" />
|
||||||
<activity android:name="dev.msfjarvis.aps.ui.pgp.PGPKeyImportActivity"
|
<activity
|
||||||
|
android:name="dev.msfjarvis.aps.ui.pgp.PGPKeyImportActivity"
|
||||||
android:theme="@style/NoBackgroundThemeM3" />
|
android:theme="@style/NoBackgroundThemeM3" />
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
|
<queries>
|
||||||
|
<package android:name="org.sufficientlysecure.keychain" />
|
||||||
|
|
||||||
|
<intent>
|
||||||
|
<action android:name="android.intent.action.SEND" />
|
||||||
|
<data android:mimeType="text/plain" />
|
||||||
|
</intent>
|
||||||
|
<intent>
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
<data android:scheme="https" />
|
||||||
|
</intent>
|
||||||
|
</queries>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
|
@@ -10,7 +10,6 @@ import com.github.michaelbull.result.onFailure
|
|||||||
import com.github.michaelbull.result.runCatching
|
import com.github.michaelbull.result.runCatching
|
||||||
import dev.msfjarvis.aps.Application
|
import dev.msfjarvis.aps.Application
|
||||||
import dev.msfjarvis.aps.data.password.PasswordItem
|
import dev.msfjarvis.aps.data.password.PasswordItem
|
||||||
import dev.msfjarvis.aps.util.extensions.getString
|
|
||||||
import dev.msfjarvis.aps.util.extensions.sharedPrefs
|
import dev.msfjarvis.aps.util.extensions.sharedPrefs
|
||||||
import dev.msfjarvis.aps.util.extensions.unsafeLazy
|
import dev.msfjarvis.aps.util.extensions.unsafeLazy
|
||||||
import dev.msfjarvis.aps.util.settings.PasswordSortOrder
|
import dev.msfjarvis.aps.util.settings.PasswordSortOrder
|
||||||
@@ -106,12 +105,7 @@ object PasswordRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun getRepositoryDirectory(): File {
|
fun getRepositoryDirectory(): File {
|
||||||
return if (settings.getBoolean(PreferenceKeys.GIT_EXTERNAL, false)) {
|
return File(filesDir.toString(), "/store")
|
||||||
val externalRepo = settings.getString(PreferenceKeys.GIT_EXTERNAL_REPO)
|
|
||||||
if (externalRepo != null) File(externalRepo) else File(filesDir.toString(), "/store")
|
|
||||||
} else {
|
|
||||||
File(filesDir.toString(), "/store")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun initialize(): Repository? {
|
fun initialize(): Repository? {
|
||||||
|
@@ -11,7 +11,10 @@ import androidx.activity.result.contract.ActivityResultContracts
|
|||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.core.content.edit
|
import androidx.core.content.edit
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
|
import com.github.michaelbull.result.onFailure
|
||||||
|
import com.github.michaelbull.result.runCatching
|
||||||
import dev.msfjarvis.aps.R
|
import dev.msfjarvis.aps.R
|
||||||
|
import dev.msfjarvis.aps.data.repo.PasswordRepository
|
||||||
import dev.msfjarvis.aps.databinding.FragmentCloneBinding
|
import dev.msfjarvis.aps.databinding.FragmentCloneBinding
|
||||||
import dev.msfjarvis.aps.ui.git.config.GitServerConfigActivity
|
import dev.msfjarvis.aps.ui.git.config.GitServerConfigActivity
|
||||||
import dev.msfjarvis.aps.util.extensions.finish
|
import dev.msfjarvis.aps.util.extensions.finish
|
||||||
@@ -20,6 +23,9 @@ import dev.msfjarvis.aps.util.extensions.sharedPrefs
|
|||||||
import dev.msfjarvis.aps.util.extensions.unsafeLazy
|
import dev.msfjarvis.aps.util.extensions.unsafeLazy
|
||||||
import dev.msfjarvis.aps.util.extensions.viewBinding
|
import dev.msfjarvis.aps.util.extensions.viewBinding
|
||||||
import dev.msfjarvis.aps.util.settings.PreferenceKeys
|
import dev.msfjarvis.aps.util.settings.PreferenceKeys
|
||||||
|
import logcat.LogPriority.ERROR
|
||||||
|
import logcat.asLog
|
||||||
|
import logcat.logcat
|
||||||
|
|
||||||
class CloneFragment : Fragment(R.layout.fragment_clone) {
|
class CloneFragment : Fragment(R.layout.fragment_clone) {
|
||||||
|
|
||||||
@@ -38,17 +44,33 @@ class CloneFragment : Fragment(R.layout.fragment_clone) {
|
|||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
binding.cloneRemote.setOnClickListener { cloneToHiddenDir() }
|
binding.cloneRemote.setOnClickListener { cloneToHiddenDir() }
|
||||||
binding.createLocal.setOnClickListener {
|
binding.createLocal.setOnClickListener { createRepository() }
|
||||||
parentFragmentManager.performTransactionWithBackStack(RepoLocationFragment.newInstance())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Clones a remote Git repository to the app's private directory */
|
/** Clones a remote Git repository to the app's private directory */
|
||||||
private fun cloneToHiddenDir() {
|
private fun cloneToHiddenDir() {
|
||||||
settings.edit { putBoolean(PreferenceKeys.GIT_EXTERNAL, false) }
|
|
||||||
cloneAction.launch(GitServerConfigActivity.createCloneIntent(requireContext()))
|
cloneAction.launch(GitServerConfigActivity.createCloneIntent(requireContext()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun createRepository() {
|
||||||
|
val localDir = PasswordRepository.getRepositoryDirectory()
|
||||||
|
runCatching {
|
||||||
|
check(localDir.exists() || localDir.mkdir()) { "Failed to create directory!" }
|
||||||
|
PasswordRepository.createRepository(localDir)
|
||||||
|
if (!PasswordRepository.isInitialized) {
|
||||||
|
PasswordRepository.initialize()
|
||||||
|
}
|
||||||
|
parentFragmentManager.performTransactionWithBackStack(KeySelectionFragment.newInstance())
|
||||||
|
}
|
||||||
|
.onFailure { e ->
|
||||||
|
logcat(ERROR) { e.asLog() }
|
||||||
|
if (!localDir.delete()) {
|
||||||
|
logcat { "Failed to delete local repository: $localDir" }
|
||||||
|
}
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
fun newInstance(): CloneFragment = CloneFragment()
|
fun newInstance(): CloneFragment = CloneFragment()
|
||||||
|
@@ -1,198 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright © 2014-2021 The Android Password Store Authors. All Rights Reserved.
|
|
||||||
* SPDX-License-Identifier: GPL-3.0-only
|
|
||||||
*/
|
|
||||||
|
|
||||||
package dev.msfjarvis.aps.ui.onboarding.fragments
|
|
||||||
|
|
||||||
import android.Manifest
|
|
||||||
import android.content.Intent
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.view.View
|
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
|
||||||
import androidx.core.content.edit
|
|
||||||
import androidx.fragment.app.Fragment
|
|
||||||
import com.github.michaelbull.result.onFailure
|
|
||||||
import com.github.michaelbull.result.runCatching
|
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
|
||||||
import dev.msfjarvis.aps.R
|
|
||||||
import dev.msfjarvis.aps.data.repo.PasswordRepository
|
|
||||||
import dev.msfjarvis.aps.databinding.FragmentRepoLocationBinding
|
|
||||||
import dev.msfjarvis.aps.ui.settings.DirectorySelectionActivity
|
|
||||||
import dev.msfjarvis.aps.util.extensions.finish
|
|
||||||
import dev.msfjarvis.aps.util.extensions.getString
|
|
||||||
import dev.msfjarvis.aps.util.extensions.isPermissionGranted
|
|
||||||
import dev.msfjarvis.aps.util.extensions.listFilesRecursively
|
|
||||||
import dev.msfjarvis.aps.util.extensions.performTransactionWithBackStack
|
|
||||||
import dev.msfjarvis.aps.util.extensions.sharedPrefs
|
|
||||||
import dev.msfjarvis.aps.util.extensions.unsafeLazy
|
|
||||||
import dev.msfjarvis.aps.util.extensions.viewBinding
|
|
||||||
import dev.msfjarvis.aps.util.settings.PasswordSortOrder
|
|
||||||
import dev.msfjarvis.aps.util.settings.PreferenceKeys
|
|
||||||
import java.io.File
|
|
||||||
import logcat.LogPriority.ERROR
|
|
||||||
import logcat.asLog
|
|
||||||
import logcat.logcat
|
|
||||||
|
|
||||||
class RepoLocationFragment : Fragment(R.layout.fragment_repo_location) {
|
|
||||||
|
|
||||||
private val settings by unsafeLazy { requireActivity().applicationContext.sharedPrefs }
|
|
||||||
private val directorySelectIntent by unsafeLazy {
|
|
||||||
Intent(requireContext(), DirectorySelectionActivity::class.java)
|
|
||||||
}
|
|
||||||
private val binding by viewBinding(FragmentRepoLocationBinding::bind)
|
|
||||||
private val sortOrder: PasswordSortOrder
|
|
||||||
get() = PasswordSortOrder.getSortOrder(settings)
|
|
||||||
|
|
||||||
private val repositoryInitAction =
|
|
||||||
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
|
||||||
if (result.resultCode == AppCompatActivity.RESULT_OK) {
|
|
||||||
initializeRepositoryInfo()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private val externalDirectorySelectAction =
|
|
||||||
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
|
||||||
if (result.resultCode == AppCompatActivity.RESULT_OK) {
|
|
||||||
if (checkExternalDirectory()) {
|
|
||||||
finish()
|
|
||||||
} else {
|
|
||||||
createRepository()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private val externalDirPermGrantedAction = createPermGrantedAction {
|
|
||||||
externalDirectorySelectAction.launch(directorySelectIntent)
|
|
||||||
}
|
|
||||||
|
|
||||||
private val repositoryUsePermGrantedAction = createPermGrantedAction {
|
|
||||||
initializeRepositoryInfo()
|
|
||||||
}
|
|
||||||
|
|
||||||
private val repositoryChangePermGrantedAction = createPermGrantedAction {
|
|
||||||
repositoryInitAction.launch(directorySelectIntent)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
|
||||||
super.onViewCreated(view, savedInstanceState)
|
|
||||||
binding.hidden.setOnClickListener { createRepoInHiddenDir() }
|
|
||||||
|
|
||||||
binding.sdcard.setOnClickListener { createRepoFromExternalDir() }
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Initializes an empty repository in the app's private directory */
|
|
||||||
private fun createRepoInHiddenDir() {
|
|
||||||
settings.edit {
|
|
||||||
putBoolean(PreferenceKeys.GIT_EXTERNAL, false)
|
|
||||||
remove(PreferenceKeys.GIT_EXTERNAL_REPO)
|
|
||||||
}
|
|
||||||
initializeRepositoryInfo()
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Initializes an empty repository in a selected directory if one does not already exist */
|
|
||||||
private fun createRepoFromExternalDir() {
|
|
||||||
settings.edit { putBoolean(PreferenceKeys.GIT_EXTERNAL, true) }
|
|
||||||
val externalRepo = settings.getString(PreferenceKeys.GIT_EXTERNAL_REPO)
|
|
||||||
if (externalRepo == null) {
|
|
||||||
if (!isPermissionGranted(Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
|
|
||||||
externalDirPermGrantedAction.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
|
||||||
} else {
|
|
||||||
// Unlikely we have storage permissions without user ever selecting a directory,
|
|
||||||
// but let's not assume.
|
|
||||||
externalDirectorySelectAction.launch(directorySelectIntent)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
MaterialAlertDialogBuilder(requireActivity())
|
|
||||||
.setTitle(resources.getString(R.string.directory_selected_title))
|
|
||||||
.setMessage(resources.getString(R.string.directory_selected_message, externalRepo))
|
|
||||||
.setPositiveButton(resources.getString(R.string.use)) { _, _ ->
|
|
||||||
if (!isPermissionGranted(Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
|
|
||||||
repositoryUsePermGrantedAction.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
|
||||||
} else {
|
|
||||||
initializeRepositoryInfo()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.setNegativeButton(resources.getString(R.string.change)) { _, _ ->
|
|
||||||
if (!isPermissionGranted(Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
|
|
||||||
repositoryChangePermGrantedAction.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
|
||||||
} else {
|
|
||||||
repositoryInitAction.launch(directorySelectIntent)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun checkExternalDirectory(): Boolean {
|
|
||||||
if (settings.getBoolean(PreferenceKeys.GIT_EXTERNAL, false) &&
|
|
||||||
settings.getString(PreferenceKeys.GIT_EXTERNAL_REPO) != null
|
|
||||||
) {
|
|
||||||
val externalRepoPath = settings.getString(PreferenceKeys.GIT_EXTERNAL_REPO)
|
|
||||||
val dir = externalRepoPath?.let { File(it) }
|
|
||||||
if (dir != null && // The directory could be opened
|
|
||||||
dir.exists() && // The directory exists
|
|
||||||
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.closeRepository()
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun createRepository() {
|
|
||||||
val localDir = PasswordRepository.getRepositoryDirectory()
|
|
||||||
runCatching {
|
|
||||||
check(localDir.exists() || localDir.mkdir()) { "Failed to create directory!" }
|
|
||||||
PasswordRepository.createRepository(localDir)
|
|
||||||
if (!PasswordRepository.isInitialized) {
|
|
||||||
PasswordRepository.initialize()
|
|
||||||
}
|
|
||||||
parentFragmentManager.performTransactionWithBackStack(KeySelectionFragment.newInstance())
|
|
||||||
}
|
|
||||||
.onFailure { e ->
|
|
||||||
logcat(ERROR) { e.asLog() }
|
|
||||||
if (!localDir.delete()) {
|
|
||||||
logcat { "Failed to delete local repository: $localDir" }
|
|
||||||
}
|
|
||||||
finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun initializeRepositoryInfo() {
|
|
||||||
val externalRepo = settings.getBoolean(PreferenceKeys.GIT_EXTERNAL, false)
|
|
||||||
val externalRepoPath = settings.getString(PreferenceKeys.GIT_EXTERNAL_REPO)
|
|
||||||
if (externalRepo && !isPermissionGranted(Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (externalRepo && externalRepoPath != null) {
|
|
||||||
if (checkExternalDirectory()) {
|
|
||||||
finish()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
createRepository()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun createPermGrantedAction(block: () -> Unit) =
|
|
||||||
registerForActivityResult(ActivityResultContracts.RequestPermission()) { granted ->
|
|
||||||
if (granted) {
|
|
||||||
block.invoke()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
|
|
||||||
fun newInstance(): RepoLocationFragment = RepoLocationFragment()
|
|
||||||
}
|
|
||||||
}
|
|
@@ -4,7 +4,6 @@
|
|||||||
*/
|
*/
|
||||||
package dev.msfjarvis.aps.ui.passwords
|
package dev.msfjarvis.aps.ui.passwords
|
||||||
|
|
||||||
import android.Manifest
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.content.ComponentName
|
import android.content.ComponentName
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
@@ -40,12 +39,10 @@ import dev.msfjarvis.aps.ui.crypto.DecryptActivity
|
|||||||
import dev.msfjarvis.aps.ui.crypto.DecryptActivityV2
|
import dev.msfjarvis.aps.ui.crypto.DecryptActivityV2
|
||||||
import dev.msfjarvis.aps.ui.crypto.PasswordCreationActivity
|
import dev.msfjarvis.aps.ui.crypto.PasswordCreationActivity
|
||||||
import dev.msfjarvis.aps.ui.crypto.PasswordCreationActivityV2
|
import dev.msfjarvis.aps.ui.crypto.PasswordCreationActivityV2
|
||||||
import dev.msfjarvis.aps.ui.dialogs.BasicBottomSheet
|
|
||||||
import dev.msfjarvis.aps.ui.dialogs.FolderCreationDialogFragment
|
import dev.msfjarvis.aps.ui.dialogs.FolderCreationDialogFragment
|
||||||
import dev.msfjarvis.aps.ui.folderselect.SelectFolderActivity
|
import dev.msfjarvis.aps.ui.folderselect.SelectFolderActivity
|
||||||
import dev.msfjarvis.aps.ui.git.base.BaseGitActivity
|
import dev.msfjarvis.aps.ui.git.base.BaseGitActivity
|
||||||
import dev.msfjarvis.aps.ui.onboarding.activity.OnboardingActivity
|
import dev.msfjarvis.aps.ui.onboarding.activity.OnboardingActivity
|
||||||
import dev.msfjarvis.aps.ui.settings.DirectorySelectionActivity
|
|
||||||
import dev.msfjarvis.aps.ui.settings.SettingsActivity
|
import dev.msfjarvis.aps.ui.settings.SettingsActivity
|
||||||
import dev.msfjarvis.aps.util.autofill.AutofillMatcher
|
import dev.msfjarvis.aps.util.autofill.AutofillMatcher
|
||||||
import dev.msfjarvis.aps.util.extensions.base64
|
import dev.msfjarvis.aps.util.extensions.base64
|
||||||
@@ -53,7 +50,6 @@ import dev.msfjarvis.aps.util.extensions.commitChange
|
|||||||
import dev.msfjarvis.aps.util.extensions.contains
|
import dev.msfjarvis.aps.util.extensions.contains
|
||||||
import dev.msfjarvis.aps.util.extensions.getString
|
import dev.msfjarvis.aps.util.extensions.getString
|
||||||
import dev.msfjarvis.aps.util.extensions.isInsideRepository
|
import dev.msfjarvis.aps.util.extensions.isInsideRepository
|
||||||
import dev.msfjarvis.aps.util.extensions.isPermissionGranted
|
|
||||||
import dev.msfjarvis.aps.util.extensions.listFilesRecursively
|
import dev.msfjarvis.aps.util.extensions.listFilesRecursively
|
||||||
import dev.msfjarvis.aps.util.extensions.sharedPrefs
|
import dev.msfjarvis.aps.util.extensions.sharedPrefs
|
||||||
import dev.msfjarvis.aps.util.features.Feature
|
import dev.msfjarvis.aps.util.features.Feature
|
||||||
@@ -206,16 +202,7 @@ class PasswordStore : BaseGitActivity() {
|
|||||||
|
|
||||||
@SuppressLint("NewApi")
|
@SuppressLint("NewApi")
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
// If user opens app with permission granted then revokes and returns,
|
super.onCreate(savedInstanceState)
|
||||||
// prevent attempt to create password list fragment
|
|
||||||
var savedInstance = savedInstanceState
|
|
||||||
if (savedInstanceState != null &&
|
|
||||||
(!settings.getBoolean(PreferenceKeys.GIT_EXTERNAL, false) ||
|
|
||||||
!isPermissionGranted(Manifest.permission.WRITE_EXTERNAL_STORAGE))
|
|
||||||
) {
|
|
||||||
savedInstance = null
|
|
||||||
}
|
|
||||||
super.onCreate(savedInstance)
|
|
||||||
setContentView(R.layout.activity_pwdstore)
|
setContentView(R.layout.activity_pwdstore)
|
||||||
|
|
||||||
model.currentDir.observe(this) { dir ->
|
model.currentDir.observe(this) { dir ->
|
||||||
@@ -233,11 +220,7 @@ class PasswordStore : BaseGitActivity() {
|
|||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
if (settings.getBoolean(PreferenceKeys.GIT_EXTERNAL, false)) {
|
checkLocalRepository()
|
||||||
hasRequiredStoragePermissions()
|
|
||||||
} else {
|
|
||||||
checkLocalRepository()
|
|
||||||
}
|
|
||||||
if (settings.getBoolean(PreferenceKeys.SEARCH_ON_START, false) && ::searchItem.isInitialized) {
|
if (settings.getBoolean(PreferenceKeys.SEARCH_ON_START, false) && ::searchItem.isInitialized) {
|
||||||
if (!searchItem.isActionViewExpanded) {
|
if (!searchItem.isActionViewExpanded) {
|
||||||
searchItem.expandActionView()
|
searchItem.expandActionView()
|
||||||
@@ -362,33 +345,9 @@ class PasswordStore : BaseGitActivity() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Validates if storage permission is granted, and requests for it if not. The return value is
|
|
||||||
* true if the permission has been granted.
|
|
||||||
*/
|
|
||||||
private fun hasRequiredStoragePermissions(): Boolean {
|
|
||||||
return if (!isPermissionGranted(Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
|
|
||||||
BasicBottomSheet.Builder(this)
|
|
||||||
.setMessageRes(R.string.access_sdcard_text)
|
|
||||||
.setPositiveButtonClickListener(getString(R.string.snackbar_action_grant)) {
|
|
||||||
storagePermissionRequest.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
|
||||||
}
|
|
||||||
.build()
|
|
||||||
.show(supportFragmentManager, "STORAGE_PERMISSION_MISSING")
|
|
||||||
false
|
|
||||||
} else {
|
|
||||||
checkLocalRepository()
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun checkLocalRepository() {
|
private fun checkLocalRepository() {
|
||||||
val repo = PasswordRepository.initialize()
|
PasswordRepository.initialize()
|
||||||
if (repo == null) {
|
checkLocalRepository(PasswordRepository.getRepositoryDirectory())
|
||||||
directorySelectAction.launch(Intent(this, DirectorySelectionActivity::class.java))
|
|
||||||
} else {
|
|
||||||
checkLocalRepository(PasswordRepository.getRepositoryDirectory())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun checkLocalRepository(localDir: File?) {
|
private fun checkLocalRepository(localDir: File?) {
|
||||||
|
@@ -1,59 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright © 2014-2021 The Android Password Store Authors. All Rights Reserved.
|
|
||||||
* SPDX-License-Identifier: GPL-3.0-only
|
|
||||||
*/
|
|
||||||
|
|
||||||
package dev.msfjarvis.aps.ui.settings
|
|
||||||
|
|
||||||
import android.net.Uri
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.os.Environment
|
|
||||||
import android.provider.DocumentsContract
|
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
|
||||||
import androidx.core.content.edit
|
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
|
||||||
import dev.msfjarvis.aps.R
|
|
||||||
import dev.msfjarvis.aps.util.extensions.sharedPrefs
|
|
||||||
import dev.msfjarvis.aps.util.settings.PreferenceKeys
|
|
||||||
import logcat.logcat
|
|
||||||
|
|
||||||
class DirectorySelectionActivity : AppCompatActivity() {
|
|
||||||
|
|
||||||
@Suppress("DEPRECATION")
|
|
||||||
private val directorySelectAction =
|
|
||||||
registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { uri: Uri? ->
|
|
||||||
if (uri == null) return@registerForActivityResult
|
|
||||||
|
|
||||||
logcat { "Selected repository URI is $uri" }
|
|
||||||
// TODO: This is fragile. Workaround until PasswordItem is backed by DocumentFile
|
|
||||||
val docId = DocumentsContract.getTreeDocumentId(uri)
|
|
||||||
val split = docId.split(":".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
|
|
||||||
val path = if (split.size > 1) split[1] else split[0]
|
|
||||||
val repoPath = "${Environment.getExternalStorageDirectory()}/$path"
|
|
||||||
val prefs = sharedPrefs
|
|
||||||
|
|
||||||
logcat { "Selected repository path is $repoPath" }
|
|
||||||
|
|
||||||
if (Environment.getExternalStorageDirectory().path == repoPath) {
|
|
||||||
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)) {
|
|
||||||
_,
|
|
||||||
_ ->
|
|
||||||
prefs.edit { putString(PreferenceKeys.GIT_EXTERNAL_REPO, uri.path) }
|
|
||||||
}
|
|
||||||
.setNegativeButton(R.string.dialog_cancel, null)
|
|
||||||
.show()
|
|
||||||
}
|
|
||||||
prefs.edit { putString(PreferenceKeys.GIT_EXTERNAL_REPO, repoPath) }
|
|
||||||
setResult(RESULT_OK)
|
|
||||||
finish()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
|
||||||
super.onCreate(savedInstanceState)
|
|
||||||
directorySelectAction.launch(null)
|
|
||||||
}
|
|
||||||
}
|
|
@@ -23,7 +23,6 @@ import dagger.hilt.components.SingletonComponent
|
|||||||
import de.Maxr1998.modernpreferences.Preference
|
import de.Maxr1998.modernpreferences.Preference
|
||||||
import de.Maxr1998.modernpreferences.PreferenceScreen
|
import de.Maxr1998.modernpreferences.PreferenceScreen
|
||||||
import de.Maxr1998.modernpreferences.helpers.checkBox
|
import de.Maxr1998.modernpreferences.helpers.checkBox
|
||||||
import de.Maxr1998.modernpreferences.helpers.onCheckedChange
|
|
||||||
import de.Maxr1998.modernpreferences.helpers.onClick
|
import de.Maxr1998.modernpreferences.helpers.onClick
|
||||||
import de.Maxr1998.modernpreferences.helpers.pref
|
import de.Maxr1998.modernpreferences.helpers.pref
|
||||||
import dev.msfjarvis.aps.R
|
import dev.msfjarvis.aps.R
|
||||||
@@ -60,17 +59,6 @@ class RepositorySettings(private val activity: FragmentActivity) : SettingsProvi
|
|||||||
|
|
||||||
private var showSshKeyPref: Preference? = null
|
private var showSshKeyPref: Preference? = null
|
||||||
|
|
||||||
private fun selectExternalGitRepository() {
|
|
||||||
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) { _, _ ->
|
|
||||||
activity.launchActivity(DirectorySelectionActivity::class.java)
|
|
||||||
}
|
|
||||||
.setNegativeButton(R.string.dialog_cancel, null)
|
|
||||||
.show()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun provideSettings(builder: PreferenceScreen.Builder) {
|
override fun provideSettings(builder: PreferenceScreen.Builder) {
|
||||||
val encryptedPreferences = hiltEntryPoint.encryptedPreferences()
|
val encryptedPreferences = hiltEntryPoint.encryptedPreferences()
|
||||||
val gitSettings = hiltEntryPoint.gitSettings()
|
val gitSettings = hiltEntryPoint.gitSettings()
|
||||||
@@ -162,64 +150,35 @@ class RepositorySettings(private val activity: FragmentActivity) : SettingsProvi
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val deleteRepoPref =
|
pref(PreferenceKeys.GIT_DELETE_REPO) {
|
||||||
pref(PreferenceKeys.GIT_DELETE_REPO) {
|
titleRes = R.string.pref_git_delete_repo_title
|
||||||
titleRes = R.string.pref_git_delete_repo_title
|
summaryRes = R.string.pref_git_delete_repo_summary
|
||||||
summaryRes = R.string.pref_git_delete_repo_summary
|
|
||||||
visible = !activity.sharedPrefs.getBoolean(PreferenceKeys.GIT_EXTERNAL, false)
|
|
||||||
onClick {
|
|
||||||
val repoDir = PasswordRepository.getRepositoryDirectory()
|
|
||||||
MaterialAlertDialogBuilder(activity)
|
|
||||||
.setTitle(R.string.pref_dialog_delete_title)
|
|
||||||
.setMessage(activity.getString(R.string.dialog_delete_msg, repoDir))
|
|
||||||
.setCancelable(false)
|
|
||||||
.setPositiveButton(R.string.dialog_delete) { dialogInterface, _ ->
|
|
||||||
runCatching {
|
|
||||||
PasswordRepository.getRepositoryDirectory().deleteRecursively()
|
|
||||||
PasswordRepository.closeRepository()
|
|
||||||
}
|
|
||||||
.onFailure { it.message?.let { message -> activity.snackbar(message = message) } }
|
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
|
|
||||||
activity.getSystemService<ShortcutManager>()?.apply {
|
|
||||||
removeDynamicShortcuts(dynamicShortcuts.map { it.id }.toMutableList())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
activity.sharedPrefs.edit {
|
|
||||||
putBoolean(PreferenceKeys.REPOSITORY_INITIALIZED, false)
|
|
||||||
}
|
|
||||||
dialogInterface.cancel()
|
|
||||||
activity.finish()
|
|
||||||
}
|
|
||||||
.setNegativeButton(R.string.dialog_do_not_delete) { dialogInterface, _ ->
|
|
||||||
run { dialogInterface.cancel() }
|
|
||||||
}
|
|
||||||
.show()
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
checkBox(PreferenceKeys.GIT_EXTERNAL) {
|
|
||||||
titleRes = R.string.pref_external_repository_title
|
|
||||||
summaryRes = R.string.pref_external_repository_summary
|
|
||||||
onCheckedChange { checked ->
|
|
||||||
deleteRepoPref.visible = !checked
|
|
||||||
deleteRepoPref.requestRebind()
|
|
||||||
PasswordRepository.closeRepository()
|
|
||||||
activity.sharedPrefs.edit { putBoolean(PreferenceKeys.REPO_CHANGED, true) }
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pref(PreferenceKeys.GIT_EXTERNAL_REPO) {
|
|
||||||
val externalRepo = activity.sharedPrefs.getString(PreferenceKeys.GIT_EXTERNAL_REPO)
|
|
||||||
if (externalRepo != null) {
|
|
||||||
summary = externalRepo
|
|
||||||
} else {
|
|
||||||
summaryRes = R.string.pref_select_external_repository_summary_no_repo_selected
|
|
||||||
}
|
|
||||||
titleRes = R.string.pref_select_external_repository_title
|
|
||||||
dependency = PreferenceKeys.GIT_EXTERNAL
|
|
||||||
onClick {
|
onClick {
|
||||||
selectExternalGitRepository()
|
val repoDir = PasswordRepository.getRepositoryDirectory()
|
||||||
|
MaterialAlertDialogBuilder(activity)
|
||||||
|
.setTitle(R.string.pref_dialog_delete_title)
|
||||||
|
.setMessage(activity.getString(R.string.dialog_delete_msg, repoDir))
|
||||||
|
.setCancelable(false)
|
||||||
|
.setPositiveButton(R.string.dialog_delete) { dialogInterface, _ ->
|
||||||
|
runCatching {
|
||||||
|
PasswordRepository.getRepositoryDirectory().deleteRecursively()
|
||||||
|
PasswordRepository.closeRepository()
|
||||||
|
}
|
||||||
|
.onFailure { it.message?.let { message -> activity.snackbar(message = message) } }
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
|
||||||
|
activity.getSystemService<ShortcutManager>()?.apply {
|
||||||
|
removeDynamicShortcuts(dynamicShortcuts.map { it.id }.toMutableList())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
activity.sharedPrefs.edit { putBoolean(PreferenceKeys.REPOSITORY_INITIALIZED, false) }
|
||||||
|
dialogInterface.cancel()
|
||||||
|
activity.finish()
|
||||||
|
}
|
||||||
|
.setNegativeButton(R.string.dialog_do_not_delete) { dialogInterface, _ ->
|
||||||
|
run { dialogInterface.cancel() }
|
||||||
|
}
|
||||||
|
.show()
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -26,6 +26,7 @@ fun runMigrations(filesDirPath: String, sharedPrefs: SharedPreferences, gitSetti
|
|||||||
migrateToSshKey(filesDirPath, sharedPrefs)
|
migrateToSshKey(filesDirPath, sharedPrefs)
|
||||||
migrateToClipboardHistory(sharedPrefs)
|
migrateToClipboardHistory(sharedPrefs)
|
||||||
migrateToDiceware(sharedPrefs)
|
migrateToDiceware(sharedPrefs)
|
||||||
|
removeExternalStorageProperties(sharedPrefs)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun migrateToGitUrlBasedConfig(sharedPrefs: SharedPreferences, gitSettings: GitSettings) {
|
private fun migrateToGitUrlBasedConfig(sharedPrefs: SharedPreferences, gitSettings: GitSettings) {
|
||||||
@@ -132,3 +133,18 @@ private fun migrateToDiceware(sharedPrefs: SharedPreferences) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun removeExternalStorageProperties(prefs: SharedPreferences) {
|
||||||
|
logcat(TAG, INFO) { "Removing preferences related to external storage" }
|
||||||
|
prefs.edit {
|
||||||
|
if (prefs.contains(PreferenceKeys.GIT_EXTERNAL)) {
|
||||||
|
if (prefs.getBoolean(PreferenceKeys.GIT_EXTERNAL, false)) {
|
||||||
|
putBoolean(PreferenceKeys.GIT_EXTERNAL_MIGRATED, true)
|
||||||
|
}
|
||||||
|
remove(PreferenceKeys.GIT_EXTERNAL)
|
||||||
|
}
|
||||||
|
if (prefs.contains(PreferenceKeys.GIT_EXTERNAL_REPO)) {
|
||||||
|
remove(PreferenceKeys.GIT_EXTERNAL_REPO)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -8,7 +8,6 @@ package dev.msfjarvis.aps.util.settings
|
|||||||
object PreferenceKeys {
|
object PreferenceKeys {
|
||||||
|
|
||||||
const val APP_THEME = "app_theme"
|
const val APP_THEME = "app_theme"
|
||||||
const val APP_VERSION = "app_version"
|
|
||||||
const val AUTOFILL_ENABLE = "autofill_enable"
|
const val AUTOFILL_ENABLE = "autofill_enable"
|
||||||
const val BIOMETRIC_AUTH = "biometric_auth"
|
const val BIOMETRIC_AUTH = "biometric_auth"
|
||||||
@Deprecated(
|
@Deprecated(
|
||||||
@@ -26,8 +25,11 @@ object PreferenceKeys {
|
|||||||
const val GIT_CONFIG = "git_config"
|
const val GIT_CONFIG = "git_config"
|
||||||
const val GIT_CONFIG_AUTHOR_EMAIL = "git_config_user_email"
|
const val GIT_CONFIG_AUTHOR_EMAIL = "git_config_user_email"
|
||||||
const val GIT_CONFIG_AUTHOR_NAME = "git_config_user_name"
|
const val GIT_CONFIG_AUTHOR_NAME = "git_config_user_name"
|
||||||
|
@Deprecated(message = "We're removing support for external storage")
|
||||||
const val GIT_EXTERNAL = "git_external"
|
const val GIT_EXTERNAL = "git_external"
|
||||||
|
@Deprecated(message = "We're removing support for external storage")
|
||||||
const val GIT_EXTERNAL_REPO = "git_external_repo"
|
const val GIT_EXTERNAL_REPO = "git_external_repo"
|
||||||
|
const val GIT_EXTERNAL_MIGRATED = "git_external_migrated"
|
||||||
const val GIT_REMOTE_AUTH = "git_remote_auth"
|
const val GIT_REMOTE_AUTH = "git_remote_auth"
|
||||||
const val GIT_REMOTE_KEY_TYPE = "git_remote_key_type"
|
const val GIT_REMOTE_KEY_TYPE = "git_remote_key_type"
|
||||||
|
|
||||||
@@ -50,10 +52,7 @@ object PreferenceKeys {
|
|||||||
const val OREO_AUTOFILL_CUSTOM_PUBLIC_SUFFIXES = "oreo_autofill_custom_public_suffixes"
|
const val OREO_AUTOFILL_CUSTOM_PUBLIC_SUFFIXES = "oreo_autofill_custom_public_suffixes"
|
||||||
const val OREO_AUTOFILL_DEFAULT_USERNAME = "oreo_autofill_default_username"
|
const val OREO_AUTOFILL_DEFAULT_USERNAME = "oreo_autofill_default_username"
|
||||||
const val OREO_AUTOFILL_DIRECTORY_STRUCTURE = "oreo_autofill_directory_structure"
|
const val OREO_AUTOFILL_DIRECTORY_STRUCTURE = "oreo_autofill_directory_structure"
|
||||||
const val PREF_KEY_CUSTOM_DICT = "pref_key_custom_dict"
|
|
||||||
const val PREF_KEY_IS_CUSTOM_DICT = "pref_key_is_custom_dict"
|
|
||||||
const val PREF_KEY_PWGEN_TYPE = "pref_key_pwgen_type"
|
const val PREF_KEY_PWGEN_TYPE = "pref_key_pwgen_type"
|
||||||
const val PREF_SELECT_EXTERNAL = "pref_select_external"
|
|
||||||
const val REPOSITORY_INITIALIZED = "repository_initialized"
|
const val REPOSITORY_INITIALIZED = "repository_initialized"
|
||||||
const val REPO_CHANGED = "repo_changed"
|
const val REPO_CHANGED = "repo_changed"
|
||||||
const val SEARCH_ON_START = "search_on_start"
|
const val SEARCH_ON_START = "search_on_start"
|
||||||
|
@@ -1,95 +0,0 @@
|
|||||||
<!--
|
|
||||||
~ Copyright © 2014-2021 The Android Password Store Authors. All Rights Reserved.
|
|
||||||
~ SPDX-License-Identifier: GPL-3.0-only
|
|
||||||
-->
|
|
||||||
|
|
||||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:background="?android:attr/colorBackground">
|
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content">
|
|
||||||
|
|
||||||
<androidx.appcompat.widget.AppCompatImageView
|
|
||||||
android:id="@+id/app_icon"
|
|
||||||
android:layout_width="64dp"
|
|
||||||
android:layout_height="64dp"
|
|
||||||
android:layout_marginStart="32dp"
|
|
||||||
android:layout_marginTop="@dimen/onboarding_icon_margin_top"
|
|
||||||
android:contentDescription="@string/app_icon_hint"
|
|
||||||
android:src="@mipmap/ic_launcher"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
|
||||||
|
|
||||||
<androidx.appcompat.widget.AppCompatTextView
|
|
||||||
android:id="@+id/app_name"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_centerHorizontal="true"
|
|
||||||
android:layout_marginStart="@dimen/fab_compat_margin"
|
|
||||||
android:layout_marginEnd="@dimen/fab_compat_margin"
|
|
||||||
android:text="@string/app_name"
|
|
||||||
android:textAppearance="?attr/textAppearanceTitleLarge"
|
|
||||||
android:textStyle="bold"
|
|
||||||
app:layout_constraintBottom_toBottomOf="@id/app_icon"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toEndOf="@id/app_icon"
|
|
||||||
app:layout_constraintTop_toTopOf="@+id/app_icon" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/repo_location"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="48dp"
|
|
||||||
android:layout_marginEnd="@dimen/fab_compat_margin"
|
|
||||||
android:text="@string/repository_n_location"
|
|
||||||
android:textAppearance="?attr/textAppearanceHeadlineSmall"
|
|
||||||
android:textStyle="bold"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="@id/app_icon"
|
|
||||||
app:layout_constraintTop_toBottomOf="@id/app_icon" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/repo_location_text"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="@dimen/onboarding_desc_margin_top"
|
|
||||||
android:layout_marginEnd="16dp"
|
|
||||||
android:text="@string/location_dialog_create_text"
|
|
||||||
android:textAppearance="?attr/textAppearanceHeadlineSmall"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="@id/repo_location"
|
|
||||||
app:layout_constraintTop_toBottomOf="@id/repo_location" />
|
|
||||||
|
|
||||||
<com.google.android.material.button.MaterialButton
|
|
||||||
android:id="@+id/hidden"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginStart="16dp"
|
|
||||||
android:layout_marginTop="@dimen/onboarding_button_margin_top"
|
|
||||||
android:layout_marginEnd="16dp"
|
|
||||||
android:maxWidth="300dp"
|
|
||||||
android:minWidth="100dp"
|
|
||||||
android:text="@string/location_hidden"
|
|
||||||
app:layout_constraintBottom_toTopOf="@id/sdcard"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toBottomOf="@id/repo_location_text" />
|
|
||||||
|
|
||||||
<com.google.android.material.button.MaterialButton
|
|
||||||
android:id="@+id/sdcard"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginBottom="128dp"
|
|
||||||
android:maxWidth="300dp"
|
|
||||||
android:minWidth="100dp"
|
|
||||||
android:text="@string/location_sdcard"
|
|
||||||
app:layout_constraintEnd_toEndOf="@id/hidden"
|
|
||||||
app:layout_constraintStart_toStartOf="@id/hidden"
|
|
||||||
app:layout_constraintTop_toBottomOf="@id/hidden" />
|
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
||||||
</ScrollView>
|
|
2
app/src/main/res/xml/backup_content.xml
Normal file
2
app/src/main/res/xml/backup_content.xml
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<full-backup-content />
|
5
app/src/main/res/xml/data_extraction_rules.xml
Normal file
5
app/src/main/res/xml/data_extraction_rules.xml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<data-extraction-rules>
|
||||||
|
<cloud-backup />
|
||||||
|
<device-transfer />
|
||||||
|
</data-extraction-rules>
|
@@ -83,7 +83,7 @@ class AutofillSmsActivity : AppCompatActivity() {
|
|||||||
context,
|
context,
|
||||||
fillOtpFromSmsRequestCode++,
|
fillOtpFromSmsRequestCode++,
|
||||||
intent,
|
intent,
|
||||||
PendingIntent.FLAG_CANCEL_CURRENT
|
PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
||||||
)
|
)
|
||||||
.intentSender
|
.intentSender
|
||||||
}
|
}
|
||||||
|
@@ -16,6 +16,7 @@ import kotlin.test.Test
|
|||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
import kotlin.test.assertFalse
|
import kotlin.test.assertFalse
|
||||||
import kotlin.test.assertNull
|
import kotlin.test.assertNull
|
||||||
|
import kotlin.test.assertTrue
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.rules.TemporaryFolder
|
import org.junit.rules.TemporaryFolder
|
||||||
|
|
||||||
@@ -50,7 +51,6 @@ class MigrationsTest {
|
|||||||
@Test
|
@Test
|
||||||
fun verifySshWithCustomPortMigration() {
|
fun verifySshWithCustomPortMigration() {
|
||||||
sharedPrefs.edit {
|
sharedPrefs.edit {
|
||||||
clear()
|
|
||||||
putString(PreferenceKeys.GIT_REMOTE_PORT, "2200")
|
putString(PreferenceKeys.GIT_REMOTE_PORT, "2200")
|
||||||
putString(PreferenceKeys.GIT_REMOTE_USERNAME, "msfjarvis")
|
putString(PreferenceKeys.GIT_REMOTE_USERNAME, "msfjarvis")
|
||||||
putString(PreferenceKeys.GIT_REMOTE_LOCATION, "/mnt/disk3/pass-repo")
|
putString(PreferenceKeys.GIT_REMOTE_LOCATION, "/mnt/disk3/pass-repo")
|
||||||
@@ -73,7 +73,6 @@ class MigrationsTest {
|
|||||||
@Test
|
@Test
|
||||||
fun verifySshWithDefaultPortMigration() {
|
fun verifySshWithDefaultPortMigration() {
|
||||||
sharedPrefs.edit {
|
sharedPrefs.edit {
|
||||||
clear()
|
|
||||||
putString(PreferenceKeys.GIT_REMOTE_USERNAME, "msfjarvis")
|
putString(PreferenceKeys.GIT_REMOTE_USERNAME, "msfjarvis")
|
||||||
putString(PreferenceKeys.GIT_REMOTE_LOCATION, "/mnt/disk3/pass-repo")
|
putString(PreferenceKeys.GIT_REMOTE_LOCATION, "/mnt/disk3/pass-repo")
|
||||||
putString(PreferenceKeys.GIT_REMOTE_SERVER, "192.168.0.102")
|
putString(PreferenceKeys.GIT_REMOTE_SERVER, "192.168.0.102")
|
||||||
@@ -95,7 +94,6 @@ class MigrationsTest {
|
|||||||
@Test
|
@Test
|
||||||
fun verifyHttpsWithGitHubMigration() {
|
fun verifyHttpsWithGitHubMigration() {
|
||||||
sharedPrefs.edit {
|
sharedPrefs.edit {
|
||||||
clear()
|
|
||||||
putString(PreferenceKeys.GIT_REMOTE_USERNAME, "msfjarvis")
|
putString(PreferenceKeys.GIT_REMOTE_USERNAME, "msfjarvis")
|
||||||
putString(PreferenceKeys.GIT_REMOTE_LOCATION, "Android-Password-Store/pass-test")
|
putString(PreferenceKeys.GIT_REMOTE_LOCATION, "Android-Password-Store/pass-test")
|
||||||
putString(PreferenceKeys.GIT_REMOTE_SERVER, "github.com")
|
putString(PreferenceKeys.GIT_REMOTE_SERVER, "github.com")
|
||||||
@@ -116,7 +114,6 @@ class MigrationsTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun verifyHiddenFoldersMigrationIfDisabled() {
|
fun verifyHiddenFoldersMigrationIfDisabled() {
|
||||||
sharedPrefs.edit { clear() }
|
|
||||||
runMigrations(
|
runMigrations(
|
||||||
filesDir,
|
filesDir,
|
||||||
sharedPrefs,
|
sharedPrefs,
|
||||||
@@ -128,10 +125,7 @@ class MigrationsTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun verifyHiddenFoldersMigrationIfEnabled() {
|
fun verifyHiddenFoldersMigrationIfEnabled() {
|
||||||
sharedPrefs.edit {
|
sharedPrefs.edit { putBoolean(PreferenceKeys.SHOW_HIDDEN_FOLDERS, true) }
|
||||||
clear()
|
|
||||||
putBoolean(PreferenceKeys.SHOW_HIDDEN_FOLDERS, true)
|
|
||||||
}
|
|
||||||
runMigrations(
|
runMigrations(
|
||||||
filesDir,
|
filesDir,
|
||||||
sharedPrefs,
|
sharedPrefs,
|
||||||
@@ -143,10 +137,7 @@ class MigrationsTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun verifyClearClipboardHistoryMigration() {
|
fun verifyClearClipboardHistoryMigration() {
|
||||||
sharedPrefs.edit {
|
sharedPrefs.edit { putBoolean(PreferenceKeys.CLEAR_CLIPBOARD_20X, true) }
|
||||||
clear()
|
|
||||||
putBoolean(PreferenceKeys.CLEAR_CLIPBOARD_20X, true)
|
|
||||||
}
|
|
||||||
runMigrations(
|
runMigrations(
|
||||||
filesDir,
|
filesDir,
|
||||||
sharedPrefs,
|
sharedPrefs,
|
||||||
@@ -158,10 +149,7 @@ class MigrationsTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun verifyClassicPasswordGeneratorMigration() {
|
fun verifyClassicPasswordGeneratorMigration() {
|
||||||
sharedPrefs.edit {
|
sharedPrefs.edit { putString(PreferenceKeys.PREF_KEY_PWGEN_TYPE, "classic") }
|
||||||
clear()
|
|
||||||
putString(PreferenceKeys.PREF_KEY_PWGEN_TYPE, "classic")
|
|
||||||
}
|
|
||||||
runMigrations(
|
runMigrations(
|
||||||
filesDir,
|
filesDir,
|
||||||
sharedPrefs,
|
sharedPrefs,
|
||||||
@@ -172,10 +160,7 @@ class MigrationsTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun verifyXkPasswdPasswordGeneratorMigration() {
|
fun verifyXkPasswdPasswordGeneratorMigration() {
|
||||||
sharedPrefs.edit {
|
sharedPrefs.edit { putString(PreferenceKeys.PREF_KEY_PWGEN_TYPE, "xkpasswd") }
|
||||||
clear()
|
|
||||||
putString(PreferenceKeys.PREF_KEY_PWGEN_TYPE, "xkpasswd")
|
|
||||||
}
|
|
||||||
runMigrations(
|
runMigrations(
|
||||||
filesDir,
|
filesDir,
|
||||||
sharedPrefs,
|
sharedPrefs,
|
||||||
@@ -183,4 +168,20 @@ class MigrationsTest {
|
|||||||
)
|
)
|
||||||
assertEquals("diceware", sharedPrefs.getString(PreferenceKeys.PREF_KEY_PWGEN_TYPE))
|
assertEquals("diceware", sharedPrefs.getString(PreferenceKeys.PREF_KEY_PWGEN_TYPE))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun verifyExternalStorageMigration() {
|
||||||
|
sharedPrefs.edit {
|
||||||
|
putBoolean(PreferenceKeys.GIT_EXTERNAL, true)
|
||||||
|
putString(PreferenceKeys.GIT_EXTERNAL_REPO, "/sdcard/")
|
||||||
|
}
|
||||||
|
runMigrations(
|
||||||
|
filesDir,
|
||||||
|
sharedPrefs,
|
||||||
|
GitSettings(sharedPrefs, encryptedSharedPreferences, proxySharedPreferences, filesDir),
|
||||||
|
)
|
||||||
|
assertFalse { sharedPrefs.contains(PreferenceKeys.GIT_EXTERNAL) }
|
||||||
|
assertFalse { sharedPrefs.contains(PreferenceKeys.GIT_EXTERNAL_REPO) }
|
||||||
|
assertTrue { sharedPrefs.getBoolean(PreferenceKeys.GIT_EXTERNAL_MIGRATED, false) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -244,7 +244,7 @@ private fun getBrowserAutofillSupportLevel(
|
|||||||
public fun getInstalledBrowsersWithAutofillSupportLevel(
|
public fun getInstalledBrowsersWithAutofillSupportLevel(
|
||||||
context: Context
|
context: Context
|
||||||
): List<Pair<String, BrowserAutofillSupportLevel>> {
|
): List<Pair<String, BrowserAutofillSupportLevel>> {
|
||||||
val testWebIntent = Intent(Intent.ACTION_VIEW).apply { data = Uri.parse("http://example.org") }
|
val testWebIntent = Intent(Intent.ACTION_VIEW).apply { data = Uri.parse("https://example.org") }
|
||||||
val installedBrowsers =
|
val installedBrowsers =
|
||||||
context.packageManager.queryIntentActivities(testWebIntent, PackageManager.MATCH_ALL)
|
context.packageManager.queryIntentActivities(testWebIntent, PackageManager.MATCH_ALL)
|
||||||
return installedBrowsers
|
return installedBrowsers
|
||||||
|
@@ -11,7 +11,7 @@ extensions.configure<TestedExtension> {
|
|||||||
setCompileSdkVersion(31)
|
setCompileSdkVersion(31)
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdk = 23
|
minSdk = 23
|
||||||
targetSdk = 29
|
targetSdk = 31
|
||||||
}
|
}
|
||||||
|
|
||||||
sourceSets {
|
sourceSets {
|
||||||
|
Reference in New Issue
Block a user