summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHarsh Shandilya <msfjarvis@gmail.com>2020-06-12 14:58:15 +0000
committerGitHub <noreply@github.com>2020-06-12 20:28:15 +0530
commitd8231e112afb501c43041a6f839ab8285f400f77 (patch)
tree6785a8d0c6a70a3017ce5d62fe6b8234d5564f07
parentbf33fb2c88a208931340201e95a524b274d70b31 (diff)
Break down PGP Activity into focused sections (#776)
-rw-r--r--CHANGELOG.md2
-rw-r--r--app/build.gradle2
-rw-r--r--app/src/main/AndroidManifest.xml21
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/ClipboardService.kt30
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/LaunchActivity.kt11
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/PasswordFragment.kt13
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/PasswordStore.kt62
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/SelectFolderActivity.kt19
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/UserPreference.kt69
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/autofill/AutofillPreferenceActivity.kt31
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/autofill/oreo/ui/AutofillSaveActivity.kt19
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/crypto/BasePgpActivity.kt270
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/crypto/DecryptActivity.kt202
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/crypto/GetKeyIdsActivity.kt89
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/crypto/PasswordCreationActivity.kt296
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/crypto/PgpActivity.kt788
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/git/GitAsyncTask.kt7
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/git/GitServerConfigActivity.kt2
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/sshkeygen/ShowSshKeyFragment.kt6
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/ui/dialogs/PasswordGeneratorDialogFragment.kt2
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/ui/dialogs/XkPasswordGeneratorDialogFragment.kt2
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/utils/ClipboardUtils.kt28
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/utils/Extensions.kt49
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/utils/PasswordItem.kt4
-rw-r--r--app/src/main/java/mozilla/components/lib/publicsuffixlist/ext/ByteArray.kt2
-rw-r--r--app/src/main/res/layout/decrypt_layout.xml12
-rw-r--r--app/src/main/res/layout/password_creation_activity.xml (renamed from app/src/main/res/layout/encrypt_layout.xml)14
-rw-r--r--app/src/main/res/menu/context_pass.xml6
-rw-r--r--app/src/main/res/menu/pgp_handler_new_password.xml6
-rw-r--r--app/src/main/res/values-ar/strings.xml9
-rw-r--r--app/src/main/res/values-cs/strings.xml15
-rw-r--r--app/src/main/res/values-de/strings.xml12
-rw-r--r--app/src/main/res/values-es/strings.xml13
-rw-r--r--app/src/main/res/values-fr/strings.xml14
-rw-r--r--app/src/main/res/values-ja/strings.xml10
-rw-r--r--app/src/main/res/values-night/colors.xml4
-rw-r--r--app/src/main/res/values-ru/strings.xml16
-rw-r--r--app/src/main/res/values-zh-rCN/strings.xml10
-rw-r--r--app/src/main/res/values-zh-rTW/strings.xml10
-rw-r--r--app/src/main/res/values/arrays.xml15
-rw-r--r--app/src/main/res/values/colors.xml4
-rw-r--r--app/src/main/res/values/dimens.xml1
-rw-r--r--app/src/main/res/values/strings.xml32
-rw-r--r--dependencies.gradle1
44 files changed, 1067 insertions, 1163 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4c787b11..317e7a95 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,12 +10,14 @@ All notable changes to this project will be documented in this file.
- Completely revamped decypted password view
- Add support for better, more secure Keyex's and MACs with a brand new SSH backend
- Allow manually marking domains for subdomain-level association. This will allow you to keep separate passwords for `site1.example.com` and `site2.example.com` and have them show as such in Autofill.
+- Provide better messages for OpenKeychain errors
### Changed
- **BREAKING**: Remove support for HOTP/TOTP secrets - Please use FIDO keys or a dedicated app like [Aegis](https://github.com/beemdevelopment/Aegis) or [andOTP](https://github.com/andOTP/andOTP)
- Reduce Autofill false positives on username fields by removing "name" from list of heuristic terms
- Reduced app size
- Improve IME experience with server config screen
+- Removed edit password option from long-press menu.
## [1.8.1] - 2020-05-24
diff --git a/app/build.gradle b/app/build.gradle
index 6175dfcd..186972a0 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -4,7 +4,6 @@
*/
plugins {
id 'kotlin-android'
- id 'kotlin-android-extensions'
}
final def keystorePropertiesFile = rootProject.file 'keystore.properties'
@@ -83,7 +82,6 @@ dependencies {
implementation deps.androidx.lifecycle_common
implementation deps.androidx.lifecycle_livedata_ktx
implementation deps.androidx.lifecycle_viewmodel_ktx
- implementation deps.androidx.local_broadcast_manager
implementation deps.androidx.material
implementation deps.androidx.preference
implementation deps.androidx.recycler_view
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 8463c352..b0c3193d 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -64,6 +64,22 @@
android:label="@string/action_settings"
android:parentActivityName=".PasswordStore" />
+ <activity
+ android:name=".crypto.PasswordCreationActivity"
+ android:label="@string/new_password_title"
+ android:parentActivityName=".PasswordStore"
+ android:windowSoftInputMode="adjustResize" />
+
+ <activity
+ android:name=".crypto.DecryptActivity"
+ android:parentActivityName=".PasswordStore"
+ android:windowSoftInputMode="adjustResize" />
+
+ <activity
+ android:name=".crypto.GetKeyIdsActivity"
+ android:parentActivityName=".PasswordStore"
+ android:theme="@style/NoBackgroundTheme" />
+
<service
android:name=".autofill.AutofillService"
android:enabled="@bool/enable_accessibility_autofill"
@@ -98,11 +114,6 @@
<activity android:name=".autofill.AutofillPreferenceActivity" />
- <activity
- android:name=".crypto.PgpActivity"
- android:configChanges="orientation|screenSize"
- android:parentActivityName=".PasswordStore"
- android:windowSoftInputMode="adjustResize" />
<activity android:name=".SelectFolderActivity" />
<activity
android:name=".sshkeygen.SshKeyGenActivity"
diff --git a/app/src/main/java/com/zeapo/pwdstore/ClipboardService.kt b/app/src/main/java/com/zeapo/pwdstore/ClipboardService.kt
index 5349d6c3..ceb84020 100644
--- a/app/src/main/java/com/zeapo/pwdstore/ClipboardService.kt
+++ b/app/src/main/java/com/zeapo/pwdstore/ClipboardService.kt
@@ -8,17 +8,16 @@ import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.app.Service
-import android.content.ClipboardManager
+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
import androidx.core.content.getSystemService
-import androidx.localbroadcastmanager.content.LocalBroadcastManager
import androidx.preference.PreferenceManager
import com.github.ajalt.timberkt.d
-import com.zeapo.pwdstore.utils.ClipboardUtils
+import com.zeapo.pwdstore.utils.clipboard
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
@@ -60,7 +59,6 @@ class ClipboardService : Service() {
startTimer(time)
}
withContext(Dispatchers.Main) {
- emitBroadcast()
clearClipboard()
stopForeground(true)
stopSelf()
@@ -85,11 +83,21 @@ class ClipboardService : Service() {
private fun clearClipboard() {
val deepClear = settings.getBoolean("clear_clipboard_20x", false)
- val clipboardManager = getSystemService<ClipboardManager>()
+ val clipboard = clipboard
- if (clipboardManager is ClipboardManager) {
+ if (clipboard != null) {
scope.launch {
- ClipboardUtils.clearClipboard(clipboardManager, deepClear)
+ d { "Clearing the clipboard" }
+ val clip = ClipData.newPlainText("pgp_handler_result_pm", "")
+ clipboard.setPrimaryClip(clip)
+ if (deepClear) {
+ withContext(Dispatchers.IO) {
+ repeat(20) {
+ val count = (it * 500).toString()
+ clipboard.setPrimaryClip(ClipData.newPlainText(count, count))
+ }
+ }
+ }
}
} else {
d { "Cannot get clipboard manager service" }
@@ -105,12 +113,6 @@ class ClipboardService : Service() {
}
}
- private fun emitBroadcast() {
- val localBroadcastManager = LocalBroadcastManager.getInstance(this)
- val clearIntent = Intent(ACTION_CLEAR)
- localBroadcastManager.sendBroadcast(clearIntent)
- }
-
private fun createNotification() {
createNotificationChannel()
val clearIntent = Intent(this, ClipboardService::class.java)
@@ -151,7 +153,7 @@ class ClipboardService : Service() {
companion object {
private const val ACTION_CLEAR = "ACTION_CLEAR_CLIPBOARD"
- private const val ACTION_START = "ACTION_START_CLIPBOARD_TIMER"
+ const val ACTION_START = "ACTION_START_CLIPBOARD_TIMER"
private const val CHANNEL_ID = "NotificationService"
}
}
diff --git a/app/src/main/java/com/zeapo/pwdstore/LaunchActivity.kt b/app/src/main/java/com/zeapo/pwdstore/LaunchActivity.kt
index b5902199..b452f521 100644
--- a/app/src/main/java/com/zeapo/pwdstore/LaunchActivity.kt
+++ b/app/src/main/java/com/zeapo/pwdstore/LaunchActivity.kt
@@ -10,7 +10,7 @@ import android.os.Handler
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.edit
import androidx.preference.PreferenceManager
-import com.zeapo.pwdstore.crypto.PgpActivity
+import com.zeapo.pwdstore.crypto.DecryptActivity
import com.zeapo.pwdstore.utils.BiometricAuthenticator
class LaunchActivity : AppCompatActivity() {
@@ -39,13 +39,12 @@ class LaunchActivity : AppCompatActivity() {
}
private fun startTargetActivity(noAuth: Boolean) {
- if (intent?.getStringExtra("OPERATION") == "DECRYPT") {
- val decryptIntent = Intent(this, PgpActivity::class.java)
+ if (intent.action == ACTION_DECRYPT_PASS) {
+ val decryptIntent = Intent(this, DecryptActivity::class.java)
decryptIntent.putExtra("NAME", intent.getStringExtra("NAME"))
decryptIntent.putExtra("FILE_PATH", intent.getStringExtra("FILE_PATH"))
decryptIntent.putExtra("REPO_PATH", intent.getStringExtra("REPO_PATH"))
decryptIntent.putExtra("LAST_CHANGED_TIMESTAMP", intent.getLongExtra("LAST_CHANGED_TIMESTAMP", 0L))
- decryptIntent.putExtra("OPERATION", "DECRYPT")
startActivity(decryptIntent)
} else {
startActivity(Intent(this, PasswordStore::class.java))
@@ -53,4 +52,8 @@ class LaunchActivity : AppCompatActivity() {
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out)
Handler().postDelayed({ finish() }, if (noAuth) 0L else 500L)
}
+
+ companion object {
+ const val ACTION_DECRYPT_PASS = "DECRYPT_PASS"
+ }
}
diff --git a/app/src/main/java/com/zeapo/pwdstore/PasswordFragment.kt b/app/src/main/java/com/zeapo/pwdstore/PasswordFragment.kt
index 03f2cfe1..24a1b7cb 100644
--- a/app/src/main/java/com/zeapo/pwdstore/PasswordFragment.kt
+++ b/app/src/main/java/com/zeapo/pwdstore/PasswordFragment.kt
@@ -155,11 +155,7 @@ class PasswordFragment : Fragment(R.layout.password_recycler_view) {
// Called each time the action mode is shown. Always called after onCreateActionMode, but
// may be called multiple times if the mode is invalidated.
override fun onPrepareActionMode(mode: ActionMode, menu: Menu): Boolean {
- menu.findItem(R.id.menu_edit_password).isVisible =
- recyclerAdapter.getSelectedItems(requireContext())
- .map { it.type == PasswordItem.TYPE_PASSWORD }
- .singleOrNull() == true
- return true // Return false if nothing is done
+ return true
}
// Called when the user selects a contextual menu item
@@ -174,13 +170,6 @@ class PasswordFragment : Fragment(R.layout.password_recycler_view) {
mode.finish() // Action picked, so close the CAB
return true
}
- R.id.menu_edit_password -> {
- requireStore().editPassword(
- recyclerAdapter.getSelectedItems(requireContext()).first()
- )
- mode.finish()
- return true
- }
R.id.menu_move_password -> {
requireStore().movePasswords(recyclerAdapter.getSelectedItems(requireContext()))
return false
diff --git a/app/src/main/java/com/zeapo/pwdstore/PasswordStore.kt b/app/src/main/java/com/zeapo/pwdstore/PasswordStore.kt
index 00cfbf9a..ab9e3944 100644
--- a/app/src/main/java/com/zeapo/pwdstore/PasswordStore.kt
+++ b/app/src/main/java/com/zeapo/pwdstore/PasswordStore.kt
@@ -46,11 +46,10 @@ import com.google.android.material.snackbar.Snackbar
import com.zeapo.pwdstore.autofill.oreo.AutofillMatcher
import com.zeapo.pwdstore.autofill.oreo.BrowserAutofillSupportLevel
import com.zeapo.pwdstore.autofill.oreo.getInstalledBrowsersWithAutofillSupportLevel
-import com.zeapo.pwdstore.crypto.PgpActivity
-import com.zeapo.pwdstore.crypto.PgpActivity.Companion.getLongName
+import com.zeapo.pwdstore.crypto.BasePgpActivity.Companion.getLongName
+import com.zeapo.pwdstore.crypto.DecryptActivity
+import com.zeapo.pwdstore.crypto.PasswordCreationActivity
import com.zeapo.pwdstore.git.BaseGitActivity
-import com.zeapo.pwdstore.git.GitAsyncTask
-import com.zeapo.pwdstore.git.GitOperation
import com.zeapo.pwdstore.git.GitOperationActivity
import com.zeapo.pwdstore.git.GitServerConfigActivity
import com.zeapo.pwdstore.git.config.ConnectionMode
@@ -65,6 +64,7 @@ import com.zeapo.pwdstore.utils.PasswordRepository.Companion.getRepositoryDirect
import com.zeapo.pwdstore.utils.PasswordRepository.Companion.initialize
import com.zeapo.pwdstore.utils.PasswordRepository.Companion.isInitialized
import com.zeapo.pwdstore.utils.PasswordRepository.PasswordSortOrder.Companion.getSortOrder
+import com.zeapo.pwdstore.utils.commitChange
import com.zeapo.pwdstore.utils.listFilesRecursively
import org.eclipse.jgit.api.Git
import org.eclipse.jgit.api.errors.GitAPIException
@@ -73,7 +73,7 @@ import java.io.File
import java.lang.Character.UnicodeBlock
import java.util.Stack
-class PasswordStore : AppCompatActivity() {
+class PasswordStore : AppCompatActivity(R.layout.activity_pwdstore) {
private lateinit var activity: PasswordStore
private lateinit var searchItem: MenuItem
@@ -123,7 +123,6 @@ class PasswordStore : AppCompatActivity() {
savedInstance = null
}
super.onCreate(savedInstance)
- setContentView(R.layout.activity_pwdstore)
// If user is eligible for Oreo autofill, prompt them to switch.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O &&
@@ -487,15 +486,16 @@ class PasswordStore : AppCompatActivity() {
}
fun decryptPassword(item: PasswordItem) {
- val decryptIntent = Intent(this, PgpActivity::class.java)
+ val decryptIntent = Intent(this, DecryptActivity::class.java)
val authDecryptIntent = Intent(this, LaunchActivity::class.java)
for (intent in arrayOf(decryptIntent, authDecryptIntent)) {
intent.putExtra("NAME", item.toString())
intent.putExtra("FILE_PATH", item.file.absolutePath)
intent.putExtra("REPO_PATH", getRepositoryDirectory(applicationContext).absolutePath)
intent.putExtra("LAST_CHANGED_TIMESTAMP", getLastChangedTimestamp(item.file.absolutePath))
- intent.putExtra("OPERATION", "DECRYPT")
}
+ // Needs an action to be a shortcut intent
+ authDecryptIntent.action = LaunchActivity.ACTION_DECRYPT_PASS
// Adds shortcut
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
@@ -503,7 +503,7 @@ class PasswordStore : AppCompatActivity() {
.setShortLabel(item.toString())
.setLongLabel(item.fullPathToParent + item.toString())
.setIcon(Icon.createWithResource(this, R.mipmap.ic_launcher))
- .setIntent(authDecryptIntent.setAction("DECRYPT_PASS")) // Needs action
+ .setIntent(authDecryptIntent)
.build()
val shortcuts = shortcutManager!!.dynamicShortcuts
if (shortcuts.size >= shortcutManager!!.maxShortcutCountPerActivity && shortcuts.size > 0) {
@@ -517,16 +517,6 @@ class PasswordStore : AppCompatActivity() {
startActivityForResult(decryptIntent, REQUEST_CODE_DECRYPT_AND_VERIFY)
}
- fun editPassword(item: PasswordItem) {
- val intent = Intent(this, PgpActivity::class.java)
- intent.putExtra("NAME", item.toString())
- intent.putExtra("FILE_PATH", item.file.absolutePath)
- intent.putExtra("PARENT_PATH", item.file.parentFile!!.absolutePath)
- intent.putExtra("REPO_PATH", getRepositoryDirectory(applicationContext).absolutePath)
- intent.putExtra("OPERATION", "EDIT")
- startActivityForResult(intent, REQUEST_CODE_EDIT)
- }
-
private fun validateState(): Boolean {
if (!isInitialized) {
MaterialAlertDialogBuilder(this)
@@ -553,10 +543,9 @@ class PasswordStore : AppCompatActivity() {
if (!validateState()) return
val currentDir = currentDir
tag(TAG).i { "Adding file to : ${currentDir.absolutePath}" }
- val intent = Intent(this, PgpActivity::class.java)
+ val intent = Intent(this, PasswordCreationActivity::class.java)
intent.putExtra("FILE_PATH", currentDir.absolutePath)
intent.putExtra("REPO_PATH", getRepositoryDirectory(applicationContext).absolutePath)
- intent.putExtra("OPERATION", "ENCRYPT")
startActivityForResult(intent, REQUEST_CODE_ENCRYPT)
}
@@ -626,10 +615,6 @@ class PasswordStore : AppCompatActivity() {
private val currentDir: File
get() = plist?.currentDir ?: getRepositoryDirectory(applicationContext)
- private fun commitChange(message: String) {
- commitChange(this, message)
- }
-
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (resultCode == Activity.RESULT_OK) {
when (requestCode) {
@@ -650,11 +635,6 @@ class PasswordStore : AppCompatActivity() {
data!!.extras!!.getString("LONG_NAME")))
refreshPasswordList()
}
- REQUEST_CODE_EDIT -> {
- commitChange(resources.getString(R.string.git_commit_edit_text,
- data!!.extras!!.getString("LONG_NAME")))
- refreshPasswordList()
- }
BaseGitActivity.REQUEST_INIT, NEW_REPO_BUTTON -> initializeRepositoryInfo()
BaseGitActivity.REQUEST_SYNC, BaseGitActivity.REQUEST_PULL -> resetPasswordList()
HOME -> checkLocalRepository()
@@ -821,7 +801,6 @@ class PasswordStore : AppCompatActivity() {
companion object {
const val REQUEST_CODE_ENCRYPT = 9911
const val REQUEST_CODE_DECRYPT_AND_VERIFY = 9913
- const val REQUEST_CODE_EDIT = 9916
const val REQUEST_CODE_SELECT_FOLDER = 9917
const val REQUEST_ARG_PATH = "PATH"
private val TAG = PasswordStore::class.java.name
@@ -836,26 +815,5 @@ class PasswordStore : AppCompatActivity() {
}
private const val PREFERENCE_SEEN_AUTOFILL_ONBOARDING = "seen_autofill_onboarding"
-
- fun commitChange(activity: Activity, message: String, finishWithResultOnEnd: Intent? = null) {
- if (!PasswordRepository.isGitRepo()) {
- if (finishWithResultOnEnd != null) {
- activity.setResult(Activity.RESULT_OK, finishWithResultOnEnd)
- activity.finish()
- }
- return
- }
- object : GitOperation(getRepositoryDirectory(activity), activity) {
- override fun execute() {
- tag(TAG).d { "Committing with message $message" }
- val git = Git(repository)
- val tasks = GitAsyncTask(activity, true, this, finishWithResultOnEnd)
- tasks.execute(
- git.add().addFilepattern("."),
- git.commit().setAll(true).setMessage(message)
- )
- }
- }.execute()
- }
}
}
diff --git a/app/src/main/java/com/zeapo/pwdstore/SelectFolderActivity.kt b/app/src/main/java/com/zeapo/pwdstore/SelectFolderActivity.kt
index 4d8475df..568f86f3 100644
--- a/app/src/main/java/com/zeapo/pwdstore/SelectFolderActivity.kt
+++ b/app/src/main/java/com/zeapo/pwdstore/SelectFolderActivity.kt
@@ -10,21 +10,16 @@ import android.view.Menu
import android.view.MenuItem
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.FragmentManager
+import androidx.fragment.app.commit
import com.zeapo.pwdstore.utils.PasswordRepository
-// TODO more work needed, this is just an extraction from PgpHandler
-class SelectFolderActivity : AppCompatActivity() {
+class SelectFolderActivity : AppCompatActivity(R.layout.select_folder_layout) {
private lateinit var passwordList: SelectFolderFragment
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- setContentView(R.layout.select_folder_layout)
-
- val fragmentManager = supportFragmentManager
- val fragmentTransaction = fragmentManager.beginTransaction()
-
passwordList = SelectFolderFragment()
val args = Bundle()
args.putString(PasswordStore.REQUEST_ARG_PATH, PasswordRepository.getRepositoryDirectory(applicationContext).absolutePath)
@@ -33,10 +28,11 @@ class SelectFolderActivity : AppCompatActivity() {
supportActionBar?.show()
- fragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE)
+ supportFragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE)
- fragmentTransaction.replace(R.id.pgp_handler_linearlayout, passwordList, "PasswordsList")
- fragmentTransaction.commit()
+ supportFragmentManager.commit {
+ replace(R.id.pgp_handler_linearlayout, passwordList, "PasswordsList")
+ }
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
@@ -45,8 +41,7 @@ class SelectFolderActivity : AppCompatActivity() {
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
- val id = item.itemId
- when (id) {
+ when (item.itemId) {
android.R.id.home -> {
setResult(Activity.RESULT_CANCELED)
finish()
diff --git a/app/src/main/java/com/zeapo/pwdstore/UserPreference.kt b/app/src/main/java/com/zeapo/pwdstore/UserPreference.kt
index 96f23724..04967c82 100644
--- a/app/src/main/java/com/zeapo/pwdstore/UserPreference.kt
+++ b/app/src/main/java/com/zeapo/pwdstore/UserPreference.kt
@@ -21,6 +21,7 @@ import android.text.TextUtils
import android.view.MenuItem
import android.view.accessibility.AccessibilityManager
import android.widget.Toast
+import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.AppCompatTextView
import androidx.biometric.BiometricManager
@@ -42,7 +43,8 @@ import com.google.android.material.snackbar.Snackbar
import com.zeapo.pwdstore.autofill.AutofillPreferenceActivity
import com.zeapo.pwdstore.autofill.oreo.BrowserAutofillSupportLevel
import com.zeapo.pwdstore.autofill.oreo.getInstalledBrowsersWithAutofillSupportLevel
-import com.zeapo.pwdstore.crypto.PgpActivity
+import com.zeapo.pwdstore.crypto.BasePgpActivity
+import com.zeapo.pwdstore.crypto.GetKeyIdsActivity
import com.zeapo.pwdstore.git.GitConfigActivity
import com.zeapo.pwdstore.git.GitServerConfigActivity
import com.zeapo.pwdstore.pwgenxkpwd.XkpwdDictionary
@@ -74,12 +76,13 @@ class UserPreference : AppCompatActivity() {
private lateinit var autofillDependencies: List<Preference>
private lateinit var oreoAutofillDependencies: List<Preference>
private lateinit var callingActivity: UserPreference
+ private lateinit var sharedPreferences: SharedPreferences
private lateinit var encryptedPreferences: SharedPreferences
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
callingActivity = requireActivity() as UserPreference
val context = requireContext()
- val sharedPreferences = preferenceManager.sharedPreferences
+ sharedPreferences = preferenceManager.sharedPreferences
encryptedPreferences = requireActivity().applicationContext.getEncryptedPrefs("git_operation")
addPreferencesFromResource(R.xml.preference)
@@ -146,15 +149,6 @@ class UserPreference : AppCompatActivity() {
viewSshKeyPreference?.isVisible = sharedPreferences.getBoolean("use_generated_key", false)
deleteRepoPreference?.isVisible = !sharedPreferences.getBoolean("git_external", false)
clearClipboard20xPreference?.isVisible = sharedPreferences.getString("general_show_time", "45")?.toInt() != 0
- val selectedKeys = (sharedPreferences.getStringSet("openpgp_key_ids_set", null)
- ?: HashSet()).toTypedArray()
- keyPreference?.summary = if (selectedKeys.isEmpty()) {
- this.resources.getString(R.string.pref_no_key_selected)
- } else {
- selectedKeys.joinToString(separator = ";") { s ->
- OpenPgpUtils.convertKeyIdToHex(java.lang.Long.valueOf(s))
- }
- }
openkeystoreIdPreference?.isVisible = sharedPreferences.getString("ssh_openkeystore_keyid", null)?.isNotEmpty()
?: false
@@ -163,16 +157,21 @@ class UserPreference : AppCompatActivity() {
appVersionPreference?.summary = "Version: ${BuildConfig.VERSION_NAME}"
- keyPreference?.onPreferenceClickListener = ClickListener {
- val providerPackageName = requireNotNull(sharedPreferences.getString("openpgp_provider_list", ""))
- if (providerPackageName.isEmpty()) {
- Snackbar.make(requireView(), resources.getString(R.string.provider_toast_text), Snackbar.LENGTH_LONG).show()
- false
- } else {
- val intent = Intent(callingActivity, PgpActivity::class.java)
- intent.putExtra("OPERATION", "GET_KEY_ID")
- startActivityForResult(intent, IMPORT_PGP_KEY)
- true
+ keyPreference?.let { pref ->
+ updateKeyIDsSummary(pref)
+ pref.onPreferenceClickListener = ClickListener {
+ val providerPackageName = requireNotNull(sharedPreferences.getString("openpgp_provider_list", ""))
+ if (providerPackageName.isEmpty()) {
+ Snackbar.make(requireView(), resources.getString(R.string.provider_toast_text), Snackbar.LENGTH_LONG).show()
+ false
+ } else {
+ val intent = Intent(callingActivity, GetKeyIdsActivity::class.java)
+ val keySelectResult = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
+ updateKeyIDsSummary(pref)
+ }
+ keySelectResult.launch(intent)
+ true
+ }
}
}
@@ -366,13 +365,25 @@ class UserPreference : AppCompatActivity() {
}
}
+ private fun updateKeyIDsSummary(preference: Preference) {
+ val selectedKeys = (sharedPreferences.getStringSet("openpgp_key_ids_set", null)
+ ?: HashSet()).toTypedArray()
+ preference.summary = if (selectedKeys.isEmpty()) {
+ resources.getString(R.string.pref_no_key_selected)
+ } else {
+ selectedKeys.joinToString(separator = ";") { s ->
+ OpenPgpUtils.convertKeyIdToHex(s.toLong())
+ }
+ }
+ }
+
private fun updateXkPasswdPrefsVisibility(newValue: Any?, prefIsCustomDict: CheckBoxPreference?, prefCustomDictPicker: Preference?) {
when (newValue as String) {
- PgpActivity.KEY_PWGEN_TYPE_CLASSIC -> {
+ BasePgpActivity.KEY_PWGEN_TYPE_CLASSIC -> {
prefIsCustomDict?.isVisible = false
prefCustomDictPicker?.isVisible = false
}
- PgpActivity.KEY_PWGEN_TYPE_XKPASSWD -> {
+ BasePgpActivity.KEY_PWGEN_TYPE_XKPASSWD -> {
prefIsCustomDict?.isVisible = true
prefCustomDictPicker?.isVisible = true
}
@@ -653,8 +664,6 @@ class UserPreference : AppCompatActivity() {
.show()
}
}
- EDIT_GIT_INFO -> {
- }
SELECT_GIT_DIRECTORY -> {
val uri = data.data
@@ -792,12 +801,10 @@ class UserPreference : AppCompatActivity() {
companion object {
private const val IMPORT_SSH_KEY = 1
- private const val IMPORT_PGP_KEY = 2
- private const val EDIT_GIT_INFO = 3
- private const val SELECT_GIT_DIRECTORY = 4
- private const val EXPORT_PASSWORDS = 5
- private const val EDIT_GIT_CONFIG = 6
- private const val SET_CUSTOM_XKPWD_DICT = 7
+ private const val SELECT_GIT_DIRECTORY = 2
+ private const val EXPORT_PASSWORDS = 3
+ private const val EDIT_GIT_CONFIG = 4
+ private const val SET_CUSTOM_XKPWD_DICT = 5
private const val TAG = "UserPreference"
/**
diff --git a/app/src/main/java/com/zeapo/pwdstore/autofill/AutofillPreferenceActivity.kt b/app/src/main/java/com/zeapo/pwdstore/autofill/AutofillPreferenceActivity.kt
index 3553d431..860d8459 100644
--- a/app/src/main/java/com/zeapo/pwdstore/autofill/AutofillPreferenceActivity.kt
+++ b/app/src/main/java/com/zeapo/pwdstore/autofill/AutofillPreferenceActivity.kt
@@ -16,31 +16,32 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.SearchView
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager
-import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.floatingactionbutton.FloatingActionButton
import com.zeapo.pwdstore.R
+import com.zeapo.pwdstore.databinding.AutofillRecyclerViewBinding
+import com.zeapo.pwdstore.utils.viewBinding
import me.zhanghai.android.fastscroll.FastScrollerBuilder
import java.lang.ref.WeakReference
import java.util.ArrayList
class AutofillPreferenceActivity : AppCompatActivity() {
+ private val binding by viewBinding(AutofillRecyclerViewBinding::inflate)
internal var recyclerAdapter: AutofillRecyclerAdapter? = null // let fragment have access
- private var recyclerView: RecyclerView? = null
private var pm: PackageManager? = null
private var recreate: Boolean = false // flag for action on up press; origin autofill dialog? different act
public override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
-
- setContentView(R.layout.autofill_recycler_view)
- recyclerView = findViewById(R.id.autofill_recycler)
+ setContentView(binding.root)
val layoutManager = LinearLayoutManager(this)
- recyclerView!!.layoutManager = layoutManager
- recyclerView!!.addItemDecoration(DividerItemDecoration(this, DividerItemDecoration.VERTICAL))
- FastScrollerBuilder(recyclerView!!).build()
+ with(binding) {
+ autofillRecycler.layoutManager = layoutManager
+ autofillRecycler.addItemDecoration(DividerItemDecoration(this@AutofillPreferenceActivity, DividerItemDecoration.VERTICAL))
+ FastScrollerBuilder(autofillRecycler).build()
+ }
pm = packageManager
@@ -105,7 +106,7 @@ class AutofillPreferenceActivity : AppCompatActivity() {
companion object {
private class PopulateTask(activity: AutofillPreferenceActivity) : AsyncTask<Void, Void, Void>() {
- val weakReference = WeakReference<AutofillPreferenceActivity>(activity)
+ val weakReference = WeakReference(activity)
override fun onPreExecute() {
weakReference.get()?.apply {
@@ -140,11 +141,13 @@ class AutofillPreferenceActivity : AppCompatActivity() {
override fun onPostExecute(ignored: Void?) {
weakReference.get()?.apply {
runOnUiThread {
- findViewById<View>(R.id.progress_bar).visibility = View.GONE
- recyclerView!!.adapter = recyclerAdapter
- val extras = intent.extras
- if (extras != null) {
- recyclerView!!.scrollToPosition(recyclerAdapter!!.getPosition(extras.getString("appName")!!))
+ with(binding) {
+ progressBar.visibility = View.GONE
+ autofillRecycler.adapter = recyclerAdapter
+ val extras = intent.extras
+ if (extras != null) {
+ autofillRecycler.scrollToPosition(recyclerAdapter!!.getPosition(extras.getString("appName")!!))
+ }
}
}
}
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 209892a7..fad13ec8 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
@@ -23,8 +23,9 @@ import com.zeapo.pwdstore.autofill.oreo.AutofillPreferences
import com.zeapo.pwdstore.autofill.oreo.Credentials
import com.zeapo.pwdstore.autofill.oreo.FillableForm
import com.zeapo.pwdstore.autofill.oreo.FormOrigin
-import com.zeapo.pwdstore.crypto.PgpActivity
+import com.zeapo.pwdstore.crypto.PasswordCreationActivity
import com.zeapo.pwdstore.utils.PasswordRepository
+import com.zeapo.pwdstore.utils.commitChange
import java.io.File
@RequiresApi(Build.VERSION_CODES.O)
@@ -97,15 +98,14 @@ class AutofillSaveActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val repo = PasswordRepository.getRepositoryDirectory(applicationContext)
- val saveIntent = Intent(this, PgpActivity::class.java).apply {
+ val saveIntent = Intent(this, PasswordCreationActivity::class.java).apply {
putExtras(
bundleOf(
"REPO_PATH" to repo.absolutePath,
"FILE_PATH" to repo.resolve(intent.getStringExtra(EXTRA_FOLDER_NAME)!!).absolutePath,
- "OPERATION" to "ENCRYPT",
- "SUGGESTED_NAME" to intent.getStringExtra(EXTRA_NAME),
- "SUGGESTED_PASS" to intent.getStringExtra(EXTRA_PASSWORD),
- "GENERATE_PASSWORD" to intent.getBooleanExtra(EXTRA_GENERATE_PASSWORD, false)
+ PasswordCreationActivity.EXTRA_FILE_NAME to intent.getStringExtra(EXTRA_NAME),
+ PasswordCreationActivity.EXTRA_PASSWORD to intent.getStringExtra(EXTRA_PASSWORD),
+ PasswordCreationActivity.EXTRA_GENERATE_PASSWORD to intent.getBooleanExtra(EXTRA_GENERATE_PASSWORD, false)
)
)
}
@@ -144,10 +144,9 @@ class AutofillSaveActivity : Activity() {
// Password was extracted from a form, there is nothing to fill.
Intent()
}
- // PgpActivity delegates committing the added file to PasswordStore. Since PasswordStore
- // is not involved in an AutofillScenario, we have to commit the file ourselves.
- PasswordStore.commitChange(
- this,
+ // PasswordCreationActivity delegates committing the added file to PasswordStore. Since
+ // PasswordStore is not involved in an AutofillScenario, we have to commit the file ourselves.
+ commitChange(
getString(R.string.git_commit_add_text, longName),
finishWithResultOnEnd = result
)
diff --git a/app/src/main/java/com/zeapo/pwdstore/crypto/BasePgpActivity.kt b/app/src/main/java/com/zeapo/pwdstore/crypto/BasePgpActivity.kt
new file mode 100644
index 00000000..5206a15f
--- /dev/null
+++ b/app/src/main/java/com/zeapo/pwdstore/crypto/BasePgpActivity.kt
@@ -0,0 +1,270 @@
+/*
+ * Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved.
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+package com.zeapo.pwdstore.crypto
+
+import android.app.PendingIntent
+import android.content.ClipData
+import android.content.Intent
+import android.content.IntentSender
+import android.content.SharedPreferences
+import android.os.Build
+import android.os.Bundle
+import android.text.format.DateUtils
+import android.view.WindowManager
+import android.widget.Toast
+import androidx.activity.result.ActivityResultLauncher
+import androidx.annotation.CallSuper
+import androidx.appcompat.app.AppCompatActivity
+import androidx.preference.PreferenceManager
+import com.github.ajalt.timberkt.Timber.tag
+import com.github.ajalt.timberkt.e
+import com.github.ajalt.timberkt.i
+import com.google.android.material.snackbar.Snackbar
+import com.zeapo.pwdstore.ClipboardService
+import com.zeapo.pwdstore.R
+import com.zeapo.pwdstore.UserPreference
+import com.zeapo.pwdstore.utils.clipboard
+import com.zeapo.pwdstore.utils.snackbar
+import me.msfjarvis.openpgpktx.util.OpenPgpApi
+import me.msfjarvis.openpgpktx.util.OpenPgpServiceConnection
+import org.openintents.openpgp.IOpenPgpService2
+import org.openintents.openpgp.OpenPgpError
+import java.io.File
+
+@Suppress("Registered")
+open class BasePgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBound {
+
+ /**
+ * Full path to the repository
+ */
+ val repoPath: String by lazy { intent.getStringExtra("REPO_PATH") }
+
+ /**
+ * Full path to the password file being worked on
+ */
+ val fullPath: String by lazy { 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 }
+
+ /**
+ * Get the timestamp for when this file was last modified.
+ */
+ val lastChangedString: CharSequence by lazy {
+ getLastChangedString(
+ intent.getLongExtra(
+ "LAST_CHANGED_TIMESTAMP",
+ -1L
+ )
+ )
+ }
+
+ /**
+ * [SharedPreferences] instance used by subclasses to persist settings
+ */
+ val settings: SharedPreferences by lazy { PreferenceManager.getDefaultSharedPreferences(this) }
+
+ /**
+ * Read-only field for getting the list of OpenPGP key IDs that we have access to.
+ */
+ var keyIDs = emptySet<String>()
+ private set
+
+ /**
+ * Handle to the [OpenPgpApi] instance that is used by subclasses to interface with OpenKeychain.
+ */
+ private var serviceConnection: OpenPgpServiceConnection? = null
+ var api: OpenPgpApi? = null
+
+ /**
+ * [onCreate] sets the window up with the right flags to prevent auth leaks through screenshots
+ * or recent apps screen and fills in [keyIDs] from [settings]
+ */
+ @CallSuper
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ window.setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE)
+ tag(TAG)
+
+ keyIDs = settings.getStringSet("openpgp_key_ids_set", null) ?: emptySet()
+ }
+
+ /**
+ * [onDestroy] handles unbinding from the OpenPgp service linked with [serviceConnection]. This
+ * is annotated with [CallSuper] because it's critical to unbind the service to ensure we're not
+ * leaking things.
+ */
+ @CallSuper
+ override fun onDestroy() {
+ super.onDestroy()
+ serviceConnection?.unbindFromService()
+ }
+
+ /**
+ * Sets up [api] once the service is bound. Downstream consumers must call super this to
+ * initialize [api]
+ */
+ @CallSuper
+ override fun onBound(service: IOpenPgpService2) {
+ api = OpenPgpApi(this, service)
+ }
+
+ /**
+ * Mandatory error handling from [OpenPgpServiceConnection.OnBound]. All subclasses must handle
+ * their own errors, and hence this class simply logs and rethrows. Subclasses Must NOT call super.
+ */
+ override fun onError(e: Exception) {
+ e(e) { "Callers must handle their own exceptions" }
+ throw e
+ }
+
+ /**
+ * Method for subclasses to initiate binding with [OpenPgpServiceConnection]. The design choices
+ * here are a bit dubious at first glance. We require passing a [ActivityResultLauncher] because
+ * it lets us react to having a OpenPgp provider selected without relying on the now deprecated
+ * [startActivityForResult].
+ */
+ fun bindToOpenKeychain(onBoundListener: OpenPgpServiceConnection.OnBound, activityResult: ActivityResultLauncher<Intent>) {
+ val providerPackageName = settings.getString("openpgp_provider_list", "")
+ if (providerPackageName.isNullOrEmpty()) {
+ Toast.makeText(this, resources.getString(R.string.provider_toast_text), Toast.LENGTH_LONG).show()
+ activityResult.launch(Intent(this, UserPreference::class.java))
+ } else {
+ serviceConnection = OpenPgpServiceConnection(this, providerPackageName, onBoundListener)
+ serviceConnection?.bindToService()
+ }
+ }
+
+ /**
+ * Handle the case where OpenKeychain returns that it needs to interact with the user
+ *
+ * @param result The intent returned by OpenKeychain
+ */
+ fun getUserInteractionRequestIntent(result: Intent): IntentSender {
+ i { "RESULT_CODE_USER_INTERACTION_REQUIRED" }
+ return (result.getParcelableExtra(OpenPgpApi.RESULT_INTENT) as PendingIntent).intentSender
+ }
+
+ /**
+ * Gets a relative string describing when this shape was last changed
+ * (e.g. "one hour ago")
+ */
+ private fun getLastChangedString(timeStamp: Long): CharSequence {
+ if (timeStamp < 0) {
+ throw RuntimeException()
+ }
+
+ return DateUtils.getRelativeTimeSpanString(this, timeStamp, true)
+ }
+ /**
+ * Base handling of OpenKeychain errors based on the error contained in [result]. Subclasses
+ * can use this when they want to default to sane error handling.
+ */
+ fun handleError(result: Intent) {
+ val error: OpenPgpError? = result.getParcelableExtra(OpenPgpApi.RESULT_ERROR)
+ if (error != null) {
+ when (error.errorId) {
+ OpenPgpError.NO_OR_WRONG_PASSPHRASE -> {
+ snackbar(message = getString(R.string.openpgp_error_wrong_passphrase))
+ }
+ OpenPgpError.NO_USER_IDS -> {
+ snackbar(message = getString(R.string.openpgp_error_no_user_ids))
+ }
+ else -> {
+ snackbar(message = getString(R.string.openpgp_error_unknown, error.message))
+ e { "onError getErrorId: ${error.errorId}" }
+ e { "onError getMessage: ${error.message}" }
+ }
+ }
+ }
+ }
+
+ /**
+ * Copies provided [text] to the clipboard. Shows a [Snackbar] which can be disabled by passing
+ * [showSnackbar] as false.
+ */
+ fun copyTextToClipboard(text: String?, showSnackbar: Boolean = true) {
+ val clipboard = clipboard ?: return
+ val clip = ClipData.newPlainText("pgp_handler_result_pm", text)
+ clipboard.setPrimaryClip(clip)
+ if (showSnackbar) {
+ snackbar(message = resources.getString(R.string.clipboard_copied_text))
+ }
+ }
+
+ /**
+ * Copies a provided [password] string to the clipboard. This wraps [copyTextToClipboard] to
+ * hide the default [Snackbar] and starts off an instance of [ClipboardService] to provide a
+ * way of clearing the clipboard.
+ */
+ fun copyPasswordToClipboard(password: String?) {
+ copyTextToClipboard(password, showSnackbar = false)
+
+ var clearAfter = 45
+ try {
+ clearAfter = (settings.getString("general_show_time", "45") ?: "45").toInt()
+ } catch (_: NumberFormatException) {
+ }
+
+ if (clearAfter != 0) {
+ val service = Intent(this, ClipboardService::class.java).apply {
+ action = ClipboardService.ACTION_START
+ }
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ startForegroundService(service)
+ } else {
+ startService(service)
+ }
+ snackbar(message = resources.getString(R.string.clipboard_password_toast_text, clearAfter))
+ } else {
+ snackbar(message = resources.getString(R.string.clipboard_password_no_clear_toast_text))
+ }
+ }
+
+ companion object {
+ private const val TAG = "APS/BasePgpActivity"
+ const val KEY_PWGEN_TYPE_CLASSIC = "classic"
+ const val KEY_PWGEN_TYPE_XKPASSWD = "xkpasswd"
+
+ /**
+ * Gets the relative path to the repository
+ */
+ fun getRelativePath(fullPath: String, repositoryPath: String): String =
+ fullPath.replace(repositoryPath, "").replace("/+".toRegex(), "/")
+
+ /**
+ * Gets the Parent path, relative to the repository
+ */
+ fun getParentPath(fullPath: String, repositoryPath: String): String {
+ val relativePath = getRelativePath(fullPath, repositoryPath)
+ val index = relativePath.lastIndexOf("/")
+ return "/${relativePath.substring(startIndex = 0, endIndex = index + 1)}/".replace("/+".toRegex(), "/")
+ }
+
+ /**
+ * /path/to/store/social/facebook.gpg -> social/facebook
+ */
+ @JvmStatic
+ fun getLongName(fullPath: String, repositoryPath: String, basename: String): String {
+ var relativePath = getRelativePath(fullPath, repositoryPath)
+ return if (relativePath.isNotEmpty() && relativePath != "/") {
+ // remove preceding '/'
+ relativePath = relativePath.substring(1)
+ if (relativePath.endsWith('/')) {
+ relativePath + basename
+ } else {
+ "$relativePath/$basename"
+ }
+ } else {
+ basename
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/com/zeapo/pwdstore/crypto/DecryptActivity.kt b/app/src/main/java/com/zeapo/pwdstore/crypto/DecryptActivity.kt
new file mode 100644
index 00000000..b7d7adcd
--- /dev/null
+++ b/app/src/main/java/com/zeapo/pwdstore/crypto/DecryptActivity.kt
@@ -0,0 +1,202 @@
+/*
+ * Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved.
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+package com.zeapo.pwdstore.crypto
+
+import android.content.Intent
+import android.graphics.Typeface
+import android.os.Bundle
+import android.text.method.PasswordTransformationMethod
+import android.view.Menu
+import android.view.MenuItem
+import android.view.View
+import androidx.activity.result.IntentSenderRequest
+import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult
+import androidx.activity.result.contract.ActivityResultContracts.StartIntentSenderForResult
+import androidx.lifecycle.lifecycleScope
+import com.github.ajalt.timberkt.e
+import com.zeapo.pwdstore.PasswordEntry
+import com.zeapo.pwdstore.R
+import com.zeapo.pwdstore.databinding.DecryptLayoutBinding
+import com.zeapo.pwdstore.utils.viewBinding
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import me.msfjarvis.openpgpktx.util.OpenPgpApi
+import me.msfjarvis.openpgpktx.util.OpenPgpServiceConnection
+import org.openintents.openpgp.IOpenPgpService2
+import java.io.ByteArrayOutputStream
+import java.io.File
+
+class DecryptActivity : BasePgpActivity(), OpenPgpServiceConnection.OnBound {
+ private val binding by viewBinding(DecryptLayoutBinding::inflate)
+
+ private val relativeParentPath by lazy { getParentPath(fullPath, repoPath) }
+ private var passwordEntry: PasswordEntry? = null
+
+ private val userInteractionRequiredResult = registerForActivityResult(StartIntentSenderForResult()) { result ->
+ if (result.data == null) {
+ setResult(RESULT_CANCELED, null)
+ finish()
+ return@registerForActivityResult
+ }
+
+ when (result.resultCode) {
+ RESULT_OK -> decryptAndVerify(result.data)
+ RESULT_CANCELED -> {
+ setResult(RESULT_CANCELED, result.data)
+ finish()
+ }
+ }
+ }
+
+ private val openKeychainResult = registerForActivityResult(StartActivityForResult()) {
+ decryptAndVerify()
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ bindToOpenKeychain(this, openKeychainResult)
+ title = name
+ with(binding) {
+ setContentView(root)
+ passwordCategory.text = relativeParentPath
+ passwordFile.text = name
+ passwordFile.setOnLongClickListener {
+ copyTextToClipboard(name)
+ true
+ }
+ try {
+ passwordLastChanged.text = resources.getString(R.string.last_changed, lastChangedString)
+ } catch (e: RuntimeException) {
+ passwordLastChanged.visibility = View.GONE
+ }
+ }
+ }
+
+ override fun onCreateOptionsMenu(menu: Menu?): Boolean {
+ menuInflater.inflate(R.menu.pgp_handler, menu)
+ return true
+ }
+
+ override fun onOptionsItemSelected(item: MenuItem): Boolean {
+ when (item.itemId) {
+ android.R.id.home -> onBackPressed()
+ R.id.edit_password -> editPassword()
+ R.id.share_password_as_plaintext -> shareAsPlaintext()
+ R.id.copy_password -> copyPasswordToClipboard(passwordEntry?.password)
+ }
+ return super.onOptionsItemSelected(item)
+ }
+
+ override fun onBound(service: IOpenPgpService2) {
+ super.onBound(service)
+ decryptAndVerify()
+ }
+
+ override fun onError(e: Exception) {
+ e(e)
+ }
+
+ /**
+ * Edit the current password and hide all the fields populated by encrypted data so that when
+ * the result triggers they can be repopulated with new data.
+ */
+ private fun editPassword() {
+ val intent = Intent(this, PasswordCreationActivity::class.java)
+ intent.putExtra("FILE_PATH", relativeParentPath)
+ intent.putExtra("REPO_PATH", repoPath)
+ intent.putExtra(PasswordCreationActivity.EXTRA_FILE_NAME, name)
+ intent.putExtra(PasswordCreationActivity.EXTRA_PASSWORD, passwordEntry?.password)
+ intent.putExtra(PasswordCreationActivity.EXTRA_EXTRA_CONTENT, passwordEntry?.extraContent)
+ intent.putExtra(PasswordCreationActivity.EXTRA_EDITING, true)
+ startActivity(intent)
+ finish()
+ }
+
+ private fun shareAsPlaintext() {
+ val sendIntent = Intent().apply {
+ action = Intent.ACTION_SEND
+ putExtra(Intent.EXTRA_TEXT, passwordEntry?.password)
+ type = "text/plain"
+ }
+ // Always show a picker to give the user a chance to cancel
+ startActivity(Intent.createChooser(sendIntent, resources.getText(R.string.send_plaintext_password_to)))
+ }
+
+ private fun decryptAndVerify(receivedIntent: Intent? = null) {
+ if (api == null) {
+ bindToOpenKeychain(this, openKeychainResult)
+ return
+ }
+ val data = receivedIntent ?: Intent()
+ data.action = OpenPgpApi.ACTION_DECRYPT_VERIFY
+
+ val inputStream = File(fullPath).inputStream()
+ val outputStream = ByteArrayOutputStream()
+
+ lifecycleScope.launch(Dispatchers.IO) {
+ api?.executeApiAsync(data, inputStream, outputStream) { result ->
+ when (result?.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR)) {
+ OpenPgpApi.RESULT_CODE_SUCCESS -> {
+ try {
+ val showPassword = settings.getBoolean("show_password", true)
+ val showExtraContent = settings.getBoolean("show_extra_content", true)
+ val monoTypeface = Typeface.createFromAsset(assets, "fonts/sourcecodepro.ttf")
+ val entry = PasswordEntry(outputStream)
+
+ passwordEntry = entry
+
+ with(binding) {
+ if (entry.password.isEmpty()) {
+ passwordTextContainer.visibility = View.GONE
+ } else {
+ passwordTextContainer.visibility = View.VISIBLE
+ passwordText.typeface = monoTypeface
+ passwordText.setText(entry.password)
+ if (!showPassword) {
+ passwordText.transformationMethod = PasswordTransformationMethod.getInstance()
+ }
+ passwordTextContainer.setOnClickListener { copyPasswordToClipboard(entry.password) }
+ passwordText.setOnClickListener { copyPasswordToClipboard(entry.password) }
+ }
+
+ if (entry.hasExtraContent()) {
+ extraContentContainer.visibility = View.VISIBLE
+ extraContent.typeface = monoTypeface
+ extraContent.setText(entry.extraContentWithoutUsername)
+ if (!showExtraContent) {
+ extraContent.transformationMethod = PasswordTransformationMethod.getInstance()
+ }
+ extraContentContainer.setOnClickListener { copyTextToClipboard(entry.extraContentWithoutUsername) }
+ extraContent.setOnClickListener { copyTextToClipboard(entry.extraContentWithoutUsername) }
+
+ if (entry.hasUsername()) {
+ usernameText.typeface = monoTypeface
+ usernameText.setText(entry.username)
+ usernameTextContainer.setEndIconOnClickListener { copyTextToClipboard(entry.username) }
+ usernameTextContainer.visibility = View.VISIBLE
+ } else {
+ usernameTextContainer.visibility = View.GONE
+ }
+ }
+ }
+
+ if (settings.getBoolean("copy_on_decrypt", true)) {
+ copyPasswordToClipboard(entry.password)
+ }
+ } catch (e: Exception) {
+ e(e)
+ }
+ }
+ OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED -> {
+ val sender = getUserInteractionRequestIntent(result)
+ userInteractionRequiredResult.launch(IntentSenderRequest.Builder(sender).build())
+ }
+ OpenPgpApi.RESULT_CODE_ERROR -> handleError(result)
+ }
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/com/zeapo/pwdstore/crypto/GetKeyIdsActivity.kt b/app/src/main/java/com/zeapo/pwdstore/crypto/GetKeyIdsActivity.kt
new file mode 100644
index 00000000..94d5b68c
--- /dev/null
+++ b/app/src/main/java/com/zeapo/pwdstore/crypto/GetKeyIdsActivity.kt
@@ -0,0 +1,89 @@
+/*
+ * Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved.
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+package com.zeapo.pwdstore.crypto
+
+import android.content.Intent
+import android.os.Bundle
+import androidx.activity.result.IntentSenderRequest
+import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult
+import androidx.activity.result.contract.ActivityResultContracts.StartIntentSenderForResult
+import androidx.core.content.edit
+import androidx.lifecycle.lifecycleScope
+import com.github.ajalt.timberkt.Timber
+import com.github.ajalt.timberkt.e
+import com.zeapo.pwdstore.utils.snackbar
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import me.msfjarvis.openpgpktx.util.OpenPgpApi
+import org.openintents.openpgp.IOpenPgpService2
+
+class GetKeyIdsActivity : BasePgpActivity() {
+
+ private val getKeyIds = registerForActivityResult(StartActivityForResult()) { getKeyIds() }
+
+ private val userInteractionRequiredResult = registerForActivityResult(StartIntentSenderForResult()) { result ->
+ if (result.data == null) {
+ setResult(RESULT_CANCELED, null)
+ finish()
+ return@registerForActivityResult
+ }
+
+ when (result.resultCode) {
+ RESULT_OK -> getKeyIds(result.data)
+ RESULT_CANCELED -> {
+ setResult(RESULT_CANCELED, result.data)
+ finish()
+ }
+ }
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ bindToOpenKeychain(this, getKeyIds)
+ }
+
+ override fun onBound(service: IOpenPgpService2) {
+ super.onBound(service)
+ getKeyIds()
+ }
+
+ override fun onError(e: Exception) {
+ e(e)
+ }
+
+ /**
+ * Get the Key ids from OpenKeychain
+ */
+ private fun getKeyIds(receivedIntent: Intent? = null) {
+ val data = receivedIntent ?: Intent()
+ data.action = OpenPgpApi.ACTION_GET_KEY_IDS
+ lifecycleScope.launch(Dispatchers.IO) {
+ api?.executeApiAsync(data, null, null) { result ->
+ when (result?.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR)) {
+ OpenPgpApi.RESULT_CODE_SUCCESS -> {
+ try {
+ val ids = result.getLongArrayExtra(OpenPgpApi.RESULT_KEY_IDS)
+ ?: LongArray(0)
+ val keys = ids.map { it.toString() }.toSet()
+ // use Long
+ settings.edit { putStringSet("openpgp_key_ids_set", keys) }
+ snackbar(message = "PGP keys selected")
+ setResult(RESULT_OK)
+ finish()
+ } catch (e: Exception) {
+ Timber.e(e) { "An Exception occurred" }
+ }
+ }
+ OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED -> {
+ val sender = getUserInteractionRequestIntent(result)
+ userInteractionRequiredResult.launch(IntentSenderRequest.Builder(sender).build())
+ }
+ OpenPgpApi.RESULT_CODE_ERROR -> handleError(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
new file mode 100644
index 00000000..7216a506
--- /dev/null
+++ b/app/src/main/java/com/zeapo/pwdstore/crypto/PasswordCreationActivity.kt
@@ -0,0 +1,296 @@
+/*
+ * Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved.
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+package com.zeapo.pwdstore.crypto
+
+import android.content.Intent
+import android.os.Bundle
+import android.text.InputType
+import android.view.Menu
+import android.view.MenuItem
+import android.view.View
+import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult
+import androidx.core.widget.doOnTextChanged
+import androidx.lifecycle.lifecycleScope
+import com.github.ajalt.timberkt.e
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
+import com.zeapo.pwdstore.PasswordEntry
+import com.zeapo.pwdstore.R
+import com.zeapo.pwdstore.autofill.oreo.AutofillPreferences
+import com.zeapo.pwdstore.autofill.oreo.DirectoryStructure
+import com.zeapo.pwdstore.databinding.PasswordCreationActivityBinding
+import com.zeapo.pwdstore.ui.dialogs.PasswordGeneratorDialogFragment
+import com.zeapo.pwdstore.ui.dialogs.XkPasswordGeneratorDialogFragment
+import com.zeapo.pwdstore.utils.PasswordRepository
+import com.zeapo.pwdstore.utils.commitChange
+import com.zeapo.pwdstore.utils.snackbar
+import com.zeapo.pwdstore.utils.viewBinding
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import org.eclipse.jgit.api.Git
+import me.msfjarvis.openpgpktx.util.OpenPgpApi
+import me.msfjarvis.openpgpktx.util.OpenPgpServiceConnection
+import java.io.ByteArrayInputStream
+import java.io.ByteArrayOutputStream
+import java.io.File
+import java.io.IOException
+
+class PasswordCreationActivity : BasePgpActivity(), OpenPgpServiceConnection.OnBound {
+
+ 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 doNothing = registerForActivityResult(StartActivityForResult()) {}
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ bindToOpenKeychain(this, doNothing)
+ title = if (intent.getBooleanExtra(EXTRA_EDITING, false))
+ getString(R.string.edit_password)
+ else
+ getString(R.string.new_password_title)
+ with(binding) {
+ setContentView(root)
+ generatePassword.setOnClickListener { generatePassword() }
+
+ category.apply {
+ if (suggestedName != null || suggestedPass != null || shouldGeneratePassword) {
+ isEnabled = true
+ } else {
+ setBackgroundColor(getColor(android.R.color.transparent))
+ }
+ val path = getRelativePath(fullPath, repoPath)
+ // Keep empty path field visible if it is editable.
+ if (path.isEmpty() && !isEnabled)
+ visibility = View.GONE
+ else
+ setText(path)
+ }
+ suggestedName?.let { filename.setText(it) }
+ // Allow the user to quickly switch between storing the username as the filename or
+ // in the encrypted extras. This only makes sense if the directory structure is
+ // FileBased.
+ if (suggestedName == null &&
+ AutofillPreferences.directoryStructure(this@PasswordCreationActivity) ==
+ DirectoryStructure.FileBased
+ ) {
+ encryptUsername.apply {
+ visibility = View.VISIBLE
+ setOnClickListener {
+ if (isChecked) {
+ // User wants to enable username encryption, so we add it to the
+ // encrypted extras as the first line.
+ val username = filename.text.toString()
+ val extras = "username:$username\n${extraContent.text}"
+
+ filename.setText("")
+ extraContent.setText(extras)
+ } else {
+ // User wants to disable username encryption, so we extract the
+ // username from the encrypted extras and use it as the filename.
+ val entry = PasswordEntry("PASSWORD\n${extraContent.text}")
+ val username = entry.username
+
+ // username should not be null here by the logic in
+ // updateEncryptUsernameState, but it could still happen due to
+ // input lag.
+ if (username != null) {
+ filename.setText(username)
+ extraContent.setText(entry.extraContentWithoutUsername)
+ }
+ }
+ updateEncryptUsernameState()
+ }
+ }
+ listOf(filename, extraContent).forEach {
+ it.doOnTextChanged { _, _, _, _ -> updateEncryptUsernameState() }
+ }
+ }
+ suggestedPass?.let {
+ password.setText(it)
+ password.inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_PASSWORD
+ }
+ suggestedExtra?.let { extraContent.setText(it) }
+ if (shouldGeneratePassword) {
+ generatePassword()
+ password.inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_PASSWORD
+ }
+ }
+ }
+
+ override fun onCreateOptionsMenu(menu: Menu?): Boolean {
+ menuInflater.inflate(R.menu.pgp_handler_new_password, menu)
+ return true
+ }
+
+ override fun onOptionsItemSelected(item: MenuItem): Boolean {
+ when (item.itemId) {
+ android.R.id.home, R.id.cancel_password_add -> {
+ setResult(RESULT_CANCELED)
+ finish()
+ }
+ R.id.save_password -> encrypt()
+ R.id.save_and_copy_password -> encrypt(copy = true)
+ else -> return super.onOptionsItemSelected(item)
+ }
+ return true
+ }
+
+ private fun generatePassword() {
+ when (settings.getString("pref_key_pwgen_type", KEY_PWGEN_TYPE_CLASSIC)) {
+ KEY_PWGEN_TYPE_CLASSIC -> PasswordGeneratorDialogFragment()
+ .show(supportFragmentManager, "generator")
+ KEY_PWGEN_TYPE_XKPASSWD -> XkPasswordGeneratorDialogFragment()
+ .show(supportFragmentManager, "xkpwgenerator")
+ }
+ }
+
+ private fun updateEncryptUsernameState() = with(binding) {
+ encryptUsername.apply {
+ if (visibility != View.VISIBLE)
+ return@with
+ val hasUsernameInFileName = filename.text.toString().isNotBlank()
+ // Use PasswordEntry to parse extras for username
+ val entry = PasswordEntry("PLACEHOLDER\n${extraContent.text}")
+ val hasUsernameInExtras = entry.hasUsername()
+ isEnabled = hasUsernameInFileName xor hasUsernameInExtras
+ isChecked = hasUsernameInExtras
+ }
+ }
+
+ /**
+ * Encrypts the password and the extra content
+ */
+ private fun encrypt(copy: Boolean = false) = with(binding) {
+ val editName = filename.text.toString().trim()
+ val editPass = password.text.toString()
+ val editExtra = extraContent.text.toString()
+
+ if (editName.isEmpty()) {
+ snackbar(message = resources.getString(R.string.file_toast_text))
+ return@with
+ }
+
+ if (editPass.isEmpty() && editExtra.isEmpty()) {
+ snackbar(message = resources.getString(R.string.empty_toast_text))
+ return@with
+ }
+
+ if (copy) {
+ copyPasswordToClipboard(editPass)
+ }
+
+ val data = Intent()
+ data.action = OpenPgpApi.ACTION_ENCRYPT
+
+ // EXTRA_KEY_IDS requires long[]
+ val longKeys = keyIDs.map { it.toLong() }
+ data.putExtra(OpenPgpApi.EXTRA_KEY_IDS, longKeys.toLongArray())
+ data.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true)
+
+ val content = "$editPass\n$editExtra"
+ val inputStream = ByteArrayInputStream(content.toByteArray())
+ val outputStream = ByteArrayOutputStream()
+
+ val path = when {
+ // If we allowed the user to edit the relative path, we have to consider it here instead
+ // of fullPath.
+ category.isEnabled -> {
+ val editRelativePath = category.text.toString().trim()
+ if (editRelativePath.isEmpty()) {
+ snackbar(message = resources.getString(R.string.path_toast_text))
+ return
+ }
+ val passwordDirectory = File("$repoPath/${editRelativePath.trim('/')}")
+ if (!passwordDirectory.exists() && !passwordDirectory.mkdir()) {
+ snackbar(message = "Failed to create directory ${editRelativePath.trim('/')}")
+ return
+ }
+
+ "${passwordDirectory.path}/$editName.gpg"
+ }
+ else -> "$fullPath/$editName.gpg"
+ }
+
+ lifecycleScope.launch(Dispatchers.IO) {
+ api?.executeApiAsync(data, inputStream, outputStream) { result ->
+ when (result?.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR)) {
+ OpenPgpApi.RESULT_CODE_SUCCESS -> {
+ try {
+ val file = File(path)
+ try {
+ file.outputStream().use {
+ it.write(outputStream.toByteArray())
+ }
+ } catch (e: IOException) {
+ e(e) { "Failed to write password file" }
+ setResult(RESULT_CANCELED)
+ MaterialAlertDialogBuilder(this@PasswordCreationActivity)
+ .setTitle(getString(R.string.password_creation_file_write_fail_title))
+ .setMessage(getString(R.string.password_creation_file_write_fail_message))
+ .setCancelable(false)
+ .setPositiveButton(android.R.string.ok) { _, _ ->
+ finish()
+ }
+ .show()
+ }
+
+ val returnIntent = Intent()
+ returnIntent.putExtra(RETURN_EXTRA_CREATED_FILE, path)
+ returnIntent.putExtra(RETURN_EXTRA_NAME, editName)
+ returnIntent.putExtra(RETURN_EXTRA_LONG_NAME, getLongName(fullPath, repoPath, editName))
+
+ if (shouldGeneratePassword) {
+ val directoryStructure =
+ AutofillPreferences.directoryStructure(applicationContext)
+ val entry = PasswordEntry(content)
+ returnIntent.putExtra(RETURN_EXTRA_PASSWORD, entry.password)
+ val username = PasswordEntry(content).username
+ ?: directoryStructure.getUsernameFor(file)
+ returnIntent.putExtra(RETURN_EXTRA_USERNAME, username)
+ }
+
+ val repo = PasswordRepository.getRepository(null)
+ if (repo != null) {
+ val status = Git(repo).status().call()
+ if (status.modified.isNotEmpty()) {
+ commitChange(
+ getString(
+ R.string.git_commit_edit_text,
+ getLongName(fullPath, repoPath, editName)
+ )
+ )
+ }
+ }
+ setResult(RESULT_OK, returnIntent)
+ finish()
+ } catch (e: Exception) {
+ e(e) { "An Exception occurred" }
+ }
+ }
+ OpenPgpApi.RESULT_CODE_ERROR -> handleError(result)
+ }
+ }
+ }
+ }
+
+ companion object {
+ private const val KEY_PWGEN_TYPE_CLASSIC = "classic"
+ private const val KEY_PWGEN_TYPE_XKPASSWD = "xkpasswd"
+ const val RETURN_EXTRA_CREATED_FILE = "CREATED_FILE"
+ const val RETURN_EXTRA_NAME = "NAME"
+ const val RETURN_EXTRA_LONG_NAME = "LONG_NAME"
+ const val RETURN_EXTRA_USERNAME = "USERNAME"
+ const val RETURN_EXTRA_PASSWORD = "PASSWORD"
+ const val EXTRA_FILE_NAME = "FILENAME"
+ const val EXTRA_PASSWORD = "PASSWORD"
+ const val EXTRA_EXTRA_CONTENT = "EXTRA_CONTENT"
+ const val EXTRA_GENERATE_PASSWORD = "GENERATE_PASSWORD"
+ const val EXTRA_EDITING = "EDITING"
+ }
+}
diff --git a/app/src/main/java/com/zeapo/pwdstore/crypto/PgpActivity.kt b/app/src/main/java/com/zeapo/pwdstore/crypto/PgpActivity.kt
deleted file mode 100644
index 77d7e70c..00000000
--- a/app/src/main/java/com/zeapo/pwdstore/crypto/PgpActivity.kt
+++ /dev/null
@@ -1,788 +0,0 @@
-/*
- * Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved.
- * SPDX-License-Identifier: GPL-3.0-only
- */
-package com.zeapo.pwdstore.crypto
-
-import android.app.PendingIntent
-import android.content.BroadcastReceiver
-import android.content.ClipData
-import android.content.ClipboardManager
-import android.content.Context
-import android.content.Intent
-import android.content.IntentFilter
-import android.content.IntentSender
-import android.content.SharedPreferences
-import android.graphics.Typeface
-import android.os.Build
-import android.os.Bundle
-import android.text.InputType
-import android.text.TextUtils
-import android.text.format.DateUtils
-import android.text.method.PasswordTransformationMethod
-import android.view.Menu
-import android.view.MenuItem
-import android.view.View
-import android.view.WindowManager
-import androidx.appcompat.app.AppCompatActivity
-import androidx.core.content.edit
-import androidx.core.content.getSystemService
-import androidx.core.widget.doOnTextChanged
-import androidx.lifecycle.lifecycleScope
-import androidx.localbroadcastmanager.content.LocalBroadcastManager
-import androidx.preference.PreferenceManager
-import com.github.ajalt.timberkt.Timber.e
-import com.github.ajalt.timberkt.Timber.i
-import com.github.ajalt.timberkt.Timber.tag
-import com.google.android.material.snackbar.Snackbar
-import com.zeapo.pwdstore.ClipboardService
-import com.zeapo.pwdstore.PasswordEntry
-import com.zeapo.pwdstore.R
-import com.zeapo.pwdstore.UserPreference
-import com.zeapo.pwdstore.autofill.oreo.AutofillPreferences
-import com.zeapo.pwdstore.autofill.oreo.DirectoryStructure
-import com.zeapo.pwdstore.ui.dialogs.PasswordGeneratorDialogFragment
-import com.zeapo.pwdstore.ui.dialogs.XkPasswordGeneratorDialogFragment
-import kotlinx.android.synthetic.main.decrypt_layout.crypto_password_category_decrypt
-import kotlinx.android.synthetic.main.decrypt_layout.crypto_password_file
-import kotlinx.android.synthetic.main.decrypt_layout.crypto_password_last_changed
-import kotlinx.android.synthetic.main.decrypt_layout.extra_content
-import kotlinx.android.synthetic.main.decrypt_layout.extra_content_container
-import kotlinx.android.synthetic.main.decrypt_layout.password_text
-import kotlinx.android.synthetic.main.decrypt_layout.password_text_container
-import kotlinx.android.synthetic.main.decrypt_layout.username_text
-import kotlinx.android.synthetic.main.decrypt_layout.username_text_container
-import kotlinx.android.synthetic.main.encrypt_layout.crypto_extra_edit
-import kotlinx.android.synthetic.main.encrypt_layout.crypto_password_category
-import kotlinx.android.synthetic.main.encrypt_layout.crypto_password_edit
-import kotlinx.android.synthetic.main.encrypt_layout.crypto_password_file_edit
-import kotlinx.android.synthetic.main.encrypt_layout.encrypt_username
-import kotlinx.android.synthetic.main.encrypt_layout.generate_password
-import kotlinx.coroutines.Dispatchers.IO
-import kotlinx.coroutines.launch
-import me.msfjarvis.openpgpktx.util.OpenPgpApi
-import me.msfjarvis.openpgpktx.util.OpenPgpApi.Companion.ACTION_DECRYPT_VERIFY
-import me.msfjarvis.openpgpktx.util.OpenPgpApi.Companion.RESULT_CODE
-import me.msfjarvis.openpgpktx.util.OpenPgpApi.Companion.RESULT_CODE_ERROR
-import me.msfjarvis.openpgpktx.util.OpenPgpApi.Companion.RESULT_CODE_SUCCESS
-import me.msfjarvis.openpgpktx.util.OpenPgpApi.Companion.RESULT_CODE_USER_INTERACTION_REQUIRED
-import me.msfjarvis.openpgpktx.util.OpenPgpApi.Companion.RESULT_ERROR
-import me.msfjarvis.openpgpktx.util.OpenPgpApi.Companion.RESULT_INTENT
-import me.msfjarvis.openpgpktx.util.OpenPgpServiceConnection
-import org.openintents.openpgp.IOpenPgpService2
-import org.openintents.openpgp.OpenPgpError
-import java.io.ByteArrayInputStream
-import java.io.ByteArrayOutputStream
-import java.io.File
-import java.nio.charset.Charset
-
-class PgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBound {
- private val clipboard by lazy { getSystemService<ClipboardManager>() }
- private var passwordEntry: PasswordEntry? = null
- private var api: OpenPgpApi? = null
-
- private var editName: String? = null
- private var editPass: String? = null
- private var editExtra: String? = null
-
- private val suggestedName by lazy { intent.getStringExtra("SUGGESTED_NAME") }
- private val suggestedPass by lazy { intent.getStringExtra("SUGGESTED_PASS") }
- private val suggestedExtra by lazy { intent.getStringExtra("SUGGESTED_EXTRA") }
- private val shouldGeneratePassword by lazy { intent.getBooleanExtra("GENERATE_PASSWORD", false) }
-
- private val operation: String by lazy { intent.getStringExtra("OPERATION") }
- private val repoPath: String by lazy { intent.getStringExtra("REPO_PATH") }
-
- private val fullPath: String by lazy { intent.getStringExtra("FILE_PATH") }
- private val name: String by lazy { File(fullPath).nameWithoutExtension }
- private val lastChangedString: CharSequence by lazy {
- getLastChangedString(
- intent.getLongExtra(
- "LAST_CHANGED_TIMESTAMP",
- -1L
- )
- )
- }
- private val relativeParentPath: String by lazy { getParentPath(fullPath, repoPath) }
-
- val settings: SharedPreferences by lazy { PreferenceManager.getDefaultSharedPreferences(this) }
- private val keyIDs get() = _keyIDs
- private var _keyIDs = emptySet<String>()
- private var serviceConnection: OpenPgpServiceConnection? = null
- private var delayTask: DelayShow? = null
- private val receiver = object : BroadcastReceiver() {
- override fun onReceive(context: Context?, intent: Intent?) {
- delayTask?.doOnPostExecute()
- }
- }
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- window.setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE)
- tag(TAG)
-
- // some persistence
- _keyIDs = settings.getStringSet("openpgp_key_ids_set", null) ?: emptySet()
- val providerPackageName = settings.getString("openpgp_provider_list", "")
-
- if (TextUtils.isEmpty(providerPackageName)) {
- showSnackbar(resources.getString(R.string.provider_toast_text), Snackbar.LENGTH_LONG)
- val intent = Intent(this, UserPreference::class.java)
- startActivityForResult(intent, OPEN_PGP_BOUND)
- } else {
- // bind to service
- serviceConnection = OpenPgpServiceConnection(this, providerPackageName, this)
- serviceConnection?.bindToService()
- supportActionBar?.setDisplayHomeAsUpEnabled(true)
- }
-
- when (operation) {
- "DECRYPT", "EDIT" -> {
- setContentView(R.layout.decrypt_layout)
- crypto_password_category_decrypt.text = relativeParentPath
- crypto_password_file.text = name
- crypto_password_file.setOnLongClickListener {
- val clipboard = clipboard ?: return@setOnLongClickListener false
- val clip = ClipData.newPlainText("pgp_handler_result_pm", name)
- clipboard.setPrimaryClip(clip)
- showSnackbar(resources.getString(R.string.clipboard_copied_text))
- true
- }
-
- crypto_password_last_changed.text = try {
- resources.getString(R.string.last_changed, lastChangedString)
- } catch (e: RuntimeException) {
- showSnackbar(getString(R.string.get_last_changed_failed))
- ""
- }
- }
- "ENCRYPT" -> {
- setContentView(R.layout.encrypt_layout)
-
- generate_password?.setOnClickListener {
- generatePassword()
- }
-
- title = getString(R.string.new_password_title)
- crypto_password_category.apply {
- // If the activity has been provided with suggested info or is meant to generate
- // a password, we allow the user to edit the path, otherwise we style the
- // EditText like a TextView.
- if (suggestedName != null || suggestedPass != null || shouldGeneratePassword) {
- isEnabled = true
- } else {
- setBackgroundColor(getColor(android.R.color.transparent))
- }
- val path = getRelativePath(fullPath, repoPath)
- // Keep empty path field visible if it is editable.
- if (path.isEmpty() && !isEnabled)
- visibility = View.GONE
- else
- setText(path)
- }
- suggestedName?.let { crypto_password_file_edit.setText(it) }
- // Allow the user to quickly switch between storing the username as the filename or
- // in the encrypted extras. This only makes sense if the directory structure is
- // FileBased.
- if (suggestedName != null &&
- AutofillPreferences.directoryStructure(this) == DirectoryStructure.FileBased
- ) {
- encrypt_username.apply {
- visibility = View.VISIBLE
- setOnClickListener {
- if (isChecked) {
- // User wants to enable username encryption, so we add it to the
- // encrypted extras as the first line.
- val username = crypto_password_file_edit.text!!.toString()
- val extras = "username:$username\n${crypto_extra_edit.text!!}"
-
- crypto_password_file_edit.setText("")
- crypto_extra_edit.setText(extras)
- } else {
- // User wants to disable username encryption, so we extract the
- // username from the encrypted extras and use it as the filename.
- val entry = PasswordEntry("PASSWORD\n${crypto_extra_edit.text!!}")
- val username = entry.username
-
- // username should not be null here by the logic in
- // updateEncryptUsernameState, but it could still happen due to
- // input lag.
- if (username != null) {
- crypto_password_file_edit.setText(username)
- crypto_extra_edit.setText(entry.extraContentWithoutUsername)
- }
- }
- updateEncryptUsernameState()
- }
- }
- crypto_password_file_edit.doOnTextChanged { _, _, _, _ -> updateEncryptUsernameState() }
- crypto_extra_edit.doOnTextChanged { _, _, _, _ -> updateEncryptUsernameState() }
- updateEncryptUsernameState()
- }
- suggestedPass?.let {
- crypto_password_edit.setText(it)
- crypto_password_edit.inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_PASSWORD
- }
- suggestedExtra?.let { crypto_extra_edit.setText(it) }
- if (shouldGeneratePassword) {
- generatePassword()
- crypto_password_edit.inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_PASSWORD
- }
- }
- }
- }
-
- private fun updateEncryptUsernameState() {
- encrypt_username.apply {
- if (visibility != View.VISIBLE)
- return
- val hasUsernameInFileName = crypto_password_file_edit.text!!.toString().isNotBlank()
- // Use PasswordEntry to parse extras for username
- val entry = PasswordEntry("PLACEHOLDER\n${crypto_extra_edit.text!!}")
- val hasUsernameInExtras = entry.hasUsername()
- isEnabled = hasUsernameInFileName xor hasUsernameInExtras
- isChecked = hasUsernameInExtras
- }
- }
-
- override fun onResume() {
- super.onResume()
- LocalBroadcastManager.getInstance(this).registerReceiver(receiver, IntentFilter(ACTION_CLEAR))
- }
-
- private fun generatePassword() {
- when (settings.getString("pref_key_pwgen_type", KEY_PWGEN_TYPE_CLASSIC)) {
- KEY_PWGEN_TYPE_CLASSIC -> PasswordGeneratorDialogFragment()
- .show(supportFragmentManager, "generator")
- KEY_PWGEN_TYPE_XKPASSWD -> XkPasswordGeneratorDialogFragment()
- .show(supportFragmentManager, "xkpwgenerator")
- }
- }
-
- override fun onStop() {
- LocalBroadcastManager.getInstance(this).unregisterReceiver(receiver)
- super.onStop()
- }
-
- override fun onDestroy() {
- super.onDestroy()
- serviceConnection?.unbindFromService()
- }
-
- override fun onCreateOptionsMenu(menu: Menu?): Boolean {
- // Inflate the menu; this adds items to the action bar if it is present.
- // Do not use the value `operation` in this case as it is not valid when editing
- val menuId = when (intent.getStringExtra("OPERATION")) {
- "ENCRYPT", "EDIT" -> R.menu.pgp_handler_new_password
- "DECRYPT" -> R.menu.pgp_handler
- else -> R.menu.pgp_handler
- }
-
- menuInflater.inflate(menuId, menu)
- return true
- }
-
- override fun onOptionsItemSelected(item: MenuItem): Boolean {
- when (item.itemId) {
- R.id.crypto_cancel_add, android.R.id.home -> finish()
- R.id.copy_password -> copyPasswordToClipBoard()
- R.id.share_password_as_plaintext -> shareAsPlaintext()
- R.id.edit_password -> editPassword()
- R.id.crypto_confirm_add -> encrypt()
- R.id.crypto_confirm_add_and_copy -> encrypt(true)
- else -> return super.onOptionsItemSelected(item)
- }
- return true
- }
-
- /**
- * Shows a simple toast message
- */
- private fun showSnackbar(message: String, length: Int = Snackbar.LENGTH_SHORT) {
- runOnUiThread { Snackbar.make(findViewById(android.R.id.content), message, length).show() }
- }
-
- /**
- * Handle the case where OpenKeychain returns that it needs to interact with the user
- *
- * @param result The intent returned by OpenKeychain
- * @param requestCode The code we'd like to use to identify the behaviour
- */
- private fun handleUserInteractionRequest(result: Intent, requestCode: Int) {
- i { "RESULT_CODE_USER_INTERACTION_REQUIRED" }
-
- val pi: PendingIntent? = result.getParcelableExtra(RESULT_INTENT)
- try {
- this@PgpActivity.startIntentSenderFromChild(
- this@PgpActivity, pi?.intentSender, requestCode,
- null, 0, 0, 0
- )
- } catch (e: IntentSender.SendIntentException) {
- e(e) { "SendIntentException" }
- }
- }
-
- /**
- * Handle the error returned by OpenKeychain
- *
- * @param result The intent returned by OpenKeychain
- */
- private fun handleError(result: Intent) {
- // TODO show what kind of error it is
- /* For example:
- * No suitable key found -> no key in OpenKeyChain
- *
- * Check in open-pgp-lib how their definitions and error code
- */
- val error: OpenPgpError? = result.getParcelableExtra(RESULT_ERROR)
- if (error != null) {
- showSnackbar("Error from OpenKeyChain : " + error.message)
- e { "onError getErrorId: ${error.errorId}" }
- e { "onError getMessage: ${error.message}" }
- }
- }
-
- private fun initOpenPgpApi() {
- api = api ?: OpenPgpApi(this, serviceConnection!!.service!!)
- }
-
- private fun decryptAndVerify(receivedIntent: Intent? = null) {
- val data = receivedIntent ?: Intent()
- data.action = ACTION_DECRYPT_VERIFY
-
- val iStream = File(fullPath).inputStream()
- val oStream = ByteArrayOutputStream()
-
- lifecycleScope.launch(IO) {
- api?.executeApiAsync(data, iStream, oStream) { result ->
- when (result?.getIntExtra(RESULT_CODE, RESULT_CODE_ERROR)) {
- RESULT_CODE_SUCCESS -> {
- try {
- val showPassword = settings.getBoolean("show_password", true)
- val showExtraContent = settings.getBoolean("show_extra_content", true)
-
- password_text_container.visibility = View.VISIBLE
-
- val monoTypeface = Typeface.createFromAsset(assets, "fonts/sourcecodepro.ttf")
- val entry = PasswordEntry(oStream)
-
- passwordEntry = entry
-
- if (intent.getStringExtra("OPERATION") == "EDIT") {
- editPassword()
- return@executeApiAsync
- }
-
- if (entry.password.isEmpty()) {
- password_text_container.visibility = View.GONE
- } else {
- password_text_container.visibility = View.VISIBLE
- password_text.setText(entry.password)
- if (!showPassword) {
- password_text.transformationMethod = PasswordTransformationMethod.getInstance()
- }
- password_text_container.setOnClickListener { copyPasswordToClipBoard() }
- password_text.setOnClickListener { copyPasswordToClipBoard() }
- }
-
- if (entry.hasExtraContent()) {
- extra_content_container.visibility = View.VISIBLE
- extra_content.typeface = monoTypeface
- extra_content.setText(entry.extraContentWithoutUsername)
- if (!showExtraContent) {
- extra_content.transformationMethod = PasswordTransformationMethod.getInstance()
- }
- extra_content_container.setOnClickListener { copyTextToClipboard(entry.extraContentWithoutUsername) }
- extra_content.setOnClickListener { copyTextToClipboard(entry.extraContentWithoutUsername) }
-
- if (entry.hasUsername()) {
- username_text.typeface = monoTypeface
- username_text.setText(entry.username)
- username_text_container.setEndIconOnClickListener { copyTextToClipboard(entry.username!!) }
- username_text_container.visibility = View.VISIBLE
- } else {
- username_text_container.visibility = View.GONE
- }
- }
-
- if (settings.getBoolean("copy_on_decrypt", true)) {
- copyPasswordToClipBoard()
- }
- } catch (e: Exception) {
- e(e) { "An Exception occurred" }
- }
- }
- RESULT_CODE_USER_INTERACTION_REQUIRED -> handleUserInteractionRequest(result, REQUEST_DECRYPT)
- RESULT_CODE_ERROR -> handleError(result)
- }
- }
- }
- }
-
- /**
- * Encrypts the password and the extra content
- */
- private fun encrypt(copy: Boolean = false) {
- editName = crypto_password_file_edit.text.toString().trim()
- editPass = crypto_password_edit.text.toString()
- editExtra = crypto_extra_edit.text.toString()
-
- if (editName?.isEmpty() == true) {
- showSnackbar(resources.getString(R.string.file_toast_text))
- return
- }
-
- if (editPass?.isEmpty() == true && editExtra?.isEmpty() == true) {
- showSnackbar(resources.getString(R.string.empty_toast_text))
- return
- }
-
- if (copy) {
- copyPasswordToClipBoard()
- }
-
- val data = Intent()
- data.action = OpenPgpApi.ACTION_ENCRYPT
-
- // EXTRA_KEY_IDS requires long[]
- val longKeys = keyIDs.map { it.toLong() }
- data.putExtra(OpenPgpApi.EXTRA_KEY_IDS, longKeys.toLongArray())
- data.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true)
-
- // TODO Check if we could use PasswordEntry to generate the file
- val content = "$editPass\n$editExtra"
- val iStream = ByteArrayInputStream(content.toByteArray(Charset.forName("UTF-8")))
- val oStream = ByteArrayOutputStream()
-
- val path = when {
- intent.getBooleanExtra("fromDecrypt", false) -> fullPath
- // If we allowed the user to edit the relative path, we have to consider it here instead
- // of fullPath.
- crypto_password_category.isEnabled -> {
- val editRelativePath = crypto_password_category.text!!.toString().trim()
- if (editRelativePath.isEmpty()) {
- showSnackbar(resources.getString(R.string.path_toast_text))
- return
- }
- "$repoPath/${editRelativePath.trim('/')}/$editName.gpg"
- }
- else -> "$fullPath/$editName.gpg"
- }
-
- lifecycleScope.launch(IO) {
- api?.executeApiAsync(data, iStream, oStream) { result ->
- when (result?.getIntExtra(RESULT_CODE, RESULT_CODE_ERROR)) {
- RESULT_CODE_SUCCESS -> {
- try {
- // TODO This might fail, we should check that the write is successful
- val file = File(path)
- val outputStream = file.outputStream()
- outputStream.write(oStream.toByteArray())
- outputStream.close()
-
- val returnIntent = Intent()
- returnIntent.putExtra("CREATED_FILE", path)
- returnIntent.putExtra("NAME", editName)
- returnIntent.putExtra("LONG_NAME", getLongName(fullPath, repoPath, editName!!))
-
- // if coming from decrypt screen->edit button
- if (intent.getBooleanExtra("fromDecrypt", false)) {
- returnIntent.putExtra("OPERATION", "EDIT")
- returnIntent.putExtra("needCommit", true)
- }
-
- if (shouldGeneratePassword) {
- val directoryStructure =
- AutofillPreferences.directoryStructure(applicationContext)
- val entry = PasswordEntry(content)
- returnIntent.putExtra("PASSWORD", entry.password)
- val username = PasswordEntry(content).username
- ?: directoryStructure.getUsernameFor(file)
- returnIntent.putExtra("USERNAME", username)
- }
-
- setResult(RESULT_OK, returnIntent)
- finish()
- } catch (e: Exception) {
- e(e) { "An Exception occurred" }
- }
- }
- RESULT_CODE_ERROR -> handleError(result)
- }
- }
- }
- }
-
- /**
- * Opens EncryptActivity with the information for this file to be edited
- */
- private fun editPassword() {
- setContentView(R.layout.encrypt_layout)
- generate_password?.setOnClickListener {
- when (settings.getString("pref_key_pwgen_type", KEY_PWGEN_TYPE_CLASSIC)) {
- KEY_PWGEN_TYPE_CLASSIC -> PasswordGeneratorDialogFragment()
- .show(supportFragmentManager, "generator")
- KEY_PWGEN_TYPE_XKPASSWD -> XkPasswordGeneratorDialogFragment()
- .show(supportFragmentManager, "xkpwgenerator")
- }
- }
-
- title = getString(R.string.edit_password_title)
-
- val monoTypeface = Typeface.createFromAsset(assets, "fonts/sourcecodepro.ttf")
- crypto_password_edit.setText(passwordEntry?.password)
- crypto_password_edit.typeface = monoTypeface
- crypto_extra_edit.setText(passwordEntry?.extraContent)
- crypto_extra_edit.typeface = monoTypeface
-
- crypto_password_category.setText(relativeParentPath)
- crypto_password_file_edit.setText(name)
- crypto_password_file_edit.isEnabled = false
-
- delayTask?.cancelAndSignal(true)
-
- val data = Intent(this, PgpActivity::class.java)
- data.putExtra("OPERATION", "EDIT")
- data.putExtra("fromDecrypt", true)
- intent = data
- invalidateOptionsMenu()
- }
-
- /**
- * Get the Key ids from OpenKeychain
- */
- private fun getKeyIds(receivedIntent: Intent? = null) {
- val data = receivedIntent ?: Intent()
- data.action = OpenPgpApi.ACTION_GET_KEY_IDS
- lifecycleScope.launch(IO) {
- api?.executeApiAsync(data, null, null) { result ->
- when (result?.getIntExtra(RESULT_CODE, RESULT_CODE_ERROR)) {
- RESULT_CODE_SUCCESS -> {
- try {
- val ids = result.getLongArrayExtra(OpenPgpApi.RESULT_KEY_IDS)
- ?: LongArray(0)
- val keys = ids.map { it.toString() }.toSet()
-
- // use Long
- settings.edit { putStringSet("openpgp_key_ids_set", keys) }
-
- showSnackbar("PGP keys selected")
-
- setResult(RESULT_OK)
- finish()
- } catch (e: Exception) {
- e(e) { "An Exception occurred" }
- }
- }
- RESULT_CODE_USER_INTERACTION_REQUIRED -> handleUserInteractionRequest(result, REQUEST_KEY_ID)
- RESULT_CODE_ERROR -> handleError(result)
- }
- }
- }
- }
-
- override fun onError(e: Exception) {}
-
- /**
- * The action to take when the PGP service is bound
- */
- override fun onBound(service: IOpenPgpService2) {
- initOpenPgpApi()
- when (operation) {
- "EDIT", "DECRYPT" -> decryptAndVerify()
- "GET_KEY_ID" -> getKeyIds()
- }
- }
-
- override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
- super.onActivityResult(requestCode, resultCode, data)
-
- if (data == null) {
- setResult(RESULT_CANCELED, null)
- finish()
- return
- }
-
- // try again after user interaction
- if (resultCode == RESULT_OK) {
- when (requestCode) {
- REQUEST_DECRYPT -> decryptAndVerify(data)
- REQUEST_KEY_ID -> getKeyIds(data)
- else -> {
- setResult(RESULT_OK)
- finish()
- }
- }
- } else if (resultCode == RESULT_CANCELED) {
- setResult(RESULT_CANCELED, data)
- finish()
- }
- }
-
- private fun copyPasswordToClipBoard() {
- val clipboard = clipboard ?: return
- val pass = passwordEntry?.password
- val clip = ClipData.newPlainText("pgp_handler_result_pm", pass)
- clipboard.setPrimaryClip(clip)
-
- var clearAfter = 45
- try {
- clearAfter = Integer.parseInt(settings.getString("general_show_time", "45") as String)
- } catch (e: NumberFormatException) {
- // ignore and keep default
- }
-
- if (clearAfter != 0) {
- setTimer()
- showSnackbar(resources.getString(R.string.clipboard_password_toast_text, clearAfter))
- } else {
- showSnackbar(resources.getString(R.string.clipboard_password_no_clear_toast_text))
- }
- }
-
- private fun copyTextToClipboard(text: String) {
- val clipboard = clipboard ?: return
- val clip = ClipData.newPlainText("pgp_handler_result_pm", text)
- clipboard.setPrimaryClip(clip)
- showSnackbar(resources.getString(R.string.clipboard_copied_text))
- }
-
- private fun shareAsPlaintext() {
- val sendIntent = Intent()
- sendIntent.action = Intent.ACTION_SEND
- sendIntent.putExtra(Intent.EXTRA_TEXT, passwordEntry?.password)
- sendIntent.type = "text/plain"
- startActivity(
- Intent.createChooser(
- sendIntent,
- resources.getText(R.string.send_plaintext_password_to)
- )
- ) // Always show a picker to give the user a chance to cancel
- }
-
- private fun setTimer() {
-
- // make sure to cancel any running tasks as soon as possible
- // if the previous task is still running, do not ask it to clear the password
- delayTask?.cancelAndSignal(true)
-
- // launch a new one
- delayTask = DelayShow()
- delayTask?.execute()
- }
-
- /**
- * Gets a relative string describing when this shape was last changed
- * (e.g. "one hour ago")
- */
- private fun getLastChangedString(timeStamp: Long): CharSequence {
- if (timeStamp < 0) {
- throw RuntimeException()
- }
-
- return DateUtils.getRelativeTimeSpanString(this, timeStamp, true)
- }
-
- @Suppress("StaticFieldLeak")
- inner class DelayShow {
-
- private var skip = false
- private var service: Intent? = null
- private var showTime: Int = 0
-
- // Custom cancellation that can be triggered from another thread.
- //
- // This signals the DelayShow task to stop and avoids it having
- // to poll the AsyncTask.isCancelled() excessively. If skipClearing
- // is true, the cancelled task won't clear the clipboard.
- fun cancelAndSignal(skipClearing: Boolean) {
- skip = skipClearing
- if (service != null) {
- stopService(service)
- service = null
- }
- }
-
- fun execute() {
- service = Intent(this@PgpActivity, ClipboardService::class.java).also {
- it.action = ACTION_START
- }
- doOnPreExecute()
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
- startForegroundService(service)
- } else {
- startService(service)
- }
- }
-
- private fun doOnPreExecute() {
- showTime = try {
- Integer.parseInt(settings.getString("general_show_time", "45") as String)
- } catch (e: NumberFormatException) {
- 45
- }
- password_text_container?.visibility = View.VISIBLE
- if (extra_content?.text?.isNotEmpty() == true)
- extra_content_container?.visibility = View.VISIBLE
- }
-
- fun doOnPostExecute() {
- if (skip) return
-
- if (password_text != null) {
- passwordEntry = null
- extra_content_container.visibility = View.INVISIBLE
- password_text_container.visibility = View.INVISIBLE
- finish()
- }
- }
- }
-
- companion object {
- const val OPEN_PGP_BOUND = 101
- const val REQUEST_DECRYPT = 202
- const val REQUEST_KEY_ID = 203
-
- private const val ACTION_CLEAR = "ACTION_CLEAR_CLIPBOARD"
- private const val ACTION_START = "ACTION_START_CLIPBOARD_TIMER"
-
- const val TAG = "PgpActivity"
-
- const val KEY_PWGEN_TYPE_CLASSIC = "classic"
- const val KEY_PWGEN_TYPE_XKPASSWD = "xkpasswd"
-
- /**
- * Gets the relative path to the repository
- */
- fun getRelativePath(fullPath: String, repositoryPath: String): String =
- fullPath.replace(repositoryPath, "").replace("/+".toRegex(), "/")
-
- /**
- * Gets the Parent path, relative to the repository
- */
- fun getParentPath(fullPath: String, repositoryPath: String): String {
- val relativePath = getRelativePath(fullPath, repositoryPath)
- val index = relativePath.lastIndexOf("/")
- return "/${relativePath.substring(startIndex = 0, endIndex = index + 1)}/".replace("/+".toRegex(), "/")
- }
-
- /**
- * /path/to/store/social/facebook.gpg -> social/facebook
- */
- @JvmStatic
- fun getLongName(fullPath: String, repositoryPath: String, basename: String): String {
- var relativePath = getRelativePath(fullPath, repositoryPath)
- return if (relativePath.isNotEmpty() && relativePath != "/") {
- // remove preceding '/'
- relativePath = relativePath.substring(1)
- if (relativePath.endsWith('/')) {
- relativePath + basename
- } else {
- "$relativePath/$basename"
- }
- } else {
- basename
- }
- }
- }
-}
diff --git a/app/src/main/java/com/zeapo/pwdstore/git/GitAsyncTask.kt b/app/src/main/java/com/zeapo/pwdstore/git/GitAsyncTask.kt
index 124f2d0a..a2b4e2a8 100644
--- a/app/src/main/java/com/zeapo/pwdstore/git/GitAsyncTask.kt
+++ b/app/src/main/java/com/zeapo/pwdstore/git/GitAsyncTask.kt
@@ -32,7 +32,9 @@ class GitAsyncTask(
activity: Activity,
private val refreshListOnEnd: Boolean,
private val operation: GitOperation,
- private val finishWithResultOnEnd: Intent?) : AsyncTask<GitCommand<*>, Int, GitAsyncTask.Result>() {
+ private val finishWithResultOnEnd: Intent?,
+ private val silentlyExecute: Boolean = false
+) : AsyncTask<GitCommand<*>, Int, GitAsyncTask.Result>() {
private val activityWeakReference: WeakReference<Activity> = WeakReference(activity)
private val activity: Activity?
@@ -46,6 +48,7 @@ class GitAsyncTask(
}
override fun onPreExecute() {
+ if (silentlyExecute) return
dialog.run {
setMessage(activity!!.resources.getString(R.string.running_dialog_text))
setCancelable(false)
@@ -141,7 +144,7 @@ class GitAsyncTask(
}
override fun onPostExecute(maybeResult: Result?) {
- dialog.dismiss()
+ if (!silentlyExecute) dialog.dismiss()
when (val result = maybeResult ?: Result.Err(IOException("Unexpected error"))) {
is Result.Err -> {
if (isExplicitlyUserInitiatedError(result.err)) {
diff --git a/app/src/main/java/com/zeapo/pwdstore/git/GitServerConfigActivity.kt b/app/src/main/java/com/zeapo/pwdstore/git/GitServerConfigActivity.kt
index 1eac569b..1f5494fd 100644
--- a/app/src/main/java/com/zeapo/pwdstore/git/GitServerConfigActivity.kt
+++ b/app/src/main/java/com/zeapo/pwdstore/git/GitServerConfigActivity.kt
@@ -58,7 +58,7 @@ class GitServerConfigActivity : BaseGitActivity() {
ConnectionMode.OpenKeychain -> check(R.id.connection_mode_open_keychain)
ConnectionMode.None -> uncheck(checkedButtonId)
}
- addOnButtonCheckedListener { group, _, _ ->
+ addOnButtonCheckedListener { _, _, _ ->
when (checkedButtonId) {
R.id.connection_mode_ssh_key -> connectionMode = ConnectionMode.SshKey
R.id.connection_mode_open_keychain -> connectionMode = ConnectionMode.OpenKeychain
diff --git a/app/src/main/java/com/zeapo/pwdstore/sshkeygen/ShowSshKeyFragment.kt b/app/src/main/java/com/zeapo/pwdstore/sshkeygen/ShowSshKeyFragment.kt
index 5d35123d..8f4fbf84 100644
--- a/app/src/main/java/com/zeapo/pwdstore/sshkeygen/ShowSshKeyFragment.kt
+++ b/app/src/main/java/com/zeapo/pwdstore/sshkeygen/ShowSshKeyFragment.kt
@@ -7,15 +7,14 @@ package com.zeapo.pwdstore.sshkeygen
import android.annotation.SuppressLint
import android.app.Dialog
import android.content.ClipData
-import android.content.ClipboardManager
import android.os.Bundle
import android.view.View
import android.widget.TextView
import androidx.appcompat.app.AlertDialog
-import androidx.core.content.getSystemService
import androidx.fragment.app.DialogFragment
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.zeapo.pwdstore.R
+import com.zeapo.pwdstore.utils.clipboard
import java.io.File
class ShowSshKeyFragment : DialogFragment() {
@@ -39,8 +38,7 @@ class ShowSshKeyFragment : DialogFragment() {
ad.setOnShowListener {
val b = ad.getButton(AlertDialog.BUTTON_NEUTRAL)
b.setOnClickListener {
- val clipboard = activity.getSystemService<ClipboardManager>()
- ?: return@setOnClickListener
+ val clipboard = activity.clipboard ?: return@setOnClickListener
val clip = ClipData.newPlainText("public key", publicKey.text.toString())
clipboard.setPrimaryClip(clip)
}
diff --git a/app/src/main/java/com/zeapo/pwdstore/ui/dialogs/PasswordGeneratorDialogFragment.kt b/app/src/main/java/com/zeapo/pwdstore/ui/dialogs/PasswordGeneratorDialogFragment.kt
index 659c3f54..8f452dca 100644
--- a/app/src/main/java/com/zeapo/pwdstore/ui/dialogs/PasswordGeneratorDialogFragment.kt
+++ b/app/src/main/java/com/zeapo/pwdstore/ui/dialogs/PasswordGeneratorDialogFragment.kt
@@ -46,7 +46,7 @@ class PasswordGeneratorDialogFragment : DialogFragment() {
val passwordText: AppCompatTextView = view.findViewById(R.id.passwordText)
passwordText.typeface = monoTypeface
builder.setPositiveButton(resources.getString(R.string.dialog_ok)) { _, _ ->
- val edit = callingActivity.findViewById<EditText>(R.id.crypto_password_edit)
+ val edit = callingActivity.findViewById<EditText>(R.id.password)
edit.setText(passwordText.text)
}
builder.setNeutralButton(resources.getString(R.string.dialog_cancel)) { _, _ -> }
diff --git a/app/src/main/java/com/zeapo/pwdstore/ui/dialogs/XkPasswordGeneratorDialogFragment.kt b/app/src/main/java/com/zeapo/pwdstore/ui/dialogs/XkPasswordGeneratorDialogFragment.kt
index 825a8890..52bc52c1 100644
--- a/app/src/main/java/com/zeapo/pwdstore/ui/dialogs/XkPasswordGeneratorDialogFragment.kt
+++ b/app/src/main/java/com/zeapo/pwdstore/ui/dialogs/XkPasswordGeneratorDialogFragment.kt
@@ -92,7 +92,7 @@ class XkPasswordGeneratorDialogFragment : DialogFragment() {
builder.setPositiveButton(resources.getString(R.string.dialog_ok)) { _, _ ->
setPreferences()
- val edit = callingActivity.findViewById<EditText>(R.id.crypto_password_edit)
+ val edit = callingActivity.findViewById<EditText>(R.id.password)
edit.setText(passwordText.text)
}
diff --git a/app/src/main/java/com/zeapo/pwdstore/utils/ClipboardUtils.kt b/app/src/main/java/com/zeapo/pwdstore/utils/ClipboardUtils.kt
deleted file mode 100644
index 2e408bfd..00000000
--- a/app/src/main/java/com/zeapo/pwdstore/utils/ClipboardUtils.kt
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved.
- * SPDX-License-Identifier: GPL-3.0-only
- */
-package com.zeapo.pwdstore.utils
-
-import android.content.ClipData
-import android.content.ClipboardManager
-import com.github.ajalt.timberkt.d
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.withContext
-
-object ClipboardUtils {
-
- suspend fun clearClipboard(clipboard: ClipboardManager, deepClear: Boolean = false) {
- d { "Clearing the clipboard" }
- val clip = ClipData.newPlainText("pgp_handler_result_pm", "")
- clipboard.setPrimaryClip(clip)
- if (deepClear) {
- withContext(Dispatchers.IO) {
- repeat(20) {
- val count = (it * 500).toString()
- clipboard.setPrimaryClip(ClipData.newPlainText(count, count))
- }
- }
- }
- }
-}
diff --git a/app/src/main/java/com/zeapo/pwdstore/utils/Extensions.kt b/app/src/main/java/com/zeapo/pwdstore/utils/Extensions.kt
index d10bdaab..ff108b30 100644
--- a/app/src/main/java/com/zeapo/pwdstore/utils/Extensions.kt
+++ b/app/src/main/java/com/zeapo/pwdstore/utils/Extensions.kt
@@ -4,7 +4,10 @@
*/
package com.zeapo.pwdstore.utils
+import android.app.Activity
+import android.content.ClipboardManager
import android.content.Context
+import android.content.Intent
import android.content.SharedPreferences
import android.os.Build
import android.util.TypedValue
@@ -16,7 +19,13 @@ import androidx.annotation.RequiresApi
import androidx.appcompat.app.AlertDialog
import androidx.core.content.getSystemService
import androidx.security.crypto.EncryptedSharedPreferences
-import androidx.security.crypto.MasterKeys
+import androidx.security.crypto.MasterKey
+import com.github.ajalt.timberkt.d
+import com.google.android.material.snackbar.Snackbar
+import com.zeapo.pwdstore.git.GitAsyncTask
+import com.zeapo.pwdstore.git.GitOperation
+import com.zeapo.pwdstore.utils.PasswordRepository.Companion.getRepositoryDirectory
+import org.eclipse.jgit.api.Git
import java.io.File
infix fun Int.hasFlag(flag: Int): Boolean {
@@ -33,6 +42,16 @@ fun CharArray.clear() {
}
}
+val Context.clipboard get() = getSystemService<ClipboardManager>()
+
+fun Activity.snackbar(
+ view: View = findViewById(android.R.id.content),
+ message: String,
+ length: Int = Snackbar.LENGTH_SHORT
+) {
+ Snackbar.make(view, message, length).show()
+}
+
fun File.listFilesRecursively() = walkTopDown().filter { !it.isDirectory }.toList()
fun Context.resolveAttribute(attr: Int): Int {
@@ -42,17 +61,39 @@ fun Context.resolveAttribute(attr: Int): Int {
}
fun Context.getEncryptedPrefs(fileName: String): SharedPreferences {
- val keyGenParameterSpec = MasterKeys.AES256_GCM_SPEC
- val masterKeyAlias = MasterKeys.getOrCreate(keyGenParameterSpec)
+ val masterKeyAlias = MasterKey.Builder(applicationContext)
+ .setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
+ .build()
return EncryptedSharedPreferences.create(
+ applicationContext,
fileName,
masterKeyAlias,
- this,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
}
+fun Activity.commitChange(message: String, finishWithResultOnEnd: Intent? = null) {
+ if (!PasswordRepository.isGitRepo()) {
+ if (finishWithResultOnEnd != null) {
+ setResult(Activity.RESULT_OK, finishWithResultOnEnd)
+ finish()
+ }
+ return
+ }
+ object : GitOperation(getRepositoryDirectory(this@commitChange), this@commitChange) {
+ override fun execute() {
+ d { "Comitting with message: '$message'" }
+ val git = Git(repository)
+ val task = GitAsyncTask(this@commitChange, true, this, finishWithResultOnEnd, silentlyExecute = true)
+ task.execute(
+ git.add().addFilepattern("."),
+ git.commit().setAll(true).setMessage(message)
+ )
+ }
+ }.execute()
+}
+
/**
* Extension function for [AlertDialog] that requests focus for the
* view whose id is [id]. Solution based on a StackOverflow
diff --git a/app/src/main/java/com/zeapo/pwdstore/utils/PasswordItem.kt b/app/src/main/java/com/zeapo/pwdstore/utils/PasswordItem.kt
index 903f6402..5ca95d31 100644
--- a/app/src/main/java/com/zeapo/pwdstore/utils/PasswordItem.kt
+++ b/app/src/main/java/com/zeapo/pwdstore/utils/PasswordItem.kt
@@ -4,7 +4,7 @@
*/
package com.zeapo.pwdstore.utils
-import com.zeapo.pwdstore.crypto.PgpActivity
+import com.zeapo.pwdstore.crypto.BasePgpActivity
import java.io.File
data class PasswordItem(
@@ -19,7 +19,7 @@ data class PasswordItem(
.replace(rootDir.absolutePath, "")
.replace(file.name, "")
- val longName = PgpActivity.getLongName(
+ val longName = BasePgpActivity.getLongName(
fullPathToParent,
rootDir.absolutePath,
toString())
diff --git a/app/src/main/java/mozilla/components/lib/publicsuffixlist/ext/ByteArray.kt b/app/src/main/java/mozilla/components/lib/publicsuffixlist/ext/ByteArray.kt
index 5abb9154..43fb7ab1 100644
--- a/app/src/main/java/mozilla/components/lib/publicsuffixlist/ext/ByteArray.kt
+++ b/app/src/main/java/mozilla/components/lib/publicsuffixlist/ext/ByteArray.kt
@@ -15,7 +15,7 @@ private const val BITMASK = 0xff.toByte()
* Performs a binary search for the provided [labels] on the [ByteArray]'s data.
*
* This algorithm is based on OkHttp's PublicSuffixDatabase class:
- * https://github.com/square/okhttp/blob/master/okhttp/src/main/java/okhttp3/internal/publicsuffix/PublicSuffixDatabase.java
+ * https://github.com/square/okhttp/blob/1977136/okhttp/src/main/kotlin/okhttp3/internal/publicsuffix/PublicSuffixDatabase.kt
*/
@Suppress("ComplexMethod", "NestedBlockDepth")
internal fun ByteArray.binarySearch(labels: List<ByteArray>, labelIndex: Int): String? {
diff --git a/app/src/main/res/layout/decrypt_layout.xml b/app/src/main/res/layout/decrypt_layout.xml
index 590661b3..664cb482 100644
--- a/app/src/main/res/layout/decrypt_layout.xml
+++ b/app/src/main/res/layout/decrypt_layout.xml
@@ -19,7 +19,7 @@
android:padding="16dp">
<androidx.appcompat.widget.AppCompatTextView
- android:id="@+id/crypto_password_category_decrypt"
+ android:id="@+id/password_category"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
@@ -32,7 +32,7 @@
tools:text="CATEGORY HERE" />
<androidx.appcompat.widget.AppCompatTextView
- android:id="@+id/crypto_password_file"
+ android:id="@+id/password_file"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/activity_horizontal_margin"
@@ -41,11 +41,11 @@
android:textSize="24sp"
android:textStyle="bold"
app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toBottomOf="@id/crypto_password_category_decrypt"
+ app:layout_constraintTop_toBottomOf="@id/password_category"
tools:text="PASSWORD FILE NAME HERE" />
<androidx.appcompat.widget.AppCompatTextView
- android:id="@+id/crypto_password_last_changed"
+ android:id="@+id/password_last_changed"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
@@ -54,7 +54,7 @@
android:textIsSelectable="false"
android:textSize="18sp"
app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toBottomOf="@id/crypto_password_file"
+ app:layout_constraintTop_toBottomOf="@id/password_file"
tools:text="LAST CHANGED HERE" />
@@ -65,7 +65,7 @@
android:layout_marginTop="16dp"
android:layout_marginBottom="16dp"
android:src="@drawable/divider"
- app:layout_constraintTop_toBottomOf="@id/crypto_password_last_changed"
+ app:layout_constraintTop_toBottomOf="@id/password_last_changed"
tools:ignore="ContentDescription" />
<com.google.android.material.textfield.TextInputLayout
diff --git a/app/src/main/res/layout/encrypt_layout.xml b/app/src/main/res/layout/password_creation_activity.xml
index 02864135..13af597c 100644
--- a/app/src/main/res/layout/encrypt_layout.xml
+++ b/app/src/main/res/layout/password_creation_activity.xml
@@ -11,10 +11,10 @@
android:background="?android:attr/windowBackground"
android:orientation="vertical"
android:padding="@dimen/activity_horizontal_margin"
- tools:context="com.zeapo.pwdstore.crypto.PgpActivity">
+ tools:context="com.zeapo.pwdstore.crypto.PasswordCreationActivity">
<androidx.appcompat.widget.AppCompatEditText
- android:id="@+id/crypto_password_category"
+ android:id="@+id/category"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/activity_horizontal_margin"
@@ -33,10 +33,10 @@
android:layout_margin="8dp"
android:hint="@string/crypto_name_hint"
app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toBottomOf="@id/crypto_password_category">
+ app:layout_constraintTop_toBottomOf="@id/category">
<com.google.android.material.textfield.TextInputEditText
- android:id="@+id/crypto_password_file_edit"
+ android:id="@+id/filename"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</com.google.android.material.textfield.TextInputLayout>
@@ -52,7 +52,7 @@
app:layout_constraintTop_toBottomOf="@id/name_input_layout">
<com.google.android.material.textfield.TextInputEditText
- android:id="@+id/crypto_password_edit"
+ android:id="@+id/password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textVisiblePassword" />
@@ -77,14 +77,14 @@
app:layout_constraintTop_toBottomOf="@id/generate_password">
<com.google.android.material.textfield.TextInputEditText
- android:id="@+id/crypto_extra_edit"
+ android:id="@+id/extra_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textMultiLine|textVisiblePassword" />
</com.google.android.material.textfield.TextInputLayout>
- <Switch
+ <com.google.android.material.switchmaterial.SwitchMaterial
android:id="@+id/encrypt_username"
android:layout_width="match_parent"
android:layout_height="wrap_content"
diff --git a/app/src/main/res/menu/context_pass.xml b/app/src/main/res/menu/context_pass.xml
index 3d262e14..41a1f705 100644
--- a/app/src/main/res/menu/context_pass.xml
+++ b/app/src/main/res/menu/context_pass.xml
@@ -15,12 +15,6 @@
app:showAsAction="ifRoom" />
<item
- android:id="@+id/menu_edit_password"
- android:icon="@drawable/ic_edit_white_24dp"
- android:title="@string/edit"
- app:showAsAction="ifRoom" />
-
- <item
android:id="@+id/menu_delete_password"
android:icon="@drawable/ic_delete_white_24dp"
android:title="@string/delete"
diff --git a/app/src/main/res/menu/pgp_handler_new_password.xml b/app/src/main/res/menu/pgp_handler_new_password.xml
index c3e73199..78ea8ced 100644
--- a/app/src/main/res/menu/pgp_handler_new_password.xml
+++ b/app/src/main/res/menu/pgp_handler_new_password.xml
@@ -8,17 +8,17 @@
xmlns:tools="http://schemas.android.com/tools"
tools:context="com.zeapo.pwdstore.crypto.PgpActivity">
<item
- android:id="@+id/crypto_cancel_add"
+ android:id="@+id/cancel_password_add"
android:icon="@drawable/ic_clear_white_24dp"
android:title="@string/crypto_cancel"
pwstore:showAsAction="ifRoom" />
<item
- android:id="@+id/crypto_confirm_add"
+ android:id="@+id/save_password"
android:icon="@drawable/ic_save_white_24dp"
android:title="@string/crypto_save"
pwstore:showAsAction="ifRoom" />
<item
- android:id="@+id/crypto_confirm_add_and_copy"
+ android:id="@+id/save_and_copy_password"
android:icon="@drawable/ic_save_copy_white_24dp"
android:title="@string/crypto_save_and_copy"
pwstore:showAsAction="ifRoom" />
diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml
index 0ab9c7f1..c76b435c 100644
--- a/app/src/main/res/values-ar/strings.xml
+++ b/app/src/main/res/values-ar/strings.xml
@@ -22,7 +22,6 @@
<string name="jgit_error_dialog_text">رسالة مِن jgit: \n</string>
<!-- Git Handler -->
- <string name="forget_username_dialog_text">هل نسيت إدخال إسم المستخدم ؟</string>
<string name="ssh_preferences_dialog_title">خال من مفاتيح الـ SSH</string>
<string name="ssh_preferences_dialog_import">إستيراد</string>
<string name="ssh_preferences_dialog_generate">توليد</string>
@@ -34,14 +33,9 @@
<string name="server_protocol">البروتوكول</string>
<string name="server_url">عنوان الخادوم</string>
<string name="server_port_hint">22</string>
- <string name="default_ssh_port">22</string>
- <string name="default_https_port">443</string>
<string name="server_path">مسار المستودع</string>
- <string name="server_path_hint">path/to/pass</string>
<string name="server_user">إسم المستخدم</string>
- <string name="server_user_hint">git_username</string>
- <string name="server_resulting_url">عنوان الرابط الناتج</string>
<string name="connection_mode">نوع المصادقة</string>
<string name="git_user_name_hint">إسم المستخدم</string>
@@ -87,7 +81,6 @@
<string name="pref_external_repository">مستودع تخزين خارجي</string>
<string name="pref_external_repository_summary">إستخدم كلمة مرور المستودع الخارجي</string>
<string name="pref_select_external_repository">إختيار مستودع التخزين الخارجي</string>
- <string name="prefs_use_default_file_picker">إستخدم أداة إختيار الملفات الإفتراضي</string>
<string name="prefs_export_passwords_title">تصدير كلمات السر</string>
<string name="prefs_version">النسخة</string>
@@ -105,7 +98,6 @@
<string name="ssh_keygen_comment">تعليق</string>
<string name="ssh_keygen_generate">توليد</string>
<string name="ssh_keygen_copy">نسخ</string>
- <string name="ssh_keygen_show_passphrase">إظهار العبارة السرية</string>
<!-- Misc -->
<string name="dialog_ok">حسناً</string>
@@ -119,7 +111,6 @@
<string name="refresh_list">تحديث القائمة</string>
<string name="show_password">إظهار كلمة السر</string>
<string name="show_extra">إظهار المزيد من المحتوى</string>
- <string name="repository_uri">عنوان المستودع</string>
<string name="app_icon_hint">أيقونة التطبيق</string>
<string name="folder_icon_hint">أيقونة المجلد</string>
diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml
index a6cde10c..24a1913d 100644
--- a/app/src/main/res/values-cs/strings.xml
+++ b/app/src/main/res/values-cs/strings.xml
@@ -26,8 +26,8 @@
<string name="password_exists_message">Toto přepíše%1$s %2$s .</string>
<!-- git commits -->
- <string name="git_commit_add_text">Přídat generované heslo pro %1$s s použítím android password store.</string>
- <string name="git_commit_edit_text">Upravit heslo %1$s s použítím android password store.</string>
+ <string name="git_commit_add_text">Přídat generované heslo pro %1$s s použítím Android Password Store.</string>
+ <string name="git_commit_edit_text">Upravit heslo %1$s s použítím Android Password Store.</string>
<string name="git_commit_remove_text">Odstranit %1$s ze store. </string>
<!-- PGPHandler -->
<string name="provider_toast_text">Nebyl vybrán poskytovatel OpenPGP!</string>
@@ -43,7 +43,6 @@
<string name="jgit_error_dialog_text">Zpráva od jgit: \n</string>
<!-- Git Handler -->
- <string name="forget_username_dialog_text">Zapomněli jste uvést přihlašovací jméno?</string>
<string name="ssh_preferences_dialog_text">Importujte nebo si prosím vygenerujte svůj SSH klíč v nastavení aplikace</string>
<string name="ssh_preferences_dialog_title">Žádný SSH klíč</string>
<string name="ssh_preferences_dialog_import">Import</string>
@@ -64,21 +63,13 @@
<string name="server_protocol">Protokol</string>
<string name="server_url">URL serveru</string>
<string name="server_port_hint">22</string>
- <string name="default_ssh_port">22</string>
- <string name="default_https_port">443</string>
<string name="server_path">Cesta k repozitáři</string>
- <string name="server_path_hint">cesta/k/heslům</string>
<string name="server_user">Jméno</string>
- <string name="server_user_hint">git_username</string>
- <string name="server_resulting_url">Výsledná URL</string>
<string name="connection_mode">Mód ověření</string>
- <string name="warn_malformed_url_port">Při použití vlastního portu, zadejte absolutní cestu (začíná "/")</string>
-
<string name="git_user_name_hint">Jméno</string>
<string name="git_user_email">Email</string>
- <string name="git_user_email_hint">email</string>
<string name="invalid_email_dialog_text">Zadejte prosím platnou emailovou adresu</string>
<string name="clone_button">Klonovat!</string>
@@ -154,13 +145,11 @@
<string name="ssh_keygen_generate">Generovat</string>
<string name="ssh_keygen_copy">Kopírovat</string>
<string name="ssh_keygen_tip">Přidat tento veřejný klíč na Git server.</string>
- <string name="ssh_keygen_show_passphrase">Zobrazit bezpečnostní frázi</string>
<!-- Misc -->
<string name="dialog_ok">OK</string>
<string name="dialog_yes">Ano</string>
<string name="dialog_no">Ne</string>
- <string name="dialog_oops">Ajaj…</string>
<string name="dialog_cancel">Zrušit</string>
<string name="git_sync">Synchronizovat repozitář</string>
<string name="git_pull">Stáhnout ze serveru</string>
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index 77d3fb4f..aae2e888 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -29,7 +29,6 @@
<string name="jgit_error_dialog_text">Message from jgit: \n</string>
<!-- Git Handler -->
- <string name="forget_username_dialog_text">Hast du vergessen einen Nutzernamen zu vergeben?</string>
<string name="ssh_preferences_dialog_text">Please import or generate your SSH key file in the preferences</string>
<string name="ssh_preferences_dialog_title">Kein SSH-Key angegeben</string>
<string name="ssh_preferences_dialog_import">Import</string>
@@ -45,18 +44,11 @@
<string name="server_protocol">Protokoll</string>
<string name="server_url">Server URL</string>
<string name="server_port_hint">22</string>
- <string name="default_ssh_port">22</string>
- <string name="default_https_port">443</string>
<string name="server_path">Repo-Pfad</string>
- <string name="server_path_hint">path/to/pass</string>
<string name="server_user">Nutzername</string>
- <string name="server_user_hint">Git-Nutzername</string>
- <string name="server_resulting_url">Erzeugte URL</string>
<string name="connection_mode">Authentifizierungsmethode</string>
- <string name="warn_malformed_url_port">Wenn du einen anderen Port nutzt, setze den absoluten Pfad (startet mit "/")</string>
-
<string name="git_user_name_hint">Nutzername</string>
<string name="invalid_email_dialog_text">Bitte valide Email eingeben</string>
<string name="clone_button">Klone!</string>
@@ -112,7 +104,6 @@
<string name="pref_external_repository">Externes Repository</string>
<string name="pref_external_repository_summary">Nutze ein externes Repository</string>
<string name="pref_select_external_repository">Wähle ein externes Repository</string>
- <string name="prefs_use_default_file_picker">Benutze Standardauswahl für Dateien</string>
<string name="prefs_export_passwords_title">Passwörter exportieren</string>
<string name="prefs_export_passwords_summary">Exportiert die verschlüsselten Passwörter in ein externes Verzeichnis</string>
<string name="prefs_version">Version</string>
@@ -132,13 +123,11 @@
<string name="ssh_keygen_generate">Generieren</string>
<string name="ssh_keygen_copy">Kopieren</string>
<string name="ssh_keygen_tip">Füge den Public-Key zu deinem Git-Server hinzu.</string>
- <string name="ssh_keygen_show_passphrase">Zeige Passwort</string>
<!-- Misc -->
<string name="dialog_ok">OK</string>
<string name="dialog_yes">Ja</string>
<string name="dialog_no">Nein</string>
- <string name="dialog_oops">Oops…</string>
<string name="dialog_cancel">Abbruch</string>
<string name="git_sync">Synchronisiere Repository</string>
<string name="git_pull">Git Pull</string>
@@ -153,7 +142,6 @@
<string name="send_plaintext_password_to">Passwort senden als Nur-Text mit behilfe von…</string>
<string name="show_password">Password wiedergeben</string>
<string name="show_extra">Zeige weiteren Inhalt</string>
- <string name="repository_uri">Repository URI</string>
<string name="app_icon_hint">App Icon</string>
<string name="folder_icon_hint">Verzeichnis Icon</string>
diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml
index 7ef91026..7a8309bb 100644
--- a/app/src/main/res/values-es/strings.xml
+++ b/app/src/main/res/values-es/strings.xml
@@ -38,7 +38,6 @@
<string name="jgit_error_dialog_text">Mensaje de jgit: \n</string>
<!-- Git Handler -->
- <string name="forget_username_dialog_text">Olvidaste especificar un nombre de usuario?</string>
<string name="ssh_preferences_dialog_text">Por favor importa o genera tu llave SSH en los ajustes</string>
<string name="ssh_preferences_dialog_title">No hay llave SSH</string>
<string name="ssh_preferences_dialog_import">Importar</string>
@@ -61,18 +60,11 @@
<string name="server_protocol">Protocolo</string>
<string name="server_url">URL de servidor</string>
<string name="server_port_hint">22</string>
- <string name="default_ssh_port">22</string>
- <string name="default_https_port">443</string>
<string name="server_path">Ruta del repositorio</string>
- <string name="server_path_hint">ruta/a/claves</string>
<string name="server_user">Nombre de usuario</string>
- <string name="server_user_hint">nombre_usuario</string>
- <string name="server_resulting_url">URL resultante</string>
<string name="connection_mode">Modo de autenticación</string>
- <string name="warn_malformed_url_port">Al usar puertos personalizados, ingresa una ruta absoluta (empieza con "/")</string>
-
<string name="git_user_name_hint">Nombre de usuario</string>
<string name="invalid_email_dialog_text">Por favor ingresa una dirección de correo</string>
<string name="clone_button">¡Clonar!</string>
@@ -134,7 +126,6 @@
<string name="pref_external_repository">Repositorio externo</string>
<string name="pref_external_repository_summary">Usar un repositorio externo para contraseñas</string>
<string name="pref_select_external_repository">Seleccionar repositorio externo</string>
- <string name="prefs_use_default_file_picker">Usar seleccionador de archivos por defecto</string>
<string name="prefs_export_passwords_title">Exportar contraseñas</string>
<string name="prefs_export_passwords_summary">Exporta las contraseñas cifradas a un directorio externo.</string>
<string name="prefs_version">Versión</string>
@@ -160,13 +151,11 @@
<string name="ssh_keygen_generate">Generar</string>
<string name="ssh_keygen_copy">Copiar</string>
<string name="ssh_keygen_tip">Registra esta llave pública en tu servidor Git.</string>
- <string name="ssh_keygen_show_passphrase">Mostrar contraseña</string>
<!-- Misc -->
<string name="dialog_ok">OK</string>
<string name="dialog_yes">Sí</string>
<string name="dialog_no">No</string>
- <string name="dialog_oops">Ups…</string>
<string name="dialog_cancel">Cancelar</string>
<string name="git_sync">Sincronizar con servidor</string>
<string name="git_pull">Descargar del servidor</string>
@@ -181,7 +170,6 @@
<string name="send_plaintext_password_to">Enviar contraseña en texto plano usando…</string>
<string name="show_password">Mostrar contraseña</string>
<string name="show_extra">Mostrar contenido extra</string>
- <string name="repository_uri">URI del repositorio</string>
<string name="app_icon_hint">Ícono de app</string>
<string name="folder_icon_hint">Ícono de directorio</string>
@@ -212,6 +200,5 @@
<string name="hackish_tools">Hackish tools</string>
<string name="abort_rebase">Abortar rebase</string>
<string name="commit_hash">Hash del commit</string>
- <string name="crypto_extra_edit_hint">Username: Nombre de usuario\n… o algún contenido extra</string>
<string name="get_last_changed_failed">Error al obtener la fecha de último cambio</string>
</resources>
diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml
index 32ee4bec..945e3559 100644
--- a/app/src/main/res/values-fr/strings.xml
+++ b/app/src/main/res/values-fr/strings.xml
@@ -44,7 +44,6 @@
<string name="jgit_error_dialog_text">Message de jgit: \n</string>
<!-- Git Handler -->
- <string name="forget_username_dialog_text">Avez-vous oublié de renseigner votre nom d\'utilisateur ?</string>
<string name="ssh_preferences_dialog_text">Vous devez importer ou générer votre fichier de clef SSH dans les préférences</string>
<string name="ssh_preferences_dialog_title">Absence de clef SSH</string>
<string name="ssh_preferences_dialog_import">Importer</string>
@@ -67,21 +66,13 @@
<string name="server_protocol">Protocole</string>
<string name="server_url">URL du serveur</string>
<string name="server_port_hint">22</string>
- <string name="default_ssh_port">22</string>
- <string name="default_https_port">443</string>
<string name="server_path">Chemin du dépôt</string>
- <string name="server_path_hint">path/to/pass</string>
<string name="server_user">Nom d\'utilisateur</string>
- <string name="server_user_hint">git_username</string>
- <string name="server_resulting_url">URL finale</string>
<string name="connection_mode">Méthode d\'authentification</string>
- <string name="warn_malformed_url_port">Lors de l\'utilisation d\'un numéro de port personnalisé, fournissez un chemin absolu (commençant par "/")</string>
-
<string name="git_user_name_hint">Nom d\'utilisateur</string>
<string name="git_user_email">Email</string>
- <string name="git_user_email_hint">email</string>
<string name="invalid_email_dialog_text">Merci de saisir une adresse mail valide</string>
<string name="clone_button">Cloner !</string>
@@ -139,7 +130,6 @@
<string name="pref_external_repository">Dépôt externe</string>
<string name="pref_external_repository_summary">Utilise un dépôt externe pour les mots de passe</string>
<string name="pref_select_external_repository">Choisissez un dépôt externe</string>
- <string name="prefs_use_default_file_picker">Utiliser le selecteur de fichier par défaut</string>
<string name="prefs_export_passwords_title">Exporter les mots de passe</string>
<string name="prefs_export_passwords_summary">Exporter les mots de passe (chiffrés) vers un répertoire externe</string>
<string name="prefs_version">Version</string>
@@ -161,13 +151,11 @@
<string name="ssh_keygen_generate">Générer</string>
<string name="ssh_keygen_copy">Copier</string>
<string name="ssh_keygen_tip">Enregistrez cette clef publique sur votre serveur Git.</string>
- <string name="ssh_keygen_show_passphrase">Afficher le mot de passe</string>
<!-- Misc -->
<string name="dialog_ok">OK</string>
<string name="dialog_yes">Oui</string>
<string name="dialog_no">Non</string>
- <string name="dialog_oops">Oups…</string>
<string name="dialog_cancel">Annuler</string>
<string name="git_sync">Synchronisation du dépôt</string>
<string name="git_pull">Importer du serveur</string>
@@ -182,7 +170,6 @@
<string name="send_plaintext_password_to">Envoyer le mot de passe en clair via…</string>
<string name="show_password">Montrer le mot de passe</string>
<string name="show_extra">Afficher le contenu supplémentaire</string>
- <string name="repository_uri">Adresse du dépot</string>
<string name="app_icon_hint">Icône de l\'application</string>
<string name="folder_icon_hint">Icône du dossier</string>
@@ -212,6 +199,5 @@
<string name="git_operation_remember_passphrase">Se rappeler de la phrase secrète dans la configuration de l\'application (peu sûr)</string>
<string name="hackish_tools">Outils de hack</string>
<string name="commit_hash">Commettre la clé</string>
- <string name="crypto_extra_edit_hint">nom d\'utilisateur: quelque chose d\'autre contenu supplémentaire</string>
<string name="get_last_changed_failed">Failed to get last changed date</string>
</resources>
diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml
index 82d99da6..8bdec1e7 100644
--- a/app/src/main/res/values-ja/strings.xml
+++ b/app/src/main/res/values-ja/strings.xml
@@ -30,7 +30,6 @@
<string name="jgit_error_dialog_text">jgit からのメッセージ: \n</string>
<!-- Git Handler -->
- <string name="forget_username_dialog_text">ユーザー名の指定を忘れましたか?</string>
<string name="ssh_preferences_dialog_text">プリファレンスで SSH 鍵ファイルをインポートまたは生成してください</string>
<string name="ssh_preferences_dialog_title">SSH 鍵がありませんkey</string>
<string name="ssh_preferences_dialog_import">インポート</string>
@@ -46,18 +45,11 @@
<string name="server_protocol">プロトコル</string>
<string name="server_url">サーバー URL</string>
<string name="server_port_hint">22</string>
- <string name="default_ssh_port">22</string>
- <string name="default_https_port">443</string>
<string name="server_path">リポジトリのパス</string>
- <string name="server_path_hint">path/to/pass</string>
<string name="server_user">ユーザー名</string>
- <string name="server_user_hint">git_username</string>
- <string name="server_resulting_url">結果 URL</string>
<string name="connection_mode">認証モード</string>
- <string name="warn_malformed_url_port">カスタムポートを使用する場合は、絶対パスを入力 ("/" で始まる)</string>
-
<string name="git_user_name_hint">ユーザー名</string>
<!-- PGP Handler -->
<string name="crypto_name_hint">名前</string>
@@ -114,13 +106,11 @@
<string name="ssh_keygen_generate">生成</string>
<string name="ssh_keygen_copy">コピー</string>
<string name="ssh_keygen_tip">この公開鍵を Git サーバーに提供してください。</string>
- <string name="ssh_keygen_show_passphrase">パスフレーズを表示</string>
<!-- Misc -->
<string name="dialog_ok">OK</string>
<string name="dialog_yes">はい</string>
<string name="dialog_no">いいえ</string>
- <string name="dialog_oops">おっと…</string>
<string name="dialog_cancel">キャンセル</string>
<string name="git_sync">リポジトリを同期</string>
<string name="git_pull">リモートからプル</string>
diff --git a/app/src/main/res/values-night/colors.xml b/app/src/main/res/values-night/colors.xml
index 53de0bed..7fcc054a 100644
--- a/app/src/main/res/values-night/colors.xml
+++ b/app/src/main/res/values-night/colors.xml
@@ -9,14 +9,10 @@
<color name="primary_light_color">#FF373737</color>
<color name="primary_dark_color">#FF000000</color>
<color name="secondary_color">#FFFF7539</color>
- <color name="secondary_light_color">#FFFFa667</color>
- <color name="secondary_dark_color">#FFC54506</color>
<color name="primary_text_color">#FFFFFFFF</color>
- <color name="secondary_text_color">#FFFFFFFF</color>
<!-- Theme variables -->
<color name="window_background">@color/primary_color</color>
- <color name="color_surface">#FF111111</color>
<color name="navigation_bar_color">@color/primary_color</color>
<color name="list_multiselect_background">#66EEEEEE</color>
<color name="status_bar_color">@color/window_background</color>
diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml
index 39d22662..836b5672 100644
--- a/app/src/main/res/values-ru/strings.xml
+++ b/app/src/main/res/values-ru/strings.xml
@@ -46,7 +46,6 @@
<string name="jgit_error_dialog_text">Сообщение от jgit: \n</string>
<!-- Git Handler -->
- <string name="forget_username_dialog_text">Вы забыли указать имя пользователя?</string>
<string name="ssh_preferences_dialog_text">Пожалуйста, импортируйте или сгенерируйте новый SSH ключ в настройках</string>
<string name="ssh_preferences_dialog_title">Нет SSH ключа</string>
<string name="ssh_preferences_dialog_import">Импортировать</string>
@@ -69,21 +68,13 @@
<string name="server_protocol">Протокол</string>
<string name="server_url">URL сервера</string>
<string name="server_port_hint">22</string>
- <string name="default_ssh_port">22</string>
- <string name="default_https_port">443</string>
<string name="server_path">Путь к репозиторию</string>
- <string name="server_path_hint">путь/до/пароля</string>
<string name="server_user">Имя пользователя</string>
- <string name="server_user_hint">git_username</string>
- <string name="server_resulting_url">Получившийся URL</string>
<string name="connection_mode">Тип авторизации</string>
- <string name="warn_malformed_url_port">При использовании нестандартных портов, укажите полный путь (начинается с "/")</string>
-
<string name="git_user_name_hint">Имя пользователя</string>
<string name="git_user_email">Электронная почта</string>
- <string name="git_user_email_hint">электронная почта</string>
<string name="invalid_email_dialog_text">Введите корректный email</string>
<string name="clone_button">Клонировать</string>
@@ -149,7 +140,6 @@
<string name="pref_external_repository">Внешний репозиторий</string>
<string name="pref_external_repository_summary">Использовать внешний репозиторий</string>
<string name="pref_select_external_repository">Выбрать внешний репозиторий</string>
- <string name="prefs_use_default_file_picker">Использовать стандартное окно выбора файлов</string>
<string name="prefs_export_passwords_title">Экспортировать пароли</string>
<string name="prefs_export_passwords_summary">Экспортировать пароли в открытом виде во внешнее хранилище</string>
<string name="prefs_version">Версия</string>
@@ -193,13 +183,11 @@
<string name="ssh_keygen_generate">Сгенерировать</string>
<string name="ssh_keygen_copy">Скоприровать</string>
<string name="ssh_keygen_tip">Поместите публичный ключ на сервер Git</string>
- <string name="ssh_keygen_show_passphrase">Показать пароль</string>
<!-- Misc -->
<string name="dialog_ok">OK</string>
<string name="dialog_yes">Да</string>
<string name="dialog_no">Нет</string>
- <string name="dialog_oops">Упс…</string>
<string name="dialog_cancel">Отмена</string>
<string name="git_sync">Синхронизировать репозиторий</string>
<string name="git_pull">Пулл с удаленного сервера</string>
@@ -215,7 +203,6 @@
<string name="show_password">Показать пароль</string>
<string name="show_extra">Показать дополнительную информацию</string>
<string name="hide_extra">Скрыть расширенный контекст</string>
- <string name="repository_uri">URI репозитория</string>
<string name="app_icon_hint">Иконка приложения</string>
<string name="folder_icon_hint">Иконка папки</string>
@@ -226,9 +213,7 @@
<string name="oreo_autofill_save_internal_error">Сохранение не удалось из-за внутренней ошибки</string>
<string name="oreo_autofill_save_app_not_supported">Это приложение в настоящее время не поддерживается</string>
<string name="oreo_autofill_save_passwords_dont_match">Пароли не совпадают</string>
- <string name="oreo_autofill_save_invalid_password">Невозможно извлечь пароль, пожалуйста, используйте другой браузер</string>
<string name="oreo_autofill_generate_password">Сгенерировать пароль...</string>
- <string name="oreo_autofill_publisher_changed">Издатель приложения изменился; это может быть попытка фишинга.</string>
<string name="oreo_autofill_max_matches_reached">Достигнуто максимальное количество совпадений (%1$d); очистите совпадения перед тем как добавите новые.</string>
<string name="oreo_autofill_warning_publisher_header">Издатель приложения изменился с тех пор как вы первый раз связали с ним запись хранилища паролей:</string>
<string name="oreo_autofill_warning_publisher_footer"><b>Установленное приложение может попытаться украсть ваши учетные данные, выдавая себя за доверенное приложение</b>\n\nПопробуйте удалить или переустановить  приложение из доверенного источника, такого как Play Store, Amazon Appstore, F-Droid или магазин приложений производителя вашего смартфона.</string>
@@ -275,7 +260,6 @@
<string name="abort_rebase">Прервать перебазирование и записать изменения в новую ветку</string>
<string name="reset_to_remote">Полный сброс до состояния удаленной ветки</string>
<string name="commit_hash">Хэш-сумма изменений</string>
- <string name="crypto_extra_edit_hint">имя пользователя: какой-то другой дополнительный контент</string>
<string name="get_last_changed_failed">Failed to get last changed date</string>
<string name="openkeychain_ssh_api_connect_fail">Ошибка при подключении к сервису OpenKeychain SSH API</string>
<string name="no_ssh_api_provider">Не найдено SSH API провайдеров. OpenKeychain установлен?</string>
diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml
index 5c5ba140..ff3ad60a 100644
--- a/app/src/main/res/values-zh-rCN/strings.xml
+++ b/app/src/main/res/values-zh-rCN/strings.xml
@@ -30,7 +30,6 @@
<string name="jgit_error_dialog_text">Message from jgit:</string>
<!-- Git Handler -->
- <string name="forget_username_dialog_text">你忘了提供用户名了吗?</string>
<string name="ssh_preferences_dialog_text">请在设置中导入或生成你的SSH密钥文件</string>
<string name="ssh_preferences_dialog_title">无SSH密钥</string>
<string name="ssh_preferences_dialog_import">导入</string>
@@ -46,18 +45,11 @@
<string name="server_protocol">接口</string>
<string name="server_url">服务器 URL</string>
<string name="server_port_hint">22</string>
- <string name="default_ssh_port">22</string>
- <string name="default_https_port">443</string>
<string name="server_path">Repo 路径</string>
- <string name="server_path_hint">path/to/pass</string>
<string name="server_user">用户名</string>
- <string name="server_user_hint">git_username</string>
- <string name="server_resulting_url">生成的 URL</string>
<string name="connection_mode">认证模式</string>
- <string name="warn_malformed_url_port">如果使用自定义端口, 请提供绝对路径 (从根目录开始)</string>
-
<string name="git_user_name_hint">用户名</string>
<!-- PGP Handler -->
<string name="crypto_name_hint">名称</string>
@@ -111,13 +103,11 @@
<string name="ssh_keygen_generate">生成</string>
<string name="ssh_keygen_copy">复制</string>
<string name="ssh_keygen_tip">在你的Git服务器上提供此公钥</string>
- <string name="ssh_keygen_show_passphrase">显示口令</string>
<!-- Misc -->
<string name="dialog_ok">确定</string>
<string name="dialog_yes">确定</string>
<string name="dialog_no">否</string>
- <string name="dialog_oops">糟糕…</string>
<string name="dialog_cancel">取消</string>
<string name="git_sync">同步 Repo</string>
<string name="git_pull">Git Pull</string>
diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml
index c9ce813d..cb35d387 100644
--- a/app/src/main/res/values-zh-rTW/strings.xml
+++ b/app/src/main/res/values-zh-rTW/strings.xml
@@ -27,7 +27,6 @@
<string name="jgit_error_dialog_text">Message from jgit:</string>
<!-- Git Handler -->
- <string name="forget_username_dialog_text">你忘記輸入使用者名稱了嗎?</string>
<string name="ssh_preferences_dialog_text">請在設定中匯入或產生你的 SSH 金鑰</string>
<string name="ssh_preferences_dialog_title">無 SSH 金鑰</string>
<string name="ssh_preferences_dialog_import">匯入</string>
@@ -43,18 +42,11 @@
<string name="server_protocol">port</string>
<string name="server_url">伺服器 URL</string>
<string name="server_port_hint">22</string>
- <string name="default_ssh_port">22</string>
- <string name="default_https_port">443</string>
<string name="server_path">Repo 路徑</string>
- <string name="server_path_hint">path/to/pass</string>
<string name="server_user">使用者名稱</string>
- <string name="server_user_hint">git_username</string>
- <string name="server_resulting_url">生成的 URL</string>
<string name="connection_mode">認證模式</string>
- <string name="warn_malformed_url_port">如果使用自定 port, 請使用绝對路徑 (從根目錄開始)</string>
-
<string name="git_user_name_hint">使用者名稱</string>
<!-- PGP Handler -->
<string name="crypto_name_hint">名稱</string>
@@ -108,13 +100,11 @@
<string name="ssh_keygen_generate">產生</string>
<string name="ssh_keygen_copy">複製</string>
<string name="ssh_keygen_tip">在你的 Git 伺服器上提供此公鑰</string>
- <string name="ssh_keygen_show_passphrase">顯示密碼</string>
<!-- Misc -->
<string name="dialog_ok">確定</string>
<string name="dialog_yes">確定</string>
<string name="dialog_no">否</string>
- <string name="dialog_oops">糟糕…</string>
<string name="dialog_cancel">取消</string>
<string name="git_sync">同步 Repo</string>
<string name="git_pull">Git Pull</string>
diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml
index 2849dd4f..5ede6b6f 100644
--- a/app/src/main/res/values/arrays.xml
+++ b/app/src/main/res/values/arrays.xml
@@ -4,15 +4,6 @@
-->
<resources>
- <string-array name="connection_modes" translatable="false">
- <item>ssh-key</item>
- <item>username/password</item>
- <item>OpenKeychain</item>
- </string-array>
- <string-array name="clone_protocols" translatable="false">
- <item>ssh://</item>
- <item>https://</item>
- </string-array>
<string-array name="sort_order_entries">
<item>@string/pref_folder_first_sort_order</item>
<item>@string/pref_file_first_sort_order</item>
@@ -23,12 +14,6 @@
<item>FILE_FIRST</item>
<item>INDEPENDENT</item>
</string-array>
- <string-array name="capitalization_type_entries">
- <item>0</item>
- <item>1</item>
- <item>2</item>
- <item>3</item>
- </string-array>
<string-array name="capitalization_type_values">
<item>lowercase</item>
<item>UPPERCASE</item>
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
index 04f1e99b..55136a37 100644
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -9,17 +9,13 @@
<color name="primary_light_color">#8eacbb</color>
<color name="primary_dark_color">#34515e</color>
<color name="secondary_color">#ff7043</color>
- <color name="secondary_light_color">#ffa270</color>
- <color name="secondary_dark_color">#c63f17</color>
<color name="primary_text_color">#212121</color>
- <color name="secondary_text_color">#ffffff</color>
<color name="white">#ffffffff</color>
<!-- Theme variables -->
<color name="window_background">#eceff1</color>
<color name="ic_launcher_background">#D4F1EA</color>
<color name="color_control_normal">@color/primary_text_color</color>
- <color name="color_surface">#FFFFFF</color>
<color name="list_multiselect_background">#668eacbb</color>
<color name="navigation_bar_color">#000000</color>
<color name="status_bar_color">@color/primary_dark_color</color>
diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml
index 26d94ff7..a76d0bab 100644
--- a/app/src/main/res/values/dimens.xml
+++ b/app/src/main/res/values/dimens.xml
@@ -8,7 +8,6 @@
<dimen name="activity_horizontal_margin">16dp</dimen>
<dimen name="activity_vertical_margin">16dp</dimen>
<dimen name="fab_compat_margin">16dp</dimen>
- <dimen name="fab_margin">8dp</dimen>
<dimen name="normal_margin">8dp</dimen>
<dimen name="bottom_sheet_item_height">56dp</dimen>
</resources>
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 2e37b0eb..4574c44a 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -38,8 +38,8 @@
<string name="password_exists_message">This will overwrite %1$s with %2$s.</string>
<!-- git commits -->
- <string name="git_commit_add_text">Add generated password for %1$s using android password store.</string>
- <string name="git_commit_edit_text">Edit password for %1$s using android password store.</string>
+ <string name="git_commit_add_text">Add generated password for %1$s using Android Password Store.</string>
+ <string name="git_commit_edit_text">Edit password for %1$s using Android Password Store.</string>
<string name="git_commit_remove_text">Remove %1$s from store.</string>
<string name="git_commit_move_text">Rename %1$s to %2$s.</string>
@@ -58,7 +58,6 @@
<string name="jgit_error_dialog_text">Message from jgit: \n</string>
<!-- Git Handler -->
- <string name="forget_username_dialog_text">Did you forget to specify a username?</string>
<string name="set_information_dialog_text">Please fix the remote server configuration in settings before proceeding</string>
<string name="ssh_preferences_dialog_text">Please import or generate your SSH key file in the preferences</string>
<string name="ssh_preferences_dialog_title">No SSH key</string>
@@ -81,26 +80,15 @@
<string name="server_name">Server</string>
<string name="server_protocol">Protocol</string>
<string name="server_url">Server URL</string>
- <string name="server_url_hint" translatable="false">server.com</string>
<string name="server_port_hint">Port</string>
- <string name="default_ssh_port">22</string>
- <string name="default_https_port">443</string>
<string name="server_path">Repo path</string>
- <string name="server_path_hint">path/to/pass</string>
<string name="server_user">Username</string>
- <string name="server_user_hint">git_username</string>
- <string name="server_resulting_url">Resulting URL</string>
<string name="connection_mode">Authentication Mode</string>
- <string name="warn_malformed_url_port">When using custom ports, provide an absolute path (starts with "/")</string>
-
<!-- Git Config fragment -->
- <string name="git_config" translatable="false">Git config</string>
- <string name="git_user_name" translatable="false">Username</string>
<string name="git_user_name_hint">Username</string>
<string name="git_user_email">Email</string>
- <string name="git_user_email_hint">email</string>
<string name="invalid_email_dialog_text">Please enter a valid email address</string>
<string name="clone_button">Clone</string>
@@ -172,7 +160,6 @@
<string name="pref_external_repository">External repository</string>
<string name="pref_external_repository_summary">Use an external password repository</string>
<string name="pref_select_external_repository">Select external repository</string>
- <string name="prefs_use_default_file_picker">Use default file picker</string>
<string name="prefs_export_passwords_title">Export passwords</string>
<string name="prefs_export_passwords_summary">Exports the encrypted passwords to an external directory</string>
<string name="prefs_version">Version</string>
@@ -216,7 +203,6 @@
<string name="ssh_keygen_generate">Generate</string>
<string name="ssh_keygen_copy">Copy</string>
<string name="ssh_keygen_tip">Provide this public key to your Git server.</string>
- <string name="ssh_keygen_show_passphrase">Show passphrase</string>
<string name="ssh_key_gen_generating_progress">Generating keys…</string>
<string name="ssh_keygen_generating_done">Done!</string>
<string name="key_length_2048" translatable="false">2048</string>
@@ -228,7 +214,6 @@
<string name="dialog_no">No</string>
<string name="dialog_positive">Go to Settings</string>
<string name="dialog_negative">Go back</string>
- <string name="dialog_oops">Oops…</string>
<string name="dialog_cancel">Cancel</string>
<string name="git_sync">Synchronize repository</string>
<string name="git_pull">Pull from remote</string>
@@ -244,7 +229,6 @@
<string name="show_password">Show password</string>
<string name="show_extra">Show extra content</string>
<string name="hide_extra">Hide extra content</string>
- <string name="repository_uri">Repository URI</string>
<string name="app_icon_hint">App icon</string>
<string name="folder_icon_hint">Folder icon</string>
@@ -257,9 +241,7 @@
<string name="oreo_autofill_save_internal_error">Save failed due to an internal error</string>
<string name="oreo_autofill_save_app_not_supported">This app is currently not supported</string>
<string name="oreo_autofill_save_passwords_dont_match">Passwords don\'t match</string>
- <string name="oreo_autofill_save_invalid_password">Couldn\'t extract password, please use a different browser for now</string>
<string name="oreo_autofill_generate_password">Generate password…</string>
- <string name="oreo_autofill_publisher_changed">The app\'s publisher has changed; this may be a phishing attempt.</string>
<string name="oreo_autofill_max_matches_reached">Maximum number of matches (%1$d) reached; clear matches before adding new ones.</string>
<string name="oreo_autofill_warning_publisher_header">This app\'s publisher has changed since you first associated a Password Store entry with it:</string>
<string name="oreo_autofill_warning_publisher_footer"><b>The currently installed app may be trying to steal your credentials by pretending to be a trusted app.</b>\n\nTry to uninstall and reinstall the app from a trusted source, such as the Play Store, Amazon Appstore, F-Droid, or your phone manufacturer\'s store.</string>
@@ -313,7 +295,6 @@
<string name="abort_rebase">Abort rebase and push new branch</string>
<string name="reset_to_remote">Hard reset to remote branch</string>
<string name="commit_hash">Commit hash</string>
- <string name="crypto_extra_edit_hint">username: something other extra content</string>
<string name="get_last_changed_failed">Failed to get last changed date</string>
<string name="openkeychain_ssh_api_connect_fail">Failed to connect to OpenKeychain SSH API service.</string>
<string name="no_ssh_api_provider">No SSH API provider found. Is OpenKeychain installed?</string>
@@ -376,4 +357,13 @@
<string name="preference_custom_public_suffixes_title">Custom domains</string>
<string name="preference_custom_public_suffixes_summary">Autofill will distinguish subdomains of these domains</string>
<string name="preference_custom_public_suffixes_hint">company.com\npersonal.com</string>
+
+ <!-- OpenKeychain errors -->
+ <string name="openpgp_error_wrong_passphrase">Incorrect passphrase</string>
+ <string name="openpgp_error_no_user_ids">No matching PGP keys found</string>
+ <string name="openpgp_error_unknown">Error from OpenKeyChain : %s</string>
+
+ <!-- Password creation failure -->
+ <string name="password_creation_file_write_fail_title">Error</string>
+ <string name="password_creation_file_write_fail_message">Failed to write password file to the store, please try again.</string>
</resources>
diff --git a/dependencies.gradle b/dependencies.gradle
index 039be9ba..fe0d0cbd 100644
--- a/dependencies.gradle
+++ b/dependencies.gradle
@@ -35,7 +35,6 @@ ext.deps = [
lifecycle_common: 'androidx.lifecycle:lifecycle-common-java8:2.3.0-alpha04',
lifecycle_livedata_ktx: 'androidx.lifecycle:lifecycle-livedata-ktx:2.3.0-alpha04',
lifecycle_viewmodel_ktx: 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.0-alpha04',
- local_broadcast_manager: 'androidx.localbroadcastmanager:localbroadcastmanager:1.1.0-alpha01',
material: 'com.google.android.material:material:1.3.0-alpha01',
preference: 'androidx.preference:preference:1.1.1',
recycler_view: 'androidx.recyclerview:recyclerview:1.2.0-alpha03',