aboutsummaryrefslogtreecommitdiff
path: root/app/src
diff options
context:
space:
mode:
Diffstat (limited to 'app/src')
-rw-r--r--app/src/main/AndroidManifest.xml3
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/Application.kt12
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/ClipboardService.kt6
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/SearchableRepositoryViewModel.kt2
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/UserPreference.kt6
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/autofill/oreo/AutofillResponseBuilder.kt9
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/autofill/oreo/ui/AutofillPublisherChangedActivity.kt14
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/autofill/oreo/ui/AutofillSaveActivity.kt2
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/crypto/BasePgpActivity.kt10
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/crypto/DecryptActivity.kt2
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/crypto/PasswordCreationActivity.kt16
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/git/BaseGitActivity.kt3
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/git/GitCommandExecutor.kt4
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/git/config/GitSettings.kt38
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/git/log/GitLogModel.kt2
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/git/sshj/SshKey.kt4
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/model/PasswordEntry.kt2
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/ui/onboarding/fragments/CloneFragment.kt2
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/ui/onboarding/fragments/KeySelectionFragment.kt66
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/ui/onboarding/fragments/RepoLocationFragment.kt15
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/ui/proxy/ProxySelectorActivity.kt75
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/utils/AndroidExtensions.kt6
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/utils/BiometricAuthenticator.kt9
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/utils/PasswordRepository.kt2
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/utils/PreferenceKeys.kt6
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/utils/ProxyUtils.kt66
-rw-r--r--app/src/main/res/layout/activity_oreo_autofill_publisher_changed.xml104
-rw-r--r--app/src/main/res/layout/activity_proxy_selector.xml106
-rw-r--r--app/src/main/res/layout/fragment_key_selection.xml81
-rw-r--r--app/src/main/res/values-v23/colors.xml8
-rw-r--r--app/src/main/res/values/colors.xml2
-rw-r--r--app/src/main/res/values/strings.xml15
-rw-r--r--app/src/main/res/xml/preference.xml3
33 files changed, 592 insertions, 109 deletions
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 3b0781f4..6cad066e 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -36,6 +36,9 @@
android:name=".ui.onboarding.activity.OnboardingActivity"
android:configChanges="orientation|screenSize" />
+ <activity android:name=".ui.proxy.ProxySelectorActivity"
+ android:windowSoftInputMode="adjustResize" />
+
<activity
android:name=".LaunchActivity"
android:configChanges="orientation|screenSize"
diff --git a/app/src/main/java/com/zeapo/pwdstore/Application.kt b/app/src/main/java/com/zeapo/pwdstore/Application.kt
index 91e47793..3f3963d0 100644
--- a/app/src/main/java/com/zeapo/pwdstore/Application.kt
+++ b/app/src/main/java/com/zeapo/pwdstore/Application.kt
@@ -14,27 +14,31 @@ import com.github.ajalt.timberkt.Timber.DebugTree
import com.github.ajalt.timberkt.Timber.plant
import com.zeapo.pwdstore.git.sshj.setUpBouncyCastleForSshj
import com.zeapo.pwdstore.utils.PreferenceKeys
+import com.zeapo.pwdstore.utils.ProxyUtils
import com.zeapo.pwdstore.utils.getString
import com.zeapo.pwdstore.utils.sharedPrefs
@Suppress("Unused")
class Application : android.app.Application(), SharedPreferences.OnSharedPreferenceChangeListener {
+ private val prefs by lazy { sharedPrefs }
+
override fun onCreate() {
super.onCreate()
instance = this
if (BuildConfig.ENABLE_DEBUG_FEATURES ||
- sharedPrefs.getBoolean(PreferenceKeys.ENABLE_DEBUG_LOGGING, false)) {
+ prefs.getBoolean(PreferenceKeys.ENABLE_DEBUG_LOGGING, false)) {
plant(DebugTree())
}
- sharedPrefs.registerOnSharedPreferenceChangeListener(this)
+ prefs.registerOnSharedPreferenceChangeListener(this)
setNightMode()
setUpBouncyCastleForSshj()
runMigrations(applicationContext)
+ ProxyUtils.setDefaultProxy()
}
override fun onTerminate() {
- sharedPrefs.unregisterOnSharedPreferenceChangeListener(this)
+ prefs.unregisterOnSharedPreferenceChangeListener(this)
super.onTerminate()
}
@@ -45,7 +49,7 @@ class Application : android.app.Application(), SharedPreferences.OnSharedPrefere
}
private fun setNightMode() {
- AppCompatDelegate.setDefaultNightMode(when (sharedPrefs.getString(PreferenceKeys.APP_THEME)
+ AppCompatDelegate.setDefaultNightMode(when (prefs.getString(PreferenceKeys.APP_THEME)
?: getString(R.string.app_theme_def)) {
"light" -> MODE_NIGHT_NO
"dark" -> MODE_NIGHT_YES
diff --git a/app/src/main/java/com/zeapo/pwdstore/ClipboardService.kt b/app/src/main/java/com/zeapo/pwdstore/ClipboardService.kt
index 46c4ecd3..0f27e5ef 100644
--- a/app/src/main/java/com/zeapo/pwdstore/ClipboardService.kt
+++ b/app/src/main/java/com/zeapo/pwdstore/ClipboardService.kt
@@ -10,7 +10,6 @@ import android.app.PendingIntent
import android.app.Service
import android.content.ClipData
import android.content.Intent
-import android.content.SharedPreferences
import android.os.Build
import android.os.IBinder
import androidx.core.app.NotificationCompat
@@ -32,7 +31,6 @@ import kotlinx.coroutines.withContext
class ClipboardService : Service() {
private val scope = CoroutineScope(Job() + Dispatchers.Main)
- private val settings: SharedPreferences by lazy { sharedPrefs }
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
if (intent != null) {
@@ -45,7 +43,7 @@ class ClipboardService : Service() {
}
ACTION_START -> {
- val time = settings.getString(PreferenceKeys.GENERAL_SHOW_TIME)?.toIntOrNull() ?: 45
+ val time = sharedPrefs.getString(PreferenceKeys.GENERAL_SHOW_TIME)?.toIntOrNull() ?: 45
if (time == 0) {
stopSelf()
@@ -80,7 +78,7 @@ class ClipboardService : Service() {
}
private fun clearClipboard() {
- val deepClear = settings.getBoolean(PreferenceKeys.CLEAR_CLIPBOARD_20X, false)
+ val deepClear = sharedPrefs.getBoolean(PreferenceKeys.CLEAR_CLIPBOARD_20X, false)
val clipboard = clipboard
if (clipboard != null) {
diff --git a/app/src/main/java/com/zeapo/pwdstore/SearchableRepositoryViewModel.kt b/app/src/main/java/com/zeapo/pwdstore/SearchableRepositoryViewModel.kt
index 6da5de7a..a73dd40d 100644
--- a/app/src/main/java/com/zeapo/pwdstore/SearchableRepositoryViewModel.kt
+++ b/app/src/main/java/com/zeapo/pwdstore/SearchableRepositoryViewModel.kt
@@ -139,7 +139,7 @@ class SearchableRepositoryViewModel(application: Application) : AndroidViewModel
private val root
get() = PasswordRepository.getRepositoryDirectory()
- private val settings by lazy { application.sharedPrefs }
+ private val settings by lazy(LazyThreadSafetyMode.NONE) { application.sharedPrefs }
private val showHiddenContents
get() = settings.getBoolean(PreferenceKeys.SHOW_HIDDEN_CONTENTS, false)
private val defaultSearchMode
diff --git a/app/src/main/java/com/zeapo/pwdstore/UserPreference.kt b/app/src/main/java/com/zeapo/pwdstore/UserPreference.kt
index 45915213..7f6727f2 100644
--- a/app/src/main/java/com/zeapo/pwdstore/UserPreference.kt
+++ b/app/src/main/java/com/zeapo/pwdstore/UserPreference.kt
@@ -50,6 +50,7 @@ import com.zeapo.pwdstore.git.sshj.SshKey
import com.zeapo.pwdstore.pwgenxkpwd.XkpwdDictionary
import com.zeapo.pwdstore.sshkeygen.ShowSshKeyFragment
import com.zeapo.pwdstore.sshkeygen.SshKeyGenActivity
+import com.zeapo.pwdstore.ui.proxy.ProxySelectorActivity
import com.zeapo.pwdstore.utils.BiometricAuthenticator
import com.zeapo.pwdstore.utils.PasswordRepository
import com.zeapo.pwdstore.utils.PreferenceKeys
@@ -418,6 +419,11 @@ class UserPreference : AppCompatActivity() {
}
}
+ findPreference<Preference>(PreferenceKeys.PROXY_SETTINGS)?.onPreferenceClickListener = ClickListener {
+ startActivity(Intent(requireContext(), ProxySelectorActivity::class.java))
+ true
+ }
+
val prefCustomXkpwdDictionary = findPreference<Preference>(PreferenceKeys.PREF_KEY_CUSTOM_DICT)
prefCustomXkpwdDictionary?.onPreferenceClickListener = ClickListener {
prefsActivity.storeCustomDictionaryPath()
diff --git a/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/AutofillResponseBuilder.kt b/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/AutofillResponseBuilder.kt
index 0e73b908..ec3d2b77 100644
--- a/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/AutofillResponseBuilder.kt
+++ b/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/AutofillResponseBuilder.kt
@@ -15,12 +15,12 @@ import android.service.autofill.SaveInfo
import android.widget.RemoteViews
import androidx.annotation.RequiresApi
import com.github.ajalt.timberkt.e
-import com.github.michaelbull.result.fold
import com.github.androidpasswordstore.autofillparser.AutofillAction
import com.github.androidpasswordstore.autofillparser.AutofillScenario
import com.github.androidpasswordstore.autofillparser.Credentials
import com.github.androidpasswordstore.autofillparser.FillableForm
import com.github.androidpasswordstore.autofillparser.fillWith
+import com.github.michaelbull.result.fold
import com.zeapo.pwdstore.autofill.oreo.ui.AutofillDecryptActivity
import com.zeapo.pwdstore.autofill.oreo.ui.AutofillFilterView
import com.zeapo.pwdstore.autofill.oreo.ui.AutofillPublisherChangedActivity
@@ -88,8 +88,13 @@ class AutofillResponseBuilder(form: FillableForm) {
publisherChangedException: AutofillPublisherChangedException
): Dataset {
val remoteView = makeWarningRemoteView(context)
+ // If the user decides to trust the new publisher, they can choose reset the list of
+ // matches. In this case we need to immediately show a new `FillResponse` as if the app were
+ // autofilled for the first time. This `FillResponse` needs to be returned as a result from
+ // `AutofillPublisherChangedActivity`, which is why we create and pass it on here.
+ val fillResponseAfterReset = makeFillResponse(context, emptyList())
val intentSender = AutofillPublisherChangedActivity.makePublisherChangedIntentSender(
- context, publisherChangedException
+ context, publisherChangedException, fillResponseAfterReset
)
return makePlaceholderDataset(remoteView, intentSender, AutofillAction.Match)
}
diff --git a/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/ui/AutofillPublisherChangedActivity.kt b/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/ui/AutofillPublisherChangedActivity.kt
index 44ed3446..34edf8f5 100644
--- a/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/ui/AutofillPublisherChangedActivity.kt
+++ b/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/ui/AutofillPublisherChangedActivity.kt
@@ -12,8 +12,10 @@ import android.content.IntentSender
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle
+import android.service.autofill.FillResponse
import android.text.format.DateUtils
import android.view.View
+import android.view.autofill.AutofillManager
import androidx.appcompat.app.AppCompatActivity
import com.github.ajalt.timberkt.e
import com.github.androidpasswordstore.autofillparser.FormOrigin
@@ -33,14 +35,18 @@ class AutofillPublisherChangedActivity : AppCompatActivity() {
private const val EXTRA_APP_PACKAGE =
"com.zeapo.pwdstore.autofill.oreo.ui.EXTRA_APP_PACKAGE"
+ private const val EXTRA_FILL_RESPONSE_AFTER_RESET =
+ "com.zeapo.pwdstore.autofill.oreo.ui.EXTRA_FILL_RESPONSE_AFTER_RESET"
private var publisherChangedRequestCode = 1
fun makePublisherChangedIntentSender(
context: Context,
- publisherChangedException: AutofillPublisherChangedException
+ publisherChangedException: AutofillPublisherChangedException,
+ fillResponseAfterReset: FillResponse?,
): IntentSender {
val intent = Intent(context, AutofillPublisherChangedActivity::class.java).apply {
putExtra(EXTRA_APP_PACKAGE, publisherChangedException.formOrigin.identifier)
+ putExtra(EXTRA_FILL_RESPONSE_AFTER_RESET, fillResponseAfterReset)
}
return PendingIntent.getActivity(
context, publisherChangedRequestCode++, intent, PendingIntent.FLAG_CANCEL_CURRENT
@@ -66,12 +72,16 @@ class AutofillPublisherChangedActivity : AppCompatActivity() {
with(binding) {
okButton.setOnClickListener { finish() }
advancedButton.setOnClickListener {
- advancedButton.visibility = View.INVISIBLE
+ advancedButton.visibility = View.GONE
warningAppAdvancedInfo.visibility = View.VISIBLE
resetButton.visibility = View.VISIBLE
}
resetButton.setOnClickListener {
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)
+ })
finish()
}
}
diff --git a/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/ui/AutofillSaveActivity.kt b/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/ui/AutofillSaveActivity.kt
index 7f83d483..f4c296ee 100644
--- a/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/ui/AutofillSaveActivity.kt
+++ b/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/ui/AutofillSaveActivity.kt
@@ -82,7 +82,7 @@ class AutofillSaveActivity : AppCompatActivity() {
}
}
- private val formOrigin: FormOrigin? by lazy {
+ private val formOrigin by lazy(LazyThreadSafetyMode.NONE) {
val shouldMatchApp: String? = intent.getStringExtra(EXTRA_SHOULD_MATCH_APP)
val shouldMatchWeb: String? = intent.getStringExtra(EXTRA_SHOULD_MATCH_WEB)
if (shouldMatchApp != null && shouldMatchWeb == null) {
diff --git a/app/src/main/java/com/zeapo/pwdstore/crypto/BasePgpActivity.kt b/app/src/main/java/com/zeapo/pwdstore/crypto/BasePgpActivity.kt
index 8380d7d0..6b6c2032 100644
--- a/app/src/main/java/com/zeapo/pwdstore/crypto/BasePgpActivity.kt
+++ b/app/src/main/java/com/zeapo/pwdstore/crypto/BasePgpActivity.kt
@@ -45,24 +45,24 @@ open class BasePgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBou
/**
* Full path to the repository
*/
- val repoPath: String by lazy { intent.getStringExtra("REPO_PATH") }
+ val repoPath: String by lazy(LazyThreadSafetyMode.NONE) { intent.getStringExtra("REPO_PATH") }
/**
* Full path to the password file being worked on
*/
- val fullPath: String by lazy { intent.getStringExtra("FILE_PATH") }
+ val fullPath: String by lazy(LazyThreadSafetyMode.NONE) { intent.getStringExtra("FILE_PATH") }
/**
* Name of the password file
*
* Converts personal/auth.foo.org/john_doe@example.org.gpg to john_doe.example.org
*/
- val name: String by lazy { File(fullPath).nameWithoutExtension }
+ val name: String by lazy(LazyThreadSafetyMode.NONE) { File(fullPath).nameWithoutExtension }
/**
* Get the timestamp for when this file was last modified.
*/
- val lastChangedString: CharSequence by lazy {
+ val lastChangedString: CharSequence by lazy(LazyThreadSafetyMode.NONE) {
getLastChangedString(
intent.getLongExtra(
"LAST_CHANGED_TIMESTAMP",
@@ -74,7 +74,7 @@ open class BasePgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBou
/**
* [SharedPreferences] instance used by subclasses to persist settings
*/
- val settings: SharedPreferences by lazy { sharedPrefs }
+ val settings: SharedPreferences by lazy(LazyThreadSafetyMode.NONE) { sharedPrefs }
/**
* Handle to the [OpenPgpApi] instance that is used by subclasses to interface with OpenKeychain.
diff --git a/app/src/main/java/com/zeapo/pwdstore/crypto/DecryptActivity.kt b/app/src/main/java/com/zeapo/pwdstore/crypto/DecryptActivity.kt
index 3685cb17..cb43534d 100644
--- a/app/src/main/java/com/zeapo/pwdstore/crypto/DecryptActivity.kt
+++ b/app/src/main/java/com/zeapo/pwdstore/crypto/DecryptActivity.kt
@@ -39,7 +39,7 @@ class DecryptActivity : BasePgpActivity(), OpenPgpServiceConnection.OnBound {
private val binding by viewBinding(DecryptLayoutBinding::inflate)
- private val relativeParentPath by lazy { getParentPath(fullPath, repoPath) }
+ private val relativeParentPath by lazy(LazyThreadSafetyMode.NONE) { getParentPath(fullPath, repoPath) }
private var passwordEntry: PasswordEntry? = null
private val userInteractionRequiredResult = registerForActivityResult(StartIntentSenderForResult()) { result ->
diff --git a/app/src/main/java/com/zeapo/pwdstore/crypto/PasswordCreationActivity.kt b/app/src/main/java/com/zeapo/pwdstore/crypto/PasswordCreationActivity.kt
index 07694089..01d85f2b 100644
--- a/app/src/main/java/com/zeapo/pwdstore/crypto/PasswordCreationActivity.kt
+++ b/app/src/main/java/com/zeapo/pwdstore/crypto/PasswordCreationActivity.kt
@@ -56,12 +56,12 @@ class PasswordCreationActivity : BasePgpActivity(), OpenPgpServiceConnection.OnB
private val binding by viewBinding(PasswordCreationActivityBinding::inflate)
- private val suggestedName by lazy { intent.getStringExtra(EXTRA_FILE_NAME) }
- private val suggestedPass by lazy { intent.getStringExtra(EXTRA_PASSWORD) }
- private val suggestedExtra by lazy { intent.getStringExtra(EXTRA_EXTRA_CONTENT) }
- private val shouldGeneratePassword by lazy { intent.getBooleanExtra(EXTRA_GENERATE_PASSWORD, false) }
- private val editing by lazy { intent.getBooleanExtra(EXTRA_EDITING, false) }
- private val oldFileName by lazy { intent.getStringExtra(EXTRA_FILE_NAME) }
+ 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 var oldCategory: String? = null
private var copy: Boolean = false
private var encryptionIntent: Intent = Intent()
@@ -277,6 +277,7 @@ class PasswordCreationActivity : BasePgpActivity(), OpenPgpServiceConnection.OnB
@OptIn(ExperimentalUnsignedTypes::class)
private fun parseGpgIdentifier(identifier: String): GpgIdentifier? {
+ if (identifier.isEmpty()) return null
// Match long key IDs:
// FF22334455667788 or 0xFF22334455667788
val maybeLongKeyId = identifier.removePrefix("0x").takeIf {
@@ -342,6 +343,9 @@ class PasswordCreationActivity : BasePgpActivity(), OpenPgpServiceConnection.OnB
.filter { it.isNotBlank() }
.map { line ->
parseGpgIdentifier(line) ?: run {
+ // The line being empty means this is most likely an empty `.gpg-id` file
+ // we created. Skip the validation so we can make the user add a real ID.
+ if (line.isEmpty()) return@run
if (line.removePrefix("0x").matches("[a-fA-F0-9]{8}".toRegex())) {
snackbar(message = resources.getString(R.string.short_key_ids_unsupported))
} else {
diff --git a/app/src/main/java/com/zeapo/pwdstore/git/BaseGitActivity.kt b/app/src/main/java/com/zeapo/pwdstore/git/BaseGitActivity.kt
index b0aed087..36b8b6fb 100644
--- a/app/src/main/java/com/zeapo/pwdstore/git/BaseGitActivity.kt
+++ b/app/src/main/java/com/zeapo/pwdstore/git/BaseGitActivity.kt
@@ -128,11 +128,12 @@ abstract class BaseGitActivity : AppCompatActivity() {
*/
private fun rootCauseException(throwable: Throwable): Throwable {
var rootCause = throwable
- // JGit's TransportException hides the more helpful SSHJ exceptions.
+ // JGit's InvalidRemoteException and TransportException hide the more helpful SSHJ exceptions.
// Also, SSHJ's UserAuthException about exhausting available authentication methods hides
// more useful exceptions.
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 = rootCause.cause ?: break
diff --git a/app/src/main/java/com/zeapo/pwdstore/git/GitCommandExecutor.kt b/app/src/main/java/com/zeapo/pwdstore/git/GitCommandExecutor.kt
index ed5411ac..ef584cf7 100644
--- a/app/src/main/java/com/zeapo/pwdstore/git/GitCommandExecutor.kt
+++ b/app/src/main/java/com/zeapo/pwdstore/git/GitCommandExecutor.kt
@@ -51,8 +51,10 @@ class GitCommandExecutor(
// the previous status will eventually be used to avoid a commit
if (nbChanges > 0) {
withContext(Dispatchers.IO) {
+ val name = GitSettings.authorName.ifEmpty { "root" }
+ val email = GitSettings.authorEmail.ifEmpty { "localhost" }
command
- .setAuthor(PersonIdent(GitSettings.authorName, GitSettings.authorEmail))
+ .setAuthor(PersonIdent(name, email))
.call()
}
}
diff --git a/app/src/main/java/com/zeapo/pwdstore/git/config/GitSettings.kt b/app/src/main/java/com/zeapo/pwdstore/git/config/GitSettings.kt
index e43deaf4..27ceb5cb 100644
--- a/app/src/main/java/com/zeapo/pwdstore/git/config/GitSettings.kt
+++ b/app/src/main/java/com/zeapo/pwdstore/git/config/GitSettings.kt
@@ -11,6 +11,7 @@ import com.zeapo.pwdstore.Application
import com.zeapo.pwdstore.utils.PasswordRepository
import com.zeapo.pwdstore.utils.PreferenceKeys
import com.zeapo.pwdstore.utils.getEncryptedGitPrefs
+import com.zeapo.pwdstore.utils.getEncryptedProxyPrefs
import com.zeapo.pwdstore.utils.getString
import com.zeapo.pwdstore.utils.sharedPrefs
import java.io.File
@@ -52,8 +53,9 @@ object GitSettings {
private const val DEFAULT_BRANCH = "master"
- private val settings by lazy { Application.instance.sharedPrefs }
- private val encryptedSettings by lazy { Application.instance.getEncryptedGitPrefs() }
+ 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() }
var authMode
get() = AuthMode.fromString(settings.getString(PreferenceKeys.GIT_REMOTE_AUTH))
@@ -108,6 +110,38 @@ object GitSettings {
}
}
+ var proxyHost
+ get() = proxySettings.getString(PreferenceKeys.PROXY_HOST)
+ set(value) {
+ proxySettings.edit {
+ putString(PreferenceKeys.PROXY_HOST, value)
+ }
+ }
+
+ var proxyPort
+ get() = proxySettings.getInt(PreferenceKeys.PROXY_PORT, -1)
+ set(value) {
+ proxySettings.edit {
+ putInt(PreferenceKeys.PROXY_PORT, value)
+ }
+ }
+
+ var proxyUsername
+ get() = settings.getString(PreferenceKeys.PROXY_USERNAME)
+ set(value) {
+ proxySettings.edit {
+ putString(PreferenceKeys.PROXY_USERNAME, value)
+ }
+ }
+
+ var proxyPassword
+ get() = proxySettings.getString(PreferenceKeys.PROXY_PASSWORD)
+ set(value) {
+ proxySettings.edit {
+ putString(PreferenceKeys.PROXY_PASSWORD, value)
+ }
+ }
+
sealed class UpdateConnectionSettingsResult {
class MissingUsername(val newProtocol: Protocol) : UpdateConnectionSettingsResult()
class AuthModeMismatch(val newProtocol: Protocol, val validModes: List<AuthMode>) : UpdateConnectionSettingsResult()
diff --git a/app/src/main/java/com/zeapo/pwdstore/git/log/GitLogModel.kt b/app/src/main/java/com/zeapo/pwdstore/git/log/GitLogModel.kt
index ccd2f88a..15b9f64d 100644
--- a/app/src/main/java/com/zeapo/pwdstore/git/log/GitLogModel.kt
+++ b/app/src/main/java/com/zeapo/pwdstore/git/log/GitLogModel.kt
@@ -41,7 +41,7 @@ class GitLogModel {
// This is because the commit graph is walked from HEAD to the last commit to obtain.
// 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 {
+ private val cache: MutableList<GitCommit> by lazy(LazyThreadSafetyMode.NONE) {
commits().map {
GitCommit(it.hash, it.shortMessage, it.authorIdent.name, it.time)
}.toMutableList()
diff --git a/app/src/main/java/com/zeapo/pwdstore/git/sshj/SshKey.kt b/app/src/main/java/com/zeapo/pwdstore/git/sshj/SshKey.kt
index 73072f7d..5e6cfb1b 100644
--- a/app/src/main/java/com/zeapo/pwdstore/git/sshj/SshKey.kt
+++ b/app/src/main/java/com/zeapo/pwdstore/git/sshj/SshKey.kt
@@ -50,7 +50,7 @@ private const val PROVIDER_ANDROID_KEY_STORE = "AndroidKeyStore"
private const val KEYSTORE_ALIAS = "sshkey"
private const val ANDROIDX_SECURITY_KEYSET_PREF_NAME = "androidx_sshkey_keyset_prefs"
-private val androidKeystore: KeyStore by lazy {
+private val androidKeystore: KeyStore by lazy(LazyThreadSafetyMode.NONE) {
KeyStore.getInstance(PROVIDER_ANDROID_KEY_STORE).apply { load(null) }
}
@@ -119,7 +119,7 @@ object SshKey {
putString(PreferenceKeys.GIT_REMOTE_KEY_TYPE, value?.value)
}
- private val isStrongBoxSupported by lazy {
+ private val isStrongBoxSupported by lazy(LazyThreadSafetyMode.NONE) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P)
context.packageManager.hasSystemFeature(PackageManager.FEATURE_STRONGBOX_KEYSTORE)
else
diff --git a/app/src/main/java/com/zeapo/pwdstore/model/PasswordEntry.kt b/app/src/main/java/com/zeapo/pwdstore/model/PasswordEntry.kt
index ea6255c4..556b2c87 100644
--- a/app/src/main/java/com/zeapo/pwdstore/model/PasswordEntry.kt
+++ b/app/src/main/java/com/zeapo/pwdstore/model/PasswordEntry.kt
@@ -59,7 +59,7 @@ class PasswordEntry(content: String, private val totpFinder: TotpFinder = UriTot
return Otp.calculateCode(totpSecret, Date().time / (1000 * totpPeriod), totpAlgorithm, digits).get()
}
- val extraContentWithoutAuthData by lazy {
+ val extraContentWithoutAuthData by lazy(LazyThreadSafetyMode.NONE) {
extraContent.splitToSequence("\n").filter { line ->
return@filter when {
USERNAME_FIELDS.any { prefix -> line.startsWith(prefix, ignoreCase = true) } -> {
diff --git a/app/src/main/java/com/zeapo/pwdstore/ui/onboarding/fragments/CloneFragment.kt b/app/src/main/java/com/zeapo/pwdstore/ui/onboarding/fragments/CloneFragment.kt
index 19a14695..acd0151c 100644
--- a/app/src/main/java/com/zeapo/pwdstore/ui/onboarding/fragments/CloneFragment.kt
+++ b/app/src/main/java/com/zeapo/pwdstore/ui/onboarding/fragments/CloneFragment.kt
@@ -25,7 +25,7 @@ class CloneFragment : Fragment(R.layout.fragment_clone) {
private val binding by viewBinding(FragmentCloneBinding::bind)
- private val settings by lazy { requireActivity().applicationContext.sharedPrefs }
+ private val settings by lazy(LazyThreadSafetyMode.NONE) { requireActivity().applicationContext.sharedPrefs }
private val cloneAction = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode == AppCompatActivity.RESULT_OK) {
diff --git a/app/src/main/java/com/zeapo/pwdstore/ui/onboarding/fragments/KeySelectionFragment.kt b/app/src/main/java/com/zeapo/pwdstore/ui/onboarding/fragments/KeySelectionFragment.kt
new file mode 100644
index 00000000..3dc03954
--- /dev/null
+++ b/app/src/main/java/com/zeapo/pwdstore/ui/onboarding/fragments/KeySelectionFragment.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved.
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+package com.zeapo.pwdstore.ui.onboarding.fragments
+
+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 androidx.lifecycle.lifecycleScope
+import com.zeapo.pwdstore.R
+import com.zeapo.pwdstore.crypto.GetKeyIdsActivity
+import com.zeapo.pwdstore.databinding.FragmentKeySelectionBinding
+import com.zeapo.pwdstore.utils.PasswordRepository
+import com.zeapo.pwdstore.utils.PreferenceKeys
+import com.zeapo.pwdstore.utils.commitChange
+import com.zeapo.pwdstore.utils.finish
+import com.zeapo.pwdstore.utils.sharedPrefs
+import com.zeapo.pwdstore.utils.viewBinding
+import java.io.File
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+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 binding by viewBinding(FragmentKeySelectionBinding::bind)
+
+ private val gpgKeySelectAction = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
+ if (result.resultCode == AppCompatActivity.RESULT_OK) {
+ result.data?.getStringArrayExtra(OpenPgpApi.EXTRA_KEY_IDS)?.let { keyIds ->
+ lifecycleScope.launch {
+ withContext(Dispatchers.IO) {
+ val gpgIdentifierFile = File(PasswordRepository.getRepositoryDirectory(), ".gpg-id")
+ 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)
+ ))
+ }
+ }
+ } else {
+ throw IllegalStateException("Failed to initialize repository state.")
+ }
+ finish()
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ binding.selectKey.setOnClickListener { gpgKeySelectAction.launch(Intent(requireContext(), GetKeyIdsActivity::class.java)) }
+ }
+
+ companion object {
+
+ fun newInstance() = KeySelectionFragment()
+ }
+}
diff --git a/app/src/main/java/com/zeapo/pwdstore/ui/onboarding/fragments/RepoLocationFragment.kt b/app/src/main/java/com/zeapo/pwdstore/ui/onboarding/fragments/RepoLocationFragment.kt
index 9e8a8f9d..d451fabe 100644
--- a/app/src/main/java/com/zeapo/pwdstore/ui/onboarding/fragments/RepoLocationFragment.kt
+++ b/app/src/main/java/com/zeapo/pwdstore/ui/onboarding/fragments/RepoLocationFragment.kt
@@ -14,6 +14,7 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.edit
import androidx.fragment.app.Fragment
import com.github.ajalt.timberkt.d
+import com.github.ajalt.timberkt.e
import com.github.michaelbull.result.onFailure
import com.github.michaelbull.result.runCatching
import com.google.android.material.dialog.MaterialAlertDialogBuilder
@@ -26,14 +27,14 @@ import com.zeapo.pwdstore.utils.finish
import com.zeapo.pwdstore.utils.getString
import com.zeapo.pwdstore.utils.isPermissionGranted
import com.zeapo.pwdstore.utils.listFilesRecursively
+import com.zeapo.pwdstore.utils.performTransactionWithBackStack
import com.zeapo.pwdstore.utils.sharedPrefs
import com.zeapo.pwdstore.utils.viewBinding
import java.io.File
class RepoLocationFragment : Fragment(R.layout.fragment_repo_location) {
- private val firstRunActivity by lazy { requireActivity() }
- private val settings by lazy { firstRunActivity.applicationContext.sharedPrefs }
+ private val settings by lazy(LazyThreadSafetyMode.NONE) { requireActivity().applicationContext.sharedPrefs }
private val binding by viewBinding(FragmentRepoLocationBinding::bind)
private val sortOrder: PasswordRepository.PasswordSortOrder
get() = PasswordRepository.PasswordSortOrder.getSortOrder(settings)
@@ -151,18 +152,14 @@ class RepoLocationFragment : Fragment(R.layout.fragment_repo_location) {
if (!PasswordRepository.isInitialized) {
PasswordRepository.initialize()
}
- if (File(localDir.absolutePath + "/.gpg-id").createNewFile()) {
- settings.edit { putBoolean(PreferenceKeys.REPOSITORY_INITIALIZED, true) }
- } else {
- throw IllegalStateException("Failed to initialize repository state.")
- }
+ parentFragmentManager.performTransactionWithBackStack(KeySelectionFragment.newInstance())
}.onFailure { e ->
- e.printStackTrace()
+ e(e)
if (!localDir.delete()) {
d { "Failed to delete local repository: $localDir" }
}
+ finish()
}
- finish()
}
private fun initializeRepositoryInfo() {
diff --git a/app/src/main/java/com/zeapo/pwdstore/ui/proxy/ProxySelectorActivity.kt b/app/src/main/java/com/zeapo/pwdstore/ui/proxy/ProxySelectorActivity.kt
new file mode 100644
index 00000000..29c4d53a
--- /dev/null
+++ b/app/src/main/java/com/zeapo/pwdstore/ui/proxy/ProxySelectorActivity.kt
@@ -0,0 +1,75 @@
+/*
+ * Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved.
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+package com.zeapo.pwdstore.ui.proxy
+
+import android.os.Bundle
+import android.os.Handler
+import android.os.Looper
+import android.util.Patterns
+import androidx.appcompat.app.AppCompatActivity
+import androidx.core.content.edit
+import androidx.core.os.postDelayed
+import androidx.core.widget.doOnTextChanged
+import com.zeapo.pwdstore.R
+import com.zeapo.pwdstore.databinding.ActivityProxySelectorBinding
+import com.zeapo.pwdstore.git.config.GitSettings
+import com.zeapo.pwdstore.utils.PreferenceKeys
+import com.zeapo.pwdstore.utils.ProxyUtils
+import com.zeapo.pwdstore.utils.getEncryptedProxyPrefs
+import com.zeapo.pwdstore.utils.getString
+import com.zeapo.pwdstore.utils.viewBinding
+
+private val IP_ADDRESS_REGEX = Patterns.IP_ADDRESS.toRegex()
+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() }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(binding.root)
+ 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")
+ }
+ proxyPassword.setText(proxyPrefs.getString(PreferenceKeys.PROXY_PASSWORD))
+ save.setOnClickListener { saveSettings() }
+ proxyHost.doOnTextChanged { text, _, _, _ ->
+ if (text != null) {
+ proxyHost.error = if (text.matches(IP_ADDRESS_REGEX) || text.matches(WEB_ADDRESS_REGEX)) {
+ null
+ } else {
+ getString(R.string.invalid_proxy_url)
+ }
+ }
+ }
+ }
+
+ }
+
+ 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
+ }
+ }
+ ProxyUtils.setDefaultProxy()
+ Handler(Looper.getMainLooper()).postDelayed(500) { finish() }
+ }
+}
diff --git a/app/src/main/java/com/zeapo/pwdstore/utils/AndroidExtensions.kt b/app/src/main/java/com/zeapo/pwdstore/utils/AndroidExtensions.kt
index 3ee1820d..408e9d5e 100644
--- a/app/src/main/java/com/zeapo/pwdstore/utils/AndroidExtensions.kt
+++ b/app/src/main/java/com/zeapo/pwdstore/utils/AndroidExtensions.kt
@@ -72,6 +72,12 @@ val Context.clipboard
fun Context.getEncryptedGitPrefs() = getEncryptedPrefs("git_operation")
/**
+ * Wrapper for [getEncryptedPrefs] to get the encrypted preference set for the HTTP
+ * proxy.
+ */
+fun Context.getEncryptedProxyPrefs() = getEncryptedPrefs("http_proxy")
+
+/**
* Get an instance of [EncryptedSharedPreferences] with the given [fileName]
*/
private fun Context.getEncryptedPrefs(fileName: String): SharedPreferences {
diff --git a/app/src/main/java/com/zeapo/pwdstore/utils/BiometricAuthenticator.kt b/app/src/main/java/com/zeapo/pwdstore/utils/BiometricAuthenticator.kt
index a1b4a279..12ba84f1 100644
--- a/app/src/main/java/com/zeapo/pwdstore/utils/BiometricAuthenticator.kt
+++ b/app/src/main/java/com/zeapo/pwdstore/utils/BiometricAuthenticator.kt
@@ -6,7 +6,6 @@ package com.zeapo.pwdstore.utils
import android.app.KeyguardManager
import androidx.annotation.StringRes
-import androidx.biometric.BiometricConstants
import androidx.biometric.BiometricManager
import androidx.biometric.BiometricManager.Authenticators
import androidx.biometric.BiometricPrompt
@@ -43,12 +42,12 @@ object BiometricAuthenticator {
super.onAuthenticationError(errorCode, errString)
tag(TAG).d { "BiometricAuthentication error: errorCode=$errorCode, msg=$errString" }
callback(when (errorCode) {
- BiometricConstants.ERROR_CANCELED, BiometricConstants.ERROR_USER_CANCELED,
- BiometricConstants.ERROR_NEGATIVE_BUTTON -> {
+ BiometricPrompt.ERROR_CANCELED, BiometricPrompt.ERROR_USER_CANCELED,
+ BiometricPrompt.ERROR_NEGATIVE_BUTTON -> {
Result.Cancelled
}
- BiometricConstants.ERROR_HW_NOT_PRESENT, BiometricConstants.ERROR_HW_UNAVAILABLE,
- BiometricConstants.ERROR_NO_BIOMETRICS, BiometricConstants.ERROR_NO_DEVICE_CREDENTIAL -> {
+ BiometricPrompt.ERROR_HW_NOT_PRESENT, BiometricPrompt.ERROR_HW_UNAVAILABLE,
+ BiometricPrompt.ERROR_NO_BIOMETRICS, BiometricPrompt.ERROR_NO_DEVICE_CREDENTIAL -> {
Result.HardwareUnavailableOrDisabled
}
else -> Result.Failure(errorCode, activity.getString(R.string.biometric_auth_error_reason, errString))
diff --git a/app/src/main/java/com/zeapo/pwdstore/utils/PasswordRepository.kt b/app/src/main/java/com/zeapo/pwdstore/utils/PasswordRepository.kt
index f1655b3b..6e090ba3 100644
--- a/app/src/main/java/com/zeapo/pwdstore/utils/PasswordRepository.kt
+++ b/app/src/main/java/com/zeapo/pwdstore/utils/PasswordRepository.kt
@@ -93,7 +93,7 @@ open class PasswordRepository protected constructor() {
companion object {
private var repository: Repository? = null
- private val settings by lazy { Application.instance.sharedPrefs }
+ private val settings by lazy(LazyThreadSafetyMode.NONE) { Application.instance.sharedPrefs }
private val filesDir
get() = Application.instance.filesDir
diff --git a/app/src/main/java/com/zeapo/pwdstore/utils/PreferenceKeys.kt b/app/src/main/java/com/zeapo/pwdstore/utils/PreferenceKeys.kt
index 1b2c7abb..5ec40639 100644
--- a/app/src/main/java/com/zeapo/pwdstore/utils/PreferenceKeys.kt
+++ b/app/src/main/java/com/zeapo/pwdstore/utils/PreferenceKeys.kt
@@ -80,4 +80,10 @@ object PreferenceKeys {
@Deprecated("To be used only in Migrations.kt")
const val USE_GENERATED_KEY = "use_generated_key"
+
+ const val PROXY_SETTINGS = "proxy_settings"
+ const val PROXY_HOST = "proxy_host"
+ const val PROXY_PORT = "proxy_port"
+ const val PROXY_USERNAME = "proxy_username"
+ const val PROXY_PASSWORD = "proxy_password"
}
diff --git a/app/src/main/java/com/zeapo/pwdstore/utils/ProxyUtils.kt b/app/src/main/java/com/zeapo/pwdstore/utils/ProxyUtils.kt
new file mode 100644
index 00000000..4bb3dfcb
--- /dev/null
+++ b/app/src/main/java/com/zeapo/pwdstore/utils/ProxyUtils.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved.
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+package com.zeapo.pwdstore.utils
+
+import com.zeapo.pwdstore.git.config.GitSettings
+import java.io.IOException
+import java.net.Authenticator
+import java.net.InetSocketAddress
+import java.net.PasswordAuthentication
+import java.net.Proxy
+import java.net.ProxySelector
+import java.net.SocketAddress
+import java.net.URI
+
+/**
+ * Utility class for [Proxy] handling.
+ */
+object ProxyUtils {
+
+ private const val HTTP_PROXY_USER_PROPERTY = "http.proxyUser"
+ private const val HTTP_PROXY_PASSWORD_PROPERTY = "http.proxyPassword"
+
+ /**
+ * Set the default [Proxy] and [Authenticator] for the app based on user provided settings.
+ */
+ fun setDefaultProxy() {
+ ProxySelector.setDefault(object : ProxySelector() {
+ override fun select(uri: URI?): MutableList<Proxy> {
+ val host = GitSettings.proxyHost
+ val port = GitSettings.proxyPort
+ return if (host == null || port == -1) {
+ mutableListOf(Proxy.NO_PROXY)
+ } else {
+ mutableListOf(Proxy(Proxy.Type.HTTP, InetSocketAddress.createUnresolved(host, port)))
+ }
+ }
+
+ override fun connectFailed(uri: URI?, sa: SocketAddress?, ioe: IOException?) {
+ if (uri == null || sa == null || ioe == null) {
+ throw IllegalArgumentException("Arguments can't be null.")
+ }
+ }
+ })
+ val user = GitSettings.proxyUsername ?: ""
+ val password = GitSettings.proxyPassword ?: ""
+ if (user.isEmpty() || password.isEmpty()) {
+ System.clearProperty(HTTP_PROXY_USER_PROPERTY)
+ System.clearProperty(HTTP_PROXY_PASSWORD_PROPERTY)
+ } else {
+ System.setProperty(HTTP_PROXY_USER_PROPERTY, user)
+ System.setProperty(HTTP_PROXY_PASSWORD_PROPERTY, password)
+ }
+ Authenticator.setDefault(object : Authenticator() {
+ override fun getPasswordAuthentication(): PasswordAuthentication? {
+ return if (requestorType == RequestorType.PROXY) {
+ PasswordAuthentication(user, password.toCharArray())
+ } else {
+ null
+ }
+ }
+ })
+ }
+}
diff --git a/app/src/main/res/layout/activity_oreo_autofill_publisher_changed.xml b/app/src/main/res/layout/activity_oreo_autofill_publisher_changed.xml
index 92d4e094..8a735e5b 100644
--- a/app/src/main/res/layout/activity_oreo_autofill_publisher_changed.xml
+++ b/app/src/main/res/layout/activity_oreo_autofill_publisher_changed.xml
@@ -6,57 +6,69 @@
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
- android:layout_width="280dp"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:animateLayoutChanges="true"
android:elevation="2dp"
+ android:paddingBottom="16dp"
android:scrollbars="vertical"
tools:context="com.zeapo.pwdstore.autofill.oreo.ui.AutofillPublisherChangedActivity">
<ImageView
android:id="@+id/cover"
- android:layout_width="0dp"
- android:layout_height="50dp"
+ android:layout_width="60dp"
+ android:layout_height="60dp"
android:background="@color/primary_color"
android:contentDescription="@string/app_name"
android:src="@mipmap/ic_launcher_foreground"
app:layout_constraintBottom_toTopOf="@id/warningSign"
- app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintEnd_toStartOf="@id/app_name"
+ app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintVertical_bias="0.0"
- app:layout_constraintVertical_chainStyle="packed" />
+ app:layout_constraintTop_toTopOf="parent" />
+
+ <TextView
+ android:id="@+id/app_name"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/app_name"
+ android:textSize="18sp"
+ android:textStyle="bold"
+ app:layout_constraintBottom_toBottomOf="@id/cover"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toEndOf="@id/cover"
+ app:layout_constraintTop_toTopOf="@id/cover" />
<ImageView
android:id="@+id/warningSign"
- android:layout_width="0dp"
- android:layout_height="50dp"
+ android:layout_width="40dp"
+ android:layout_height="40dp"
+ android:layout_marginStart="@dimen/activity_horizontal_margin"
android:contentDescription="@string/oreo_autofill_warning_publisher_warning_sign_description"
android:src="@drawable/ic_warning_red_24dp"
- app:layout_constraintBottom_toTopOf="@id/warningHeader"
- app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintTop_toTopOf="@id/warningHeader"
app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toBottomOf="@id/cover" />
+ app:layout_constraintBottom_toBottomOf="@id/warningHeader" />
<TextView
android:id="@+id/warningHeader"
android:layout_width="0dp"
android:layout_height="wrap_content"
- android:layout_marginStart="@dimen/activity_horizontal_margin"
+ android:layout_marginTop="@dimen/activity_vertical_margin"
+ android:layout_marginStart="8dp"
android:layout_marginEnd="@dimen/activity_horizontal_margin"
- android:layout_marginBottom="@dimen/activity_vertical_margin"
android:text="@string/oreo_autofill_warning_publisher_header"
android:textSize="12sp"
- app:layout_constraintBottom_toTopOf="@id/warningAppName"
app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toBottomOf="@id/warningSign" />
+ app:layout_constraintStart_toEndOf="@id/warningSign"
+ app:layout_constraintTop_toBottomOf="@id/cover" />
<TextView
android:id="@+id/warningAppName"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/activity_horizontal_margin"
+ android:layout_marginTop="@dimen/activity_vertical_margin"
android:layout_marginEnd="@dimen/activity_horizontal_margin"
android:gravity="center_horizontal"
android:textSize="12sp"
@@ -87,79 +99,77 @@
android:layout_marginStart="@dimen/activity_horizontal_margin"
android:layout_marginTop="@dimen/activity_vertical_margin"
android:layout_marginEnd="@dimen/activity_horizontal_margin"
- android:layout_marginBottom="@dimen/activity_vertical_margin"
android:text="@string/oreo_autofill_warning_publisher_footer"
android:textSize="12sp"
- app:layout_constraintBottom_toTopOf="@id/okButton"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/warningAppInstallDate" />
- <Button
+ <com.google.android.material.button.MaterialButton
android:id="@+id/okButton"
- style="@style/Widget.MaterialComponents.Button.OutlinedButton"
+ style="@style/AppTheme.OutlinedButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/activity_horizontal_margin"
+ android:layout_marginTop="@dimen/activity_vertical_margin"
android:layout_marginEnd="@dimen/activity_horizontal_margin"
+ android:minWidth="240dp"
android:text="@string/oreo_autofill_warning_publisher_changed_disable_autofill_button"
android:textSize="12sp"
- app:layout_constraintBottom_toTopOf="@id/advancedButton"
app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/warningAppFooter" />
- <Button
+ <com.google.android.material.button.MaterialButton
android:id="@+id/advancedButton"
- style="@style/Widget.MaterialComponents.Button.OutlinedButton"
+ style="@style/AppTheme.OutlinedButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/activity_horizontal_margin"
android:layout_marginEnd="@dimen/activity_horizontal_margin"
+ android:minWidth="240dp"
android:text="@string/oreo_autofill_warning_publisher_advanced_info_button"
android:textSize="12sp"
- app:layout_constraintBottom_toTopOf="@id/warningAppAdvancedInfo"
app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toBottomOf="@id/okButton" />
+ app:layout_constraintTop_toBottomOf="@id/okButton"
+ tools:visibility="visible" />
- <TextView
- android:id="@+id/warningAppAdvancedInfo"
- android:layout_width="0dp"
+ <com.google.android.material.button.MaterialButton
+ android:id="@+id/resetButton"
+ style="@style/AppTheme.OutlinedButton"
+ android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/activity_horizontal_margin"
android:layout_marginEnd="@dimen/activity_horizontal_margin"
- android:fontFamily="monospace"
- android:gravity="center_horizontal"
- android:textIsSelectable="true"
- android:textSize="10sp"
- android:visibility="invisible"
- app:layout_constraintBottom_toTopOf="@id/resetButton"
+ android:minWidth="240dp"
+ android:text="@string/oreo_autofill_warning_publisher_reenable_button"
+ android:textColor="?attr/colorOnSurface"
+ android:textSize="12sp"
+ android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/advancedButton"
- tools:ignore="SmallSp"
- tools:text="Package: com.example.banking\n\nHash:\n8P1sW0EPJcslw7UzRsiXL64w+O50Ed+RBICtay1g24M="
tools:visibility="visible" />
- <Button
- android:id="@+id/resetButton"
- style="@style/Widget.MaterialComponents.Button.TextButton"
- android:layout_width="wrap_content"
+ <TextView
+ android:id="@+id/warningAppAdvancedInfo"
+ android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/activity_horizontal_margin"
+ android:layout_marginTop="16dp"
android:layout_marginEnd="@dimen/activity_horizontal_margin"
- android:text="@string/oreo_autofill_warning_publisher_reenable_button"
+ android:fontFamily="monospace"
+ android:gravity="center_horizontal"
+ android:textIsSelectable="true"
android:textSize="10sp"
- android:visibility="invisible"
+ android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toBottomOf="@id/warningAppAdvancedInfo"
+ app:layout_constraintTop_toBottomOf="@id/resetButton"
tools:ignore="SmallSp"
+ tools:text="Package: com.example.banking\n\nHash:\n8P1sW0EPJcslw7UzRsiXL64w+O50Ed+RBICtay1g24M="
tools:visibility="visible" />
</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/app/src/main/res/layout/activity_proxy_selector.xml b/app/src/main/res/layout/activity_proxy_selector.xml
new file mode 100644
index 00000000..e732dfe0
--- /dev/null
+++ b/app/src/main/res/layout/activity_proxy_selector.xml
@@ -0,0 +1,106 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved.
+ ~ SPDX-License-Identifier: GPL-3.0-only
+ -->
+
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <com.google.android.material.textfield.TextInputLayout
+ android:id="@+id/proxy_host_input_layout"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="@dimen/activity_horizontal_margin"
+ android:layout_marginTop="@dimen/activity_vertical_margin"
+ android:layout_marginEnd="@dimen/activity_horizontal_margin"
+ android:hint="@string/proxy_hostname"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
+ tools:layout_editor_absoluteY="64dp">
+
+ <com.google.android.material.textfield.TextInputEditText
+ android:id="@+id/proxy_host"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:inputType="textUri"
+ android:nextFocusForward="@id/proxy_user" />
+
+ </com.google.android.material.textfield.TextInputLayout>
+
+ <com.google.android.material.textfield.TextInputLayout
+ android:id="@+id/proxy_user_input_layout"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="@dimen/activity_horizontal_margin"
+ android:layout_marginTop="@dimen/normal_margin"
+ android:layout_marginEnd="@dimen/normal_margin"
+ android:hint="@string/username"
+ app:layout_constraintEnd_toStartOf="@id/proxy_port_input_layout"
+ app:layout_constraintHorizontal_weight="0.65"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/proxy_host_input_layout">
+
+ <com.google.android.material.textfield.TextInputEditText
+ android:id="@+id/proxy_user"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:inputType="textWebEmailAddress"
+ android:nextFocusForward="@id/proxy_port" />
+
+ </com.google.android.material.textfield.TextInputLayout>
+
+ <com.google.android.material.textfield.TextInputLayout
+ android:id="@+id/proxy_port_input_layout"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="@dimen/normal_margin"
+ android:layout_marginTop="@dimen/normal_margin"
+ android:layout_marginEnd="@dimen/activity_horizontal_margin"
+ android:hint="@string/port"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintHorizontal_weight="0.35"
+ app:layout_constraintStart_toEndOf="@id/proxy_user_input_layout"
+ app:layout_constraintTop_toBottomOf="@id/proxy_host_input_layout">
+
+ <com.google.android.material.textfield.TextInputEditText
+ android:id="@+id/proxy_port"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:inputType="number"
+ android:nextFocusForward="@id/proxy_password" />
+ </com.google.android.material.textfield.TextInputLayout>
+
+ <com.google.android.material.textfield.TextInputLayout
+ android:id="@+id/proxy_password_input_layout"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="@dimen/activity_horizontal_margin"
+ android:layout_marginTop="@dimen/normal_margin"
+ android:layout_marginEnd="@dimen/activity_horizontal_margin"
+ android:hint="@string/password"
+ app:endIconMode="password_toggle"
+ app:layout_constraintStart_toStartOf="@id/proxy_user_input_layout"
+ app:layout_constraintTop_toBottomOf="@id/proxy_user_input_layout">
+
+ <com.google.android.material.textfield.TextInputEditText
+ android:id="@+id/proxy_password"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:inputType="textPassword" />
+ </com.google.android.material.textfield.TextInputLayout>
+
+ <com.google.android.material.button.MaterialButton
+ android:id="@+id/save"
+ style="@style/AppTheme.OutlinedButton"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/normal_margin"
+ android:layout_marginEnd="@dimen/activity_horizontal_margin"
+ android:text="@string/crypto_save"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/proxy_password_input_layout" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/app/src/main/res/layout/fragment_key_selection.xml b/app/src/main/res/layout/fragment_key_selection.xml
new file mode 100644
index 00000000..9a391276
--- /dev/null
+++ b/app/src/main/res/layout/fragment_key_selection.xml
@@ -0,0 +1,81 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved.
+ ~ SPDX-License-Identifier: GPL-3.0-only
+ -->
+
+<androidx.constraintlayout.widget.ConstraintLayout 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="?attr/colorPrimary"
+ android:orientation="vertical">
+
+ <androidx.appcompat.widget.AppCompatImageView
+ android:id="@+id/app_icon"
+ android:layout_width="64dp"
+ android:layout_height="64dp"
+ android:layout_marginStart="32dp"
+ android:layout_marginTop="100dp"
+ 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="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_centerHorizontal="true"
+ android:layout_marginStart="16dp"
+ android:text="@string/app_name"
+ android:textAppearance="@style/TextAppearance.MaterialComponents.Headline5"
+ android:textColor="@color/color_control_normal"
+ android:textStyle="bold"
+ app:layout_constraintBottom_toBottomOf="@id/app_icon"
+ app:layout_constraintStart_toEndOf="@id/app_icon"
+ app:layout_constraintTop_toTopOf="@+id/app_icon" />
+
+ <TextView
+ android:id="@+id/gpg_key"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="48dp"
+ android:text="@string/select_gpg_key_title"
+ android:textAppearance="@style/TextAppearance.MaterialComponents.Headline4"
+ android:textColor="@color/color_control_normal"
+ android:textStyle="bold"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintHorizontal_bias="0"
+ app:layout_constraintStart_toStartOf="@id/app_icon"
+ app:layout_constraintTop_toBottomOf="@id/app_icon" />
+
+ <TextView
+ android:id="@+id/gpg_key_text"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="48dp"
+ android:layout_marginEnd="16dp"
+ android:text="@string/select_gpg_key_message"
+ android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
+ android:textColor="@color/color_control_normal"
+ android:textStyle="bold"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="@id/gpg_key"
+ app:layout_constraintTop_toBottomOf="@id/gpg_key" />
+
+ <com.google.android.material.button.MaterialButton
+ android:id="@+id/select_key"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="16dp"
+ android:layout_marginTop="48dp"
+ android:layout_marginEnd="16dp"
+ android:maxWidth="300dp"
+ android:minWidth="100dp"
+ android:text="@string/gpg_key_select"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/gpg_key_text" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/app/src/main/res/values-v23/colors.xml b/app/src/main/res/values-v23/colors.xml
deleted file mode 100644
index 7c3be280..00000000
--- a/app/src/main/res/values-v23/colors.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?><!--
- ~ Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved.
- ~ SPDX-License-Identifier: GPL-3.0-only
- -->
-
-<resources>
- <color name="navigation_bar_color">#000000</color>
-</resources>
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
index bea2e960..b090c761 100644
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -15,7 +15,7 @@
<!-- Theme variables -->
<color name="color_control_normal">@color/primary_text_color</color>
<color name="list_multiselect_background">#668eacbb</color>
- <color name="navigation_bar_color">@color/primary_color</color>
+ <color name="navigation_bar_color">#000000</color>
<color name="status_bar_color">@color/primary_color</color>
<color name="ripple_color">#aa003e5b</color>
<color name="button_color">#44003e5b</color>
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 57c32126..cc9f1f24 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -100,10 +100,10 @@
<!-- DECRYPT Layout -->
<string name="action_search">Search</string>
- <string name="password">Password:</string>
- <string name="otp">OTP:</string>
+ <string name="password">Password</string>
+ <string name="otp">OTP</string>
<string name="extra_content">Extra content:</string>
- <string name="username">Username:</string>
+ <string name="username">Username</string>
<string name="edit_password">Edit password</string>
<string name="copy_password">Copy password</string>
<string name="share_as_plaintext">Share as plaintext</string>
@@ -400,9 +400,18 @@
<string name="select_repo_type_text">Select if you want to create a local repo or clone a remote repo.</string>
<string name="clone_remote_repo">Clone Remote Repo</string>
<string name="create_local_repo">Create Local Repo</string>
+ <string name="select_gpg_key_title">Select\nGPG\nKey</string>
+ <string name="select_gpg_key_message">Select a GPG key to initialize your store with</string>
+ <string name="gpg_key_select">Select key</string>
<!-- SSH port validation -->
<string name="ssh_scheme_needed_title">Potentially incorrect URL</string>
<string name="ssh_scheme_needed_message">It appears that your URL contains a custom port, but does not specify the ssh:// scheme.\nThis can cause the port to be considered a part of your path. Press OK here to fix the URL.</string>
+ <!-- Proxy configuration activity -->
+ <string name="proxy_hostname">Proxy hostname</string>
+ <string name="port">Port</string>
+ <string name="pref_proxy_settings">HTTP(S) proxy settings</string>
+ <string name="invalid_proxy_url">Invalid URL</string>
+
</resources>
diff --git a/app/src/main/res/xml/preference.xml b/app/src/main/res/xml/preference.xml
index 1d82df18..58a173c5 100644
--- a/app/src/main/res/xml/preference.xml
+++ b/app/src/main/res/xml/preference.xml
@@ -49,6 +49,9 @@
app:key="git_server_info"
app:title="@string/pref_edit_server_info" />
<Preference
+ app:key="proxy_settings"
+ app:title="@string/pref_proxy_settings" />
+ <Preference
app:key="git_config"
app:title="@string/pref_edit_git_config" />
<Preference