diff options
| author | Harsh Shandilya <msfjarvis@gmail.com> | 2020-06-12 14:58:15 +0000 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2020-06-12 20:28:15 +0530 |
| commit | d8231e112afb501c43041a6f839ab8285f400f77 (patch) | |
| tree | 6785a8d0c6a70a3017ce5d62fe6b8234d5564f07 /app/src/main/java/com/zeapo/pwdstore/crypto/PgpActivity.kt | |
| parent | bf33fb2c88a208931340201e95a524b274d70b31 (diff) | |
| download | HardenedPass-d8231e112afb501c43041a6f839ab8285f400f77.tar.gz HardenedPass-d8231e112afb501c43041a6f839ab8285f400f77.zip | |
Break down PGP Activity into focused sections (#776)
Diffstat (limited to 'app/src/main/java/com/zeapo/pwdstore/crypto/PgpActivity.kt')
| -rw-r--r-- | app/src/main/java/com/zeapo/pwdstore/crypto/PgpActivity.kt | 788 |
1 files changed, 0 insertions, 788 deletions
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 - } - } - } -} |
