fix: better static utils implementation

This commit is contained in:
J-Jamet 2023-05-08 20:30:16 +02:00
parent c8868f31e6
commit dfb418c12a
39 changed files with 226 additions and 310 deletions

View file

@ -2,10 +2,10 @@ package com.kunzisoft.keepass.tests.stream
import android.content.Context import android.content.Context
import androidx.test.platform.app.InstrumentationRegistry import androidx.test.platform.app.InstrumentationRegistry
import com.kunzisoft.keepass.utils.readAllBytes
import com.kunzisoft.keepass.database.element.binary.BinaryCache import com.kunzisoft.keepass.database.element.binary.BinaryCache
import com.kunzisoft.keepass.database.element.binary.BinaryFile import com.kunzisoft.keepass.database.element.binary.BinaryFile
import com.kunzisoft.keepass.utils.UriUtil import com.kunzisoft.keepass.utils.UriUtil.getBinaryDir
import com.kunzisoft.keepass.utils.readAllBytes
import junit.framework.TestCase.assertEquals import junit.framework.TestCase.assertEquals
import org.junit.Test import org.junit.Test
import java.io.DataInputStream import java.io.DataInputStream
@ -19,7 +19,7 @@ class BinaryDataTest {
InstrumentationRegistry.getInstrumentation().context InstrumentationRegistry.getInstrumentation().context
} }
private val cacheDirectory = UriUtil.getBinaryDir(InstrumentationRegistry.getInstrumentation().targetContext) private val cacheDirectory = InstrumentationRegistry.getInstrumentation().targetContext.getBinaryDir()
private val fileA = File(cacheDirectory, TEST_FILE_CACHE_A) private val fileA = File(cacheDirectory, TEST_FILE_CACHE_A)
private val fileB = File(cacheDirectory, TEST_FILE_CACHE_B) private val fileB = File(cacheDirectory, TEST_FILE_CACHE_B)
private val fileC = File(cacheDirectory, TEST_FILE_CACHE_C) private val fileC = File(cacheDirectory, TEST_FILE_CACHE_C)

View file

@ -30,7 +30,7 @@ import androidx.core.text.HtmlCompat
import com.kunzisoft.keepass.BuildConfig import com.kunzisoft.keepass.BuildConfig
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.stylish.StylishActivity import com.kunzisoft.keepass.activities.stylish.StylishActivity
import com.kunzisoft.keepass.utils.UriUtil import com.kunzisoft.keepass.utils.UriUtil.isContributingUser
import org.joda.time.DateTime import org.joda.time.DateTime
class AboutActivity : StylishActivity() { class AboutActivity : StylishActivity() {
@ -46,7 +46,7 @@ class AboutActivity : StylishActivity() {
supportActionBar?.setDisplayHomeAsUpEnabled(true) supportActionBar?.setDisplayHomeAsUpEnabled(true)
supportActionBar?.setDisplayShowHomeEnabled(true) supportActionBar?.setDisplayShowHomeEnabled(true)
val appName = if (UriUtil.contributingUser(this)) val appName = if (this.isContributingUser())
getString(R.string.app_name) + " " + getString(R.string.app_name_part3) getString(R.string.app_name) + " " + getString(R.string.app_name_part3)
else else
getString(R.string.app_name) getString(R.string.app_name)

View file

@ -36,11 +36,11 @@ import androidx.activity.result.ActivityResultLauncher
import androidx.activity.viewModels import androidx.activity.viewModels
import androidx.appcompat.widget.Toolbar import androidx.appcompat.widget.Toolbar
import androidx.coordinatorlayout.widget.CoordinatorLayout import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.core.graphics.BlendModeColorFilterCompat import androidx.core.graphics.BlendModeColorFilterCompat
import androidx.core.graphics.BlendModeCompat import androidx.core.graphics.BlendModeCompat
import androidx.core.graphics.ColorUtils import androidx.core.graphics.ColorUtils
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.appbar.AppBarLayout import com.google.android.material.appbar.AppBarLayout
import com.google.android.material.appbar.CollapsingToolbarLayout import com.google.android.material.appbar.CollapsingToolbarLayout
import com.google.android.material.progressindicator.LinearProgressIndicator import com.google.android.material.progressindicator.LinearProgressIndicator
@ -67,7 +67,7 @@ import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.tasks.ActionRunnable import com.kunzisoft.keepass.tasks.ActionRunnable
import com.kunzisoft.keepass.tasks.AttachmentFileBinderManager import com.kunzisoft.keepass.tasks.AttachmentFileBinderManager
import com.kunzisoft.keepass.timeout.TimeoutHelper import com.kunzisoft.keepass.timeout.TimeoutHelper
import com.kunzisoft.keepass.utils.UriUtil import com.kunzisoft.keepass.utils.UriUtil.openUrl
import com.kunzisoft.keepass.utils.UuidUtil import com.kunzisoft.keepass.utils.UuidUtil
import com.kunzisoft.keepass.view.changeControlColor import com.kunzisoft.keepass.view.changeControlColor
import com.kunzisoft.keepass.view.changeTitleColor import com.kunzisoft.keepass.view.changeTitleColor
@ -473,7 +473,7 @@ class EntryActivity : DatabaseLockActivity() {
} }
R.id.menu_goto_url -> { R.id.menu_goto_url -> {
mUrl?.let { url -> mUrl?.let { url ->
UriUtil.gotoUrl(this, url) this.openUrl(url)
} }
return true return true
} }

View file

@ -76,7 +76,7 @@ import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.tasks.ActionRunnable import com.kunzisoft.keepass.tasks.ActionRunnable
import com.kunzisoft.keepass.tasks.AttachmentFileBinderManager import com.kunzisoft.keepass.tasks.AttachmentFileBinderManager
import com.kunzisoft.keepass.timeout.TimeoutHelper import com.kunzisoft.keepass.timeout.TimeoutHelper
import com.kunzisoft.keepass.utils.UriUtil import com.kunzisoft.keepass.utils.UriUtil.getDocumentFile
import com.kunzisoft.keepass.view.* import com.kunzisoft.keepass.view.*
import com.kunzisoft.keepass.viewmodels.ColorPickerViewModel import com.kunzisoft.keepass.viewmodels.ColorPickerViewModel
import com.kunzisoft.keepass.viewmodels.EntryEditViewModel import com.kunzisoft.keepass.viewmodels.EntryEditViewModel
@ -185,7 +185,7 @@ class EntryEditActivity : DatabaseLockActivity(),
mExternalFileHelper = ExternalFileHelper(this) mExternalFileHelper = ExternalFileHelper(this)
mExternalFileHelper?.buildOpenDocument { uri -> mExternalFileHelper?.buildOpenDocument { uri ->
uri?.let { attachmentToUploadUri -> uri?.let { attachmentToUploadUri ->
UriUtil.getFileData(this, attachmentToUploadUri)?.also { documentFile -> attachmentToUploadUri.getDocumentFile(this)?.also { documentFile ->
documentFile.name?.let { fileName -> documentFile.name?.let { fileName ->
if (documentFile.length() > MAX_WARNING_BINARY_FILE) { if (documentFile.length() > MAX_WARNING_BINARY_FILE) {
FileTooBigDialogFragment.build(attachmentToUploadUri, fileName) FileTooBigDialogFragment.build(attachmentToUploadUri, fileName)

View file

@ -54,8 +54,8 @@ import com.kunzisoft.keepass.app.database.FileDatabaseHistoryAction
import com.kunzisoft.keepass.autofill.AutofillComponent import com.kunzisoft.keepass.autofill.AutofillComponent
import com.kunzisoft.keepass.autofill.AutofillHelper import com.kunzisoft.keepass.autofill.AutofillHelper
import com.kunzisoft.keepass.database.element.Database import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.education.FileDatabaseSelectActivityEducation
import com.kunzisoft.keepass.database.element.MainCredential import com.kunzisoft.keepass.database.element.MainCredential
import com.kunzisoft.keepass.education.FileDatabaseSelectActivityEducation
import com.kunzisoft.keepass.hardware.HardwareKey import com.kunzisoft.keepass.hardware.HardwareKey
import com.kunzisoft.keepass.model.RegisterInfo import com.kunzisoft.keepass.model.RegisterInfo
import com.kunzisoft.keepass.model.SearchInfo import com.kunzisoft.keepass.model.SearchInfo
@ -65,7 +65,12 @@ import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.DATABASE_URI_KEY import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.DATABASE_URI_KEY
import com.kunzisoft.keepass.settings.PreferencesUtil import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.tasks.ActionRunnable import com.kunzisoft.keepass.tasks.ActionRunnable
import com.kunzisoft.keepass.utils.* import com.kunzisoft.keepass.utils.DexUtil
import com.kunzisoft.keepass.utils.MagikeyboardUtil
import com.kunzisoft.keepass.utils.MenuUtil
import com.kunzisoft.keepass.utils.UriHelper.parseUri
import com.kunzisoft.keepass.utils.UriUtil.isContributingUser
import com.kunzisoft.keepass.utils.UriUtil.openUrl
import com.kunzisoft.keepass.view.asError import com.kunzisoft.keepass.view.asError
import com.kunzisoft.keepass.view.showActionErrorIfNeeded import com.kunzisoft.keepass.view.showActionErrorIfNeeded
import com.kunzisoft.keepass.viewmodels.DatabaseFilesViewModel import com.kunzisoft.keepass.viewmodels.DatabaseFilesViewModel
@ -179,7 +184,7 @@ class FileDatabaseSelectActivity : DatabaseModeActivity(),
&& savedInstanceState.getBoolean(EXTRA_STAY, false))) { && savedInstanceState.getBoolean(EXTRA_STAY, false))) {
val databasePath = PreferencesUtil.getDefaultDatabasePath(this) val databasePath = PreferencesUtil.getDefaultDatabasePath(this)
UriUtil.parse(databasePath)?.let { databaseFileUri -> databasePath?.parseUri()?.let { databaseFileUri ->
launchPasswordActivityWithPath(databaseFileUri) launchPasswordActivityWithPath(databaseFileUri)
} ?: run { } ?: run {
Log.i(TAG, "No default database to prepare") Log.i(TAG, "No default database to prepare")
@ -326,7 +331,7 @@ class FileDatabaseSelectActivity : DatabaseModeActivity(),
super.onResume() super.onResume()
// Define special title // Define special title
specialTitle?.isVisible = UriUtil.contributingUser(this) specialTitle?.isVisible = this.isContributingUser()
// Show open and create button or special mode // Show open and create button or special mode
when (mSpecialMode) { when (mSpecialMode) {
@ -426,7 +431,7 @@ class FileDatabaseSelectActivity : DatabaseModeActivity(),
override fun onOptionsItemSelected(item: MenuItem): Boolean { override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) { when (item.itemId) {
android.R.id.home -> UriUtil.gotoUrl(this, R.string.file_manager_explanation_url) android.R.id.home -> this.openUrl(R.string.file_manager_explanation_url)
} }
MenuUtil.onDefaultMenuOptionsItemSelected(this, item) MenuUtil.onDefaultMenuOptionsItemSelected(this, item)
return super.onOptionsItemSelected(item) return super.onOptionsItemSelected(item)

View file

@ -69,7 +69,6 @@ import com.kunzisoft.keepass.database.search.SearchParameters
import com.kunzisoft.keepass.education.GroupActivityEducation import com.kunzisoft.keepass.education.GroupActivityEducation
import com.kunzisoft.keepass.magikeyboard.MagikeyboardService import com.kunzisoft.keepass.magikeyboard.MagikeyboardService
import com.kunzisoft.keepass.model.GroupInfo import com.kunzisoft.keepass.model.GroupInfo
import com.kunzisoft.keepass.database.element.MainCredential
import com.kunzisoft.keepass.model.RegisterInfo import com.kunzisoft.keepass.model.RegisterInfo
import com.kunzisoft.keepass.model.SearchInfo import com.kunzisoft.keepass.model.SearchInfo
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_ENTRY_TASK import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_ENTRY_TASK
@ -81,7 +80,7 @@ import com.kunzisoft.keepass.settings.SettingsActivity
import com.kunzisoft.keepass.tasks.ActionRunnable import com.kunzisoft.keepass.tasks.ActionRunnable
import com.kunzisoft.keepass.timeout.TimeoutHelper import com.kunzisoft.keepass.timeout.TimeoutHelper
import com.kunzisoft.keepass.utils.BACK_PREVIOUS_KEYBOARD_ACTION import com.kunzisoft.keepass.utils.BACK_PREVIOUS_KEYBOARD_ACTION
import com.kunzisoft.keepass.utils.UriUtil import com.kunzisoft.keepass.utils.UriUtil.openUrl
import com.kunzisoft.keepass.view.* import com.kunzisoft.keepass.view.*
import com.kunzisoft.keepass.viewmodels.GroupEditViewModel import com.kunzisoft.keepass.viewmodels.GroupEditViewModel
import com.kunzisoft.keepass.viewmodels.GroupViewModel import com.kunzisoft.keepass.viewmodels.GroupViewModel
@ -302,7 +301,7 @@ class GroupActivity : DatabaseLockActivity(),
lockAndExit() lockAndExit()
} }
R.id.menu_contribute -> { R.id.menu_contribute -> {
UriUtil.gotoUrl(this@GroupActivity, R.string.contribution_url) this@GroupActivity.openUrl(R.string.contribution_url)
} }
R.id.menu_about -> { R.id.menu_about -> {
startActivity(Intent(this@GroupActivity, AboutActivity::class.java)) startActivity(Intent(this@GroupActivity, AboutActivity::class.java))

View file

@ -46,7 +46,8 @@ import com.kunzisoft.keepass.database.element.icon.IconImage
import com.kunzisoft.keepass.database.element.icon.IconImageCustom import com.kunzisoft.keepass.database.element.icon.IconImageCustom
import com.kunzisoft.keepass.settings.PreferencesUtil import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.tasks.BinaryDatabaseManager import com.kunzisoft.keepass.tasks.BinaryDatabaseManager
import com.kunzisoft.keepass.utils.UriUtil import com.kunzisoft.keepass.utils.UriUtil.getDocumentFile
import com.kunzisoft.keepass.utils.UriUtil.openUrl
import com.kunzisoft.keepass.view.asError import com.kunzisoft.keepass.view.asError
import com.kunzisoft.keepass.view.updateLockPaddingLeft import com.kunzisoft.keepass.view.updateLockPaddingLeft
import com.kunzisoft.keepass.viewmodels.IconPickerViewModel import com.kunzisoft.keepass.viewmodels.IconPickerViewModel
@ -243,7 +244,7 @@ class IconPickerActivity : DatabaseLockActivity() {
} }
} }
R.id.menu_external_icon -> { R.id.menu_external_icon -> {
UriUtil.gotoUrl(this, R.string.external_icon_url) this.openUrl(R.string.external_icon_url)
} }
} }
@ -257,7 +258,7 @@ class IconPickerActivity : DatabaseLockActivity() {
// on Progress with thread // on Progress with thread
val asyncResult: Deferred<IconPickerViewModel.IconCustomState?> = async { val asyncResult: Deferred<IconPickerViewModel.IconCustomState?> = async {
val iconCustomState = IconPickerViewModel.IconCustomState(null, true, R.string.error_upload_file) val iconCustomState = IconPickerViewModel.IconCustomState(null, true, R.string.error_upload_file)
UriUtil.getFileData(this@IconPickerActivity, iconToUploadUri)?.also { documentFile -> iconToUploadUri?.getDocumentFile(this@IconPickerActivity)?.also { documentFile ->
if (documentFile.length() > MAX_ICON_SIZE) { if (documentFile.length() > MAX_ICON_SIZE) {
iconCustomState.errorStringId = R.string.error_file_to_big iconCustomState.errorStringId = R.string.error_file_to_big
} else { } else {

View file

@ -73,6 +73,7 @@ import com.kunzisoft.keepass.tasks.ActionRunnable
import com.kunzisoft.keepass.utils.BACK_PREVIOUS_KEYBOARD_ACTION import com.kunzisoft.keepass.utils.BACK_PREVIOUS_KEYBOARD_ACTION
import com.kunzisoft.keepass.utils.MenuUtil import com.kunzisoft.keepass.utils.MenuUtil
import com.kunzisoft.keepass.utils.UriUtil import com.kunzisoft.keepass.utils.UriUtil
import com.kunzisoft.keepass.utils.UriUtil.getUri
import com.kunzisoft.keepass.view.MainCredentialView import com.kunzisoft.keepass.view.MainCredentialView
import com.kunzisoft.keepass.view.asError import com.kunzisoft.keepass.view.asError
import com.kunzisoft.keepass.view.showActionErrorIfNeeded import com.kunzisoft.keepass.view.showActionErrorIfNeeded
@ -344,7 +345,7 @@ class MainCredentialActivity : DatabaseModeActivity(), AdvancedUnlockFragment.Bu
if (action == VIEW_INTENT) { if (action == VIEW_INTENT) {
fillCredentials( fillCredentials(
intent.data, intent.data,
UriUtil.getUriFromIntent(intent, KEY_KEYFILE), intent.getUri(KEY_KEYFILE),
HardwareKey.getHardwareKeyFromString(intent.getStringExtra(KEY_HARDWARE_KEY)) HardwareKey.getHardwareKeyFromString(intent.getStringExtra(KEY_HARDWARE_KEY))
) )
} else { } else {
@ -357,7 +358,7 @@ class MainCredentialActivity : DatabaseModeActivity(), AdvancedUnlockFragment.Bu
try { try {
intent?.removeExtra(KEY_KEYFILE) intent?.removeExtra(KEY_KEYFILE)
intent?.removeExtra(KEY_HARDWARE_KEY) intent?.removeExtra(KEY_HARDWARE_KEY)
} catch (e: Exception) {} } catch (_: Exception) {}
mDatabaseFileUri?.let { mDatabaseFileUri?.let {
mDatabaseFileViewModel.checkIfIsDefaultDatabase(it) mDatabaseFileViewModel.checkIfIsDefaultDatabase(it)
} }

View file

@ -21,12 +21,12 @@ package com.kunzisoft.keepass.activities.dialogs
import android.app.Dialog import android.app.Dialog
import android.os.Bundle import android.os.Bundle
import androidx.fragment.app.DialogFragment
import androidx.appcompat.app.AlertDialog
import android.widget.Button import android.widget.Button
import android.widget.TextView import android.widget.TextView
import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.DialogFragment
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.utils.UriUtil import com.kunzisoft.keepass.utils.UriUtil.openUrl
class FileManagerDialogFragment : DialogFragment() { class FileManagerDialogFragment : DialogFragment() {
@ -42,7 +42,7 @@ class FileManagerDialogFragment : DialogFragment() {
textDescription.text = getString(R.string.file_manager_install_description) textDescription.text = getString(R.string.file_manager_install_description)
root.findViewById<Button>(R.id.file_manager_button).setOnClickListener { root.findViewById<Button>(R.id.file_manager_button).setOnClickListener {
UriUtil.gotoUrl(requireContext(), R.string.file_manager_explanation_url) context?.openUrl(R.string.file_manager_explanation_url)
dismiss() dismiss()
} }

View file

@ -28,7 +28,7 @@ import androidx.appcompat.app.AlertDialog
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.helpers.ExternalFileHelper import com.kunzisoft.keepass.activities.helpers.ExternalFileHelper
import com.kunzisoft.keepass.database.element.MainCredential import com.kunzisoft.keepass.database.element.MainCredential
import com.kunzisoft.keepass.utils.UriUtil import com.kunzisoft.keepass.utils.UriUtil.getDocumentFile
import com.kunzisoft.keepass.view.MainCredentialView import com.kunzisoft.keepass.view.MainCredentialView
class MainCredentialDialogFragment : DatabaseDialogFragment() { class MainCredentialDialogFragment : DatabaseDialogFragment() {
@ -74,7 +74,7 @@ class MainCredentialDialogFragment : DatabaseDialogFragment() {
mainCredentialView = root.findViewById(R.id.main_credential_view) mainCredentialView = root.findViewById(R.id.main_credential_view)
databaseUri?.let { databaseUri?.let {
root.findViewById<TextView>(R.id.title_database)?.text = root.findViewById<TextView>(R.id.title_database)?.text =
UriUtil.getFileData(requireContext(), it)?.name it.getDocumentFile(requireContext())?.name
} }
builder.setView(root) builder.setView(root)
// Add action buttons // Add action buttons

View file

@ -28,7 +28,7 @@ import androidx.core.text.HtmlCompat.FROM_HTML_MODE_LEGACY
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
import com.kunzisoft.keepass.BuildConfig import com.kunzisoft.keepass.BuildConfig
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.utils.UriUtil import com.kunzisoft.keepass.utils.UriUtil.openUrl
/** /**
* Custom Dialog that asks the user to download the pro version or make a donation. * Custom Dialog that asks the user to download the pro version or make a donation.
@ -45,7 +45,7 @@ class ProFeatureDialogFragment : DialogFragment() {
stringBuilder.append(HtmlCompat.fromHtml(getString(R.string.html_text_ad_free), FROM_HTML_MODE_LEGACY)).append("\n\n") stringBuilder.append(HtmlCompat.fromHtml(getString(R.string.html_text_ad_free), FROM_HTML_MODE_LEGACY)).append("\n\n")
stringBuilder.append(HtmlCompat.fromHtml(getString(R.string.html_text_buy_pro), FROM_HTML_MODE_LEGACY)) stringBuilder.append(HtmlCompat.fromHtml(getString(R.string.html_text_buy_pro), FROM_HTML_MODE_LEGACY))
builder.setPositiveButton(R.string.download) { _, _ -> builder.setPositiveButton(R.string.download) { _, _ ->
UriUtil.gotoUrl(activity, activity.openUrl(
activity.getString(R.string.play_store_url, activity.getString(R.string.play_store_url,
activity.getString(R.string.keepro_app_id)) activity.getString(R.string.keepro_app_id))
) )
@ -54,7 +54,7 @@ class ProFeatureDialogFragment : DialogFragment() {
stringBuilder.append(HtmlCompat.fromHtml(getString(R.string.html_text_feature_generosity), FROM_HTML_MODE_LEGACY)).append("\n\n") stringBuilder.append(HtmlCompat.fromHtml(getString(R.string.html_text_feature_generosity), FROM_HTML_MODE_LEGACY)).append("\n\n")
stringBuilder.append(HtmlCompat.fromHtml(getString(R.string.html_text_donation), FROM_HTML_MODE_LEGACY)) stringBuilder.append(HtmlCompat.fromHtml(getString(R.string.html_text_donation), FROM_HTML_MODE_LEGACY))
builder.setPositiveButton(R.string.contribute) { _, _ -> builder.setPositiveButton(R.string.contribute) { _, _ ->
UriUtil.gotoUrl(activity, R.string.contribution_url) activity.openUrl(R.string.contribution_url)
} }
} }
builder.setMessage(stringBuilder) builder.setMessage(stringBuilder)

View file

@ -35,11 +35,12 @@ import com.google.android.material.textfield.TextInputLayout
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.helpers.ExternalFileHelper import com.kunzisoft.keepass.activities.helpers.ExternalFileHelper
import com.kunzisoft.keepass.activities.helpers.setOpenDocumentClickListener import com.kunzisoft.keepass.activities.helpers.setOpenDocumentClickListener
import com.kunzisoft.keepass.database.element.MainCredential
import com.kunzisoft.keepass.hardware.HardwareKey import com.kunzisoft.keepass.hardware.HardwareKey
import com.kunzisoft.keepass.hardware.HardwareKeyActivity import com.kunzisoft.keepass.hardware.HardwareKeyActivity
import com.kunzisoft.keepass.database.element.MainCredential
import com.kunzisoft.keepass.password.PasswordEntropy import com.kunzisoft.keepass.password.PasswordEntropy
import com.kunzisoft.keepass.utils.UriUtil import com.kunzisoft.keepass.utils.UriUtil.getDocumentFile
import com.kunzisoft.keepass.utils.UriUtil.openUrl
import com.kunzisoft.keepass.view.HardwareKeySelectionView import com.kunzisoft.keepass.view.HardwareKeySelectionView
import com.kunzisoft.keepass.view.KeyFileSelectionView import com.kunzisoft.keepass.view.KeyFileSelectionView
import com.kunzisoft.keepass.view.PassKeyView import com.kunzisoft.keepass.view.PassKeyView
@ -136,7 +137,7 @@ class SetMainCredentialDialogFragment : DatabaseDialogFragment() {
.setNegativeButton(android.R.string.cancel) { _, _ -> } .setNegativeButton(android.R.string.cancel) { _, _ -> }
rootView.findViewById<View>(R.id.credentials_information)?.setOnClickListener { rootView.findViewById<View>(R.id.credentials_information)?.setOnClickListener {
UriUtil.gotoUrl(activity, R.string.credentials_explanation_url) activity.openUrl(R.string.credentials_explanation_url)
} }
passwordCheckBox = rootView.findViewById(R.id.password_checkbox) passwordCheckBox = rootView.findViewById(R.id.password_checkbox)
@ -154,7 +155,7 @@ class SetMainCredentialDialogFragment : DatabaseDialogFragment() {
mExternalFileHelper = ExternalFileHelper(this) mExternalFileHelper = ExternalFileHelper(this)
mExternalFileHelper?.buildOpenDocument { uri -> mExternalFileHelper?.buildOpenDocument { uri ->
uri?.let { pathUri -> uri?.let { pathUri ->
UriUtil.getFileData(requireContext(), uri)?.length()?.let { lengthFile -> pathUri.getDocumentFile(requireContext())?.length()?.let { lengthFile ->
keyFileSelectionView.error = null keyFileSelectionView.error = null
keyFileCheckBox.isChecked = true keyFileCheckBox.isChecked = true
keyFileSelectionView.uri = pathUri keyFileSelectionView.uri = pathUri

View file

@ -44,7 +44,8 @@ import com.kunzisoft.keepass.otp.OtpElement.Companion.MIN_TOTP_PERIOD
import com.kunzisoft.keepass.otp.OtpTokenType import com.kunzisoft.keepass.otp.OtpTokenType
import com.kunzisoft.keepass.otp.OtpType import com.kunzisoft.keepass.otp.OtpType
import com.kunzisoft.keepass.otp.TokenCalculator import com.kunzisoft.keepass.otp.TokenCalculator
import com.kunzisoft.keepass.utils.UriUtil import com.kunzisoft.keepass.utils.UriUtil.isContributingUser
import com.kunzisoft.keepass.utils.UriUtil.openUrl
import java.util.* import java.util.*
class SetOTPDialogFragment : DatabaseDialogFragment() { class SetOTPDialogFragment : DatabaseDialogFragment() {
@ -205,7 +206,7 @@ class SetOTPDialogFragment : DatabaseDialogFragment() {
} }
// Proprietary only on full version // Proprietary only on full version
mTotpTokenTypeArray = OtpTokenType.getTotpTokenTypeValues( mTotpTokenTypeArray = OtpTokenType.getTotpTokenTypeValues(
UriUtil.contributingUser(activity) activity.isContributingUser()
) )
totpTokenTypeAdapter = ArrayAdapter(activity, totpTokenTypeAdapter = ArrayAdapter(activity,
android.R.layout.simple_spinner_item, mTotpTokenTypeArray!!).apply { android.R.layout.simple_spinner_item, mTotpTokenTypeArray!!).apply {
@ -241,7 +242,7 @@ class SetOTPDialogFragment : DatabaseDialogFragment() {
} }
root?.findViewById<View>(R.id.otp_information)?.setOnClickListener { root?.findViewById<View>(R.id.otp_information)?.setOnClickListener {
UriUtil.gotoUrl(activity, R.string.otp_explanation_url) activity.openUrl(R.string.otp_explanation_url)
} }
return builder.create() return builder.create()

View file

@ -26,7 +26,7 @@ import androidx.appcompat.app.AlertDialog
import androidx.core.text.HtmlCompat import androidx.core.text.HtmlCompat
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.utils.UriUtil import com.kunzisoft.keepass.utils.UriUtil.openUrl
/** /**
* Custom Dialog that asks the user to download the pro version or make a donation. * Custom Dialog that asks the user to download the pro version or make a donation.
@ -40,7 +40,7 @@ class UnderDevelopmentFeatureDialogFragment : DialogFragment() {
val stringBuilder = SpannableStringBuilder() val stringBuilder = SpannableStringBuilder()
/* /*
if (UriUtil.contributingUser(activity)) { if (activity.isContributingUser()) {
stringBuilder.append(HtmlCompat.fromHtml(getString(R.string.html_text_dev_feature_thanks), HtmlCompat.FROM_HTML_MODE_LEGACY)).append("\n\n") stringBuilder.append(HtmlCompat.fromHtml(getString(R.string.html_text_dev_feature_thanks), HtmlCompat.FROM_HTML_MODE_LEGACY)).append("\n\n")
.append(HtmlCompat.fromHtml(getString(R.string.html_rose), HtmlCompat.FROM_HTML_MODE_LEGACY)).append("\n\n") .append(HtmlCompat.fromHtml(getString(R.string.html_rose), HtmlCompat.FROM_HTML_MODE_LEGACY)).append("\n\n")
.append(HtmlCompat.fromHtml(getString(R.string.html_text_dev_feature_work_hard), HtmlCompat.FROM_HTML_MODE_LEGACY)).append("\n") .append(HtmlCompat.fromHtml(getString(R.string.html_text_dev_feature_work_hard), HtmlCompat.FROM_HTML_MODE_LEGACY)).append("\n")
@ -52,7 +52,7 @@ class UnderDevelopmentFeatureDialogFragment : DialogFragment() {
.append(HtmlCompat.fromHtml(getString(R.string.html_text_dev_feature_contibute), HtmlCompat.FROM_HTML_MODE_LEGACY)).append(" ") .append(HtmlCompat.fromHtml(getString(R.string.html_text_dev_feature_contibute), HtmlCompat.FROM_HTML_MODE_LEGACY)).append(" ")
.append(HtmlCompat.fromHtml(getString(R.string.html_text_dev_feature_encourage), HtmlCompat.FROM_HTML_MODE_LEGACY)) .append(HtmlCompat.fromHtml(getString(R.string.html_text_dev_feature_encourage), HtmlCompat.FROM_HTML_MODE_LEGACY))
builder.setPositiveButton(R.string.contribute) { _, _ -> builder.setPositiveButton(R.string.contribute) { _, _ ->
UriUtil.gotoUrl(requireContext(), R.string.contribution_url) context?.openUrl(R.string.contribution_url)
} }
//} //}
builder.setMessage(stringBuilder) builder.setMessage(stringBuilder)

View file

@ -33,7 +33,7 @@ import androidx.activity.result.contract.ActivityResultContracts
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import com.kunzisoft.keepass.activities.dialogs.FileManagerDialogFragment import com.kunzisoft.keepass.activities.dialogs.FileManagerDialogFragment
import com.kunzisoft.keepass.utils.UriUtil import com.kunzisoft.keepass.utils.UriUtil.takeUriPermission
class ExternalFileHelper { class ExternalFileHelper {
@ -57,10 +57,8 @@ class ExternalFileHelper {
fun buildOpenDocument(onFileSelected: ((uri: Uri?) -> Unit)?) { fun buildOpenDocument(onFileSelected: ((uri: Uri?) -> Unit)?) {
val resultCallback = ActivityResultCallback<Uri?> { result -> val resultCallback = ActivityResultCallback<Uri?> { result ->
result?.let { uri -> activity?.contentResolver?.takeUriPermission(result)
UriUtil.takeUriPermission(activity?.contentResolver, uri) onFileSelected?.invoke(result)
onFileSelected?.invoke(uri)
}
} }
getContentResultLauncher = if (fragment != null) { getContentResultLauncher = if (fragment != null) {

View file

@ -9,6 +9,7 @@ import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.MainCredential import com.kunzisoft.keepass.database.element.MainCredential
import com.kunzisoft.keepass.model.CipherEncryptDatabase import com.kunzisoft.keepass.model.CipherEncryptDatabase
import com.kunzisoft.keepass.tasks.ActionRunnable import com.kunzisoft.keepass.tasks.ActionRunnable
import com.kunzisoft.keepass.utils.UriUtil.getBinaryDir
import com.kunzisoft.keepass.viewmodels.DatabaseViewModel import com.kunzisoft.keepass.viewmodels.DatabaseViewModel
abstract class DatabaseActivity: StylishActivity(), DatabaseRetrieval { abstract class DatabaseActivity: StylishActivity(), DatabaseRetrieval {
@ -76,7 +77,7 @@ abstract class DatabaseActivity: StylishActivity(), DatabaseRetrieval {
} }
protected fun closeDatabase() { protected fun closeDatabase() {
mDatabase?.clearAndClose(this) mDatabase?.clearAndClose(this.getBinaryDir())
} }
override fun onResume() { override fun onResume() {

View file

@ -27,7 +27,8 @@ import com.kunzisoft.keepass.model.DatabaseFile
import com.kunzisoft.keepass.settings.PreferencesUtil import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.utils.IOActionTask import com.kunzisoft.keepass.utils.IOActionTask
import com.kunzisoft.keepass.utils.SingletonHolderParameter import com.kunzisoft.keepass.utils.SingletonHolderParameter
import com.kunzisoft.keepass.utils.UriUtilDatabase import com.kunzisoft.keepass.utils.UriHelper.decodeUri
import com.kunzisoft.keepass.utils.UriHelper.parseUri
import com.kunzisoft.keepass.viewmodels.FileDatabaseInfo import com.kunzisoft.keepass.viewmodels.FileDatabaseInfo
class FileDatabaseHistoryAction(private val applicationContext: Context) { class FileDatabaseHistoryAction(private val applicationContext: Context) {
@ -46,9 +47,9 @@ class FileDatabaseHistoryAction(private val applicationContext: Context) {
databaseUri) databaseUri)
DatabaseFile( DatabaseFile(
databaseUri, databaseUri,
UriUtilDatabase.parse(fileDatabaseHistoryEntity?.keyFileUri), fileDatabaseHistoryEntity?.keyFileUri?.parseUri(),
HardwareKey.getHardwareKeyFromString(fileDatabaseHistoryEntity?.hardwareKey), HardwareKey.getHardwareKeyFromString(fileDatabaseHistoryEntity?.hardwareKey),
UriUtilDatabase.decode(fileDatabaseHistoryEntity?.databaseUri), fileDatabaseHistoryEntity?.databaseUri?.decodeUri(),
fileDatabaseInfo.retrieveDatabaseAlias(fileDatabaseHistoryEntity?.databaseAlias fileDatabaseInfo.retrieveDatabaseAlias(fileDatabaseHistoryEntity?.databaseAlias
?: ""), ?: ""),
fileDatabaseInfo.exists, fileDatabaseInfo.exists,
@ -71,8 +72,7 @@ class FileDatabaseHistoryAction(private val applicationContext: Context) {
{ {
it?.let { fileHistoryEntity -> it?.let { fileHistoryEntity ->
fileHistoryEntity.keyFileUri?.let { keyFileUri -> fileHistoryEntity.keyFileUri?.let { keyFileUri ->
keyFileUriResultListener.invoke(UriUtilDatabase.parse( keyFileUriResultListener.invoke(keyFileUri.parseUri())
keyFileUri))
} }
} ?: keyFileUriResultListener.invoke(null) } ?: keyFileUriResultListener.invoke(null)
} }
@ -96,10 +96,10 @@ class FileDatabaseHistoryAction(private val applicationContext: Context) {
) { ) {
databaseFileListLoaded.add( databaseFileListLoaded.add(
DatabaseFile( DatabaseFile(
UriUtilDatabase.parse(fileDatabaseHistoryEntity.databaseUri), fileDatabaseHistoryEntity.databaseUri.parseUri(),
UriUtilDatabase.parse(fileDatabaseHistoryEntity.keyFileUri), fileDatabaseHistoryEntity.keyFileUri?.parseUri(),
HardwareKey.getHardwareKeyFromString(fileDatabaseHistoryEntity.hardwareKey), HardwareKey.getHardwareKeyFromString(fileDatabaseHistoryEntity.hardwareKey),
UriUtilDatabase.decode(fileDatabaseHistoryEntity.databaseUri), fileDatabaseHistoryEntity.databaseUri.decodeUri(),
fileDatabaseInfo.retrieveDatabaseAlias(fileDatabaseHistoryEntity.databaseAlias), fileDatabaseInfo.retrieveDatabaseAlias(fileDatabaseHistoryEntity.databaseAlias),
fileDatabaseInfo.exists, fileDatabaseInfo.exists,
fileDatabaseInfo.getLastModificationString(), fileDatabaseInfo.getLastModificationString(),
@ -165,10 +165,10 @@ class FileDatabaseHistoryAction(private val applicationContext: Context) {
FileDatabaseInfo(applicationContext, FileDatabaseInfo(applicationContext,
fileDatabaseHistory.databaseUri) fileDatabaseHistory.databaseUri)
DatabaseFile( DatabaseFile(
UriUtilDatabase.parse(fileDatabaseHistory.databaseUri), fileDatabaseHistory.databaseUri.parseUri(),
UriUtilDatabase.parse(fileDatabaseHistory.keyFileUri), fileDatabaseHistory.keyFileUri?.parseUri(),
HardwareKey.getHardwareKeyFromString(fileDatabaseHistory.hardwareKey), HardwareKey.getHardwareKeyFromString(fileDatabaseHistory.hardwareKey),
UriUtilDatabase.decode(fileDatabaseHistory.databaseUri), fileDatabaseHistory.databaseUri.decodeUri(),
fileDatabaseInfo.retrieveDatabaseAlias(fileDatabaseHistory.databaseAlias), fileDatabaseInfo.retrieveDatabaseAlias(fileDatabaseHistory.databaseAlias),
fileDatabaseInfo.exists, fileDatabaseInfo.exists,
fileDatabaseInfo.getLastModificationString(), fileDatabaseInfo.getLastModificationString(),
@ -192,10 +192,10 @@ class FileDatabaseHistoryAction(private val applicationContext: Context) {
val returnValue = databaseFileHistoryDao.delete(fileDatabaseHistory) val returnValue = databaseFileHistoryDao.delete(fileDatabaseHistory)
if (returnValue > 0) { if (returnValue > 0) {
DatabaseFile( DatabaseFile(
UriUtilDatabase.parse(fileDatabaseHistory.databaseUri), fileDatabaseHistory.databaseUri.parseUri(),
UriUtilDatabase.parse(fileDatabaseHistory.keyFileUri), fileDatabaseHistory.keyFileUri?.parseUri(),
HardwareKey.getHardwareKeyFromString(fileDatabaseHistory.hardwareKey), HardwareKey.getHardwareKeyFromString(fileDatabaseHistory.hardwareKey),
UriUtilDatabase.decode(fileDatabaseHistory.databaseUri), fileDatabaseHistory.databaseUri.decodeUri(),
databaseFileToDelete.databaseAlias databaseFileToDelete.databaseAlias
) )
} else { } else {

View file

@ -27,6 +27,7 @@ import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.MainCredential import com.kunzisoft.keepass.database.element.MainCredential
import com.kunzisoft.keepass.hardware.HardwareKey import com.kunzisoft.keepass.hardware.HardwareKey
import com.kunzisoft.keepass.settings.PreferencesUtil import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.utils.UriUtil.getBinaryDir
class CreateDatabaseRunnable( class CreateDatabaseRunnable(
context: Context, context: Context,
@ -47,7 +48,7 @@ class CreateDatabaseRunnable(
createData(mDatabaseUri, databaseName, rootName, templateGroupName) createData(mDatabaseUri, databaseName, rootName, templateGroupName)
} }
} catch (e: Exception) { } catch (e: Exception) {
mDatabase.clearAndClose(context) mDatabase.clearAndClose(context.getBinaryDir())
setError(e) setError(e)
} }

View file

@ -32,7 +32,7 @@ import com.kunzisoft.keepass.model.CipherEncryptDatabase
import com.kunzisoft.keepass.settings.PreferencesUtil import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.tasks.ActionRunnable import com.kunzisoft.keepass.tasks.ActionRunnable
import com.kunzisoft.keepass.tasks.ProgressTaskUpdater import com.kunzisoft.keepass.tasks.ProgressTaskUpdater
import com.kunzisoft.keepass.utils.UriUtilDatabase import com.kunzisoft.keepass.utils.UriUtil.getBinaryDir
class LoadDatabaseRunnable( class LoadDatabaseRunnable(
private val context: Context, private val context: Context,
@ -47,9 +47,11 @@ class LoadDatabaseRunnable(
private val mLoadDatabaseResult: ((Result) -> Unit)?, private val mLoadDatabaseResult: ((Result) -> Unit)?,
) : ActionRunnable() { ) : ActionRunnable() {
private val binaryDir = context.getBinaryDir()
override fun onStartRun() { override fun onStartRun() {
// Clear before we load // Clear before we load
mDatabase.clearAndClose(context) mDatabase.clearAndClose(binaryDir)
} }
override fun onActionRun() { override fun onActionRun() {
@ -60,7 +62,7 @@ class LoadDatabaseRunnable(
mMainCredential, mMainCredential,
mChallengeResponseRetriever, mChallengeResponseRetriever,
mReadonly, mReadonly,
UriUtilDatabase.getBinaryDir(context), binaryDir,
{ memoryWanted -> { memoryWanted ->
BinaryData.canMemoryBeAllocatedInRAM(context, memoryWanted) BinaryData.canMemoryBeAllocatedInRAM(context, memoryWanted)
}, },
@ -91,7 +93,7 @@ class LoadDatabaseRunnable(
// Register the current time to init the lock timer // Register the current time to init the lock timer
PreferencesUtil.saveCurrentTime(context) PreferencesUtil.saveCurrentTime(context)
} else { } else {
mDatabase.clearAndClose(context) mDatabase.clearAndClose(binaryDir)
} }
} }

View file

@ -26,7 +26,7 @@ import com.kunzisoft.keepass.database.exception.DatabaseException
import com.kunzisoft.keepass.settings.PreferencesUtil import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.tasks.ActionRunnable import com.kunzisoft.keepass.tasks.ActionRunnable
import com.kunzisoft.keepass.tasks.ProgressTaskUpdater import com.kunzisoft.keepass.tasks.ProgressTaskUpdater
import com.kunzisoft.keepass.utils.UriUtilDatabase import com.kunzisoft.keepass.utils.UriUtil.getBinaryDir
class ReloadDatabaseRunnable( class ReloadDatabaseRunnable(
private val context: Context, private val context: Context,
@ -35,10 +35,11 @@ class ReloadDatabaseRunnable(
private val mLoadDatabaseResult: ((Result) -> Unit)? private val mLoadDatabaseResult: ((Result) -> Unit)?
) : ActionRunnable() { ) : ActionRunnable() {
private val binaryDir = context.getBinaryDir()
override fun onStartRun() { override fun onStartRun() {
// Clear before we load // Clear before we load
mDatabase.clearIndexesAndBinaries(UriUtilDatabase.getBinaryDir( mDatabase.clearIndexesAndBinaries(binaryDir)
context))
mDatabase.wasReloaded = true mDatabase.wasReloaded = true
} }
@ -57,7 +58,7 @@ class ReloadDatabaseRunnable(
// Register the current time to init the lock timer // Register the current time to init the lock timer
PreferencesUtil.saveCurrentTime(context) PreferencesUtil.saveCurrentTime(context)
} else { } else {
mDatabase.clearAndClose(context) mDatabase.clearAndClose(binaryDir)
} }
} }

View file

@ -11,11 +11,10 @@ import androidx.activity.result.ActivityResultCallback
import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import com.kunzisoft.keepass.BuildConfig
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.legacy.DatabaseModeActivity import com.kunzisoft.keepass.activities.legacy.DatabaseModeActivity
import com.kunzisoft.keepass.database.element.Database import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.utils.UriUtil import com.kunzisoft.keepass.utils.UriUtil.openExternalApp
/** /**
* Special activity to deal with hardware key drivers, * Special activity to deal with hardware key drivers,
@ -158,8 +157,7 @@ class HardwareKeyActivity: DatabaseModeActivity(){
context.getString(R.string.error_driver_required, hardwareKey.toString()) context.getString(R.string.error_driver_required, hardwareKey.toString())
) )
.setPositiveButton(R.string.download) { _, _ -> .setPositiveButton(R.string.download) { _, _ ->
UriUtil.openExternalApp( context.openExternalApp(
context,
context.getString(R.string.key_driver_app_id), context.getString(R.string.key_driver_app_id),
context.getString(R.string.key_driver_url) context.getString(R.string.key_driver_url)
) )

View file

@ -13,7 +13,7 @@ import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.utils.UriUtil import com.kunzisoft.keepass.utils.UriUtil.openExternalApp
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
class HardwareKeyResponseHelper { class HardwareKeyResponseHelper {
@ -134,7 +134,7 @@ class HardwareKeyResponseHelper {
activity.getString(R.string.error_driver_required, hardwareKey.toString()) activity.getString(R.string.error_driver_required, hardwareKey.toString())
) )
.setPositiveButton(R.string.download) { _, _ -> .setPositiveButton(R.string.download) { _, _ ->
UriUtil.openExternalApp(activity, activity.getString(R.string.key_driver_app_id)) activity.openExternalApp(activity.getString(R.string.key_driver_app_id))
} }
.setNegativeButton(android.R.string.cancel) { _, _ -> } .setNegativeButton(android.R.string.cancel) { _, _ -> }
builder.create().show() builder.create().show()

View file

@ -36,7 +36,7 @@ import com.kunzisoft.keepass.model.AttachmentState
import com.kunzisoft.keepass.model.EntryAttachmentState import com.kunzisoft.keepass.model.EntryAttachmentState
import com.kunzisoft.keepass.model.StreamDirection import com.kunzisoft.keepass.model.StreamDirection
import com.kunzisoft.keepass.tasks.BinaryDatabaseManager import com.kunzisoft.keepass.tasks.BinaryDatabaseManager
import com.kunzisoft.keepass.utils.UriUtil import com.kunzisoft.keepass.utils.UriUtil.getDocumentFile
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -218,7 +218,7 @@ class AttachmentFileNotificationService: LockNotificationService() {
} }
) )
val fileName = UriUtil.getFileData(this, attachmentNotification.uri)?.name val fileName = attachmentNotification.uri.getDocumentFile(this)?.name
?: attachmentNotification.uri.path ?: attachmentNotification.uri.path
val builder = buildNewNotification().apply { val builder = buildNewNotification().apply {

View file

@ -47,7 +47,9 @@ import com.kunzisoft.keepass.icons.IconPackChooser
import com.kunzisoft.keepass.services.ClipboardEntryNotificationService import com.kunzisoft.keepass.services.ClipboardEntryNotificationService
import com.kunzisoft.keepass.settings.preference.IconPackListPreference import com.kunzisoft.keepass.settings.preference.IconPackListPreference
import com.kunzisoft.keepass.settings.preferencedialogfragment.DurationDialogFragmentCompat import com.kunzisoft.keepass.settings.preferencedialogfragment.DurationDialogFragmentCompat
import com.kunzisoft.keepass.utils.UriUtil import com.kunzisoft.keepass.utils.UriUtil.isContributingUser
import com.kunzisoft.keepass.utils.UriUtil.openUrl
import com.kunzisoft.keepass.utils.UriUtil.releaseAllUnnecessaryPermissionUris
class NestedAppSettingsFragment : NestedSettingsFragment() { class NestedAppSettingsFragment : NestedSettingsFragment() {
@ -81,7 +83,7 @@ class NestedAppSettingsFragment : NestedSettingsFragment() {
findPreference<Preference>(getString(R.string.remember_database_locations_key))?.setOnPreferenceChangeListener { _, newValue -> findPreference<Preference>(getString(R.string.remember_database_locations_key))?.setOnPreferenceChangeListener { _, newValue ->
if (!(newValue as Boolean)) { if (!(newValue as Boolean)) {
FileDatabaseHistoryAction.getInstance(activity.applicationContext).deleteAll { FileDatabaseHistoryAction.getInstance(activity.applicationContext).deleteAll {
UriUtil.releaseAllUnnecessaryPermissionUris(activity.applicationContext) activity.releaseAllUnnecessaryPermissionUris()
} }
} }
true true
@ -90,7 +92,7 @@ class NestedAppSettingsFragment : NestedSettingsFragment() {
findPreference<Preference>(getString(R.string.remember_keyfile_locations_key))?.setOnPreferenceChangeListener { _, newValue -> findPreference<Preference>(getString(R.string.remember_keyfile_locations_key))?.setOnPreferenceChangeListener { _, newValue ->
if (!(newValue as Boolean)) { if (!(newValue as Boolean)) {
FileDatabaseHistoryAction.getInstance(activity.applicationContext).deleteAllKeyFiles { FileDatabaseHistoryAction.getInstance(activity.applicationContext).deleteAllKeyFiles {
UriUtil.releaseAllUnnecessaryPermissionUris(activity.applicationContext) activity.releaseAllUnnecessaryPermissionUris()
} }
} }
true true
@ -168,7 +170,7 @@ class NestedAppSettingsFragment : NestedSettingsFragment() {
} }
findPreference<Preference>(getString(R.string.magic_keyboard_explanation_key))?.setOnPreferenceClickListener { findPreference<Preference>(getString(R.string.magic_keyboard_explanation_key))?.setOnPreferenceClickListener {
UriUtil.gotoUrl(requireContext(), R.string.magic_keyboard_explanation_url) context?.openUrl(R.string.magic_keyboard_explanation_url)
false false
} }
@ -185,7 +187,7 @@ class NestedAppSettingsFragment : NestedSettingsFragment() {
} }
findPreference<Preference>(getString(R.string.autofill_explanation_key))?.setOnPreferenceClickListener { findPreference<Preference>(getString(R.string.autofill_explanation_key))?.setOnPreferenceClickListener {
UriUtil.gotoUrl(requireContext(), R.string.autofill_explanation_url) context?.openUrl(R.string.autofill_explanation_url)
false false
} }
@ -202,7 +204,7 @@ class NestedAppSettingsFragment : NestedSettingsFragment() {
} }
findPreference<Preference>(getString(R.string.clipboard_explanation_key))?.setOnPreferenceClickListener { findPreference<Preference>(getString(R.string.clipboard_explanation_key))?.setOnPreferenceClickListener {
UriUtil.gotoUrl(requireContext(), R.string.clipboard_explanation_url) context?.openUrl(R.string.clipboard_explanation_url)
false false
} }
@ -360,7 +362,7 @@ class NestedAppSettingsFragment : NestedSettingsFragment() {
} }
findPreference<Preference>(getString(R.string.advanced_unlock_explanation_key))?.setOnPreferenceClickListener { findPreference<Preference>(getString(R.string.advanced_unlock_explanation_key))?.setOnPreferenceClickListener {
UriUtil.gotoUrl(requireContext(), R.string.advanced_unlock_explanation_url) context?.openUrl(R.string.advanced_unlock_explanation_url)
false false
} }
} }
@ -404,7 +406,7 @@ class NestedAppSettingsFragment : NestedSettingsFragment() {
findPreference<ListPreference>(getString(R.string.setting_style_key))?.setOnPreferenceChangeListener { _, newValue -> findPreference<ListPreference>(getString(R.string.setting_style_key))?.setOnPreferenceChangeListener { _, newValue ->
var styleEnabled = true var styleEnabled = true
val styleIdString = newValue as String val styleIdString = newValue as String
if (!UriUtil.contributingUser(activity)) { if (!activity.isContributingUser()) {
for (themeIdDisabled in BuildConfig.STYLES_DISABLED) { for (themeIdDisabled in BuildConfig.STYLES_DISABLED) {
if (themeIdDisabled == styleIdString) { if (themeIdDisabled == styleIdString) {
styleEnabled = false styleEnabled = false
@ -435,7 +437,7 @@ class NestedAppSettingsFragment : NestedSettingsFragment() {
findPreference<IconPackListPreference>(getString(R.string.setting_icon_pack_choose_key))?.setOnPreferenceChangeListener { _, newValue -> findPreference<IconPackListPreference>(getString(R.string.setting_icon_pack_choose_key))?.setOnPreferenceChangeListener { _, newValue ->
var iconPackEnabled = true var iconPackEnabled = true
val iconPackId = newValue as String val iconPackId = newValue as String
if (!UriUtil.contributingUser(activity)) { if (!activity.isContributingUser()) {
for (iconPackIdDisabled in BuildConfig.ICON_PACKS_DISABLED) { for (iconPackIdDisabled in BuildConfig.ICON_PACKS_DISABLED) {
if (iconPackIdDisabled == iconPackId) { if (iconPackIdDisabled == iconPackId) {
iconPackEnabled = false iconPackEnabled = false

View file

@ -36,7 +36,7 @@ import com.kunzisoft.keepass.education.Education
import com.kunzisoft.keepass.magikeyboard.MagikeyboardService import com.kunzisoft.keepass.magikeyboard.MagikeyboardService
import com.kunzisoft.keepass.password.PassphraseGenerator import com.kunzisoft.keepass.password.PassphraseGenerator
import com.kunzisoft.keepass.timeout.TimeoutHelper import com.kunzisoft.keepass.timeout.TimeoutHelper
import com.kunzisoft.keepass.utils.UriUtil import com.kunzisoft.keepass.utils.UriUtil.isContributingUser
import java.util.* import java.util.*
object PreferencesUtil { object PreferencesUtil {
@ -173,7 +173,7 @@ object PreferencesUtil {
fun setStyle(context: Context, styleString: String) { fun setStyle(context: Context, styleString: String) {
var tempThemeString = styleString var tempThemeString = styleString
if (!UriUtil.contributingUser(context)) { if (!context.isContributingUser()) {
if (tempThemeString in BuildConfig.STYLES_DISABLED) { if (tempThemeString in BuildConfig.STYLES_DISABLED) {
tempThemeString = Stylish.defaultStyle(context) tempThemeString = Stylish.defaultStyle(context)
} }

View file

@ -5,11 +5,12 @@ import android.graphics.Bitmap
import android.graphics.BitmapFactory import android.graphics.BitmapFactory
import android.net.Uri import android.net.Uri
import android.util.Log import android.util.Log
import com.kunzisoft.keepass.utils.readAllBytes
import com.kunzisoft.keepass.database.element.Database import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.binary.BinaryCache import com.kunzisoft.keepass.database.element.binary.BinaryCache
import com.kunzisoft.keepass.database.element.binary.BinaryData import com.kunzisoft.keepass.database.element.binary.BinaryData
import com.kunzisoft.keepass.utils.UriUtil import com.kunzisoft.keepass.utils.UriHelper.getUriInputStream
import com.kunzisoft.keepass.utils.UriHelper.getUriOutputStream
import com.kunzisoft.keepass.utils.readAllBytes
import kotlinx.coroutines.* import kotlinx.coroutines.*
import java.io.ByteArrayInputStream import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
@ -29,7 +30,7 @@ object BinaryDatabaseManager {
update: ((percent: Int)->Unit)? = null, update: ((percent: Int)->Unit)? = null,
canceled: ()-> Boolean = { false }, canceled: ()-> Boolean = { false },
bufferSize: Int = DEFAULT_BUFFER_SIZE) { bufferSize: Int = DEFAULT_BUFFER_SIZE) {
UriUtil.getUriOutputStream(contentResolver, attachmentToUploadUri)?.use { outputStream -> contentResolver.getUriOutputStream(attachmentToUploadUri)?.use { outputStream ->
downloadFromDatabase(database.binaryCache, outputStream, binaryData, update, canceled, bufferSize) downloadFromDatabase(database.binaryCache, outputStream, binaryData, update, canceled, bufferSize)
} }
} }
@ -64,7 +65,7 @@ object BinaryDatabaseManager {
canceled: ()-> Boolean = { false }, canceled: ()-> Boolean = { false },
bufferSize: Int = DEFAULT_BUFFER_SIZE) { bufferSize: Int = DEFAULT_BUFFER_SIZE) {
val fileSize = contentResolver.openFileDescriptor(attachmentFromDownloadUri, "r")?.statSize ?: 0 val fileSize = contentResolver.openFileDescriptor(attachmentFromDownloadUri, "r")?.statSize ?: 0
UriUtil.getUriInputStream(contentResolver, attachmentFromDownloadUri)?.use { inputStream -> contentResolver.getUriInputStream(attachmentFromDownloadUri)?.use { inputStream ->
uploadToDatabase(database.binaryCache, inputStream, fileSize, binaryData, update, canceled, bufferSize) uploadToDatabase(database.binaryCache, inputStream, fileSize, binaryData, update, canceled, bufferSize)
} }
} }
@ -97,7 +98,7 @@ object BinaryDatabaseManager {
binaryData: BinaryData?) { binaryData: BinaryData?) {
try { try {
binaryData?.let { binaryData?.let {
UriUtil.getUriInputStream(contentResolver, bitmapUri)?.use { inputStream -> contentResolver.getUriInputStream(bitmapUri)?.use { inputStream ->
BitmapFactory.decodeStream(inputStream)?.let { bitmap -> BitmapFactory.decodeStream(inputStream)?.let { bitmap ->
val bitmapResized = bitmap.resize(DEFAULT_ICON_WIDTH) val bitmapResized = bitmap.resize(DEFAULT_ICON_WIDTH)
val byteArrayOutputStream = ByteArrayOutputStream() val byteArrayOutputStream = ByteArrayOutputStream()

View file

@ -36,6 +36,8 @@ import com.kunzisoft.keepass.services.ClipboardEntryNotificationService
import com.kunzisoft.keepass.services.KeyboardEntryNotificationService import com.kunzisoft.keepass.services.KeyboardEntryNotificationService
import com.kunzisoft.keepass.settings.PreferencesUtil import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.timeout.TimeoutHelper import com.kunzisoft.keepass.timeout.TimeoutHelper
import com.kunzisoft.keepass.utils.UriUtil.getBinaryDir
import com.kunzisoft.keepass.utils.UriUtil.releaseAllUnnecessaryPermissionUris
const val DATABASE_START_TASK_ACTION = "com.kunzisoft.keepass.DATABASE_START_TASK_ACTION" const val DATABASE_START_TASK_ACTION = "com.kunzisoft.keepass.DATABASE_START_TASK_ACTION"
const val DATABASE_STOP_TASK_ACTION = "com.kunzisoft.keepass.DATABASE_STOP_TASK_ACTION" const val DATABASE_STOP_TASK_ACTION = "com.kunzisoft.keepass.DATABASE_STOP_TASK_ACTION"
@ -161,8 +163,8 @@ fun Context.closeDatabase(database: Database?) {
cancelAll() cancelAll()
} }
// Clear data // Clear data
database?.clearAndClose(this) database?.clearAndClose(this.getBinaryDir())
// Release not useful URI permission // Release not useful URI permission
UriUtil.releaseAllUnnecessaryPermissionUris(applicationContext) applicationContext.releaseAllUnnecessaryPermissionUris()
} }

View file

@ -28,13 +28,15 @@ import android.view.MenuItem
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.AboutActivity import com.kunzisoft.keepass.activities.AboutActivity
import com.kunzisoft.keepass.settings.SettingsActivity import com.kunzisoft.keepass.settings.SettingsActivity
import com.kunzisoft.keepass.utils.UriUtil.isContributingUser
import com.kunzisoft.keepass.utils.UriUtil.openUrl
object MenuUtil { object MenuUtil {
fun defaultMenuInflater(context: Context, inflater: MenuInflater, menu: Menu) { fun defaultMenuInflater(context: Context, inflater: MenuInflater, menu: Menu) {
inflater.inflate(R.menu.settings, menu) inflater.inflate(R.menu.settings, menu)
inflater.inflate(R.menu.about, menu) inflater.inflate(R.menu.about, menu)
if (!UriUtil.contributingUser(context)) if (!context.isContributingUser())
menu.findItem(R.id.menu_contribute)?.isVisible = false menu.findItem(R.id.menu_contribute)?.isVisible = false
} }
@ -46,7 +48,7 @@ object MenuUtil {
timeoutEnable: Boolean = false) { timeoutEnable: Boolean = false) {
when (item.itemId) { when (item.itemId) {
R.id.menu_contribute -> { R.id.menu_contribute -> {
UriUtil.gotoUrl(activity, R.string.contribution_url) activity.openUrl(R.string.contribution_url)
} }
R.id.menu_app_settings -> { R.id.menu_app_settings -> {
// To avoid flickering when launch settings in a LockingActivity // To avoid flickering when launch settings in a LockingActivity

View file

@ -1,34 +0,0 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.utils
open class SingletonHolderParameter<out T, in A>(private val constructor: (A) -> T) {
@Volatile
private var instance: T? = null
fun getInstance(arg: A): T {
return when {
instance != null -> instance!!
else -> synchronized(this) {
if (instance == null) instance = constructor(arg)
instance!!
}
}
}
}

View file

@ -32,26 +32,25 @@ import com.kunzisoft.keepass.BuildConfig
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.app.database.FileDatabaseHistoryAction import com.kunzisoft.keepass.app.database.FileDatabaseHistoryAction
import com.kunzisoft.keepass.education.Education import com.kunzisoft.keepass.education.Education
import java.io.* import com.kunzisoft.keepass.utils.UriHelper.withContentScheme
import java.util.* import com.kunzisoft.keepass.utils.UriHelper.withFileScheme
import java.io.File
object UriUtil { object UriUtil {
fun getFileData(context: Context, fileUri: Uri?): DocumentFile? { fun Uri.getDocumentFile(context: Context): DocumentFile? {
if (fileUri == null)
return null
return try { return try {
when { when {
isFileScheme(fileUri) -> { this.withFileScheme() -> {
fileUri.path?.let { this.path?.let {
File(it).let { file -> File(it).let { file ->
return DocumentFile.fromFile(file) return DocumentFile.fromFile(file)
} }
} }
} }
isContentScheme(fileUri) -> { this.withContentScheme() -> {
DocumentFile.fromSingleUri(context, fileUri) DocumentFile.fromSingleUri(context, this)
} }
else -> { else -> {
Log.e("FileData", "Content scheme not known") Log.e("FileData", "Content scheme not known")
@ -64,66 +63,6 @@ object UriUtil {
} }
} }
@Throws(FileNotFoundException::class)
fun getUriOutputStream(contentResolver: ContentResolver, fileUri: Uri?): OutputStream? {
if (fileUri == null)
return null
return when {
isFileScheme(fileUri) -> fileUri.path?.let { FileOutputStream(it) }
isContentScheme(fileUri) -> {
try {
contentResolver.openOutputStream(fileUri, "wt")
} catch (e: FileNotFoundException) {
Log.e(TAG, "Unable to open stream in `wt` mode, retry in `rwt` mode.", e)
// https://issuetracker.google.com/issues/180526528
// Try with rwt to fix content provider issue
val outStream = contentResolver.openOutputStream(fileUri, "rwt")
Log.w(TAG, "`rwt` mode used.")
outStream
}
}
else -> null
}
}
@Throws(FileNotFoundException::class)
fun getUriInputStream(contentResolver: ContentResolver, fileUri: Uri?): InputStream? {
if (fileUri == null)
return null
return when {
isFileScheme(fileUri) -> fileUri.path?.let { FileInputStream(it) }
isContentScheme(fileUri) -> contentResolver.openInputStream(fileUri)
else -> null
}
}
private fun isFileScheme(fileUri: Uri): Boolean {
val scheme = fileUri.scheme
if (scheme == null || scheme.isEmpty() || scheme.lowercase(Locale.ENGLISH) == "file") {
return true
}
return false
}
private fun isContentScheme(fileUri: Uri): Boolean {
val scheme = fileUri.scheme
if (scheme != null && scheme.lowercase(Locale.ENGLISH) == "content") {
return true
}
return false
}
fun parse(stringUri: String?): Uri? {
return if (stringUri?.isNotEmpty() == true) {
Uri.parse(stringUri)
} else
null
}
fun decode(uri: String?): String {
return Uri.decode(uri) ?: ""
}
private fun persistUriPermission(contentResolver: ContentResolver?, private fun persistUriPermission(contentResolver: ContentResolver?,
uri: Uri, uri: Uri,
release: Boolean, release: Boolean,
@ -190,18 +129,19 @@ object UriUtil {
} }
} }
fun takeUriPermission(contentResolver: ContentResolver?, fun ContentResolver.takeUriPermission(uri: Uri?, readOnly: Boolean = false) {
uri: Uri, uri?.let {
readOnly: Boolean = false) { persistUriPermission(this, it, false, readOnly)
persistUriPermission(contentResolver, uri, false, readOnly) }
} }
fun releaseUriPermission(contentResolver: ContentResolver?, fun ContentResolver.releaseUriPermission(uri: Uri?) {
uri: Uri) { uri?.let {
persistUriPermission(contentResolver, uri, release = true, readOnly = false) persistUriPermission(this, it, release = true, readOnly = false)
}
} }
fun releaseAllUnnecessaryPermissionUris(applicationContext: Context?) { fun Context.releaseAllUnnecessaryPermissionUris() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
applicationContext?.let { appContext -> applicationContext?.let { appContext ->
val fileDatabaseHistoryAction = FileDatabaseHistoryAction.getInstance(appContext) val fileDatabaseHistoryAction = FileDatabaseHistoryAction.getInstance(appContext)
@ -220,17 +160,17 @@ object UriUtil {
resolver.persistedUriPermissions.forEach { uriPermission -> resolver.persistedUriPermissions.forEach { uriPermission ->
val uri = uriPermission.uri val uri = uriPermission.uri
if (!listToNotRemove.contains(uri)) if (!listToNotRemove.contains(uri))
releaseUriPermission(resolver, uri) resolver.releaseUriPermission(uri)
} }
} }
} }
} }
} }
fun getUriFromIntent(intent: Intent?, key: String): Uri? { fun Intent.getUri(key: String): Uri? {
try { try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
val clipData = intent?.clipData val clipData = this.clipData
if (clipData != null) { if (clipData != null) {
if (clipData.description.label == key) { if (clipData.description.label == key) {
if (clipData.itemCount == 1) { if (clipData.itemCount == 1) {
@ -243,12 +183,12 @@ object UriUtil {
} }
} }
} catch (e: Exception) { } catch (e: Exception) {
return intent?.getParcelableExtra(key) return this.getParcelableExtra(key)
} }
return null return null
} }
fun gotoUrl(context: Context, url: String?) { fun Context.openUrl(url: String?) {
try { try {
if (url != null && url.isNotEmpty()) { if (url != null && url.isNotEmpty()) {
// Default http:// if no protocol specified // Default http:// if no protocol specified
@ -257,28 +197,30 @@ object UriUtil {
} else { } else {
url url
} }
context.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(newUrl))) this.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(newUrl)))
} }
} catch (e: Exception) { } catch (e: Exception) {
Toast.makeText(context, R.string.no_url_handler, Toast.LENGTH_LONG).show() Toast.makeText(this, R.string.no_url_handler, Toast.LENGTH_LONG).show()
} }
} }
fun gotoUrl(context: Context, resId: Int) { fun Context.openUrl(resId: Int) {
gotoUrl(context, context.getString(resId)) this.openUrl(this.getString(resId))
} }
fun contributingUser(context: Context): Boolean { fun Context.isContributingUser(): Boolean {
return (Education.isEducationScreenReclickedPerformed(context) return (Education.isEducationScreenReclickedPerformed(this)
|| isExternalAppInstalled( || isExternalAppInstalled(
context, this,
context.getString(R.string.keepro_app_id), this.getString(R.string.keepro_app_id),
false false
) )
) )
} }
fun isExternalAppInstalled(context: Context, packageName: String, showError: Boolean = true): Boolean { private fun isExternalAppInstalled(context: Context,
packageName: String,
showError: Boolean = true): Boolean {
try { try {
context.applicationContext.packageManager.getPackageInfo(packageName, PackageManager.GET_ACTIVITIES) context.applicationContext.packageManager.getPackageInfo(packageName, PackageManager.GET_ACTIVITIES)
Education.setEducationScreenReclickedPerformed(context) Education.setEducationScreenReclickedPerformed(context)
@ -290,16 +232,16 @@ object UriUtil {
return false return false
} }
fun openExternalApp(context: Context, packageName: String, sourcesURL: String? = null) { fun Context.openExternalApp(packageName: String, sourcesURL: String? = null) {
var launchIntent: Intent? = null var launchIntent: Intent? = null
try { try {
launchIntent = context.packageManager.getLaunchIntentForPackage(packageName)?.apply { launchIntent = this.packageManager.getLaunchIntentForPackage(packageName)?.apply {
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
} }
} catch (ignored: Exception) { } } catch (ignored: Exception) { }
try { try {
if (launchIntent == null) { if (launchIntent == null) {
context.startActivity( this.startActivity(
Intent(Intent.ACTION_VIEW) Intent(Intent.ACTION_VIEW)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
.setData( .setData(
@ -309,7 +251,7 @@ object UriUtil {
) { ) {
sourcesURL sourcesURL
} else { } else {
context.getString( this.getString(
if (BuildConfig.CLOSED_STORE) if (BuildConfig.CLOSED_STORE)
R.string.play_store_url R.string.play_store_url
else else
@ -321,18 +263,18 @@ object UriUtil {
) )
) )
} else { } else {
context.startActivity(launchIntent) this.startActivity(launchIntent)
} }
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "App cannot be open", e) Log.e(TAG, "App cannot be open", e)
} }
} }
fun getBinaryDir(context: Context): File { fun Context.getBinaryDir(): File {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
context.applicationContext.noBackupFilesDir this.applicationContext.noBackupFilesDir
} else { } else {
context.applicationContext.filesDir this.applicationContext.filesDir
} }
} }

View file

@ -12,7 +12,7 @@ import android.widget.TextView
import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintLayout
import com.google.android.material.textfield.TextInputLayout import com.google.android.material.textfield.TextInputLayout
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.utils.UriUtil import com.kunzisoft.keepass.utils.UriUtil.getDocumentFile
class KeyFileSelectionView @JvmOverloads constructor(context: Context, class KeyFileSelectionView @JvmOverloads constructor(context: Context,
@ -66,7 +66,7 @@ class KeyFileSelectionView @JvmOverloads constructor(context: Context,
} }
} }
keyFileNameView.text = value?.let { keyFileNameView.text = value?.let {
UriUtil.getFileData(context, value)?.name ?: value.path value.getDocumentFile(context)?.name ?: value.path
} ?: "" } ?: ""
} }

View file

@ -43,7 +43,7 @@ import com.kunzisoft.keepass.database.helper.isStandardPasswordName
import com.kunzisoft.keepass.model.EntryInfo.Companion.APPLICATION_ID_FIELD_NAME import com.kunzisoft.keepass.model.EntryInfo.Companion.APPLICATION_ID_FIELD_NAME
import com.kunzisoft.keepass.password.PasswordGenerator import com.kunzisoft.keepass.password.PasswordGenerator
import com.kunzisoft.keepass.settings.PreferencesUtil import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.utils.UriUtil import com.kunzisoft.keepass.utils.UriUtil.openExternalApp
class TextFieldView @JvmOverloads constructor(context: Context, class TextFieldView @JvmOverloads constructor(context: Context,
@ -253,7 +253,7 @@ class TextFieldView @JvmOverloads constructor(context: Context,
val packageName = valueView.text.toString() val packageName = valueView.text.toString()
// TODO #996 if (UriUtil.isExternalAppInstalled(context, packageName)) { // TODO #996 if (UriUtil.isExternalAppInstalled(context, packageName)) {
valueView.customLink { valueView.customLink {
UriUtil.openExternalApp(context, packageName) context.openExternalApp(packageName)
} }
//} //}
} }

View file

@ -6,10 +6,10 @@ import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import com.kunzisoft.keepass.app.App import com.kunzisoft.keepass.app.App
import com.kunzisoft.keepass.app.database.FileDatabaseHistoryAction import com.kunzisoft.keepass.app.database.FileDatabaseHistoryAction
import com.kunzisoft.keepass.utils.IOActionTask
import com.kunzisoft.keepass.model.DatabaseFile import com.kunzisoft.keepass.model.DatabaseFile
import com.kunzisoft.keepass.settings.PreferencesUtil import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.utils.UriUtil import com.kunzisoft.keepass.utils.IOActionTask
import com.kunzisoft.keepass.utils.UriHelper.parseUri
class DatabaseFileViewModel(application: Application) : AndroidViewModel(application) { class DatabaseFileViewModel(application: Application) : AndroidViewModel(application) {
@ -26,8 +26,8 @@ class DatabaseFileViewModel(application: Application) : AndroidViewModel(applica
fun checkIfIsDefaultDatabase(databaseUri: Uri) { fun checkIfIsDefaultDatabase(databaseUri: Uri) {
IOActionTask( IOActionTask(
{ {
(UriUtil.parse(PreferencesUtil.getDefaultDatabasePath(getApplication<App>().applicationContext)) (PreferencesUtil.getDefaultDatabasePath(getApplication<App>().applicationContext)
== databaseUri) ?.parseUri() == databaseUri)
}, },
{ {
isDefaultDatabase.value = it isDefaultDatabase.value = it

View file

@ -6,11 +6,12 @@ import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import com.kunzisoft.keepass.app.App import com.kunzisoft.keepass.app.App
import com.kunzisoft.keepass.app.database.FileDatabaseHistoryAction import com.kunzisoft.keepass.app.database.FileDatabaseHistoryAction
import com.kunzisoft.keepass.utils.IOActionTask
import com.kunzisoft.keepass.hardware.HardwareKey import com.kunzisoft.keepass.hardware.HardwareKey
import com.kunzisoft.keepass.model.DatabaseFile import com.kunzisoft.keepass.model.DatabaseFile
import com.kunzisoft.keepass.settings.PreferencesUtil import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.utils.UriUtil import com.kunzisoft.keepass.utils.IOActionTask
import com.kunzisoft.keepass.utils.UriHelper.parseUri
import com.kunzisoft.keepass.utils.UriUtil.releaseUriPermission
class DatabaseFilesViewModel(application: Application) : AndroidViewModel(application) { class DatabaseFilesViewModel(application: Application) : AndroidViewModel(application) {
@ -31,7 +32,8 @@ class DatabaseFilesViewModel(application: Application) : AndroidViewModel(applic
fun checkDefaultDatabase() { fun checkDefaultDatabase() {
IOActionTask( IOActionTask(
{ {
UriUtil.parse(PreferencesUtil.getDefaultDatabasePath(getApplication<App>().applicationContext)) PreferencesUtil.getDefaultDatabasePath(getApplication<App>().applicationContext)
?.parseUri()
}, },
{ {
defaultDatabase.value = it defaultDatabase.value = it
@ -118,18 +120,8 @@ class DatabaseFilesViewModel(application: Application) : AndroidViewModel(applic
databaseFileDeleted?.let { _ -> databaseFileDeleted?.let { _ ->
// Release database and keyfile URIs permissions // Release database and keyfile URIs permissions
val contentResolver = getApplication<App>().applicationContext.contentResolver val contentResolver = getApplication<App>().applicationContext.contentResolver
databaseFileDeleted.databaseUri?.let { databaseUri -> contentResolver.releaseUriPermission(databaseFileDeleted.databaseUri)
UriUtil.releaseUriPermission( contentResolver.releaseUriPermission(databaseFileDeleted.keyFileUri)
contentResolver,
databaseUri
)
}
databaseFileDeleted.keyFileUri?.let { keyFileUri ->
UriUtil.releaseUriPermission(
contentResolver,
keyFileUri
)
}
// Call the feedback // Call the feedback
databaseFilesLoaded.value = getDatabaseFilesLoadedValue().apply { databaseFilesLoaded.value = getDatabaseFilesLoadedValue().apply {
databaseFileAction = DatabaseFileAction.DELETE databaseFileAction = DatabaseFileAction.DELETE

View file

@ -23,10 +23,12 @@ import android.content.Context
import android.net.Uri import android.net.Uri
import android.text.format.Formatter import android.text.format.Formatter
import androidx.documentfile.provider.DocumentFile import androidx.documentfile.provider.DocumentFile
import com.kunzisoft.keepass.utils.UriUtil import com.kunzisoft.keepass.utils.UriHelper.parseUri
import com.kunzisoft.keepass.utils.UriUtil.getDocumentFile
import com.kunzisoft.keepass.utils.UriUtil.takeUriPermission
import java.io.Serializable import java.io.Serializable
import java.text.DateFormat import java.text.DateFormat
import java.util.Date import java.util.*
class FileDatabaseInfo : Serializable { class FileDatabaseInfo : Serializable {
@ -43,16 +45,14 @@ class FileDatabaseInfo : Serializable {
constructor(context: Context, filePath: String) { constructor(context: Context, filePath: String) {
this.context = context this.context = context
this.fileUri = UriUtil.parse(filePath) this.fileUri = filePath.parseUri()
init() init()
} }
fun init() { fun init() {
// Check permission // Check permission
fileUri?.let { uri -> context.contentResolver.takeUriPermission(fileUri)
UriUtil.takeUriPermission(context.contentResolver, uri) documentFile = fileUri?.getDocumentFile(context)
}
documentFile = UriUtil.getFileData(context, fileUri)
} }
var exists: Boolean = false var exists: Boolean = false

View file

@ -20,7 +20,6 @@
package com.kunzisoft.keepass.database.element package com.kunzisoft.keepass.database.element
import android.content.ContentResolver import android.content.ContentResolver
import android.content.Context
import android.graphics.Color import android.graphics.Color
import android.net.Uri import android.net.Uri
import android.util.Log import android.util.Log
@ -58,6 +57,8 @@ import com.kunzisoft.keepass.icons.IconDrawableFactory
import com.kunzisoft.keepass.icons.InterfaceIconPackChooser import com.kunzisoft.keepass.icons.InterfaceIconPackChooser
import com.kunzisoft.keepass.tasks.ProgressTaskUpdater import com.kunzisoft.keepass.tasks.ProgressTaskUpdater
import com.kunzisoft.keepass.utils.* import com.kunzisoft.keepass.utils.*
import com.kunzisoft.keepass.utils.UriHelper.getUriInputStream
import com.kunzisoft.keepass.utils.UriHelper.getUriOutputStream
import java.io.* import java.io.*
import java.util.* import java.util.*
@ -786,7 +787,7 @@ class Database(private val iconPackChooser: InterfaceIconPackChooser) {
openDatabaseKDBX: (InputStream) -> Unit) { openDatabaseKDBX: (InputStream) -> Unit) {
try { try {
// Load Data, pass Uris as InputStreams // Load Data, pass Uris as InputStreams
val databaseStream = UriUtilDatabase.getUriInputStream(contentResolver, databaseUri) val databaseStream = contentResolver.getUriInputStream(databaseUri)
?: throw UnknownDatabaseLocationException() ?: throw UnknownDatabaseLocationException()
BufferedInputStream(databaseStream).use { databaseInputStream -> BufferedInputStream(databaseStream).use { databaseInputStream ->
@ -867,7 +868,7 @@ class Database(private val iconPackChooser: InterfaceIconPackChooser) {
} }
} }
// Copy from the cache to the final stream // Copy from the cache to the final stream
UriUtilDatabase.getUriOutputStream(contentResolver, saveUri)?.use { outputStream -> contentResolver.getUriOutputStream(saveUri)?.use { outputStream ->
cacheFile.inputStream().use { inputStream -> cacheFile.inputStream().use { inputStream ->
inputStream.readAllBytes { buffer -> inputStream.readAllBytes { buffer ->
outputStream.write(buffer) outputStream.write(buffer)
@ -1005,8 +1006,8 @@ class Database(private val iconPackChooser: InterfaceIconPackChooser) {
} }
} }
fun clearAndClose(context: Context? = null) { fun clearAndClose(filesDirectory: File? = null) {
clearIndexesAndBinaries(context?.let { UriUtilDatabase.getBinaryDir(context) }) clearIndexesAndBinaries(filesDirectory)
this.mDatabaseKDB = null this.mDatabaseKDB = null
this.mDatabaseKDBX = null this.mDatabaseKDBX = null
this.fileUri = null this.fileUri = null

View file

@ -29,7 +29,7 @@ import com.kunzisoft.keepass.database.element.database.DatabaseKDBX
import com.kunzisoft.keepass.hardware.HardwareKey import com.kunzisoft.keepass.hardware.HardwareKey
import com.kunzisoft.keepass.utils.StringUtil.removeSpaceChars import com.kunzisoft.keepass.utils.StringUtil.removeSpaceChars
import com.kunzisoft.keepass.utils.StringUtil.toHexString import com.kunzisoft.keepass.utils.StringUtil.toHexString
import com.kunzisoft.keepass.utils.UriUtilDatabase import com.kunzisoft.keepass.utils.UriHelper.getUriInputStream
import com.kunzisoft.keepass.utils.readEnum import com.kunzisoft.keepass.utils.readEnum
import com.kunzisoft.keepass.utils.writeEnum import com.kunzisoft.keepass.utils.writeEnum
import org.apache.commons.codec.binary.Hex import org.apache.commons.codec.binary.Hex
@ -148,7 +148,7 @@ data class MainCredential(var password: String? = null,
@Throws(Exception::class) @Throws(Exception::class)
private fun getKeyFileData(contentResolver: ContentResolver, private fun getKeyFileData(contentResolver: ContentResolver,
keyFileUri: Uri): ByteArray? { keyFileUri: Uri): ByteArray? {
UriUtilDatabase.getUriInputStream(contentResolver, keyFileUri)?.use { keyFileInputStream -> contentResolver.getUriInputStream(keyFileUri)?.use { keyFileInputStream ->
return keyFileInputStream.readBytes() return keyFileInputStream.readBytes()
} }
return null return null

View file

@ -36,3 +36,17 @@ open class SingletonHolder<out T>(private val constructor: (iconPackChooser : In
} }
} }
} }
open class SingletonHolderParameter<out T, in A>(private val constructor: (A) -> T) {
@Volatile
private var instance: T? = null
fun getInstance(arg: A): T {
return when {
instance != null -> instance!!
else -> synchronized(this) {
if (instance == null) instance = constructor(arg)
instance!!
}
}
}
}

View file

@ -19,65 +19,50 @@
*/ */
package com.kunzisoft.keepass.utils package com.kunzisoft.keepass.utils
import android.annotation.SuppressLint
import android.content.ContentResolver import android.content.ContentResolver
import android.content.Context
import android.net.Uri import android.net.Uri
import android.os.Build
import android.util.Log import android.util.Log
import java.io.File import java.io.*
import java.io.FileInputStream import java.util.*
import java.io.FileNotFoundException
import java.io.FileOutputStream
import java.io.InputStream
import java.io.OutputStream
import java.util.Locale
object UriUtilDatabase { object UriHelper {
fun parse(stringUri: String?): Uri? {
return if (stringUri?.isNotEmpty() == true) { fun String.parseUri(): Uri? {
Uri.parse(stringUri) return if (this.isNotEmpty()) Uri.parse(this) else null
} else
null
} }
fun decode(uri: String?): String { fun String.decodeUri(): String {
return Uri.decode(uri) ?: "" return Uri.decode(this) ?: ""
}
fun getBinaryDir(context: Context): File {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
context.applicationContext.noBackupFilesDir
} else {
context.applicationContext.filesDir
}
} }
@Throws(FileNotFoundException::class) @Throws(FileNotFoundException::class)
fun getUriInputStream(contentResolver: ContentResolver, fileUri: Uri?): InputStream? { fun ContentResolver.getUriInputStream(fileUri: Uri?): InputStream? {
if (fileUri == null) if (fileUri == null)
return null return null
return when { return when {
isFileScheme(fileUri) -> fileUri.path?.let { FileInputStream(it) } fileUri.withFileScheme() -> fileUri.path?.let { FileInputStream(it) }
isContentScheme(fileUri) -> contentResolver.openInputStream(fileUri) fileUri.withContentScheme() -> this.openInputStream(fileUri)
else -> null else -> null
} }
} }
@SuppressLint("Recycle")
@Throws(FileNotFoundException::class) @Throws(FileNotFoundException::class)
fun getUriOutputStream(contentResolver: ContentResolver, fileUri: Uri?): OutputStream? { fun ContentResolver.getUriOutputStream(fileUri: Uri?): OutputStream? {
if (fileUri == null) if (fileUri == null)
return null return null
return when { return when {
isFileScheme(fileUri) -> fileUri.path?.let { FileOutputStream(it) } fileUri.withFileScheme() -> fileUri.path?.let { FileOutputStream(it) }
isContentScheme(fileUri) -> { fileUri.withContentScheme() -> {
try { try {
contentResolver.openOutputStream(fileUri, "wt") this.openOutputStream(fileUri, "wt")
} catch (e: FileNotFoundException) { } catch (e: FileNotFoundException) {
Log.e(TAG, "Unable to open stream in `wt` mode, retry in `rwt` mode.", e) Log.e(TAG, "Unable to open stream in `wt` mode, retry in `rwt` mode.", e)
// https://issuetracker.google.com/issues/180526528 // https://issuetracker.google.com/issues/180526528
// Try with rwt to fix content provider issue // Try with rwt to fix content provider issue
val outStream = contentResolver.openOutputStream(fileUri, "rwt") val outStream = this.openOutputStream(fileUri, "rwt")
Log.w(TAG, "`rwt` mode used.") Log.w(TAG, "`rwt` mode used.")
outStream outStream
} }
@ -86,16 +71,16 @@ object UriUtilDatabase {
} }
} }
private fun isFileScheme(fileUri: Uri): Boolean { fun Uri.withFileScheme(): Boolean {
val scheme = fileUri.scheme val scheme = this.scheme
if (scheme == null || scheme.isEmpty() || scheme.lowercase(Locale.ENGLISH) == "file") { if (scheme == null || scheme.isEmpty() || scheme.lowercase(Locale.ENGLISH) == "file") {
return true return true
} }
return false return false
} }
private fun isContentScheme(fileUri: Uri): Boolean { fun Uri.withContentScheme(): Boolean {
val scheme = fileUri.scheme val scheme = this.scheme
if (scheme != null && scheme.lowercase(Locale.ENGLISH) == "content") { if (scheme != null && scheme.lowercase(Locale.ENGLISH) == "content") {
return true return true
} }