Merge branch 'develop' of github.com:Kunzisoft/KeePassDX into develop

This commit is contained in:
J-Jamet
2019-08-30 13:18:36 +02:00
191 changed files with 2154 additions and 3119 deletions

View File

@@ -1,3 +1,7 @@
KeepassDX (2.5.0.0beta23)
* New, more secure database creation workflow
* Add alias for history files (WARNING: history is erased)
KeepassDX (2.5.0.0beta22)
* Rebuild code for actions
* Add UUID as entry view

1
app/.gitignore vendored
View File

@@ -1 +1,2 @@
.cxx
.externalNativeBuild

View File

@@ -4,15 +4,15 @@ apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'
android {
compileSdkVersion 27
compileSdkVersion 28
buildToolsVersion '28.0.3'
defaultConfig {
applicationId "com.kunzisoft.keepass"
minSdkVersion 14
targetSdkVersion 27
versionCode = 22
versionName = "2.5.0.0beta22"
targetSdkVersion 28
versionCode = 23
versionName = "2.5.0.0beta23"
multiDexEnabled true
testApplicationId = "com.kunzisoft.keepass.tests"
@@ -79,42 +79,34 @@ android {
}
}
def supportVersion = "27.1.1"
def spongycastleVersion = "1.58.0.0"
def permissionDispatcherVersion = "3.3.1"
def room_version = "2.1.0"
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation "com.android.support:appcompat-v7:$supportVersion"
implementation "com.android.support:design:$supportVersion"
implementation "com.android.support:preference-v7:$supportVersion"
implementation "com.android.support:preference-v14:$supportVersion"
implementation "com.android.support:cardview-v7:$supportVersion"
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
implementation 'androidx.appcompat:appcompat:1.0.2'
implementation 'androidx.preference:preference:1.0.0'
implementation 'androidx.legacy:legacy-preference-v14:1.0.0'
implementation 'androidx.cardview:cardview:1.0.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'com.google.android.material:material:1.0.0'
implementation "androidx.room:room-runtime:$room_version"
kapt "androidx.room:room-compiler:$room_version"
implementation "com.madgag.spongycastle:core:$spongycastleVersion"
implementation "com.madgag.spongycastle:prov:$spongycastleVersion"
// Expandable view
implementation 'net.cachapa.expandablelayout:expandablelayout:2.9.2'
// Time
implementation 'joda-time:joda-time:2.9.9'
implementation 'org.sufficientlysecure:html-textview:3.5'
implementation 'com.nononsenseapps:filepicker:4.1.0'
// Education
implementation 'com.getkeepsafe.taptargetview:taptargetview:1.12.0'
// Permissions
implementation("com.github.hotchemi:permissionsdispatcher:$permissionDispatcherVersion") {
// if you don't use android.app.Fragment you can exclude support for them
exclude module: "support-v13"
}
kapt "com.github.hotchemi:permissionsdispatcher-processor:$permissionDispatcherVersion"
// Apache Commons Collections
implementation 'commons-collections:commons-collections:3.2.1'
implementation 'org.apache.commons:commons-io:1.3.2'
// Base64
implementation 'biz.source_code:base64coder:2010-12-19'
// IO-Extras
implementation 'com.github.davidmoten:io-extras:0.1'
implementation 'com.google.code.gson:gson:2.8.4'
implementation 'com.google.guava:guava:23.0-android'
// Icon pack
implementation project(path: ':icon-pack-classic')
implementation project(path: ':icon-pack-material')

View File

@@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.kunzisoft.keepass"
android:installLocation="auto">
<supports-screens
@@ -8,6 +7,7 @@
android:normalScreens="true"
android:largeScreens="true"
android:anyDensity="true" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.USE_FINGERPRINT"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.VIBRATE"/>
@@ -48,7 +48,7 @@
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="file" />
<data android:scheme="content" />
<data android:mimeType="application/octet-stream" />
<data android:mimeType="*/*" />
<data android:host="*" />
<data android:pathPattern=".*\\.kdb" />
<data android:pathPattern=".*\\..*\\.kdb" />
@@ -71,29 +71,28 @@
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.kdbx" />
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.kdbx" />
</intent-filter>
<intent-filter tools:ignore="AppLinkUrlError">
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:mimeType="application/octet-stream"/>
</intent-filter>
</activity>
<!-- Folder picker -->
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/nnf_provider_paths" />
</provider>
<activity
android:name=".activities.stylish.FilePickerStylishActivity"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.GET_CONTENT" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="file" />
<data android:scheme="content" />
<data android:mimeType="application/octet-stream" />
<data android:mimeType="application/x-kdb" />
<data android:mimeType="application/x-kdbx" />
<data android:mimeType="application/x-keepass" />
<data android:host="*" />
<data android:pathPattern=".*" />
<data android:pathPattern=".*\\.*" />
<data android:pathPattern=".*\\..*\\.*" />
<data android:pathPattern=".*\\..*\\..*\\.*" />
<data android:pathPattern=".*\\..*\\..*\\..*\\.*" />
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\.*" />
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\.*" />
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\.*" />
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.*" />
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.*" />
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.*" />
</intent-filter>
</activity>
<!-- Main Activity -->

View File

@@ -21,7 +21,7 @@ package com.kunzisoft.keepass.activities
import android.content.pm.PackageManager.NameNotFoundException
import android.os.Bundle
import android.support.v7.widget.Toolbar
import androidx.appcompat.widget.Toolbar
import android.util.Log
import android.view.MenuItem
import android.widget.TextView

View File

@@ -19,15 +19,14 @@
package com.kunzisoft.keepass.activities
import android.app.Activity
import android.content.ActivityNotFoundException
import android.content.Intent
import android.graphics.Color
import android.net.Uri
import android.os.Bundle
import android.os.Handler
import android.support.design.widget.CollapsingToolbarLayout
import android.support.v7.app.AlertDialog
import android.support.v7.widget.Toolbar
import com.google.android.material.appbar.CollapsingToolbarLayout
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.widget.Toolbar
import android.util.Log
import android.view.Menu
import android.view.MenuItem
@@ -45,12 +44,11 @@ import com.kunzisoft.keepass.icons.assignDatabaseIcon
import com.kunzisoft.keepass.magikeyboard.MagikIME
import com.kunzisoft.keepass.notifications.ClipboardEntryNotificationService
import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.settings.PreferencesUtil.isFirstTimeAskAllowCopyPasswordAndProtectedFields
import com.kunzisoft.keepass.settings.SettingsAutofillActivity
import com.kunzisoft.keepass.timeout.ClipboardHelper
import com.kunzisoft.keepass.timeout.TimeoutHelper
import com.kunzisoft.keepass.utils.MenuUtil
import com.kunzisoft.keepass.utils.Util
import com.kunzisoft.keepass.utils.UriUtil
import com.kunzisoft.keepass.view.EntryContentsView
class EntryActivity : LockingHideActivity() {
@@ -164,9 +162,33 @@ class EntryActivity : LockingHideActivity() {
getString(R.string.entry_user_name)))
})
val allowCopyPassword = PreferencesUtil.allowCopyPasswordAndProtectedFields(this)
entryContentsView?.assignPassword(entry.password, allowCopyPassword)
if (allowCopyPassword) {
val isFirstTimeAskAllowCopyPasswordAndProtectedFields =
PreferencesUtil.isFirstTimeAskAllowCopyPasswordAndProtectedFields(this)
val allowCopyPasswordAndProtectedFields =
PreferencesUtil.allowCopyPasswordAndProtectedFields(this)
val showWarningClipboardDialogOnClickListener = View.OnClickListener {
AlertDialog.Builder(this@EntryActivity)
.setMessage(getString(R.string.allow_copy_password_warning) +
"\n\n" +
getString(R.string.clipboard_warning))
.create().apply {
setButton(AlertDialog.BUTTON_POSITIVE, getText(R.string.enable)) {dialog, _ ->
PreferencesUtil.setAllowCopyPasswordAndProtectedFields(this@EntryActivity, true)
dialog.dismiss()
fillEntryDataInContentsView(entry)
}
setButton(AlertDialog.BUTTON_NEGATIVE, getText(R.string.disable)) { dialog, _ ->
PreferencesUtil.setAllowCopyPasswordAndProtectedFields(this@EntryActivity, false)
dialog.dismiss()
fillEntryDataInContentsView(entry)
}
show()
}
}
entryContentsView?.assignPassword(entry.password, allowCopyPasswordAndProtectedFields)
if (allowCopyPasswordAndProtectedFields) {
entryContentsView?.assignPasswordCopyListener(View.OnClickListener {
clipboardHelper?.timeoutCopyToClipboard(entry.password,
getString(R.string.copy_field,
@@ -174,27 +196,8 @@ class EntryActivity : LockingHideActivity() {
})
} else {
// If dialog not already shown
if (isFirstTimeAskAllowCopyPasswordAndProtectedFields(this)) {
entryContentsView?.assignPasswordCopyListener(View.OnClickListener {
val message = getString(R.string.allow_copy_password_warning) +
"\n\n" +
getString(R.string.clipboard_warning)
val warningDialog = AlertDialog.Builder(this@EntryActivity)
.setMessage(message).create()
warningDialog.setButton(AlertDialog.BUTTON_POSITIVE, getText(android.R.string.ok)
) { dialog, _ ->
PreferencesUtil.setAllowCopyPasswordAndProtectedFields(this@EntryActivity, true)
dialog.dismiss()
fillEntryDataInContentsView(entry)
}
warningDialog.setButton(AlertDialog.BUTTON_NEGATIVE, getText(android.R.string.cancel)
) { dialog, _ ->
PreferencesUtil.setAllowCopyPasswordAndProtectedFields(this@EntryActivity, false)
dialog.dismiss()
fillEntryDataInContentsView(entry)
}
warningDialog.show()
})
if (isFirstTimeAskAllowCopyPasswordAndProtectedFields) {
entryContentsView?.assignPasswordCopyListener(showWarningClipboardDialogOnClickListener)
} else {
entryContentsView?.assignPasswordCopyListener(null)
}
@@ -209,13 +212,23 @@ class EntryActivity : LockingHideActivity() {
entryContentsView?.clearExtraFields()
entry.fields.doActionToAllCustomProtectedField { label, value ->
val showAction = !value.isProtected || PreferencesUtil.allowCopyPasswordAndProtectedFields(this@EntryActivity)
entryContentsView?.addExtraField(label, value, showAction, View.OnClickListener {
clipboardHelper?.timeoutCopyToClipboard(
value.toString(),
getString(R.string.copy_field, label)
)
})
val allowCopyProtectedField = !value.isProtected || allowCopyPasswordAndProtectedFields
if (allowCopyProtectedField) {
entryContentsView?.addExtraField(label, value, allowCopyProtectedField, View.OnClickListener {
clipboardHelper?.timeoutCopyToClipboard(
value.toString(),
getString(R.string.copy_field, label)
)
})
} else {
// If dialog not already shown
if (isFirstTimeAskAllowCopyPasswordAndProtectedFields) {
entryContentsView?.addExtraField(label, value, allowCopyProtectedField, showWarningClipboardDialogOnClickListener)
} else {
entryContentsView?.addExtraField(label, value, allowCopyProtectedField, null)
}
}
}
}
@@ -306,7 +319,7 @@ class EntryActivity : LockingHideActivity() {
private fun performedNextEducation(entryActivityEducation: EntryActivityEducation,
menu: Menu) {
if (entryContentsView?.isUserNamePresent == true
val entryCopyEducationPerformed = entryContentsView?.isUserNamePresent == true
&& entryActivityEducation.checkAndPerformedEntryCopyEducation(
findViewById(R.id.entry_user_name_action_image),
{
@@ -317,23 +330,29 @@ class EntryActivity : LockingHideActivity() {
{
// Launch autofill settings
startActivity(Intent(this@EntryActivity, SettingsAutofillActivity::class.java))
}))
else if (toolbar?.findViewById<View>(R.id.menu_edit) != null && entryActivityEducation.checkAndPerformedEntryEditEducation(
toolbar!!.findViewById(R.id.menu_edit),
{
onOptionsItemSelected(menu.findItem(R.id.menu_edit))
},
{
// Open Keepass doc to create field references
startActivity(Intent(Intent.ACTION_VIEW,
Uri.parse(getString(R.string.field_references_url))))
}))
;
})
if (!entryCopyEducationPerformed) {
// entryEditEducationPerformed
toolbar?.findViewById<View>(R.id.menu_edit) != null && entryActivityEducation.checkAndPerformedEntryEditEducation(
toolbar!!.findViewById(R.id.menu_edit),
{
onOptionsItemSelected(menu.findItem(R.id.menu_edit))
},
{
// Open Keepass doc to create field references
startActivity(Intent(Intent.ACTION_VIEW,
Uri.parse(getString(R.string.field_references_url))))
})
}
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.menu_contribute -> return MenuUtil.onContributionItemSelected(this)
R.id.menu_contribute -> {
MenuUtil.onContributionItemSelected(this)
return true
}
R.id.menu_toggle_pass -> {
mShowPassword = !mShowPassword
@@ -357,11 +376,7 @@ class EntryActivity : LockingHideActivity() {
url = "http://$url"
}
try {
Util.gotoUrl(this, url)
} catch (e: ActivityNotFoundException) {
Toast.makeText(this, R.string.no_url_handler, Toast.LENGTH_LONG).show()
}
UriUtil.gotoUrl(this, url)
return true
}

View File

@@ -22,7 +22,7 @@ import android.app.Activity
import android.content.Intent
import android.os.Bundle
import android.os.Handler
import android.support.v7.widget.Toolbar
import androidx.appcompat.widget.Toolbar
import android.util.Log
import android.view.Menu
import android.view.MenuItem
@@ -45,7 +45,9 @@ import com.kunzisoft.keepass.timeout.TimeoutHelper
import com.kunzisoft.keepass.utils.MenuUtil
import com.kunzisoft.keepass.view.EntryEditContentsView
class EntryEditActivity : LockingHideActivity(), IconPickerDialogFragment.IconPickerListener, GeneratePasswordDialogFragment.GeneratePasswordListener {
class EntryEditActivity : LockingHideActivity(),
IconPickerDialogFragment.IconPickerListener,
GeneratePasswordDialogFragment.GeneratePasswordListener {
private var mDatabase: Database? = null
@@ -154,33 +156,6 @@ class EntryEditActivity : LockingHideActivity(), IconPickerDialogFragment.IconPi
// Verify the education views
entryEditActivityEducation = EntryEditActivityEducation(this)
entryEditActivityEducation?.let {
Handler().post { performedNextEducation(it) }
}
}
private fun performedNextEducation(entryEditActivityEducation: EntryEditActivityEducation) {
val passwordView = entryEditContentsView?.generatePasswordView
val addNewFieldView = entryEditContentsView?.addNewFieldView
if (passwordView != null
&& entryEditActivityEducation.checkAndPerformedGeneratePasswordEducation(
passwordView,
{
openPasswordGenerator()
},
{
performedNextEducation(entryEditActivityEducation)
}
))
else if (mNewEntry != null && mNewEntry!!.allowExtraFields() && !mNewEntry!!.containsCustomFields()
&& addNewFieldView != null && addNewFieldView.visibility == View.VISIBLE
&& entryEditActivityEducation.checkAndPerformedEntryNewFieldEducation(
addNewFieldView,
{
addNewCustomField()
}))
;
}
private fun populateViewsWithEntry(newEntry: EntryVersioned) {
@@ -309,9 +284,39 @@ class EntryEditActivity : LockingHideActivity(), IconPickerDialogFragment.IconPi
inflater.inflate(R.menu.database_lock, menu)
MenuUtil.contributionMenuInflater(inflater, menu)
entryEditActivityEducation?.let {
Handler().post { performedNextEducation(it) }
}
return true
}
private fun performedNextEducation(entryEditActivityEducation: EntryEditActivityEducation) {
val passwordView = entryEditContentsView?.generatePasswordView
val addNewFieldView = entryEditContentsView?.addNewFieldView
val generatePasswordEducationPerformed = passwordView != null
&& entryEditActivityEducation.checkAndPerformedGeneratePasswordEducation(
passwordView,
{
openPasswordGenerator()
},
{
performedNextEducation(entryEditActivityEducation)
}
)
if (!generatePasswordEducationPerformed) {
// entryNewFieldEducationPerformed
mNewEntry != null && mNewEntry!!.allowExtraFields() && !mNewEntry!!.containsCustomFields()
&& addNewFieldView != null && addNewFieldView.visibility == View.VISIBLE
&& entryEditActivityEducation.checkAndPerformedEntryNewFieldEducation(
addNewFieldView,
{
addNewCustomField()
})
}
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.menu_lock -> {
@@ -319,7 +324,10 @@ class EntryEditActivity : LockingHideActivity(), IconPickerDialogFragment.IconPi
return true
}
R.id.menu_contribute -> return MenuUtil.onContributionItemSelected(this)
R.id.menu_contribute -> {
MenuUtil.onContributionItemSelected(this)
return true
}
android.R.id.home -> finish()
}

View File

@@ -19,7 +19,7 @@
*/
package com.kunzisoft.keepass.activities
import android.Manifest
import android.annotation.SuppressLint
import android.app.Activity
import android.app.assist.AssistStructure
import android.content.Intent
@@ -29,56 +29,43 @@ import android.os.Bundle
import android.os.Environment
import android.os.Handler
import android.preference.PreferenceManager
import android.support.annotation.RequiresApi
import android.support.v7.app.AlertDialog
import android.support.v7.widget.LinearLayoutManager
import android.support.v7.widget.RecyclerView
import android.support.v7.widget.Toolbar
import androidx.annotation.RequiresApi
import com.google.android.material.snackbar.Snackbar
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.SimpleItemAnimator
import androidx.appcompat.widget.Toolbar
import android.util.Log
import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.widget.EditText
import android.widget.TextView
import android.widget.Toast
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.dialogs.AssignMasterKeyDialogFragment
import com.kunzisoft.keepass.activities.dialogs.CreateFileDialogFragment
import com.kunzisoft.keepass.activities.dialogs.FileInformationDialogFragment
import com.kunzisoft.keepass.activities.dialogs.BrowserDialogFragment
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
import com.kunzisoft.keepass.activities.helpers.KeyFileHelper
import com.kunzisoft.keepass.activities.helpers.OpenFileHelper
import com.kunzisoft.keepass.activities.stylish.StylishActivity
import com.kunzisoft.keepass.adapters.FileDatabaseHistoryAdapter
import com.kunzisoft.keepass.app.database.FileDatabaseHistory
import com.kunzisoft.keepass.autofill.AutofillHelper
import com.kunzisoft.keepass.database.action.CreateDatabaseRunnable
import com.kunzisoft.keepass.database.action.ProgressDialogThread
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.education.FileDatabaseSelectActivityEducation
import com.kunzisoft.keepass.fileselect.DeleteFileHistoryAsyncTask
import com.kunzisoft.keepass.fileselect.FileDatabaseModel
import com.kunzisoft.keepass.fileselect.OpenFileHistoryAsyncTask
import com.kunzisoft.keepass.fileselect.database.FileDatabaseHistory
import com.kunzisoft.keepass.magikeyboard.KeyboardHelper
import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.tasks.ActionRunnable
import com.kunzisoft.keepass.utils.MenuUtil
import com.kunzisoft.keepass.utils.UriUtil
import com.kunzisoft.keepass.view.asError
import kotlinx.android.synthetic.main.activity_file_selection.*
import net.cachapa.expandablelayout.ExpandableLayout
import permissions.dispatcher.*
import java.io.File
import java.io.FileNotFoundException
import java.io.IOException
import java.lang.ref.WeakReference
import java.net.URLDecoder
import java.util.*
@RuntimePermissions
class FileDatabaseSelectActivity : StylishActivity(),
CreateFileDialogFragment.DefinePathDialogListener,
AssignMasterKeyDialogFragment.AssignPasswordDialogListener,
FileDatabaseHistoryAdapter.FileItemOpenListener,
FileDatabaseHistoryAdapter.FileSelectClearListener,
FileDatabaseHistoryAdapter.FileInformationShowListener {
AssignMasterKeyDialogFragment.AssignPasswordDialogListener {
// Views
private var fileListContainer: View? = null
@@ -96,14 +83,14 @@ class FileDatabaseSelectActivity : StylishActivity(),
private var mDatabaseFileUri: Uri? = null
private var mKeyFileHelper: KeyFileHelper? = null
private var mOpenFileHelper: OpenFileHelper? = null
private var mDefaultPath: String? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mFileDatabaseHistory = FileDatabaseHistory.getInstance(WeakReference(applicationContext))
mFileDatabaseHistory = FileDatabaseHistory.getInstance(applicationContext)
setContentView(R.layout.activity_file_selection)
fileListContainer = findViewById(R.id.container_file_list)
@@ -131,10 +118,6 @@ class FileDatabaseSelectActivity : StylishActivity(),
fileSelectExpandableLayout?.expand()
}
// History list
val databaseFileListView = findViewById<RecyclerView>(R.id.file_list)
databaseFileListView.layoutManager = LinearLayoutManager(this)
// Open button
openButtonView = findViewById(R.id.open_database)
openButtonView?.setOnClickListener { _ ->
@@ -148,21 +131,56 @@ class FileDatabaseSelectActivity : StylishActivity(),
// Create button
createButtonView = findViewById(R.id.create_database)
createButtonView?.setOnClickListener { openCreateFileDialogFragmentWithPermissionCheck() }
if (Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = "application/x-keepass"
}.resolveActivity(packageManager) == null) {
// No Activity found that can handle this intent.
createButtonView?.visibility = View.GONE
}
else{
// There is an activity which can handle this intent.
createButtonView?.visibility = View.VISIBLE
}
mKeyFileHelper = KeyFileHelper(this)
createButtonView?.setOnClickListener { createNewFile() }
mOpenFileHelper = OpenFileHelper(this)
browseButtonView = findViewById(R.id.browse_button)
browseButtonView?.setOnClickListener(mKeyFileHelper!!.getOpenFileOnClickViewListener {
browseButtonView?.setOnClickListener(mOpenFileHelper!!.getOpenFileOnClickViewListener {
Uri.parse("file://" + openFileNameView!!.text.toString())
})
// History list
val fileDatabaseHistoryRecyclerView = findViewById<RecyclerView>(R.id.file_list)
fileDatabaseHistoryRecyclerView.layoutManager = LinearLayoutManager(this, RecyclerView.VERTICAL, false)
// Removes blinks
(fileDatabaseHistoryRecyclerView.itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false
// Construct adapter with listeners
mAdapterDatabaseHistory = FileDatabaseHistoryAdapter(this@FileDatabaseSelectActivity,
mFileDatabaseHistory?.databaseUriList ?: ArrayList())
mAdapterDatabaseHistory?.setOnItemClickListener(this)
mAdapterDatabaseHistory?.setFileSelectClearListener(this)
mAdapterDatabaseHistory?.setFileInformationShowListener(this)
databaseFileListView.adapter = mAdapterDatabaseHistory
mAdapterDatabaseHistory = FileDatabaseHistoryAdapter(this)
mAdapterDatabaseHistory?.setOnFileDatabaseHistoryOpenListener { fileDatabaseHistoryEntityToOpen ->
launchPasswordActivity(
fileDatabaseHistoryEntityToOpen.databaseUri,
fileDatabaseHistoryEntityToOpen.keyFileUri)
updateFileListVisibility()
}
mAdapterDatabaseHistory?.setOnFileDatabaseHistoryDeleteListener { fileDatabaseHistoryToDelete ->
// Remove from app database
mFileDatabaseHistory?.deleteFileDatabaseHistory(fileDatabaseHistoryToDelete) { fileHistoryDeleted ->
// Remove from adapter
fileHistoryDeleted?.let { databaseFileHistoryDeleted ->
mAdapterDatabaseHistory?.deleteDatabaseFileHistory(databaseFileHistoryDeleted)
mAdapterDatabaseHistory?.notifyDataSetChanged()
updateFileListVisibility()
}
}
true
}
mAdapterDatabaseHistory?.setOnSaveAliasListener { fileDatabaseHistoryWithNewAlias ->
mFileDatabaseHistory?.addOrUpdateFileDatabaseHistory(fileDatabaseHistoryWithNewAlias)
}
fileDatabaseHistoryRecyclerView.adapter = mAdapterDatabaseHistory
// Load default database if not an orientation change
if (!(savedInstanceState != null
@@ -171,68 +189,48 @@ class FileDatabaseSelectActivity : StylishActivity(),
val prefs = PreferenceManager.getDefaultSharedPreferences(this)
val fileName = prefs.getString(PasswordActivity.KEY_DEFAULT_FILENAME, "")
if (fileName != null && fileName.isNotEmpty()) {
val dbUri = UriUtil.parseUriFile(fileName)
var scheme: String? = null
if (dbUri != null)
scheme = dbUri.scheme
if (scheme != null && scheme.isNotEmpty() && scheme.equals("file", ignoreCase = true)) {
val path = dbUri!!.path
val db = File(path!!)
if (db.exists()) {
launchPasswordActivityWithPath(path)
}
} else {
if (dbUri != null)
launchPasswordActivityWithPath(dbUri.toString())
try {
UriUtil.verifyFilePath(fileName) { path ->
launchPasswordActivityWithPath(path)
}
} catch (e: FileNotFoundException) {
Log.e(TAG, "Unable to launch Password Activity", e)
}
}
Handler().post { performedNextEducation(FileDatabaseSelectActivityEducation(this)) }
// Retrieve the database URI provided by file manager after an orientation change
if (savedInstanceState != null
&& savedInstanceState.containsKey(EXTRA_DATABASE_URI)) {
mDatabaseFileUri = savedInstanceState.getParcelable(EXTRA_DATABASE_URI)
}
}
private fun performedNextEducation(fileDatabaseSelectActivityEducation: FileDatabaseSelectActivityEducation) {
// If no recent files
if (createButtonView != null
&& mFileDatabaseHistory != null
&& !mFileDatabaseHistory!!.hasRecentFiles() && fileDatabaseSelectActivityEducation.checkAndPerformedCreateDatabaseEducation(
createButtonView!!,
{
openCreateFileDialogFragmentWithPermissionCheck()
},
{
// But if the user cancel, it can also select a database
performedNextEducation(fileDatabaseSelectActivityEducation)
}))
else if (browseButtonView != null
&& fileDatabaseSelectActivityEducation.checkAndPerformedSelectDatabaseEducation(
browseButtonView!!,
{tapTargetView ->
tapTargetView?.let {
mKeyFileHelper?.openFileOnClickViewListener?.onClick(it)
}
},
{
fileSelectExpandableButtonView?.let {
fileDatabaseSelectActivityEducation
.checkAndPerformedOpenLinkDatabaseEducation(it)
}
}
))
;
/**
* Create a new file by calling the content provider
*/
@SuppressLint("InlinedApi")
private fun createNewFile() {
try {
startActivityForResult(Intent(
Intent.ACTION_CREATE_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = "application/x-keepass"
putExtra(Intent.EXTRA_TITLE, getString(R.string.database_file_name_default) +
getString(R.string.database_file_extension_default))
},
CREATE_FILE_REQUEST_CODE)
} catch (e: Exception) {
BrowserDialogFragment().show(supportFragmentManager, "browserDialog")
}
}
private fun fileNoFoundAction(e: FileNotFoundException) {
val error = getString(R.string.file_not_found_content)
Toast.makeText(this@FileDatabaseSelectActivity,
error, Toast.LENGTH_LONG).show()
Snackbar.make(activity_file_selection_coordinator_layout, error, Snackbar.LENGTH_LONG).asError().show()
Log.e(TAG, error, e)
}
private fun launchPasswordActivity(fileName: String, keyFile: String) {
private fun launchPasswordActivity(fileName: String, keyFile: String?) {
EntrySelectionHelper.doEntrySelectionAction(intent,
{
try {
@@ -295,26 +293,23 @@ class FileDatabaseSelectActivity : StylishActivity(),
super.onResume()
updateExternalStorageWarning()
updateFileListVisibility()
mAdapterDatabaseHistory!!.notifyDataSetChanged()
// Construct adapter with listeners
mFileDatabaseHistory?.getAllFileDatabaseHistories { databaseFileHistoryList ->
databaseFileHistoryList?.let {
mAdapterDatabaseHistory?.addDatabaseFileHistoryList(it)
updateFileListVisibility()
mAdapterDatabaseHistory?.notifyDataSetChanged()
}
}
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
// only to keep the current activity
outState.putBoolean(EXTRA_STAY, true)
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
// NOTE: delegate the permission handling to generated method
onRequestPermissionsResult(requestCode, grantResults)
}
@NeedsPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)
fun openCreateFileDialogFragment() {
val createFileDialogFragment = CreateFileDialogFragment()
createFileDialogFragment.show(supportFragmentManager, "createFileDialogFragment")
// to retrieve the URI of a created database after an orientation change
outState.putParcelable(EXTRA_DATABASE_URI, mDatabaseFileUri)
}
private fun updateFileListVisibility() {
@@ -324,82 +319,6 @@ class FileDatabaseSelectActivity : StylishActivity(),
fileListContainer?.visibility = View.VISIBLE
}
/**
* Create file for database
* @return If not created, return false
*/
private fun createDatabaseFile(path: Uri): Boolean {
val pathString = URLDecoder.decode(path.path, "UTF-8")
// Make sure file name exists
if (pathString.isEmpty()) {
Log.e(TAG, getString(R.string.error_filename_required))
Toast.makeText(this@FileDatabaseSelectActivity,
R.string.error_filename_required,
Toast.LENGTH_LONG).show()
return false
}
// Try to create the file
val file = File(pathString)
try {
if (file.exists()) {
Log.e(TAG, getString(R.string.error_database_exists) + " " + file)
Toast.makeText(this@FileDatabaseSelectActivity,
R.string.error_database_exists,
Toast.LENGTH_LONG).show()
return false
}
val parent = file.parentFile
if (parent == null || parent.exists() && !parent.isDirectory) {
Log.e(TAG, getString(R.string.error_invalid_path) + " " + file)
Toast.makeText(this@FileDatabaseSelectActivity,
R.string.error_invalid_path,
Toast.LENGTH_LONG).show()
return false
}
if (!parent.exists()) {
// Create parent directory
if (!parent.mkdirs()) {
Log.e(TAG, getString(R.string.error_could_not_create_parent) + " " + parent)
Toast.makeText(this@FileDatabaseSelectActivity,
R.string.error_could_not_create_parent,
Toast.LENGTH_LONG).show()
return false
}
}
return file.createNewFile()
} catch (e: IOException) {
Log.e(TAG, getString(R.string.error_could_not_create_parent) + " " + e.localizedMessage)
e.printStackTrace()
Toast.makeText(
this@FileDatabaseSelectActivity,
getText(R.string.error_file_not_create).toString() + " "
+ e.localizedMessage,
Toast.LENGTH_LONG).show()
return false
}
}
override fun onDefinePathDialogPositiveClick(pathFile: Uri?): Boolean {
mDatabaseFileUri = pathFile
if (pathFile == null)
return false
return if (createDatabaseFile(pathFile)) {
AssignMasterKeyDialogFragment().show(supportFragmentManager, "passwordDialog")
true
} else
false
}
override fun onDefinePathDialogNegativeClick(pathFile: Uri?): Boolean {
return true
}
override fun onAssignKeyDialogPositiveClick(
masterPasswordChecked: Boolean, masterPassword: String?,
keyFileChecked: Boolean, keyFile: Uri?) {
@@ -418,22 +337,21 @@ class FileDatabaseSelectActivity : StylishActivity(),
keyFileChecked,
keyFile,
true, // TODO get readonly
LaunchGroupActivityFinish(databaseUri)
LaunchGroupActivityFinish(databaseUri, keyFile)
)
},
R.string.progress_create)
.start()
}
} catch (e: Exception) {
val error = "Unable to create database with this password and key file"
Toast.makeText(this, error, Toast.LENGTH_LONG).show()
Log.e(TAG, error + " " + e.message)
// TODO remove
e.printStackTrace()
val error = getString(R.string.error_create_database_file)
Snackbar.make(activity_file_selection_coordinator_layout, error, Snackbar.LENGTH_LONG).asError().show()
Log.e(TAG, error, e)
}
}
private inner class LaunchGroupActivityFinish internal constructor(private val fileURI: Uri) : ActionRunnable() {
private inner class LaunchGroupActivityFinish(private val databaseFileUri: Uri,
private val keyFileUri: Uri?) : ActionRunnable() {
override fun run() {
finishRun(true, null)
@@ -443,7 +361,7 @@ class FileDatabaseSelectActivity : StylishActivity(),
runOnUiThread {
if (result.isSuccess) {
// Add database to recent files
mFileDatabaseHistory?.addDatabaseUri(fileURI)
mFileDatabaseHistory?.addOrUpdateDatabaseUri(databaseFileUri, keyFileUri)
mAdapterDatabaseHistory?.notifyDataSetChanged()
updateFileListVisibility()
GroupActivity.launch(this@FileDatabaseSelectActivity)
@@ -460,29 +378,6 @@ class FileDatabaseSelectActivity : StylishActivity(),
}
override fun onFileItemOpenListener(itemPosition: Int) {
OpenFileHistoryAsyncTask({ fileName, keyFile ->
if (fileName != null && keyFile != null)
launchPasswordActivity(fileName, keyFile)
updateFileListVisibility()
}, mFileDatabaseHistory).execute(itemPosition)
}
override fun onClickFileInformation(fileDatabaseModel: FileDatabaseModel) {
FileInformationDialogFragment.newInstance(fileDatabaseModel).show(supportFragmentManager, "fileInformation")
}
override fun onFileSelectClearListener(fileDatabaseModel: FileDatabaseModel): Boolean {
DeleteFileHistoryAsyncTask({
fileDatabaseModel.fileUri?.let {
mFileDatabaseHistory?.deleteDatabaseUri(it)
}
mAdapterDatabaseHistory?.notifyDataSetChanged()
updateFileListVisibility()
}, mFileDatabaseHistory, mAdapterDatabaseHistory).execute(fileDatabaseModel)
return true
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
@@ -490,7 +385,7 @@ class FileDatabaseSelectActivity : StylishActivity(),
AutofillHelper.onActivityResultSetResultAndFinish(this, requestCode, resultCode, data)
}
mKeyFileHelper?.onActivityResultCallback(requestCode, resultCode, data
mOpenFileHelper?.onActivityResultCallback(requestCode, resultCode, data
) { uri ->
if (uri != null) {
if (PreferencesUtil.autoOpenSelectedFile(this@FileDatabaseSelectActivity)) {
@@ -501,33 +396,62 @@ class FileDatabaseSelectActivity : StylishActivity(),
}
}
}
}
@OnShowRationale(Manifest.permission.WRITE_EXTERNAL_STORAGE)
internal fun showRationaleForExternalStorage(request: PermissionRequest) {
AlertDialog.Builder(this)
.setMessage(R.string.permission_external_storage_rationale_write_database)
.setPositiveButton(R.string.allow) { _, _ -> request.proceed() }
.setNegativeButton(R.string.cancel) { _, _ -> request.cancel() }
.show()
}
@OnPermissionDenied(Manifest.permission.WRITE_EXTERNAL_STORAGE)
internal fun showDeniedForExternalStorage() {
Toast.makeText(this, R.string.permission_external_storage_denied, Toast.LENGTH_SHORT).show()
}
@OnNeverAskAgain(Manifest.permission.WRITE_EXTERNAL_STORAGE)
internal fun showNeverAskForExternalStorage() {
Toast.makeText(this, R.string.permission_external_storage_never_ask, Toast.LENGTH_SHORT).show()
// Retrieve the created URI from the file manager
if (requestCode == CREATE_FILE_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
mDatabaseFileUri = data?.data
if (mDatabaseFileUri != null) {
AssignMasterKeyDialogFragment().show(supportFragmentManager, "passwordDialog")
}
// else {
// TODO Show error
// }
}
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
super.onCreateOptionsMenu(menu)
MenuUtil.defaultMenuInflater(menuInflater, menu)
Handler().post { performedNextEducation(FileDatabaseSelectActivityEducation(this)) }
return true
}
private fun performedNextEducation(fileDatabaseSelectActivityEducation: FileDatabaseSelectActivityEducation) {
// If no recent files
val createDatabaseEducationPerformed = createButtonView != null && createButtonView!!.visibility == View.VISIBLE
&& mAdapterDatabaseHistory != null
&& mAdapterDatabaseHistory!!.itemCount > 0
&& fileDatabaseSelectActivityEducation.checkAndPerformedCreateDatabaseEducation(
createButtonView!!,
{
createNewFile()
},
{
// But if the user cancel, it can also select a database
performedNextEducation(fileDatabaseSelectActivityEducation)
})
if (!createDatabaseEducationPerformed) {
// selectDatabaseEducationPerformed
browseButtonView != null
&& fileDatabaseSelectActivityEducation.checkAndPerformedSelectDatabaseEducation(
browseButtonView!!,
{tapTargetView ->
tapTargetView?.let {
mOpenFileHelper?.openFileOnClickViewListener?.onClick(it)
}
},
{
fileSelectExpandableButtonView?.let {
fileDatabaseSelectActivityEducation
.checkAndPerformedOpenLinkDatabaseEducation(it)
}
}
)
}
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return MenuUtil.onDefaultMenuOptionsItemSelected(this, item) && super.onOptionsItemSelected(item)
}
@@ -536,6 +460,9 @@ class FileDatabaseSelectActivity : StylishActivity(),
private const val TAG = "FileDbSelectActivity"
private const val EXTRA_STAY = "EXTRA_STAY"
private const val EXTRA_DATABASE_URI = "EXTRA_DATABASE_URI"
private const val CREATE_FILE_REQUEST_CODE = 3853
/*
* -------------------------

View File

@@ -29,11 +29,10 @@ import android.graphics.Color
import android.os.Build
import android.os.Bundle
import android.os.Handler
import android.preference.PreferenceManager
import android.support.annotation.RequiresApi
import android.support.v4.app.FragmentManager
import android.support.v7.widget.SearchView
import android.support.v7.widget.Toolbar
import androidx.annotation.RequiresApi
import androidx.fragment.app.FragmentManager
import androidx.appcompat.widget.SearchView
import androidx.appcompat.widget.Toolbar
import android.util.Log
import android.view.Menu
import android.view.MenuItem
@@ -183,7 +182,7 @@ class GroupActivity : LockingActivity(),
// Attach fragment to content view
supportFragmentManager.beginTransaction().replace(
R.id.nodes_list_fragment_container,
mListNodesFragment,
mListNodesFragment!!,
fragmentTag)
.commit()
@@ -628,8 +627,9 @@ class GroupActivity : LockingActivity(),
private fun performedNextEducation(groupActivityEducation: GroupActivityEducation,
menu: Menu) {
// If no node, show education to add new one
if (mListNodesFragment != null
val addNodeButtonEducationPerformed = mListNodesFragment != null
&& mListNodesFragment!!.isEmpty
&& addNodeButtonView?.addButtonView != null
&& addNodeButtonView!!.isEnable
@@ -641,38 +641,48 @@ class GroupActivity : LockingActivity(),
{
performedNextEducation(groupActivityEducation, menu)
}
))
else if (toolbar != null
&& toolbar!!.findViewById<View>(R.id.menu_search) != null
&& groupActivityEducation.checkAndPerformedSearchMenuEducation(
toolbar!!.findViewById(R.id.menu_search),
{
menu.findItem(R.id.menu_search).expandActionView()
},
{
performedNextEducation(groupActivityEducation, menu)
}))
else if (toolbar != null
&& toolbar!!.findViewById<View>(R.id.menu_sort) != null
&& groupActivityEducation.checkAndPerformedSortMenuEducation(
)
if (!addNodeButtonEducationPerformed) {
val searchMenuEducationPerformed = toolbar != null
&& toolbar!!.findViewById<View>(R.id.menu_search) != null
&& groupActivityEducation.checkAndPerformedSearchMenuEducation(
toolbar!!.findViewById(R.id.menu_search),
{
menu.findItem(R.id.menu_search).expandActionView()
},
{
performedNextEducation(groupActivityEducation, menu)
})
if (!searchMenuEducationPerformed) {
val sortMenuEducationPerformed = toolbar != null
&& toolbar!!.findViewById<View>(R.id.menu_sort) != null
&& groupActivityEducation.checkAndPerformedSortMenuEducation(
toolbar!!.findViewById(R.id.menu_sort),
{
onOptionsItemSelected(menu.findItem(R.id.menu_sort))
},
{
performedNextEducation(groupActivityEducation, menu)
}))
else if (toolbar != null
&& toolbar!!.findViewById<View>(R.id.menu_lock) != null
&& groupActivityEducation.checkAndPerformedLockMenuEducation(
toolbar!!.findViewById(R.id.menu_lock),
{
onOptionsItemSelected(menu.findItem(R.id.menu_lock))
},
{
performedNextEducation(groupActivityEducation, menu)
}))
;
})
if (!sortMenuEducationPerformed) {
// lockMenuEducationPerformed
toolbar != null
&& toolbar!!.findViewById<View>(R.id.menu_lock) != null
&& groupActivityEducation.checkAndPerformedLockMenuEducation(
toolbar!!.findViewById(R.id.menu_lock),
{
onOptionsItemSelected(menu.findItem(R.id.menu_lock))
},
{
performedNextEducation(groupActivityEducation, menu)
})
}
}
}
}
override fun startActivity(intent: Intent) {
@@ -857,11 +867,9 @@ class GroupActivity : LockingActivity(),
}
private fun showWarnings() {
// TODO Preferences
if (Database.getInstance().isReadOnly) {
val prefs = PreferenceManager.getDefaultSharedPreferences(this)
if (prefs.getBoolean(getString(R.string.show_read_only_warning), true)) {
ReadOnlyDialog(this).show()
if (PreferencesUtil.showReadOnlyWarning(this)) {
ReadOnlyDialog().show(supportFragmentManager, "readOnlyDialog")
}
}
}

View File

@@ -5,8 +5,8 @@ import android.content.Intent
import android.content.SharedPreferences
import android.os.Bundle
import android.preference.PreferenceManager
import android.support.v7.widget.LinearLayoutManager
import android.support.v7.widget.RecyclerView
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import android.util.Log
import android.view.LayoutInflater
import android.view.Menu
@@ -130,7 +130,7 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
onScrollListener?.let { onScrollListener ->
listView?.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView?, dx: Int, dy: Int) {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
onScrollListener.onScrolled(dy)
}

View File

@@ -19,7 +19,6 @@
*/
package com.kunzisoft.keepass.activities
import android.Manifest
import android.app.Activity
import android.app.assist.AssistStructure
import android.app.backup.BackupManager
@@ -32,11 +31,12 @@ import android.os.Build
import android.os.Bundle
import android.os.Handler
import android.preference.PreferenceManager
import android.support.annotation.RequiresApi
import android.support.v7.app.AlertDialog
import android.support.v7.widget.Toolbar
import androidx.annotation.RequiresApi
import com.google.android.material.snackbar.Snackbar
import androidx.appcompat.widget.Toolbar
import android.text.Editable
import android.text.TextWatcher
import android.util.Log
import android.view.KeyEvent
import android.view.Menu
import android.view.MenuItem
@@ -45,9 +45,14 @@ import android.view.inputmethod.EditorInfo.IME_ACTION_DONE
import android.widget.*
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.dialogs.PasswordEncodingDialogFragment
import com.kunzisoft.keepass.activities.helpers.*
import com.kunzisoft.keepass.utils.ClipDataCompat
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
import com.kunzisoft.keepass.activities.helpers.OpenFileHelper
import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper
import com.kunzisoft.keepass.activities.lock.LockingActivity
import com.kunzisoft.keepass.activities.stylish.StylishActivity
import com.kunzisoft.keepass.utils.FileDatabaseInfo
import com.kunzisoft.keepass.app.database.FileDatabaseHistory
import com.kunzisoft.keepass.autofill.AutofillHelper
import com.kunzisoft.keepass.database.action.LoadDatabaseRunnable
import com.kunzisoft.keepass.database.action.ProgressDialogThread
@@ -61,14 +66,13 @@ import com.kunzisoft.keepass.tasks.ActionRunnable
import com.kunzisoft.keepass.utils.MenuUtil
import com.kunzisoft.keepass.utils.UriUtil
import com.kunzisoft.keepass.view.FingerPrintInfoView
import permissions.dispatcher.*
import java.io.File
import com.kunzisoft.keepass.view.asError
import kotlinx.android.synthetic.main.activity_password.*
import java.io.FileNotFoundException
import java.lang.Exception
import java.lang.ref.WeakReference
@RuntimePermissions
class PasswordActivity : StylishActivity(),
UriIntentInitTaskCallback {
class PasswordActivity : StylishActivity() {
// Views
private var toolbar: Toolbar? = null
@@ -87,7 +91,7 @@ class PasswordActivity : StylishActivity(),
private var prefs: SharedPreferences? = null
private var mRememberKeyFile: Boolean = false
private var mKeyFileHelper: KeyFileHelper? = null
private var mOpenFileHelper: OpenFileHelper? = null
private var readOnly: Boolean = false
@@ -121,8 +125,8 @@ class PasswordActivity : StylishActivity(),
readOnly = ReadOnlyHelper.retrieveReadOnlyFromInstanceStateOrPreference(this, savedInstanceState)
val browseView = findViewById<View>(R.id.browse_button)
mKeyFileHelper = KeyFileHelper(this@PasswordActivity)
browseView.setOnClickListener(mKeyFileHelper!!.openFileOnClickViewListener)
mOpenFileHelper = OpenFileHelper(this@PasswordActivity)
browseView.setOnClickListener(mOpenFileHelper!!.openFileOnClickViewListener)
passwordView?.setOnEditorActionListener(onEditorActionListener)
passwordView?.addTextChangedListener(object : TextWatcher {
@@ -172,8 +176,7 @@ class PasswordActivity : StylishActivity(),
// For check shutdown
super.onResume()
UriIntentInitTask(WeakReference(this), this, mRememberKeyFile)
.execute(intent)
initUriFromIntent()
}
override fun onSaveInstanceState(outState: Bundle) {
@@ -181,26 +184,56 @@ class PasswordActivity : StylishActivity(),
super.onSaveInstanceState(outState)
}
override fun onPostInitTask(databaseFileUri: Uri?, keyFileUri: Uri?, errorStringId: Int?) {
mDatabaseFileUri = databaseFileUri
private fun initUriFromIntent() {
if (errorStringId != null) {
Toast.makeText(this@PasswordActivity, errorStringId, Toast.LENGTH_LONG).show()
finish()
return
val databaseUri: Uri?
val keyFileUri: Uri?
// If is a view intent
val action = intent.action
if (action != null && action == VIEW_INTENT) {
val databaseUriRetrieve = intent.data
// Stop activity here if we can't verify database URI
try {
UriUtil.verifyFileUri(databaseUriRetrieve)
} catch (e : Exception) {
Log.e(TAG, "File URI not validate", e)
Toast.makeText(this@PasswordActivity, e.message, Toast.LENGTH_LONG).show()
finish()
return
}
databaseUri = databaseUriRetrieve
keyFileUri = ClipDataCompat.getUriFromIntent(intent, KEY_KEYFILE)
} else {
databaseUri = UriUtil.parseUriFile(intent.getStringExtra(KEY_FILENAME))
keyFileUri = UriUtil.parseUriFile(intent.getStringExtra(KEY_KEYFILE))
}
// Verify permission to read file
if (databaseFileUri != null && !databaseFileUri.scheme!!.contains("content"))
doNothingWithPermissionCheck()
// Post init uri with KeyFile if needed
if (mRememberKeyFile && (keyFileUri == null || keyFileUri.toString().isEmpty())) {
// Retrieve KeyFile in a thread
databaseUri?.let { databaseUriNotNull ->
FileDatabaseHistory.getInstance(applicationContext)
.getKeyFileUriByDatabaseUri(databaseUriNotNull) {
onPostInitUri(databaseUri, it)
}
}
} else {
onPostInitUri(databaseUri, keyFileUri)
}
}
private fun onPostInitUri(databaseFileUri: Uri?, keyFileUri: Uri?) {
mDatabaseFileUri = databaseFileUri
// Define title
val dbUriString = databaseFileUri?.toString() ?: ""
if (dbUriString.isNotEmpty()) {
if (PreferencesUtil.isFullFilePathEnable(this))
filenameView?.text = dbUriString
else
filenameView?.text = File(databaseFileUri!!.path!!).name // TODO Encapsulate
databaseFileUri?.let {
FileDatabaseInfo(this, it).retrieveDatabaseTitle { title ->
filenameView?.text = title
}
}
// Define Key File text
@@ -216,7 +249,7 @@ class PasswordActivity : StylishActivity(),
newDefaultFileName = databaseFileUri?.toString() ?: newDefaultFileName
}
prefs?.edit()?.apply() {
prefs?.edit()?.apply {
putString(KEY_DEFAULT_FILENAME, newDefaultFileName)
apply()
}
@@ -418,7 +451,7 @@ class PasswordActivity : StylishActivity(),
}
} else {
if (result.message != null && result.message!!.isNotEmpty()) {
Toast.makeText(this@PasswordActivity, result.message, Toast.LENGTH_LONG).show()
Snackbar.make(activity_password_coordinator_layout, result.message!!, Snackbar.LENGTH_LONG).asError().show()
}
}
}
@@ -465,32 +498,40 @@ class PasswordActivity : StylishActivity(),
private fun performedNextEducation(passwordActivityEducation: PasswordActivityEducation,
menu: Menu) {
if (toolbar != null
&& passwordActivityEducation.checkAndPerformedFingerprintUnlockEducation(
val unlockEducationPerformed = toolbar != null
&& passwordActivityEducation.checkAndPerformedUnlockEducation(
toolbar!!,
{
performedNextEducation(passwordActivityEducation, menu)
},
{
performedNextEducation(passwordActivityEducation, menu)
}))
else if (toolbar != null
&& toolbar!!.findViewById<View>(R.id.menu_open_file_read_mode_key) != null
&& passwordActivityEducation.checkAndPerformedReadOnlyEducation(
toolbar!!.findViewById(R.id.menu_open_file_read_mode_key),
{
onOptionsItemSelected(menu.findItem(R.id.menu_open_file_read_mode_key))
performedNextEducation(passwordActivityEducation, menu)
},
{
performedNextEducation(passwordActivityEducation, menu)
}))
else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
&& PreferencesUtil.isFingerprintEnable(applicationContext)
&& FingerPrintHelper.isFingerprintSupported(getSystemService(FingerprintManager::class.java))
&& fingerPrintInfoView != null && fingerPrintInfoView?.fingerPrintImageView != null
&& passwordActivityEducation.checkAndPerformedFingerprintEducation(fingerPrintInfoView?.fingerPrintImageView!!))
;
})
if (!unlockEducationPerformed) {
val readOnlyEducationPerformed = toolbar != null
&& toolbar!!.findViewById<View>(R.id.menu_open_file_read_mode_key) != null
&& passwordActivityEducation.checkAndPerformedReadOnlyEducation(
toolbar!!.findViewById(R.id.menu_open_file_read_mode_key),
{
onOptionsItemSelected(menu.findItem(R.id.menu_open_file_read_mode_key))
performedNextEducation(passwordActivityEducation, menu)
},
{
performedNextEducation(passwordActivityEducation, menu)
})
if (!readOnlyEducationPerformed) {
// fingerprintEducationPerformed
(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
&& PreferencesUtil.isFingerprintEnable(applicationContext)
&& FingerPrintHelper.isFingerprintSupported(getSystemService(FingerprintManager::class.java))
&& fingerPrintInfoView != null && fingerPrintInfoView?.fingerPrintImageView != null
&& passwordActivityEducation.checkAndPerformedFingerprintEducation(fingerPrintInfoView?.fingerPrintImageView!!))
}
}
}
private fun changeOpenFileReadIcon(togglePassword: MenuItem) {
@@ -520,12 +561,6 @@ class PasswordActivity : StylishActivity(),
return super.onOptionsItemSelected(item)
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
// NOTE: delegate the permission handling to generated method
onRequestPermissionsResult(requestCode, grantResults)
}
override fun onActivityResult(
requestCode: Int,
resultCode: Int,
@@ -538,7 +573,7 @@ class PasswordActivity : StylishActivity(),
}
var keyFileResult = false
mKeyFileHelper?.let {
mOpenFileHelper?.let {
keyFileResult = it.onActivityResultCallback(requestCode, resultCode, data
) { uri ->
if (uri != null) {
@@ -557,64 +592,28 @@ class PasswordActivity : StylishActivity(),
}
}
@NeedsPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)
fun doNothing() {
}
@OnShowRationale(Manifest.permission.WRITE_EXTERNAL_STORAGE)
internal fun showRationaleForExternalStorage(request: PermissionRequest) {
AlertDialog.Builder(this)
.setMessage(R.string.permission_external_storage_rationale_read_database)
.setPositiveButton(R.string.allow) { _, _ -> request.proceed() }
.setNegativeButton(R.string.cancel) { _, _ -> request.cancel() }
.show()
}
@OnPermissionDenied(Manifest.permission.WRITE_EXTERNAL_STORAGE)
internal fun showDeniedForExternalStorage() {
Toast.makeText(this, R.string.permission_external_storage_denied, Toast.LENGTH_SHORT).show()
finish()
}
@OnNeverAskAgain(Manifest.permission.WRITE_EXTERNAL_STORAGE)
internal fun showNeverAskForExternalStorage() {
Toast.makeText(this, R.string.permission_external_storage_never_ask, Toast.LENGTH_SHORT).show()
finish()
}
companion object {
private val TAG = PasswordActivity::class.java.name
const val KEY_DEFAULT_FILENAME = "defaultFileName"
private const val KEY_FILENAME = "fileName"
private const val KEY_KEYFILE = "keyFile"
private const val VIEW_INTENT = "android.intent.action.VIEW"
private const val KEY_PASSWORD = "password"
private const val KEY_LAUNCH_IMMEDIATELY = "launchImmediately"
private fun buildAndLaunchIntent(activity: Activity, fileName: String, keyFile: String,
private fun buildAndLaunchIntent(activity: Activity, fileName: String, keyFile: String?,
intentBuildLauncher: (Intent) -> Unit) {
val intent = Intent(activity, PasswordActivity::class.java)
intent.putExtra(UriIntentInitTask.KEY_FILENAME, fileName)
intent.putExtra(UriIntentInitTask.KEY_KEYFILE, keyFile)
intent.putExtra(KEY_FILENAME, fileName)
if (keyFile != null)
intent.putExtra(KEY_KEYFILE, keyFile)
intentBuildLauncher.invoke(intent)
}
@Throws(FileNotFoundException::class)
private fun verifyFileNameUriFromLaunch(fileName: String) {
if (fileName.isEmpty()) {
throw FileNotFoundException()
}
val uri = UriUtil.parseUriFile(fileName)
val scheme = uri?.scheme
if (scheme != null && scheme.isNotEmpty() && scheme.equals("file", ignoreCase = true)) {
val dbFile = File(uri.path!!)
if (!dbFile.exists()) {
throw FileNotFoundException()
}
}
}
/*
* -------------------------
* Standard Launch
@@ -625,8 +624,8 @@ class PasswordActivity : StylishActivity(),
fun launch(
activity: Activity,
fileName: String,
keyFile: String) {
verifyFileNameUriFromLaunch(fileName)
keyFile: String?) {
UriUtil.verifyFilePath(fileName)
buildAndLaunchIntent(activity, fileName, keyFile) { intent ->
activity.startActivity(intent)
}
@@ -642,8 +641,8 @@ class PasswordActivity : StylishActivity(),
fun launchForKeyboardResult(
activity: Activity,
fileName: String,
keyFile: String) {
verifyFileNameUriFromLaunch(fileName)
keyFile: String?) {
UriUtil.verifyFilePath(fileName)
buildAndLaunchIntent(activity, fileName, keyFile) { intent ->
KeyboardHelper.startActivityForKeyboardSelection(activity, intent)
@@ -661,9 +660,9 @@ class PasswordActivity : StylishActivity(),
fun launchForAutofillResult(
activity: Activity,
fileName: String,
keyFile: String,
keyFile: String?,
assistStructure: AssistStructure?) {
verifyFileNameUriFromLaunch(fileName)
UriUtil.verifyFilePath(fileName)
if (assistStructure != null) {
buildAndLaunchIntent(activity, fileName, keyFile) { intent ->

View File

@@ -25,16 +25,16 @@ import android.content.DialogInterface
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.support.v4.app.DialogFragment
import android.support.v7.app.AlertDialog
import com.google.android.material.textfield.TextInputLayout
import androidx.fragment.app.DialogFragment
import androidx.appcompat.app.AlertDialog
import android.text.Editable
import android.text.TextWatcher
import android.view.View
import android.widget.CompoundButton
import android.widget.TextView
import android.widget.Toast
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.helpers.KeyFileHelper
import com.kunzisoft.keepass.activities.helpers.OpenFileHelper
import com.kunzisoft.keepass.utils.UriUtil
class AssignMasterKeyDialogFragment : DialogFragment() {
@@ -43,15 +43,39 @@ class AssignMasterKeyDialogFragment : DialogFragment() {
private var mKeyFile: Uri? = null
private var rootView: View? = null
private var passwordCheckBox: CompoundButton? = null
private var passView: TextView? = null
private var passConfView: TextView? = null
private var passwordView: TextView? = null
private var passwordRepeatTextInputLayout: TextInputLayout? = null
private var passwordRepeatView: TextView? = null
private var keyFileTextInputLayout: TextInputLayout? = null
private var keyFileCheckBox: CompoundButton? = null
private var keyFileView: TextView? = null
private var mListener: AssignPasswordDialogListener? = null
private var mKeyFileHelper: KeyFileHelper? = null
private var mOpenFileHelper: OpenFileHelper? = null
private val passwordTextWatcher = object : TextWatcher {
override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
override fun onTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
override fun afterTextChanged(editable: Editable) {
passwordCheckBox?.isChecked = true
}
}
private val keyFileTextWatcher = object : TextWatcher {
override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
override fun onTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
override fun afterTextChanged(editable: Editable) {
keyFileCheckBox?.isChecked = true
}
}
interface AssignPasswordDialogListener {
fun onAssignKeyDialogPositiveClick(masterPasswordChecked: Boolean, masterPassword: String?,
@@ -83,33 +107,17 @@ class AssignMasterKeyDialogFragment : DialogFragment() {
.setNegativeButton(R.string.cancel) { _, _ -> }
passwordCheckBox = rootView?.findViewById(R.id.password_checkbox)
passView = rootView?.findViewById(R.id.pass_password)
passView?.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
override fun onTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
override fun afterTextChanged(editable: Editable) {
passwordCheckBox?.isChecked = true
}
})
passConfView = rootView?.findViewById(R.id.pass_conf_password)
passwordView = rootView?.findViewById(R.id.pass_password)
passwordRepeatTextInputLayout = rootView?.findViewById(R.id.password_repeat_input_layout)
passwordRepeatView = rootView?.findViewById(R.id.pass_conf_password)
keyFileTextInputLayout = rootView?.findViewById(R.id.keyfile_input_layout)
keyFileCheckBox = rootView?.findViewById(R.id.keyfile_checkox)
keyFileView = rootView?.findViewById(R.id.pass_keyfile)
keyFileView?.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
override fun onTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
override fun afterTextChanged(editable: Editable) {
keyFileCheckBox?.isChecked = true
}
})
mKeyFileHelper = KeyFileHelper(this)
mOpenFileHelper = OpenFileHelper(this)
rootView?.findViewById<View>(R.id.browse_button)?.setOnClickListener { view ->
mKeyFileHelper?.openFileOnClickViewListener?.onClick(view) }
mOpenFileHelper?.openFileOnClickViewListener?.onClick(view) }
val dialog = builder.create()
@@ -149,20 +157,35 @@ class AssignMasterKeyDialogFragment : DialogFragment() {
return super.onCreateDialog(savedInstanceState)
}
override fun onResume() {
super.onResume()
// To check checkboxes if a text is present
passwordView?.addTextChangedListener(passwordTextWatcher)
keyFileView?.addTextChangedListener(keyFileTextWatcher)
}
override fun onPause() {
super.onPause()
passwordView?.removeTextChangedListener(passwordTextWatcher)
keyFileView?.removeTextChangedListener(keyFileTextWatcher)
}
private fun verifyPassword(): Boolean {
var error = false
if (passwordCheckBox != null
&& passwordCheckBox!!.isChecked
&& passView != null
&& passConfView != null) {
mMasterPassword = passView!!.text.toString()
val confPassword = passConfView!!.text.toString()
&& passwordView != null
&& passwordRepeatView != null) {
mMasterPassword = passwordView!!.text.toString()
val confPassword = passwordRepeatView!!.text.toString()
// Verify that passwords match
if (mMasterPassword != confPassword) {
error = true
// Passwords do not match
Toast.makeText(context, R.string.error_pass_match, Toast.LENGTH_LONG).show()
passwordRepeatTextInputLayout?.error = getString(R.string.error_pass_match)
}
if (mMasterPassword == null || mMasterPassword!!.isEmpty()) {
@@ -183,7 +206,8 @@ class AssignMasterKeyDialogFragment : DialogFragment() {
// Verify that a keyfile is set
if (keyFile == null || keyFile.toString().isEmpty()) {
error = true
Toast.makeText(context, R.string.error_nokeyfile, Toast.LENGTH_LONG).show()
// TODO better keyfile check
keyFileTextInputLayout?.error = getString(R.string.error_nokeyfile)
}
}
return error
@@ -224,7 +248,7 @@ class AssignMasterKeyDialogFragment : DialogFragment() {
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
mKeyFileHelper?.onActivityResultCallback(requestCode, resultCode, data
mOpenFileHelper?.onActivityResultCallback(requestCode, resultCode, data
) { uri ->
UriUtil.parseUriFile(uri)?.let { pathUri ->
keyFileCheckBox?.isChecked = true

View File

@@ -21,11 +21,12 @@ package com.kunzisoft.keepass.activities.dialogs
import android.app.Dialog
import android.os.Bundle
import android.support.v4.app.DialogFragment
import android.support.v7.app.AlertDialog
import androidx.fragment.app.DialogFragment
import androidx.appcompat.app.AlertDialog
import android.widget.Button
import android.widget.TextView
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.utils.Util
import com.kunzisoft.keepass.utils.UriUtil
class BrowserDialogFragment : DialogFragment() {
@@ -37,15 +38,18 @@ class BrowserDialogFragment : DialogFragment() {
builder.setView(root)
.setNegativeButton(R.string.cancel) { _, _ -> }
val market = root.findViewById<Button>(R.id.install_market)
val textDescription = root.findViewById<TextView>(R.id.file_manager_install_description)
textDescription.text = getString(R.string.file_manager_install_description)
val market = root.findViewById<Button>(R.id.file_manager_install_play_store)
market.setOnClickListener {
Util.gotoUrl(context!!, R.string.filemanager_play_store)
UriUtil.gotoUrl(context!!, R.string.filemanager_play_store)
dismiss()
}
val web = root.findViewById<Button>(R.id.install_web)
val web = root.findViewById<Button>(R.id.file_manager_install_f_droid)
web.setOnClickListener {
Util.gotoUrl(context!!, R.string.filemanager_f_droid)
UriUtil.gotoUrl(context!!, R.string.filemanager_f_droid)
dismiss()
}

View File

@@ -1,213 +0,0 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePass DX.
*
* KeePass DX 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.
*
* KeePass DX 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 KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.activities.dialogs
import android.app.Activity
import android.app.Dialog
import android.content.Context
import android.content.DialogInterface
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.os.Environment
import android.support.v4.app.DialogFragment
import android.support.v7.app.AlertDialog
import android.view.ActionMode
import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import android.widget.AdapterView
import android.widget.ArrayAdapter
import android.widget.Button
import android.widget.EditText
import android.widget.Spinner
import android.widget.TextView
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.stylish.FilePickerStylishActivity
import com.kunzisoft.keepass.utils.UriUtil
import com.nononsenseapps.filepicker.FilePickerActivity
import com.nononsenseapps.filepicker.Utils
class CreateFileDialogFragment : DialogFragment(), AdapterView.OnItemSelectedListener {
private val FILE_CODE = 3853
private var folderPathView: EditText? = null
private var fileNameView: EditText? = null
private var positiveButton: Button? = null
private var negativeButton: Button? = null
private var mDefinePathDialogListener: DefinePathDialogListener? = null
private var mDatabaseFileExtension: String? = null
private var mUriPath: Uri? = null
interface DefinePathDialogListener {
fun onDefinePathDialogPositiveClick(pathFile: Uri?): Boolean
fun onDefinePathDialogNegativeClick(pathFile: Uri?): Boolean
}
override fun onAttach(activity: Context?) {
super.onAttach(activity)
try {
mDefinePathDialogListener = activity as DefinePathDialogListener?
} catch (e: ClassCastException) {
throw ClassCastException(activity?.toString()
+ " must implement " + DefinePathDialogListener::class.java.name)
}
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
activity?.let { activity ->
val builder = AlertDialog.Builder(activity)
val inflater = activity.layoutInflater
val rootView = inflater.inflate(R.layout.fragment_file_creation, null)
builder.setView(rootView)
.setTitle(R.string.create_keepass_file)
// Add action buttons
.setPositiveButton(android.R.string.ok) { _, _ -> }
.setNegativeButton(R.string.cancel) { _, _ -> }
// To prevent crash issue #69 https://github.com/Kunzisoft/KeePassDX/issues/69
val actionCopyBarCallback = object : ActionMode.Callback {
override fun onPrepareActionMode(mode: ActionMode, menu: Menu): Boolean {
positiveButton?.isEnabled = false
negativeButton?.isEnabled = false
return true
}
override fun onDestroyActionMode(mode: ActionMode) {
positiveButton?.isEnabled = true
negativeButton?.isEnabled = true
}
override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean {
return true
}
override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean {
return true
}
}
// Folder selection
val browseView = rootView.findViewById<View>(R.id.browse_button)
folderPathView = rootView.findViewById(R.id.folder_path)
folderPathView?.customSelectionActionModeCallback = actionCopyBarCallback
fileNameView = rootView.findViewById(R.id.filename)
fileNameView?.customSelectionActionModeCallback = actionCopyBarCallback
val defaultPath = Environment.getExternalStorageDirectory().path + getString(R.string.database_file_path_default)
folderPathView?.setText(defaultPath)
browseView.setOnClickListener { _ ->
Intent(context, FilePickerStylishActivity::class.java).apply {
putExtra(FilePickerActivity.EXTRA_ALLOW_MULTIPLE, false)
putExtra(FilePickerActivity.EXTRA_ALLOW_CREATE_DIR, true)
putExtra(FilePickerActivity.EXTRA_MODE, FilePickerActivity.MODE_DIR)
putExtra(FilePickerActivity.EXTRA_START_PATH,
Environment.getExternalStorageDirectory().path)
startActivityForResult(this, FILE_CODE)
}
}
// Init path
mUriPath = null
// Extension
mDatabaseFileExtension = getString(R.string.database_file_extension_default)
val spinner = rootView.findViewById<Spinner>(R.id.file_types)
spinner.onItemSelectedListener = this
// Spinner Drop down elements
val fileTypes = resources.getStringArray(R.array.file_types)
val dataAdapter = ArrayAdapter(activity, android.R.layout.simple_spinner_item, fileTypes)
dataAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
spinner.adapter = dataAdapter
// Or text if only one item https://github.com/Kunzisoft/KeePassDX/issues/105
if (fileTypes.size == 1) {
val params = spinner.layoutParams
spinner.visibility = View.GONE
val extensionTextView = TextView(context)
extensionTextView.text = mDatabaseFileExtension
extensionTextView.layoutParams = params
val parentView = spinner.parent as ViewGroup
parentView.addView(extensionTextView)
}
val dialog = builder.create()
dialog.setOnShowListener { _ ->
positiveButton = dialog.getButton(DialogInterface.BUTTON_POSITIVE)
negativeButton = dialog.getButton(DialogInterface.BUTTON_NEGATIVE)
positiveButton?.setOnClickListener { _ ->
mDefinePathDialogListener?.let {
if (it.onDefinePathDialogPositiveClick(buildPath()))
dismiss()
}
}
negativeButton?.setOnClickListener { _->
mDefinePathDialogListener?.let {
if (it.onDefinePathDialogNegativeClick(buildPath())) {
dismiss()
}
}
}
}
return dialog
}
return super.onCreateDialog(savedInstanceState)
}
private fun buildPath(): Uri? {
if (folderPathView != null && fileNameView != null && mDatabaseFileExtension != null) {
var path = Uri.Builder().path(folderPathView!!.text.toString())
.appendPath(fileNameView!!.text.toString() + mDatabaseFileExtension!!)
.build()
context?.let { context ->
path = UriUtil.translateUri(context, path)
}
return path
}
return null
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (requestCode == FILE_CODE && resultCode == Activity.RESULT_OK) {
mUriPath = data?.data
mUriPath?.let {
val file = Utils.getFileForUri(it)
folderPathView?.setText(file.path)
}
}
}
override fun onItemSelected(adapterView: AdapterView<*>, view: View, position: Int, id: Long) {
mDatabaseFileExtension = adapterView.getItemAtPosition(position).toString()
}
override fun onNothingSelected(adapterView: AdapterView<*>) {
// Do nothing
}
}

View File

@@ -1,99 +0,0 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePass DX.
*
* KeePass DX 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.
*
* KeePass DX 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 KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.activities.dialogs
import android.app.Dialog
import android.net.Uri
import android.os.Bundle
import android.support.v4.app.DialogFragment
import android.support.v7.app.AlertDialog
import android.view.View
import android.widget.TextView
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.fileselect.FileDatabaseModel
import java.text.DateFormat
class FileInformationDialogFragment : DialogFragment() {
private var fileSizeContainerView: View? = null
private var fileModificationContainerView: View? = null
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
activity?.let { activity ->
val builder = AlertDialog.Builder(activity)
val inflater = activity.layoutInflater
val root = inflater.inflate(R.layout.fragment_file_selection_information, null)
val fileNameView = root.findViewById<TextView>(R.id.file_filename)
val filePathView = root.findViewById<TextView>(R.id.file_path)
fileSizeContainerView = root.findViewById(R.id.file_size_container)
val fileSizeView = root.findViewById<TextView>(R.id.file_size)
fileModificationContainerView = root.findViewById(R.id.file_modification_container)
val fileModificationView = root.findViewById<TextView>(R.id.file_modification)
arguments?.apply {
if (containsKey(FILE_SELECT_BEEN_ARG)) {
(getSerializable(FILE_SELECT_BEEN_ARG) as FileDatabaseModel?)?.let { fileDatabaseModel ->
fileDatabaseModel.fileUri?.let { fileUri ->
filePathView.text = Uri.decode(fileUri.toString())
}
fileNameView.text = fileDatabaseModel.fileName
if (fileDatabaseModel.notFound()) {
hideFileInfo()
} else {
showFileInfo()
fileSizeView.text = fileDatabaseModel.size.toString()
fileModificationView.text = DateFormat.getDateTimeInstance()
.format(fileDatabaseModel.lastModification)
}
} ?: hideFileInfo()
}
}
builder.setView(root)
builder.setPositiveButton(android.R.string.ok) { _, _ -> }
return builder.create()
}
return super.onCreateDialog(savedInstanceState)
}
private fun showFileInfo() {
fileSizeContainerView?.visibility = View.VISIBLE
fileModificationContainerView?.visibility = View.VISIBLE
}
private fun hideFileInfo() {
fileSizeContainerView?.visibility = View.GONE
fileModificationContainerView?.visibility = View.GONE
}
companion object {
private const val FILE_SELECT_BEEN_ARG = "FILE_SELECT_BEEN_ARG"
fun newInstance(fileDatabaseModel: FileDatabaseModel): FileInformationDialogFragment {
val fileInformationDialogFragment = FileInformationDialogFragment()
val args = Bundle()
args.putSerializable(FILE_SELECT_BEEN_ARG, fileDatabaseModel)
fileInformationDialogFragment.arguments = args
return fileInformationDialogFragment
}
}
}

View File

@@ -22,14 +22,18 @@ package com.kunzisoft.keepass.activities.dialogs
import android.app.Dialog
import android.content.Context
import android.os.Bundle
import android.support.v4.app.DialogFragment
import android.support.v7.app.AlertDialog
import com.google.android.material.textfield.TextInputLayout
import androidx.fragment.app.DialogFragment
import androidx.appcompat.app.AlertDialog
import android.view.View
import android.widget.*
import android.widget.Button
import android.widget.CompoundButton
import android.widget.EditText
import android.widget.SeekBar
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.password.PasswordGenerator
import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.utils.applyFontVisibility
import com.kunzisoft.keepass.view.applyFontVisibility
class GeneratePasswordDialogFragment : DialogFragment() {
@@ -37,6 +41,7 @@ class GeneratePasswordDialogFragment : DialogFragment() {
private var root: View? = null
private var lengthTextView: EditText? = null
private var passwordInputLayoutView: TextInputLayout? = null
private var passwordView: EditText? = null
private var uppercaseBox: CompoundButton? = null
@@ -65,6 +70,7 @@ class GeneratePasswordDialogFragment : DialogFragment() {
val inflater = activity.layoutInflater
root = inflater.inflate(R.layout.fragment_generate_password, null)
passwordInputLayoutView = root?.findViewById(R.id.password_input_layout)
passwordView = root?.findViewById(R.id.password)
passwordView?.applyFontVisibility()
@@ -162,8 +168,7 @@ class GeneratePasswordDialogFragment : DialogFragment() {
try {
val length = Integer.valueOf(root?.findViewById<EditText>(R.id.length)?.text.toString())
val generator = PasswordGenerator(resources)
password = generator.generatePassword(length,
password = PasswordGenerator(resources).generatePassword(length,
uppercaseBox?.isChecked == true,
lowercaseBox?.isChecked == true,
digitsBox?.isChecked == true,
@@ -174,9 +179,9 @@ class GeneratePasswordDialogFragment : DialogFragment() {
bracketsBox?.isChecked == true,
extendedBox?.isChecked == true)
} catch (e: NumberFormatException) {
Toast.makeText(context, R.string.error_wrong_length, Toast.LENGTH_LONG).show()
passwordInputLayoutView?.error = getString(R.string.error_wrong_length)
} catch (e: IllegalArgumentException) {
Toast.makeText(context, e.message, Toast.LENGTH_LONG).show()
passwordInputLayoutView?.error = e.message
}
return password

View File

@@ -23,9 +23,9 @@ import android.app.Dialog
import android.content.Context
import android.graphics.Color
import android.os.Bundle
import android.support.design.widget.TextInputLayout
import android.support.v4.app.DialogFragment
import android.support.v7.app.AlertDialog
import com.google.android.material.textfield.TextInputLayout
import androidx.fragment.app.DialogFragment
import androidx.appcompat.app.AlertDialog
import android.widget.Button
import android.widget.ImageView
import android.widget.TextView

View File

@@ -24,9 +24,9 @@ import android.content.Context
import android.content.res.ColorStateList
import android.graphics.Color
import android.os.Bundle
import android.support.v4.app.DialogFragment
import android.support.v4.widget.ImageViewCompat
import android.support.v7.app.AlertDialog
import androidx.fragment.app.DialogFragment
import androidx.core.widget.ImageViewCompat
import androidx.appcompat.app.AlertDialog
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup

View File

@@ -24,12 +24,12 @@ import android.content.Intent
import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
import android.os.Bundle
import android.provider.Settings
import android.support.v4.app.DialogFragment
import android.support.v7.app.AlertDialog
import androidx.fragment.app.DialogFragment
import androidx.appcompat.app.AlertDialog
import android.view.View
import com.kunzisoft.keepass.BuildConfig
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.utils.Util
import com.kunzisoft.keepass.utils.UriUtil
class KeyboardExplanationDialogFragment : DialogFragment() {
@@ -47,9 +47,9 @@ class KeyboardExplanationDialogFragment : DialogFragment() {
val containerKeyboardSwitcher = rootView.findViewById<View>(R.id.container_keyboard_switcher)
if (BuildConfig.CLOSED_STORE) {
containerKeyboardSwitcher.setOnClickListener { Util.gotoUrl(context!!, R.string.keyboard_switcher_play_store) }
containerKeyboardSwitcher.setOnClickListener { UriUtil.gotoUrl(context!!, R.string.keyboard_switcher_play_store) }
} else {
containerKeyboardSwitcher.setOnClickListener { Util.gotoUrl(context!!, R.string.keyboard_switcher_f_droid) }
containerKeyboardSwitcher.setOnClickListener { UriUtil.gotoUrl(context!!, R.string.keyboard_switcher_f_droid) }
}
builder.setView(rootView)

View File

@@ -23,7 +23,7 @@ import android.app.AlertDialog
import android.app.Dialog
import android.content.DialogInterface
import android.os.Bundle
import android.support.v4.app.DialogFragment
import androidx.fragment.app.DialogFragment
import com.kunzisoft.keepass.R
class PasswordEncodingDialogFragment : DialogFragment() {

View File

@@ -20,17 +20,15 @@
package com.kunzisoft.keepass.activities.dialogs
import android.app.Dialog
import android.content.ActivityNotFoundException
import android.os.Bundle
import android.support.v4.app.DialogFragment
import android.support.v7.app.AlertDialog
import android.text.Html
import android.text.SpannableStringBuilder
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.core.text.HtmlCompat
import androidx.core.text.HtmlCompat.FROM_HTML_MODE_LEGACY
import androidx.fragment.app.DialogFragment
import com.kunzisoft.keepass.BuildConfig
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.utils.Util
import com.kunzisoft.keepass.utils.UriUtil
/**
* Custom Dialog that asks the user to download the pro version or make a donation.
@@ -44,25 +42,16 @@ class ProFeatureDialogFragment : DialogFragment() {
val stringBuilder = SpannableStringBuilder()
if (BuildConfig.CLOSED_STORE) {
// TODO HtmlCompat with androidX
stringBuilder.append(Html.fromHtml(getString(R.string.html_text_ad_free))).append("\n\n")
stringBuilder.append(Html.fromHtml(getString(R.string.html_text_buy_pro)))
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))
builder.setPositiveButton(R.string.download) { _, _ ->
try {
Util.gotoUrl(context!!, R.string.app_pro_url)
} catch (e: ActivityNotFoundException) {
Toast.makeText(context, R.string.error_failed_to_launch_link, Toast.LENGTH_LONG).show()
}
UriUtil.gotoUrl(context!!, R.string.app_pro_url)
}
} else {
stringBuilder.append(Html.fromHtml(getString(R.string.html_text_feature_generosity))).append("\n\n")
stringBuilder.append(Html.fromHtml(getString(R.string.html_text_donation)))
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))
builder.setPositiveButton(R.string.contribute) { _, _ ->
try {
Util.gotoUrl(context!!, R.string.contribution_url)
} catch (e: ActivityNotFoundException) {
Toast.makeText(context, R.string.error_failed_to_launch_link, Toast.LENGTH_LONG).show()
}
UriUtil.gotoUrl(context!!, R.string.contribution_url)
}
}
builder.setMessage(stringBuilder)

View File

@@ -19,33 +19,38 @@
*/
package com.kunzisoft.keepass.activities.dialogs
import android.app.AlertDialog
import android.content.Context
import android.app.Dialog
import android.os.Build
import android.os.Bundle
import android.preference.PreferenceManager
import androidx.fragment.app.DialogFragment
import com.kunzisoft.keepass.R
class ReadOnlyDialog(context: Context) : AlertDialog(context) {
class ReadOnlyDialog : DialogFragment() {
override fun onCreate(savedInstanceState: Bundle) {
val ctx = context
var warning = ctx.getString(R.string.read_only_warning)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
warning = warning + "\n\n" + context.getString(R.string.read_only_kitkat_warning)
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
activity?.let { activity ->
// Use the Builder class for convenient dialog construction
val builder = androidx.appcompat.app.AlertDialog.Builder(activity)
var warning = getString(R.string.read_only_warning)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
warning = warning + "\n\n" + getString(R.string.read_only_kitkat_warning)
}
builder.setMessage(warning)
builder.setPositiveButton(getString(android.R.string.ok)) { _, _ -> dismiss() }
builder.setNegativeButton(getString(R.string.beta_dontask)) { _, _ ->
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
val edit = prefs.edit()
edit.putBoolean(getString(R.string.show_read_only_warning), false)
edit.apply()
dismiss()
}
// Create the AlertDialog object and return it
return builder.create()
}
setMessage(warning)
setButton(BUTTON_POSITIVE, ctx.getText(android.R.string.ok)) { _, _ -> dismiss() }
setButton(BUTTON_NEGATIVE, ctx.getText(R.string.beta_dontask)) { _, _ ->
val prefs = PreferenceManager.getDefaultSharedPreferences(ctx)
val edit = prefs.edit()
edit.putBoolean(ctx.getString(R.string.show_read_only_warning), false)
edit.apply()
dismiss()
}
super.onCreate(savedInstanceState)
return super.onCreateDialog(savedInstanceState)
}
}

View File

@@ -22,9 +22,9 @@ package com.kunzisoft.keepass.activities.dialogs
import android.app.Dialog
import android.content.Context
import android.os.Bundle
import android.support.annotation.IdRes
import android.support.v4.app.DialogFragment
import android.support.v7.app.AlertDialog
import androidx.annotation.IdRes
import androidx.fragment.app.DialogFragment
import androidx.appcompat.app.AlertDialog
import android.view.View
import android.widget.CompoundButton
import android.widget.RadioGroup

View File

@@ -22,12 +22,13 @@ package com.kunzisoft.keepass.activities.dialogs
import android.app.Dialog
import android.os.Build
import android.os.Bundle
import android.support.v4.app.DialogFragment
import android.support.v7.app.AlertDialog
import androidx.fragment.app.DialogFragment
import androidx.appcompat.app.AlertDialog
import android.text.Html
import android.text.SpannableStringBuilder
import android.text.method.LinkMovementMethod
import android.widget.TextView
import androidx.core.text.HtmlCompat
import com.kunzisoft.keepass.R
class UnavailableFeatureDialogFragment : DialogFragment() {
@@ -53,7 +54,7 @@ class UnavailableFeatureDialogFragment : DialogFragment() {
androidNameFromApiNumber(Build.VERSION.SDK_INT, Build.VERSION.RELEASE),
androidNameFromApiNumber(minVersionRequired)))
message.append("\n\n")
.append(Html.fromHtml("<a href=\"https://source.android.com/setup/build-numbers\">CodeNames</a>"))
.append(HtmlCompat.fromHtml("<a href=\"https://source.android.com/setup/build-numbers\">CodeNames</a>", HtmlCompat.FROM_HTML_MODE_LEGACY))
} else
message.append(getString(R.string.unavailable_feature_hardware))

View File

@@ -20,17 +20,14 @@
package com.kunzisoft.keepass.activities.dialogs
import android.app.Dialog
import android.content.ActivityNotFoundException
import android.os.Bundle
import android.support.v4.app.DialogFragment
import android.support.v7.app.AlertDialog
import android.text.Html
import android.text.SpannableStringBuilder
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.core.text.HtmlCompat
import androidx.fragment.app.DialogFragment
import com.kunzisoft.keepass.BuildConfig
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.utils.Util
import com.kunzisoft.keepass.utils.UriUtil
/**
* Custom Dialog that asks the user to download the pro version or make a donation.
@@ -45,34 +42,26 @@ class UnderDevelopmentFeatureDialogFragment : DialogFragment() {
val stringBuilder = SpannableStringBuilder()
if (BuildConfig.CLOSED_STORE) {
if (BuildConfig.FULL_VERSION) {
stringBuilder.append(Html.fromHtml(getString(R.string.html_text_dev_feature_thanks))).append("\n\n")
.append(Html.fromHtml(getString(R.string.html_rose))).append("\n\n")
.append(Html.fromHtml(getString(R.string.html_text_dev_feature_work_hard))).append("\n")
.append(Html.fromHtml(getString(R.string.html_text_dev_feature_upgrade))).append(" ")
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_text_dev_feature_work_hard), HtmlCompat.FROM_HTML_MODE_LEGACY)).append("\n")
.append(HtmlCompat.fromHtml(getString(R.string.html_text_dev_feature_upgrade), HtmlCompat.FROM_HTML_MODE_LEGACY)).append(" ")
builder.setPositiveButton(android.R.string.ok) { _, _ -> dismiss() }
} else {
stringBuilder.append(Html.fromHtml(getString(R.string.html_text_dev_feature))).append("\n\n")
.append(Html.fromHtml(getString(R.string.html_text_dev_feature_buy_pro))).append("\n")
.append(Html.fromHtml(getString(R.string.html_text_dev_feature_encourage)))
stringBuilder.append(HtmlCompat.fromHtml(getString(R.string.html_text_dev_feature), HtmlCompat.FROM_HTML_MODE_LEGACY)).append("\n\n")
.append(HtmlCompat.fromHtml(getString(R.string.html_text_dev_feature_buy_pro), HtmlCompat.FROM_HTML_MODE_LEGACY)).append("\n")
.append(HtmlCompat.fromHtml(getString(R.string.html_text_dev_feature_encourage), HtmlCompat.FROM_HTML_MODE_LEGACY))
builder.setPositiveButton(R.string.download) { _, _ ->
try {
Util.gotoUrl(context!!, R.string.app_pro_url)
} catch (e: ActivityNotFoundException) {
Toast.makeText(context, R.string.error_failed_to_launch_link, Toast.LENGTH_LONG).show()
}
UriUtil.gotoUrl(context!!, R.string.app_pro_url)
}
builder.setNegativeButton(android.R.string.cancel) { _, _ -> dismiss() }
}
} else {
stringBuilder.append(Html.fromHtml(getString(R.string.html_text_dev_feature))).append("\n\n")
.append(Html.fromHtml(getString(R.string.html_text_dev_feature_contibute))).append(" ")
.append(Html.fromHtml(getString(R.string.html_text_dev_feature_encourage)))
stringBuilder.append(HtmlCompat.fromHtml(getString(R.string.html_text_dev_feature), HtmlCompat.FROM_HTML_MODE_LEGACY)).append("\n\n")
.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))
builder.setPositiveButton(R.string.contribute) { _, _ ->
try {
Util.gotoUrl(context!!, R.string.contribution_url)
} catch (e: ActivityNotFoundException) {
Toast.makeText(context, R.string.error_failed_to_launch_link, Toast.LENGTH_LONG).show()
}
UriUtil.gotoUrl(context!!, R.string.contribution_url)
}
builder.setNegativeButton(android.R.string.cancel) { _, _ -> dismiss() }
}

View File

@@ -26,15 +26,14 @@ import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Build
import android.support.v4.app.Fragment
import android.support.v4.app.FragmentActivity
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import android.util.Log
import android.view.View
import com.kunzisoft.keepass.activities.dialogs.BrowserDialogFragment
import com.kunzisoft.keepass.fileselect.StorageAF
import com.kunzisoft.keepass.utils.UriUtil
class KeyFileHelper {
class OpenFileHelper {
private var activity: Activity? = null
private var fragment: Fragment? = null
@@ -56,9 +55,9 @@ class KeyFileHelper {
override fun onClick(v: View) {
try {
if (activity != null && StorageAF.useStorageFramework(activity!!)) {
try {
openActivityWithActionOpenDocument()
} else {
} catch(e: Exception) {
openActivityWithActionGetContent()
}
} catch (e: Exception) {
@@ -72,7 +71,7 @@ class KeyFileHelper {
}
private fun openActivityWithActionOpenDocument() {
val i = Intent(StorageAF.ACTION_OPEN_DOCUMENT)
val i = Intent(ACTION_OPEN_DOCUMENT)
i.addCategory(Intent.CATEGORY_OPENABLE)
i.type = "*/*"
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
@@ -158,7 +157,7 @@ class KeyFileHelper {
val browserDialogFragment = BrowserDialogFragment()
if (fragment != null && fragment!!.fragmentManager != null)
browserDialogFragment.show(fragment!!.fragmentManager!!, "browserDialog")
else if (activity!!.fragmentManager != null)
else if ((activity as FragmentActivity).supportFragmentManager != null)
browserDialogFragment.show((activity as FragmentActivity).supportFragmentManager, "browserDialog")
} catch (e: Exception) {
Log.e(TAG, "Can't open BrowserDialog", e)
@@ -193,18 +192,16 @@ class KeyFileHelper {
if (data != null) {
var uri = data.data
if (uri != null) {
if (StorageAF.useStorageFramework(activity!!)) {
try {
// try to persist read and write permissions
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
activity?.contentResolver?.apply {
takePersistableUriPermission(uri!!, Intent.FLAG_GRANT_READ_URI_PERMISSION)
takePersistableUriPermission(uri!!, Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
}
try {
// try to persist read and write permissions
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
activity?.contentResolver?.apply {
takePersistableUriPermission(uri!!, Intent.FLAG_GRANT_READ_URI_PERMISSION)
takePersistableUriPermission(uri!!, Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
}
} catch (e: Exception) {
// nop
}
} catch (e: Exception) {
// nop
}
if (requestCode == GET_CONTENT) {
uri = UriUtil.translateUri(activity!!, uri)
@@ -221,7 +218,18 @@ class KeyFileHelper {
companion object {
private const val TAG = "KeyFileHelper"
private const val TAG = "OpenFileHelper"
private var ACTION_OPEN_DOCUMENT: String
init {
ACTION_OPEN_DOCUMENT = try {
val openDocument = Intent::class.java.getField("ACTION_OPEN_DOCUMENT")
openDocument.get(null) as String
} catch (e: Exception) {
"android.intent.action.OPEN_DOCUMENT"
}
}
const val OPEN_INTENTS_FILE_BROWSE = "org.openintents.action.PICK_FILE"

View File

@@ -1,88 +0,0 @@
package com.kunzisoft.keepass.activities.helpers
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.AsyncTask
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.fileselect.database.FileDatabaseHistory
import com.kunzisoft.keepass.utils.UriUtil
import java.io.File
import java.lang.ref.WeakReference
class UriIntentInitTask(private val weakContext: WeakReference<Context>,
private val uriIntentInitTaskCallback: UriIntentInitTaskCallback,
private val isKeyFileNeeded: Boolean)
: AsyncTask<Intent, Void, Int>() {
private var databaseUri: Uri? = null
private var keyFileUri: Uri? = null
override fun doInBackground(vararg args: Intent): Int? {
val intent = args[0]
val action = intent.action
// If is a view intent
if (action != null && action == VIEW_INTENT) {
val incoming = intent.data
databaseUri = incoming
keyFileUri = ClipDataCompat.getUriFromIntent(intent, KEY_KEYFILE)
if (incoming == null) {
return R.string.error_can_not_handle_uri
} else if (incoming.scheme == "file") {
val fileName = incoming.path
if (fileName?.isNotEmpty() == true) {
// No file name
return R.string.file_not_found
}
val dbFile = File(fileName)
if (!dbFile.exists()) {
// File does not exist
return R.string.file_not_found
}
if (keyFileUri == null) {
keyFileUri = getKeyFileUri(databaseUri)
}
} else if (incoming.scheme == "content") {
if (keyFileUri == null) {
keyFileUri = getKeyFileUri(databaseUri)
}
} else {
return R.string.error_can_not_handle_uri
}
} else {
databaseUri = UriUtil.parseUriFile(intent.getStringExtra(KEY_FILENAME))
keyFileUri = UriUtil.parseUriFile(intent.getStringExtra(KEY_KEYFILE))
if (keyFileUri == null || keyFileUri!!.toString().isEmpty()) {
keyFileUri = getKeyFileUri(databaseUri)
}
}
return null
}
public override fun onPostExecute(result: Int?) {
uriIntentInitTaskCallback.onPostInitTask(databaseUri, keyFileUri, result)
}
private fun getKeyFileUri(databaseUri: Uri?): Uri? {
return if (isKeyFileNeeded) {
FileDatabaseHistory.getInstance(weakContext).getKeyFileUriByDatabaseUri(databaseUri!!)
} else {
null
}
}
companion object {
const val KEY_FILENAME = "fileName"
const val KEY_KEYFILE = "keyFile"
private const val VIEW_INTENT = "android.intent.action.VIEW"
}
}

View File

@@ -1,26 +0,0 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePass DX.
*
* KeePass DX 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.
*
* KeePass DX 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 KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.activities.helpers
import android.net.Uri
interface UriIntentInitTaskCallback {
fun onPostInitTask(databaseFileUri: Uri?, keyFileUri: Uri?, errorStringId: Int?)
}

View File

@@ -1,49 +0,0 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePass DX.
*
* KeePass DX 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.
*
* KeePass DX 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 KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.activities.stylish
import android.os.Bundle
import android.support.annotation.StyleRes
import android.util.Log
import com.nononsenseapps.filepicker.FilePickerActivity
/**
* FilePickerActivity class with a style compatibility
*/
class FilePickerStylishActivity : FilePickerActivity() {
@StyleRes
private var themeId: Int = 0
override fun onCreate(savedInstanceState: Bundle?) {
this.themeId = Stylish.getFilePickerThemeId(this)
setTheme(themeId)
super.onCreate(savedInstanceState)
}
override fun onResume() {
super.onResume()
if (Stylish.getFilePickerThemeId(this) != this.themeId) {
Log.d(this.javaClass.name, "Theme change detected, restarting activity")
this.recreate()
}
}
}

View File

@@ -20,8 +20,8 @@
package com.kunzisoft.keepass.activities.stylish
import android.content.Context
import android.support.annotation.StyleRes
import android.support.v7.preference.PreferenceManager
import androidx.annotation.StyleRes
import androidx.preference.PreferenceManager
import android.util.Log
import com.kunzisoft.keepass.R
@@ -68,15 +68,4 @@ object Stylish {
else -> R.style.KeepassDXStyle_Light
}
}
@StyleRes
fun getFilePickerThemeId(context: Context): Int {
return when {
themeString.equals(context.getString(R.string.list_style_name_dark)) -> R.style.KeepassDXStyle_FilePickerStyle_Dark
themeString.equals(context.getString(R.string.list_style_name_blue)) -> R.style.KeepassDXStyle_FilePickerStyle_Blue
themeString.equals(context.getString(R.string.list_style_name_red)) -> R.style.KeepassDXStyle_FilePickerStyle_Red
themeString.equals(context.getString(R.string.list_style_name_purple)) -> R.style.KeepassDXStyle_FilePickerStyle_Purple
else -> R.style.KeepassDXStyle_FilePickerStyle
}
}
}

View File

@@ -20,8 +20,8 @@
package com.kunzisoft.keepass.activities.stylish
import android.os.Bundle
import android.support.annotation.StyleRes
import android.support.v7.app.AppCompatActivity
import androidx.annotation.StyleRes
import androidx.appcompat.app.AppCompatActivity
import android.util.Log
abstract class StylishActivity : AppCompatActivity() {

View File

@@ -23,9 +23,9 @@ import android.content.Context
import android.graphics.Color
import android.os.Build
import android.os.Bundle
import android.support.annotation.StyleRes
import android.support.v4.app.Fragment
import android.support.v7.view.ContextThemeWrapper
import androidx.annotation.StyleRes
import androidx.fragment.app.Fragment
import androidx.appcompat.view.ContextThemeWrapper
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup

View File

@@ -1,7 +1,7 @@
package com.kunzisoft.keepass.magikeyboard.adapter
package com.kunzisoft.keepass.adapters
import android.content.Context
import android.support.v7.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup

View File

@@ -21,23 +21,30 @@ package com.kunzisoft.keepass.adapters
import android.content.Context
import android.net.Uri
import android.support.annotation.ColorInt
import android.support.v7.widget.RecyclerView
import androidx.annotation.ColorInt
import androidx.recyclerview.widget.RecyclerView
import android.util.TypedValue
import android.view.*
import android.widget.EditText
import android.widget.ImageView
import android.widget.TextView
import android.widget.ViewSwitcher
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.fileselect.FileDatabaseModel
import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.app.database.FileDatabaseHistoryEntity
import com.kunzisoft.keepass.utils.FileDatabaseInfo
class FileDatabaseHistoryAdapter(private val context: Context, private val listFiles: List<String>)
class FileDatabaseHistoryAdapter(private val context: Context)
: RecyclerView.Adapter<FileDatabaseHistoryAdapter.FileDatabaseHistoryViewHolder>() {
private val inflater: LayoutInflater = LayoutInflater.from(context)
private var fileItemOpenListener: FileItemOpenListener? = null
private var fileSelectClearListener: FileSelectClearListener? = null
private var fileInformationShowListener: FileInformationShowListener? = null
private var fileItemOpenListener: ((FileDatabaseHistoryEntity)->Unit)? = null
private var fileSelectClearListener: ((FileDatabaseHistoryEntity)->Boolean)? = null
private var saveAliasListener: ((FileDatabaseHistoryEntity)->Unit)? = null
private val listDatabaseFiles = ArrayList<FileDatabaseHistoryEntity>()
private var mExpandedPosition = -1
private var mPreviousExpandedPosition = -1
@ColorInt
private val defaultColor: Int
@@ -45,7 +52,6 @@ class FileDatabaseHistoryAdapter(private val context: Context, private val listF
private val warningColor: Int
init {
val typedValue = TypedValue()
val theme = context.theme
theme.resolveAttribute(R.attr.colorAccent, typedValue, true)
@@ -60,91 +66,120 @@ class FileDatabaseHistoryAdapter(private val context: Context, private val listF
}
override fun onBindViewHolder(holder: FileDatabaseHistoryViewHolder, position: Int) {
val fileDatabaseModel = FileDatabaseModel(context, listFiles[position])
// Context menu creation
holder.fileContainer.setOnCreateContextMenuListener(ContextMenuBuilder(fileDatabaseModel))
// Get info from position
val fileHistoryEntity = listDatabaseFiles[position]
val fileDatabaseInfo = FileDatabaseInfo(context, fileHistoryEntity.databaseUri)
// Click item to open file
if (fileItemOpenListener != null)
holder.fileContainer.setOnClickListener(FileItemClickListener(position))
// Assign file name
if (PreferencesUtil.isFullFilePathEnable(context))
holder.fileName.text = Uri.decode(fileDatabaseModel.fileUri.toString())
else
holder.fileName.text = fileDatabaseModel.fileName
holder.fileName.textSize = PreferencesUtil.getListTextSize(context)
holder.fileContainer.setOnClickListener {
fileItemOpenListener?.invoke(fileHistoryEntity)
}
// File alias
holder.fileAlias.text = fileDatabaseInfo.retrieveDatabaseAlias(fileHistoryEntity.databaseAlias)
// File path
holder.filePath.text = Uri.decode(fileDatabaseInfo.fileUri.toString())
holder.filePreciseInfoContainer.visibility = if (fileDatabaseInfo.found()) {
// Modification
holder.fileModification.text = fileDatabaseInfo.getModificationString()
// Size
holder.fileSize.text = fileDatabaseInfo.getSizeString()
View.VISIBLE
} else
View.GONE
// Click on information
if (fileInformationShowListener != null)
holder.fileInformation.setOnClickListener(FileInformationClickListener(fileDatabaseModel))
val isExpanded = position == mExpandedPosition
//This line hides or shows the layout in question
holder.fileExpandContainer.visibility = if (isExpanded) View.VISIBLE else View.GONE
// Save alias modification
holder.fileAliasCloseButton.setOnClickListener {
// Change the alias
fileHistoryEntity.databaseAlias = holder.fileAliasEdit.text.toString()
saveAliasListener?.invoke(fileHistoryEntity)
// Finish save mode
holder.fileMainSwitcher.showPrevious()
// Refresh current position to show alias
notifyItemChanged(position)
}
// Open alias modification
holder.fileModifyButton.setOnClickListener {
holder.fileAliasEdit.setText(holder.fileAlias.text)
holder.fileMainSwitcher.showNext()
}
holder.fileDeleteButton.setOnClickListener {
fileSelectClearListener?.invoke(fileHistoryEntity)
}
if (isExpanded) {
mPreviousExpandedPosition = position
}
holder.fileInformation.setOnClickListener {
mExpandedPosition = if (isExpanded) -1 else position
// Notify change
if (mPreviousExpandedPosition < itemCount)
notifyItemChanged(mPreviousExpandedPosition)
notifyItemChanged(position)
}
// Refresh View / Close alias modification if not contains fileAlias
if (holder.fileMainSwitcher.currentView.findViewById<View>(R.id.file_alias)
!= holder.fileAlias)
holder.fileMainSwitcher.showPrevious()
}
override fun getItemCount(): Int {
return listFiles.size
return listDatabaseFiles.size
}
fun setOnItemClickListener(fileItemOpenListener: FileItemOpenListener) {
this.fileItemOpenListener = fileItemOpenListener
fun addDatabaseFileHistoryList(listFileDatabaseHistoryToAdd: List<FileDatabaseHistoryEntity>) {
listDatabaseFiles.clear()
listDatabaseFiles.addAll(listFileDatabaseHistoryToAdd)
}
fun setFileSelectClearListener(fileSelectClearListener: FileSelectClearListener) {
this.fileSelectClearListener = fileSelectClearListener
fun deleteDatabaseFileHistory(fileDatabaseHistoryToDelete: FileDatabaseHistoryEntity) {
listDatabaseFiles.remove(fileDatabaseHistoryToDelete)
}
fun setFileInformationShowListener(fileInformationShowListener: FileInformationShowListener) {
this.fileInformationShowListener = fileInformationShowListener
fun setOnFileDatabaseHistoryOpenListener(listener : ((FileDatabaseHistoryEntity)->Unit)?) {
this.fileItemOpenListener = listener
}
interface FileItemOpenListener {
fun onFileItemOpenListener(itemPosition: Int)
fun setOnFileDatabaseHistoryDeleteListener(listener : ((FileDatabaseHistoryEntity)->Boolean)?) {
this.fileSelectClearListener = listener
}
interface FileSelectClearListener {
fun onFileSelectClearListener(fileDatabaseModel: FileDatabaseModel): Boolean
}
interface FileInformationShowListener {
fun onClickFileInformation(fileDatabaseModel: FileDatabaseModel)
}
private inner class FileItemClickListener(private val position: Int) : View.OnClickListener {
override fun onClick(v: View) {
fileItemOpenListener?.onFileItemOpenListener(position)
}
}
private inner class FileInformationClickListener(private val fileDatabaseModel: FileDatabaseModel) : View.OnClickListener {
override fun onClick(view: View) {
fileInformationShowListener?.onClickFileInformation(fileDatabaseModel)
}
}
private inner class ContextMenuBuilder(private val fileDatabaseModel: FileDatabaseModel) : View.OnCreateContextMenuListener {
private val mOnMyActionClickListener = MenuItem.OnMenuItemClickListener { item ->
if (fileSelectClearListener == null)
return@OnMenuItemClickListener false
when (item.itemId) {
MENU_CLEAR -> fileSelectClearListener!!.onFileSelectClearListener(fileDatabaseModel)
else -> false
}
}
override fun onCreateContextMenu(contextMenu: ContextMenu?, view: View?, contextMenuInfo: ContextMenu.ContextMenuInfo?) {
contextMenu?.add(Menu.NONE, MENU_CLEAR, Menu.NONE, R.string.remove_from_filelist)
?.setOnMenuItemClickListener(mOnMyActionClickListener)
}
fun setOnSaveAliasListener(listener : ((FileDatabaseHistoryEntity)->Unit)?) {
this.saveAliasListener = listener
}
inner class FileDatabaseHistoryViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var fileContainer: View = itemView.findViewById(R.id.file_container)
var fileName: TextView = itemView.findViewById(R.id.file_filename)
var fileContainer: ViewGroup = itemView.findViewById(R.id.file_container_basic_info)
var fileAlias: TextView = itemView.findViewById(R.id.file_alias)
var fileInformation: ImageView = itemView.findViewById(R.id.file_information)
}
companion object {
var fileMainSwitcher: ViewSwitcher = itemView.findViewById(R.id.file_main_switcher)
var fileAliasEdit: EditText = itemView.findViewById(R.id.file_alias_edit)
var fileAliasCloseButton: ImageView = itemView.findViewById(R.id.file_alias_save)
private const val MENU_CLEAR = 1
var fileExpandContainer: ViewGroup = itemView.findViewById(R.id.file_expand_container)
var fileModifyButton: ImageView = itemView.findViewById(R.id.file_modify_button)
var fileDeleteButton: ImageView = itemView.findViewById(R.id.file_delete_button)
var filePath: TextView = itemView.findViewById(R.id.file_path)
var filePreciseInfoContainer: ViewGroup = itemView.findViewById(R.id.file_precise_info_container)
var fileModification: TextView = itemView.findViewById(R.id.file_modification)
var fileSize: TextView = itemView.findViewById(R.id.file_size)
}
}

View File

@@ -21,9 +21,9 @@ package com.kunzisoft.keepass.adapters
import android.content.Context
import android.graphics.Color
import android.support.v7.util.SortedList
import android.support.v7.widget.RecyclerView
import android.support.v7.widget.util.SortedListAdapterCallback
import androidx.recyclerview.widget.SortedList
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.SortedListAdapterCallback
import android.util.Log
import android.view.*
import android.widget.ImageView
@@ -45,10 +45,13 @@ class NodeAdapter
private val nodeSortedList: SortedList<NodeVersioned>
private val inflater: LayoutInflater = LayoutInflater.from(context)
private var textSize: Float = 0.toFloat()
private var subtextSize: Float = 0.toFloat()
private var infoTextSize: Float = 0.toFloat()
private var iconSize: Float = 0.toFloat()
private var calculateViewTypeTextSize = Array(2) { true} // number of view type
private var prefTextSize: Float = 0F
private var subtextSize: Float = 0F
private var infoTextSize: Float = 0F
private var numberChildrenTextSize: Float = 0F
private var iconSize: Float = 0F
private var listSort: SortNodeEnum = SortNodeEnum.DB
private var ascendingSort: Boolean = true
private var groupsBeforeSort: Boolean = true
@@ -122,19 +125,16 @@ class NodeAdapter
}
private fun assignPreferences() {
val textSizeDefault = java.lang.Float.parseFloat(context.getString(R.string.list_size_default))
this.textSize = PreferencesUtil.getListTextSize(context)
this.subtextSize = context.resources.getInteger(R.integer.list_small_size_default) * textSize / textSizeDefault
this.infoTextSize = context.resources.getInteger(R.integer.list_tiny_size_default) * textSize / textSizeDefault
// Retrieve the icon size
val iconDefaultSize = context.resources.getDimension(R.dimen.list_icon_size_default)
this.iconSize = iconDefaultSize * textSize / textSizeDefault
this.prefTextSize = PreferencesUtil.getListTextSize(context) / java.lang.Float.parseFloat(context.getString(R.string.list_size_default))
this.listSort = PreferencesUtil.getListSort(context)
this.ascendingSort = PreferencesUtil.getAscendingSort(context)
this.groupsBeforeSort = PreferencesUtil.getGroupsBeforeSort(context)
this.recycleBinBottomSort = PreferencesUtil.getRecycleBinBottomSort(context)
this.showUserNames = PreferencesUtil.showUsernamesListEntries(context)
this.showNumberEntries = PreferencesUtil.showNumberEntries(context)
// Reinit textSize for all view type
calculateViewTypeTextSize.forEachIndexed { index, _ -> calculateViewTypeTextSize[index] = true }
}
/**
@@ -212,16 +212,41 @@ class NodeAdapter
return NodeViewHolder(view)
}
private fun calculateTextSize(holder: NodeViewHolder, viewType: Int) {
if (calculateViewTypeTextSize[viewType]) {
this.subtextSize = holder.subText.textSize * prefTextSize
this.infoTextSize = holder.text.textSize * prefTextSize
holder.numberChildren?.let {
this.numberChildrenTextSize = it.textSize * prefTextSize
}
this.iconSize = context.resources.getDimension(R.dimen.list_icon_size_default) * prefTextSize
calculateViewTypeTextSize[viewType] = false
}
}
override fun onBindViewHolder(holder: NodeViewHolder, position: Int) {
val subNode = nodeSortedList.get(position)
calculateTextSize(holder, getItemViewType(position))
// Assign image
val iconColor = when (subNode.type) {
Type.GROUP -> iconGroupColor
Type.ENTRY -> iconEntryColor
}
holder.icon.assignDatabaseIcon(mDatabase.drawFactory, subNode.icon, iconColor)
holder.icon.apply {
assignDatabaseIcon(mDatabase.drawFactory, subNode.icon, iconColor)
// Relative size of the icon
layoutParams?.apply {
height = iconSize.toInt()
width = iconSize.toInt()
}
}
// Assign text
holder.text.text = subNode.title
holder.text.apply {
text = subNode.title
textSize = infoTextSize
}
// Assign click
holder.container.setOnClickListener { nodeClickCallback?.onNodeClick(subNode) }
// Context menu
@@ -230,36 +255,34 @@ class NodeAdapter
ContextMenuBuilder(menuInflater, subNode, readOnly, isASearchResult, nodeMenuListener))
}
// Add username
holder.subText.text = ""
holder.subText.visibility = View.GONE
if (subNode.type == Type.ENTRY) {
val entry = subNode as EntryVersioned
// Add subText with username
holder.subText.apply {
text = ""
visibility = View.GONE
if (subNode.type == Type.ENTRY) {
val entry = subNode as EntryVersioned
mDatabase.startManageEntry(entry)
mDatabase.startManageEntry(entry)
holder.text.text = entry.getVisualTitle()
holder.text.text = entry.getVisualTitle()
val username = entry.username
if (showUserNames && username.isNotEmpty()) {
holder.subText.visibility = View.VISIBLE
holder.subText.text = username
val username = entry.username
if (showUserNames && username.isNotEmpty()) {
visibility = View.VISIBLE
text = username
}
mDatabase.stopManageEntry(entry)
}
mDatabase.stopManageEntry(entry)
textSize = subtextSize
}
// Assign image and text size
// Relative size of the icon
holder.icon.layoutParams?.height = iconSize.toInt()
holder.icon.layoutParams?.width = iconSize.toInt()
holder.text.textSize = textSize
holder.subText.textSize = subtextSize
// Add number of entries in groups
if (subNode.type == Type.GROUP) {
if (showNumberEntries) {
holder.numberChildren?.apply {
text = (subNode as GroupVersioned).getChildEntries(true).size.toString()
textSize = infoTextSize
textSize = numberChildrenTextSize
visibility = View.VISIBLE
}
} else {

View File

@@ -22,7 +22,7 @@ package com.kunzisoft.keepass.adapters
import android.content.Context
import android.database.Cursor
import android.graphics.Color
import android.support.v4.widget.CursorAdapter
import androidx.cursoradapter.widget.CursorAdapter
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
@@ -38,7 +38,7 @@ import com.kunzisoft.keepass.settings.PreferencesUtil
import java.util.*
class SearchEntryCursorAdapter(context: Context, private val database: Database)
: CursorAdapter(context, null, FLAG_REGISTER_CONTENT_OBSERVER) {
: androidx.cursoradapter.widget.CursorAdapter(context, null, FLAG_REGISTER_CONTENT_OBSERVER) {
private val cursorInflater: LayoutInflater = context.getSystemService(
Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater

View File

@@ -19,7 +19,7 @@
*/
package com.kunzisoft.keepass.app
import android.support.multidex.MultiDexApplication
import androidx.multidex.MultiDexApplication
import com.kunzisoft.keepass.activities.stylish.Stylish
import com.kunzisoft.keepass.database.element.Database

View File

@@ -0,0 +1,20 @@
package com.kunzisoft.keepass.app.database
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
import android.content.Context
@Database(entities = [FileDatabaseHistoryEntity::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
abstract fun databaseFileHistoryDao(): FileDatabaseHistoryDao
companion object {
fun getDatabase(applicationContext: Context): AppDatabase {
return Room.databaseBuilder(
applicationContext,
AppDatabase::class.java, "com.kunzisoft.keepass.database"
).build()
}
}
}

View File

@@ -0,0 +1,152 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePass DX.
*
* KeePass DX 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.
*
* KeePass DX 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 KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.app.database
import androidx.sqlite.db.SimpleSQLiteQuery
import android.content.Context
import android.net.Uri
import android.os.AsyncTask
import com.kunzisoft.keepass.utils.SingletonHolderParameter
class FileDatabaseHistory(applicationContext: Context) {
private val databaseFileHistoryDao =
AppDatabase
.getDatabase(applicationContext)
.databaseFileHistoryDao()
fun getFileDatabaseHistory(databaseUri: Uri,
fileHistoryResultListener: (fileDatabaseHistoryResult: FileDatabaseHistoryEntity?) -> Unit) {
ActionFileHistoryAsyncTask(
{
databaseFileHistoryDao.getByDatabaseUri(databaseUri.toString())
},
{
fileHistoryResultListener.invoke(it)
}
).execute()
}
fun getKeyFileUriByDatabaseUri(databaseUri: Uri,
keyFileUriResultListener: (Uri?) -> Unit) {
ActionFileHistoryAsyncTask(
{
databaseFileHistoryDao.getByDatabaseUri(databaseUri.toString())
},
{
it?.let { fileHistoryEntity ->
fileHistoryEntity.keyFileUri?.let { keyFileUri ->
keyFileUriResultListener.invoke(Uri.parse(keyFileUri))
}
} ?: keyFileUriResultListener.invoke(null)
}
).execute()
}
fun getAllFileDatabaseHistories(fileHistoryResultListener: (fileDatabaseHistoryResult: List<FileDatabaseHistoryEntity>?) -> Unit) {
ActionFileHistoryAsyncTask(
{
databaseFileHistoryDao.getAll()
},
{
fileHistoryResultListener.invoke(it)
}
).execute()
}
fun addOrUpdateDatabaseUri(databaseUri: Uri, keyFileUri: Uri? = null) {
addOrUpdateFileDatabaseHistory(FileDatabaseHistoryEntity(
databaseUri.toString(),
"",
keyFileUri?.toString(),
System.currentTimeMillis()
), true)
}
fun addOrUpdateFileDatabaseHistory(fileDatabaseHistory: FileDatabaseHistoryEntity, unmodifiedAlias: Boolean = false) {
ActionFileHistoryAsyncTask(
{
val fileDatabaseHistoryRetrieve = databaseFileHistoryDao.getByDatabaseUri(fileDatabaseHistory.databaseUri)
if (unmodifiedAlias) {
fileDatabaseHistory.databaseAlias = fileDatabaseHistoryRetrieve?.databaseAlias ?: ""
}
// Update values if history element not yet in the database
if (fileDatabaseHistoryRetrieve == null) {
databaseFileHistoryDao.add(fileDatabaseHistory)
} else {
databaseFileHistoryDao.update(fileDatabaseHistory)
}
}
).execute()
}
fun deleteFileDatabaseHistory(fileDatabaseHistory: FileDatabaseHistoryEntity,
fileHistoryDeletedResult: (FileDatabaseHistoryEntity?) -> Unit) {
ActionFileHistoryAsyncTask(
{
databaseFileHistoryDao.delete(fileDatabaseHistory)
},
{
if (it != null && it > 0)
fileHistoryDeletedResult.invoke(fileDatabaseHistory)
else
fileHistoryDeletedResult.invoke(null)
}
).execute()
}
fun deleteAllKeyFiles() {
// TODO replace for unsupported query databaseFileHistoryDao.deleteAllKeyFiles()
ActionFileHistoryAsyncTask(
{
databaseFileHistoryDao
.deleteAllKeyFiles(SimpleSQLiteQuery("REPLACE INTO file_database_history(keyfile_uri) VALUES(null)"))
}
).execute()
}
fun deleteAll() {
ActionFileHistoryAsyncTask(
{
databaseFileHistoryDao.deleteAll()
}
).execute()
}
/**
* Private class to invoke each method in a separate thread
*/
private class ActionFileHistoryAsyncTask<T>(
private val action: () -> T ,
private val afterActionFileHistoryListener: ((fileHistoryResult: T?) -> Unit)? = null
) : AsyncTask<Void, Void, T>() {
override fun doInBackground(vararg args: Void?): T? {
return action.invoke()
}
override fun onPostExecute(result: T?) {
afterActionFileHistoryListener?.invoke(result)
}
}
companion object : SingletonHolderParameter<FileDatabaseHistory, Context>(::FileDatabaseHistory)
}

View File

@@ -0,0 +1,32 @@
package com.kunzisoft.keepass.app.database
import androidx.sqlite.db.SupportSQLiteQuery
import androidx.room.*
@Dao
interface FileDatabaseHistoryDao {
@Query("SELECT * FROM file_database_history ORDER BY updated DESC")
fun getAll(): List<FileDatabaseHistoryEntity>
@Query("SELECT * FROM file_database_history WHERE database_uri = :databaseUriString")
fun getByDatabaseUri(databaseUriString: String): FileDatabaseHistoryEntity?
@Insert
fun add(vararg fileDatabaseHistory: FileDatabaseHistoryEntity)
@Update
fun update(vararg fileDatabaseHistory: FileDatabaseHistoryEntity)
@Delete
fun delete(fileDatabaseHistory: FileDatabaseHistoryEntity): Int
/* TODO Replace (Insert not yet supported)
@Query("REPLACE INTO file_database_history(keyfile_uri) VALUES(null)")
fun deleteAllKeyFiles()
*/
@RawQuery
fun deleteAllKeyFiles(query: SupportSQLiteQuery): Boolean
@Query("DELETE FROM file_database_history")
fun deleteAll()
}

View File

@@ -0,0 +1,36 @@
package com.kunzisoft.keepass.app.database
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "file_database_history")
data class FileDatabaseHistoryEntity(
@PrimaryKey
@ColumnInfo(name = "database_uri")
val databaseUri: String,
@ColumnInfo(name = "database_alias")
var databaseAlias: String,
@ColumnInfo(name = "keyfile_uri")
var keyFileUri: String?,
@ColumnInfo(name = "updated")
val updated: Long
) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as FileDatabaseHistoryEntity
if (databaseUri != other.databaseUri) return false
return true
}
override fun hashCode(): Int {
return databaseUri.hashCode()
}
}

View File

@@ -26,7 +26,7 @@ import android.content.Intent
import android.os.Build
import android.service.autofill.Dataset
import android.service.autofill.FillResponse
import android.support.annotation.RequiresApi
import androidx.annotation.RequiresApi
import android.util.Log
import android.view.autofill.AutofillManager
import android.view.autofill.AutofillValue

View File

@@ -26,8 +26,8 @@ import android.content.Intent
import android.content.IntentSender
import android.os.Build
import android.os.Bundle
import android.support.annotation.RequiresApi
import android.support.v7.app.AppCompatActivity
import androidx.annotation.RequiresApi
import androidx.appcompat.app.AppCompatActivity
import com.kunzisoft.keepass.activities.FileDatabaseSelectActivity
import com.kunzisoft.keepass.activities.GroupActivity
import com.kunzisoft.keepass.database.element.Database

View File

@@ -22,7 +22,7 @@ package com.kunzisoft.keepass.autofill
import android.os.Build
import android.os.CancellationSignal
import android.service.autofill.*
import android.support.annotation.RequiresApi
import androidx.annotation.RequiresApi
import android.util.Log
import android.widget.RemoteViews
import com.kunzisoft.keepass.R

View File

@@ -20,7 +20,7 @@ package com.kunzisoft.keepass.autofill
import android.app.assist.AssistStructure
import android.os.Build
import android.support.annotation.RequiresApi
import androidx.annotation.RequiresApi
import android.text.InputType
import android.util.Log
import android.view.View

View File

@@ -26,6 +26,8 @@ import android.app.backup.SharedPreferencesBackupHelper
@SuppressLint("NewApi")
class SettingsBackupAgent : BackupAgentHelper() {
//TODO Backup
override fun onCreate() {
val defaultPrefs = this.packageName + "_preferences"
val prefHelper = SharedPreferencesBackupHelper(this, defaultPrefs)

View File

@@ -22,12 +22,12 @@ package com.kunzisoft.keepass.database.action
import android.content.Context
import android.net.Uri
import android.preference.PreferenceManager
import android.support.annotation.StringRes
import androidx.annotation.StringRes
import android.util.Log
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.exception.*
import com.kunzisoft.keepass.fileselect.database.FileDatabaseHistory
import com.kunzisoft.keepass.app.database.FileDatabaseHistory
import com.kunzisoft.keepass.tasks.ActionRunnable
import com.kunzisoft.keepass.tasks.ProgressTaskUpdater
import java.io.FileNotFoundException
@@ -119,7 +119,9 @@ class LoadDatabaseRunnable(private val mWeakContext: WeakReference<Context>,
if (!mRememberKeyFile) {
keyFileUri = null
}
FileDatabaseHistory.getInstance(mWeakContext).addDatabaseUri(uri, keyFileUri)
mWeakContext.get()?.let {
FileDatabaseHistory.getInstance(it).addOrUpdateDatabaseUri(uri, keyFileUri)
}
}
override fun onFinishRun(result: Result) {

View File

@@ -1,6 +1,6 @@
package com.kunzisoft.keepass.database.action
import android.support.v4.app.FragmentActivity
import androidx.fragment.app.FragmentActivity
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.tasks.ActionRunnable
import com.kunzisoft.keepass.tasks.ProgressTaskUpdater

View File

@@ -3,8 +3,8 @@ package com.kunzisoft.keepass.database.action
import android.content.Intent
import android.os.AsyncTask
import android.os.Build
import android.support.annotation.StringRes
import android.support.v4.app.FragmentActivity
import androidx.annotation.StringRes
import androidx.fragment.app.FragmentActivity
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.DATABASE_TASK_TITLE_KEY
import com.kunzisoft.keepass.tasks.ActionRunnable

View File

@@ -1,6 +1,6 @@
package com.kunzisoft.keepass.database.action.node
import android.support.v4.app.FragmentActivity
import androidx.fragment.app.FragmentActivity
import android.util.Log
import com.kunzisoft.keepass.database.action.SaveDatabaseRunnable
import com.kunzisoft.keepass.database.element.Database

View File

@@ -19,7 +19,7 @@
*/
package com.kunzisoft.keepass.database.action.node
import android.support.v4.app.FragmentActivity
import androidx.fragment.app.FragmentActivity
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.EntryVersioned
import com.kunzisoft.keepass.database.element.GroupVersioned

View File

@@ -19,7 +19,7 @@
*/
package com.kunzisoft.keepass.database.action.node
import android.support.v4.app.FragmentActivity
import androidx.fragment.app.FragmentActivity
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.GroupVersioned

View File

@@ -19,7 +19,7 @@
*/
package com.kunzisoft.keepass.database.action.node
import android.support.v4.app.FragmentActivity
import androidx.fragment.app.FragmentActivity
import android.util.Log
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.element.Database

View File

@@ -20,7 +20,7 @@
package com.kunzisoft.keepass.database.action.node
import android.os.Bundle
import android.support.v4.app.FragmentActivity
import androidx.fragment.app.FragmentActivity
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.EntryVersioned
import com.kunzisoft.keepass.database.element.GroupVersioned

View File

@@ -20,7 +20,7 @@
package com.kunzisoft.keepass.database.action.node
import android.os.Bundle
import android.support.v4.app.FragmentActivity
import androidx.fragment.app.FragmentActivity
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.GroupVersioned
@@ -61,10 +61,11 @@ class DeleteGroupRunnable(context: FragmentActivity,
mParent?.let {
database.undoRecycle(mGroupToDelete, it)
}
} else {
}
// else {
// Let's not bother recovering from a failure to save a deleted tree. It is too much work.
// TODO database.undoDeleteGroupFrom(mGroup, mParent);
}
// }
}
// Add position in bundle to delete the node in view

View File

@@ -19,7 +19,7 @@
*/
package com.kunzisoft.keepass.database.action.node
import android.support.v4.app.FragmentActivity
import androidx.fragment.app.FragmentActivity
import android.util.Log
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.element.Database

View File

@@ -19,7 +19,7 @@
*/
package com.kunzisoft.keepass.database.action.node
import android.support.v4.app.FragmentActivity
import androidx.fragment.app.FragmentActivity
import android.util.Log
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.element.Database

View File

@@ -19,7 +19,7 @@
*/
package com.kunzisoft.keepass.database.action.node
import android.support.v4.app.FragmentActivity
import androidx.fragment.app.FragmentActivity
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.EntryVersioned

View File

@@ -19,7 +19,7 @@
*/
package com.kunzisoft.keepass.database.action.node
import android.support.v4.app.FragmentActivity
import androidx.fragment.app.FragmentActivity
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.GroupVersioned

View File

@@ -2,7 +2,7 @@ package com.kunzisoft.keepass.education
import android.app.Activity
import android.graphics.Color
import android.support.v4.content.ContextCompat
import androidx.core.content.ContextCompat
import android.view.View
import com.getkeepsafe.taptargetview.TapTarget
import com.getkeepsafe.taptargetview.TapTargetView

View File

@@ -2,7 +2,7 @@ package com.kunzisoft.keepass.education
import android.app.Activity
import android.graphics.Color
import android.support.v4.content.ContextCompat
import androidx.core.content.ContextCompat
import android.view.View
import com.getkeepsafe.taptargetview.TapTarget
import com.getkeepsafe.taptargetview.TapTargetView
@@ -11,9 +11,9 @@ import com.kunzisoft.keepass.R
class PasswordActivityEducation(activity: Activity)
: Education(activity) {
fun checkAndPerformedFingerprintUnlockEducation(educationView: View,
onEducationViewClick: ((TapTargetView?) -> Unit)? = null,
onOuterViewClick: ((TapTargetView?) -> Unit)? = null): Boolean {
fun checkAndPerformedUnlockEducation(educationView: View,
onEducationViewClick: ((TapTargetView?) -> Unit)? = null,
onOuterViewClick: ((TapTargetView?) -> Unit)? = null): Boolean {
return checkAndPerformedEducation(!isEducationUnlockPerformed(activity),
TapTarget.forView(educationView,
activity.getString(R.string.education_unlock_title),

View File

@@ -1,45 +0,0 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePass DX.
*
* KeePass DX 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.
*
* KeePass DX 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 KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.fileselect
import android.os.AsyncTask
import com.kunzisoft.keepass.adapters.FileDatabaseHistoryAdapter
import com.kunzisoft.keepass.fileselect.database.FileDatabaseHistory
class DeleteFileHistoryAsyncTask(private val afterDeleteFileHistoryListener: (() -> Unit)?,
private val fileHistory: FileDatabaseHistory?,
private val adapter: FileDatabaseHistoryAdapter?)
: AsyncTask<FileDatabaseModel, Void, Void>() {
override fun doInBackground(vararg args: FileDatabaseModel): Void? {
args[0].fileUri?.let {
fileHistory?.deleteDatabaseUri(it)
}
return null
}
override fun onPostExecute(v: Void?) {
adapter?.notifyDataSetChanged()
if (adapter == null || adapter.itemCount == 0) {
afterDeleteFileHistoryListener?.invoke()
}
}
}

View File

@@ -1,64 +0,0 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePass DX.
*
* KeePass DX 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.
*
* KeePass DX 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 KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.fileselect
import android.content.Context
import android.net.Uri
import android.support.v4.provider.DocumentFile
import java.io.File
import java.io.Serializable
import java.util.Date
class FileDatabaseModel(context: Context, pathFile: String) : Serializable {
var fileName: String? = ""
var fileUri: Uri? = null
var lastModification = Date()
var size: Long = 0L
init {
fileUri = Uri.parse(pathFile)
if (EXTERNAL_STORAGE_AUTHORITY == fileUri!!.authority) {
val file = DocumentFile.fromSingleUri(context, fileUri)
size = file.length()
fileName = file.name
lastModification = Date(file.lastModified())
} else {
val file = File(fileUri!!.path!!)
size = file.length()
fileName = file.name
lastModification = Date(file.lastModified())
}
if (fileName == null || fileName!!.isEmpty()) {
fileName = fileUri!!.path
}
}
fun notFound(): Boolean {
return size == 0L
}
companion object {
private const val EXTERNAL_STORAGE_AUTHORITY = "com.android.externalstorage.documents"
}
}

View File

@@ -1,44 +0,0 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePass DX.
*
* KeePass DX 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.
*
* KeePass DX 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 KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.fileselect
import android.os.AsyncTask
import com.kunzisoft.keepass.fileselect.database.FileDatabaseHistory
class OpenFileHistoryAsyncTask(private val afterOpenFileHistoryListener: ((fileName: String?, keyFile: String?) -> Unit)?,
private val fileHistory: FileDatabaseHistory?)
: AsyncTask<Int, Void, Void>() {
private var fileName: String? = null
private var keyFile: String? = null
override fun doInBackground(vararg args: Int?): Void? {
args[0]?.let {
fileName = fileHistory?.getDatabaseAt(it)
keyFile = fileHistory?.getKeyFileAt(it)
}
return null
}
override fun onPostExecute(v: Void?) {
afterOpenFileHistoryListener?.invoke(fileName, keyFile)
}
}

View File

@@ -1,49 +0,0 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePass DX.
*
* KeePass DX 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.
*
* KeePass DX 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 KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.fileselect
import android.content.Context
import android.content.Intent
import android.os.Build
import android.preference.PreferenceManager
import com.kunzisoft.keepass.R
object StorageAF {
var ACTION_OPEN_DOCUMENT: String
init {
ACTION_OPEN_DOCUMENT = try {
val openDocument = Intent::class.java.getField("ACTION_OPEN_DOCUMENT")
openDocument.get(null) as String
} catch (e: Exception) {
"android.intent.action.OPEN_DOCUMENT"
}
}
fun useStorageFramework(context: Context): Boolean {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
return false
}
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
return prefs.getBoolean(context.getString(R.string.saf_key),
context.resources.getBoolean(R.bool.settings_saf_default))
}
}

View File

@@ -1,227 +0,0 @@
/*
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePass DX.
*
* KeePass DX 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.
*
* KeePass DX 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 KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.fileselect.database;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import java.io.File;
import java.io.FileFilter;
public class FileDatabaseHelper {
private static final String TAG = FileDatabaseHelper.class.getName();
static final String LAST_FILENAME = "lastFile";
static final String LAST_KEYFILE = "lastKey";
public static final String DATABASE_NAME = "keepassdroid"; // TODO Change db name
static final String FILE_TABLE = "files";
static final int DATABASE_VERSION = 1;
public static final int MAX_FILES = 5;
public static final String KEY_FILE_ID = "_id";
public static final String KEY_FILE_FILENAME = "fileName";
public static final String KEY_FILE_KEYFILE = "keyFile";
public static final String KEY_FILE_UPDATED = "updated";
static final String DATABASE_CREATE =
"create table " + FILE_TABLE + " ( " + KEY_FILE_ID + " integer primary key autoincrement, "
+ KEY_FILE_FILENAME + " text not null, " + KEY_FILE_KEYFILE + " text, "
+ KEY_FILE_UPDATED + " integer not null);";
private final Context mCtx;
private SQLiteDatabase mDb;
public FileDatabaseHelper(Context ctx) {
mCtx = ctx;
}
public FileDatabaseHelper open() throws SQLException {
mDb = new FileDatabaseHistoryHelper(mCtx).getWritableDatabase();
return this;
}
public boolean isOpen() {
return mDb.isOpen();
}
public void close() {
mDb.close();
}
public long createFile(String fileName, String keyFile) {
// Check to see if this filename is already used
Cursor cursor;
try {
cursor = mDb.query(true, FILE_TABLE, new String[] {KEY_FILE_ID},
KEY_FILE_FILENAME + "=?", new String[] {fileName}, null, null, null, null);
} catch (Exception e ) {
return -1;
}
long result;
// If there is an existing entry update it with the new key file
if ( cursor.getCount() > 0 ) {
cursor.moveToFirst();
long id = cursor.getLong(cursor.getColumnIndexOrThrow(KEY_FILE_ID));
ContentValues vals = new ContentValues();
vals.put(KEY_FILE_KEYFILE, keyFile);
vals.put(KEY_FILE_UPDATED, System.currentTimeMillis());
result = mDb.update(FILE_TABLE, vals, KEY_FILE_ID + " = " + id, null);
// Otherwise add the new entry
} else {
ContentValues vals = new ContentValues();
vals.put(KEY_FILE_FILENAME, fileName);
vals.put(KEY_FILE_KEYFILE, keyFile);
vals.put(KEY_FILE_UPDATED, System.currentTimeMillis());
result = mDb.insert(FILE_TABLE, null, vals);
}
// Delete all but the last five records
try {
deleteAllBut(MAX_FILES);
} catch (Exception e) {
e.printStackTrace();
}
cursor.close();
return result;
}
private void deleteAllBut(int limit) {
Cursor cursor = mDb.query(FILE_TABLE, new String[] {KEY_FILE_UPDATED}, null, null, null, null, KEY_FILE_UPDATED);
if ( cursor.getCount() > limit ) {
cursor.moveToFirst();
long time = cursor.getLong(cursor.getColumnIndexOrThrow(KEY_FILE_UPDATED));
mDb.execSQL("DELETE FROM " + FILE_TABLE + " WHERE " + KEY_FILE_UPDATED + "<" + time + ";");
}
cursor.close();
}
public void deleteAllKeys() {
ContentValues vals = new ContentValues();
vals.put(KEY_FILE_KEYFILE, "");
mDb.update(FILE_TABLE, vals, null, null);
}
public void deleteFile(String filename) {
mDb.delete(FILE_TABLE, KEY_FILE_FILENAME + " = ?", new String[] {filename});
}
public Cursor fetchAllFiles() {
Cursor ret;
ret = mDb.query(FILE_TABLE, new String[] {KEY_FILE_ID, KEY_FILE_FILENAME, KEY_FILE_KEYFILE }, null, null, null, null, KEY_FILE_UPDATED + " DESC", Integer.toString(MAX_FILES));
return ret;
}
public Cursor fetchFile(long fileId) throws SQLException {
Cursor cursor = mDb.query(true, FILE_TABLE, new String[] {KEY_FILE_FILENAME, KEY_FILE_KEYFILE},
KEY_FILE_ID + "=" + fileId, null, null, null, null, null);
if ( cursor != null ) {
cursor.moveToFirst();
}
return cursor;
}
public String getFileByName(String name) {
Cursor cursor = mDb.query(true, FILE_TABLE, new String[] {KEY_FILE_KEYFILE},
KEY_FILE_FILENAME + "= ?", new String[] {name}, null, null, null, null);
if ( cursor == null ) {
return "";
}
String filename;
if ( cursor.moveToFirst() ) {
filename = cursor.getString(0);
} else {
// Cursor is empty
filename = "";
}
cursor.close();
return filename;
}
public boolean hasRecentFiles() {
Cursor cursor = fetchAllFiles();
boolean hasRecent = cursor.getCount() > 0;
cursor.close();
return hasRecent;
}
/**
* Deletes a database including its journal file and other auxiliary files
* that may have been created by the database engine.
*
* @param ctx Context to get database path
* @return True if the database was successfully deleted.
*/
public static boolean deleteDatabase(Context ctx) {
File file = ctx.getDatabasePath(DATABASE_NAME);
if (file == null) {
throw new IllegalArgumentException("file must not be null");
}
boolean deleted = false;
deleted |= file.delete();
deleted |= new File(file.getPath() + "-journal").delete();
deleted |= new File(file.getPath() + "-shm").delete();
deleted |= new File(file.getPath() + "-wal").delete();
File dir = file.getParentFile();
if (dir != null) {
final String prefix = file.getName() + "-mj";
final FileFilter filter = new FileFilter() {
@Override
public boolean accept(File candidate) {
return candidate.getName().startsWith(prefix);
}
};
for (File masterJournal : dir.listFiles(filter)) {
deleted |= masterJournal.delete();
}
}
return deleted;
}
}

View File

@@ -1,258 +0,0 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePass DX.
*
* KeePass DX 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.
*
* KeePass DX 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 KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.fileselect.database
import android.content.Context
import android.content.SharedPreferences
import android.content.SharedPreferences.OnSharedPreferenceChangeListener
import android.net.Uri
import android.preference.PreferenceManager
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.utils.SingletonHolderParameter
import com.kunzisoft.keepass.utils.UriUtil
import java.lang.ref.WeakReference
import java.util.*
class FileDatabaseHistory private constructor(private val context: WeakReference<Context>) {
private val mDatabasesUriList = ArrayList<String>()
private val mKeyFilesUriList = ArrayList<String>()
private val mPreferences: SharedPreferences = PreferenceManager.getDefaultSharedPreferences(context.get())
var isEnabled: Boolean = false
val databaseUriList: List<String>
get() {
init()
return mDatabasesUriList
}
private val onSharedPreferenceChangeListener = OnSharedPreferenceChangeListener { sharedPreferences, key ->
if (key == context.get()?.getString(R.string.recentfile_key)) {
isEnabled = sharedPreferences.getBoolean(
context.get()?.getString(R.string.recentfile_key),
context.get()?.resources?.getBoolean(R.bool.recentfile_default) ?: isEnabled)
}
}
init {
mPreferences.unregisterOnSharedPreferenceChangeListener(onSharedPreferenceChangeListener)
context.get()?.resources?.let {
isEnabled = mPreferences.getBoolean(
it.getString(R.string.recentfile_key),
it.getBoolean(R.bool.recentfile_default))
}
mPreferences.registerOnSharedPreferenceChangeListener(onSharedPreferenceChangeListener)
}
private var init = false
@Synchronized
private fun init() {
if (!init) {
if (!upgradeFromSQL()) {
loadPrefs()
}
init = true
}
}
private fun upgradeFromSQL(): Boolean {
try {
// Check for a database to upgrade from
if (context.get()?.getDatabasePath(FileDatabaseHelper.DATABASE_NAME)?.exists() != true) {
return false
}
mDatabasesUriList.clear()
mKeyFilesUriList.clear()
val helper = FileDatabaseHelper(context.get())
helper.open()
val cursor = helper.fetchAllFiles()
val dbIndex = cursor.getColumnIndex(FileDatabaseHelper.KEY_FILE_FILENAME)
val keyIndex = cursor.getColumnIndex(FileDatabaseHelper.KEY_FILE_KEYFILE)
if (cursor.moveToFirst()) {
while (cursor.moveToNext()) {
mDatabasesUriList.add(cursor.getString(dbIndex))
mKeyFilesUriList.add(cursor.getString(keyIndex))
}
}
savePrefs()
cursor.close()
helper.close()
} catch (e: Exception) {
// If upgrading fails, we'll just give up on it.
}
try {
FileDatabaseHelper.deleteDatabase(context.get())
} catch (e: Exception) {
// If we fail to delete it, just move on
}
return true
}
@JvmOverloads
fun addDatabaseUri(databaseUri: Uri?, keyFileUri: Uri? = null) {
if (!isEnabled || databaseUri == null) return
init()
// Remove any existing instance of the same filename
deleteDatabaseUri(databaseUri, false)
mDatabasesUriList.add(0, databaseUri.toString())
val key = keyFileUri?.toString() ?: ""
mKeyFilesUriList.add(0, key)
trimLists()
savePrefs()
}
fun hasRecentFiles(): Boolean {
if (!isEnabled) return false
init()
return mDatabasesUriList.size > 0
}
fun getDatabaseAt(i: Int): String {
init()
return mDatabasesUriList[i]
}
fun getKeyFileAt(i: Int): String {
init()
return mKeyFilesUriList[i]
}
private fun loadPrefs() {
loadList(DB_KEY, mDatabasesUriList)
loadList(KEY_FILE_KEY, mKeyFilesUriList)
}
private fun savePrefs() {
saveList(DB_KEY, mDatabasesUriList)
saveList(KEY_FILE_KEY, mKeyFilesUriList)
}
private fun loadList(keyPrefix: String, list: MutableList<String>) {
val size = mPreferences.getInt(keyPrefix, 0)
list.clear()
for (i in 0 until size) {
mPreferences.getString(keyPrefix + "_" + i, "")?.let {
list.add(it)
}
}
}
private fun saveList(keyPrefix: String, list: List<String>) {
val edit = mPreferences.edit()
val size = list.size
edit.putInt(keyPrefix, size)
for (i in 0 until size) {
edit.putString(keyPrefix + "_" + i, list[i])
}
edit.apply()
}
@JvmOverloads
fun deleteDatabaseUri(uri: Uri, save: Boolean = true) {
init()
val uriName = uri.toString()
val fileName = uri.path
for (i in mDatabasesUriList.indices) {
val entry = mDatabasesUriList[i]
if (uriName == entry || fileName == entry) {
mDatabasesUriList.removeAt(i)
mKeyFilesUriList.removeAt(i)
break
}
}
if (save) {
savePrefs()
}
}
fun getKeyFileUriByDatabaseUri(uri: Uri): Uri? {
if (!isEnabled)
return null
init()
val size = mDatabasesUriList.size
for (i in 0 until size) {
if (uri == UriUtil.parseUriFile(mDatabasesUriList[i])) {
return UriUtil.parseUriFile(mKeyFilesUriList[i])
}
}
return null
}
fun deleteAll() {
init()
mDatabasesUriList.clear()
mKeyFilesUriList.clear()
savePrefs()
}
fun deleteAllKeys() {
init()
mKeyFilesUriList.clear()
val size = mDatabasesUriList.size
for (i in 0 until size) {
mKeyFilesUriList.add("")
}
savePrefs()
}
private fun trimLists() {
val size = mDatabasesUriList.size
for (i in FileDatabaseHelper.MAX_FILES until size) {
mDatabasesUriList.removeAt(i)
mKeyFilesUriList.removeAt(i)
}
}
companion object : SingletonHolderParameter<FileDatabaseHistory, WeakReference<Context>>(::FileDatabaseHistory) {
private const val DB_KEY = "recent_databases"
private const val KEY_FILE_KEY = "recent_keyfiles"
}
}

View File

@@ -1,58 +0,0 @@
package com.kunzisoft.keepass.fileselect.database;
import android.content.ContentValues;
import android.content.Context;
import android.content.SharedPreferences;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;
class FileDatabaseHistoryHelper extends SQLiteOpenHelper {
private final SharedPreferences settings;
FileDatabaseHistoryHelper(Context context) {
super(context, FileDatabaseHelper.DATABASE_NAME, null, FileDatabaseHelper.DATABASE_VERSION);
settings = context.getSharedPreferences("PasswordActivity", Context.MODE_PRIVATE);
}
@Override
public void onCreate(SQLiteDatabase sqLiteDatabase) {
sqLiteDatabase.execSQL(FileDatabaseHelper.DATABASE_CREATE);
// Migrate preference to database if it is set.
String lastFile = settings.getString(FileDatabaseHelper.LAST_FILENAME, "");
String lastKey = settings.getString(FileDatabaseHelper.LAST_KEYFILE,"");
if ( lastFile.length() > 0 ) {
ContentValues contentValues = new ContentValues();
contentValues.put(FileDatabaseHelper.KEY_FILE_FILENAME, lastFile);
contentValues.put(FileDatabaseHelper.KEY_FILE_UPDATED, System.currentTimeMillis());
if ( lastKey.length() > 0 ) {
contentValues.put(FileDatabaseHelper.KEY_FILE_KEYFILE, lastKey);
}
sqLiteDatabase.insert(FileDatabaseHelper.FILE_TABLE, null, contentValues);
// Clear old preferences
deletePrefs(settings);
}
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// Only one database version so far
}
private void deletePrefs(SharedPreferences prefs) {
// We won't worry too much if this fails
try {
SharedPreferences.Editor editor = prefs.edit();
editor.remove(FileDatabaseHelper.LAST_FILENAME);
editor.remove(FileDatabaseHelper.LAST_KEYFILE);
editor.apply();
} catch (Exception e) {
Log.e(FileDatabaseHelper.class.getName(), "Unable to delete database preference", e);
}
}
}

View File

@@ -24,7 +24,7 @@ import android.graphics.drawable.Animatable2
import android.graphics.drawable.AnimatedVectorDrawable
import android.graphics.drawable.Drawable
import android.os.Build
import android.support.annotation.RequiresApi
import androidx.annotation.RequiresApi
import android.widget.ImageView
import com.kunzisoft.keepass.R

View File

@@ -23,9 +23,9 @@ import android.app.Dialog
import android.content.Intent
import android.os.Build
import android.os.Bundle
import android.support.annotation.RequiresApi
import android.support.v4.app.DialogFragment
import android.support.v7.app.AlertDialog
import androidx.annotation.RequiresApi
import androidx.fragment.app.DialogFragment
import androidx.appcompat.app.AlertDialog
import android.view.View
import com.kunzisoft.keepass.R

View File

@@ -28,7 +28,7 @@ import android.os.CancellationSignal
import android.security.keystore.KeyGenParameterSpec
import android.security.keystore.KeyPermanentlyInvalidatedException
import android.security.keystore.KeyProperties
import android.support.annotation.RequiresApi
import androidx.annotation.RequiresApi
import android.util.Base64
import android.util.Log
@@ -289,7 +289,7 @@ class FingerPrintHelper(context: Context, private val fingerPrintCallback: Finge
// fingerprint hardware supported and api level OK
return (isFingerprintSupported(fingerprintManager)
// fingerprints enrolled
&& fingerprintManager != null && fingerprintManager!!.hasEnrolledFingerprints()
&& fingerprintManager != null && fingerprintManager.hasEnrolledFingerprints()
// and lockscreen configured
&& keyguardManager != null && keyguardManager!!.isKeyguardSecure)
}

View File

@@ -6,8 +6,8 @@ import android.hardware.fingerprint.FingerprintManager
import android.net.Uri
import android.os.Build
import android.os.Handler
import android.support.annotation.RequiresApi
import android.support.v7.app.AppCompatActivity
import androidx.annotation.RequiresApi
import androidx.appcompat.app.AppCompatActivity
import android.util.Log
import android.view.Menu
import android.view.MenuInflater

View File

@@ -28,8 +28,8 @@ import android.graphics.Color
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.Drawable
import android.support.v4.content.res.ResourcesCompat
import android.support.v4.widget.ImageViewCompat
import androidx.core.content.res.ResourcesCompat
import androidx.core.widget.ImageViewCompat
import android.util.Log
import android.widget.ImageView
import com.kunzisoft.keepass.R

View File

@@ -1,7 +1,7 @@
package com.kunzisoft.keepass.magikeyboard
import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import androidx.appcompat.app.AppCompatActivity
import com.kunzisoft.keepass.activities.FileDatabaseSelectActivity
import com.kunzisoft.keepass.activities.GroupActivity
import com.kunzisoft.keepass.database.element.Database
@@ -10,10 +10,6 @@ import com.kunzisoft.keepass.timeout.TimeoutHelper
class KeyboardLauncherActivity : AppCompatActivity() {
companion object {
val TAG = KeyboardLauncherActivity::class.java.name!!
}
override fun onCreate(savedInstanceState: Bundle?) {
if (Database.getInstance().loaded && TimeoutHelper.checkTime(this))
GroupActivity.launchForKeyboardSelection(this, PreferencesUtil.enableReadOnlyDatabase(this))

View File

@@ -27,8 +27,6 @@ import android.inputmethodservice.InputMethodService
import android.inputmethodservice.Keyboard
import android.inputmethodservice.KeyboardView
import android.media.AudioManager
import android.support.v7.widget.LinearLayoutManager
import android.support.v7.widget.RecyclerView
import android.util.Log
import android.view.*
import android.view.inputmethod.EditorInfo
@@ -37,7 +35,7 @@ import android.widget.FrameLayout
import android.widget.PopupWindow
import android.widget.TextView
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.magikeyboard.adapter.FieldsAdapter
import com.kunzisoft.keepass.adapters.FieldsAdapter
import com.kunzisoft.keepass.model.EntryInfo
import com.kunzisoft.keepass.model.Field
import com.kunzisoft.keepass.notifications.KeyboardEntryNotificationService
@@ -107,14 +105,14 @@ class MagikIME : InputMethodService(), KeyboardView.OnKeyboardActionListener {
popupCustomKeys?.inputMethodMode = PopupWindow.INPUT_METHOD_NEEDED
popupCustomKeys?.contentView = popupFieldsView
val recyclerView = popupFieldsView.findViewById<RecyclerView>(R.id.keyboard_popup_fields_list)
val recyclerView = popupFieldsView.findViewById<androidx.recyclerview.widget.RecyclerView>(R.id.keyboard_popup_fields_list)
fieldsAdapter = FieldsAdapter(this)
fieldsAdapter?.onItemClickListener = object : FieldsAdapter.OnItemClickListener {
override fun onItemClick(item: Field) {
currentInputConnection.commitText(item.protectedValue.toString(), 1)
}
}
recyclerView.layoutManager = LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, true)
recyclerView.layoutManager = androidx.recyclerview.widget.LinearLayoutManager(this, androidx.recyclerview.widget.LinearLayoutManager.HORIZONTAL, true)
recyclerView.adapter = fieldsAdapter
val closeView = popupFieldsView.findViewById<View>(R.id.keyboard_popup_close)
@@ -194,7 +192,7 @@ class MagikIME : InputMethodService(), KeyboardView.OnKeyboardActionListener {
KEY_BACK_KEYBOARD -> try {
val imeManager = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
if (window.window != null)
imeManager.switchToLastInputMethod(window.window!!.attributes.token)
imeManager.switchToLastInputMethod(window.window!!.attributes.token) // TODO Deprecated
} catch (e: Exception) {
Log.e(TAG, "Unable to switch to the previous IME", e)
val imeManager = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager

View File

@@ -5,7 +5,7 @@ import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.support.v7.preference.PreferenceManager
import androidx.preference.PreferenceManager
import android.util.Log
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.magikeyboard.MagikIME
@@ -103,7 +103,9 @@ class KeyboardEntryNotificationService : NotificationService() {
val keyboardTimeout = sharedPreferences.getString(getString(R.string.keyboard_entry_timeout_key),
getString(R.string.timeout_default))
notificationTimeoutMilliSecs = try {
java.lang.Long.parseLong(keyboardTimeout)
keyboardTimeout?.let {
java.lang.Long.parseLong(keyboardTimeout)
} ?: 0
} catch (e: NumberFormatException) {
TimeoutHelper.DEFAULT_TIMEOUT
}

View File

@@ -7,7 +7,7 @@ import android.content.Context
import android.content.Intent
import android.os.Build
import android.os.IBinder
import android.support.v4.app.NotificationCompat
import androidx.core.app.NotificationCompat
import android.util.TypedValue
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.stylish.Stylish

View File

@@ -20,7 +20,7 @@
package com.kunzisoft.keepass.settings
import android.os.Bundle
import android.support.v7.widget.Toolbar
import androidx.appcompat.widget.Toolbar
import android.view.MenuItem
import com.kunzisoft.keepass.R

View File

@@ -20,7 +20,7 @@
package com.kunzisoft.keepass.settings
import android.os.Bundle
import android.support.v7.preference.PreferenceFragmentCompat
import androidx.preference.PreferenceFragmentCompat
import com.kunzisoft.keepass.R

View File

@@ -21,8 +21,8 @@ package com.kunzisoft.keepass.settings
import android.content.Context
import android.os.Bundle
import android.support.v7.preference.Preference
import android.support.v7.preference.PreferenceFragmentCompat
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.dialogs.AssignMasterKeyDialogFragment
import com.kunzisoft.keepass.database.element.Database

View File

@@ -27,13 +27,13 @@ import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.provider.Settings
import android.support.annotation.RequiresApi
import android.support.v14.preference.SwitchPreference
import android.support.v4.app.DialogFragment
import android.support.v7.app.AlertDialog
import android.support.v7.preference.Preference
import android.support.v7.preference.PreferenceCategory
import android.support.v7.preference.PreferenceFragmentCompat
import androidx.annotation.RequiresApi
import androidx.preference.SwitchPreference
import androidx.fragment.app.DialogFragment
import androidx.appcompat.app.AlertDialog
import androidx.preference.Preference
import androidx.preference.PreferenceCategory
import androidx.preference.PreferenceFragmentCompat
import android.util.Log
import android.view.autofill.AutofillManager
import android.widget.Toast
@@ -45,14 +45,13 @@ import com.kunzisoft.keepass.activities.dialogs.UnavailableFeatureDialogFragment
import com.kunzisoft.keepass.activities.dialogs.UnderDevelopmentFeatureDialogFragment
import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper
import com.kunzisoft.keepass.activities.stylish.Stylish
import com.kunzisoft.keepass.app.database.FileDatabaseHistory
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.education.Education
import com.kunzisoft.keepass.fileselect.database.FileDatabaseHistory
import com.kunzisoft.keepass.fingerprint.FingerPrintHelper
import com.kunzisoft.keepass.fingerprint.FingerPrintViewsManager
import com.kunzisoft.keepass.icons.IconPackChooser
import com.kunzisoft.keepass.settings.preferencedialogfragment.*
import java.lang.ref.WeakReference
class NestedSettingsFragment : PreferenceFragmentCompat(), Preference.OnPreferenceClickListener {
@@ -119,7 +118,7 @@ class NestedSettingsFragment : PreferenceFragmentCompat(), Preference.OnPreferen
val keyFile = findPreference(getString(R.string.keyfile_key))
keyFile.setOnPreferenceChangeListener { _, newValue ->
if (!(newValue as Boolean)) {
FileDatabaseHistory.getInstance(WeakReference(activity.applicationContext)).deleteAllKeys()
FileDatabaseHistory.getInstance(activity.applicationContext).deleteAllKeyFiles()
}
true
}
@@ -127,24 +126,7 @@ class NestedSettingsFragment : PreferenceFragmentCompat(), Preference.OnPreferen
val recentHistory = findPreference(getString(R.string.recentfile_key))
recentHistory.setOnPreferenceChangeListener { _, newValue ->
if (!(newValue as Boolean)) {
FileDatabaseHistory.getInstance(WeakReference(activity.applicationContext)).deleteAll()
}
true
}
val storageAccessFramework = findPreference(getString(R.string.saf_key)) as SwitchPreference
storageAccessFramework.setOnPreferenceChangeListener { _, newValue ->
if (!(newValue as Boolean) && context != null) {
val alertDialog = AlertDialog.Builder(context!!)
.setMessage(getString(R.string.warning_disabling_storage_access_framework)).create()
alertDialog.setButton(AlertDialog.BUTTON_POSITIVE, getText(android.R.string.ok)
) { dialog, _ -> dialog.dismiss() }
alertDialog.setButton(AlertDialog.BUTTON_NEGATIVE, getText(android.R.string.cancel)
) { dialog, _ ->
storageAccessFramework.isChecked = true
dialog.dismiss()
}
alertDialog.show()
FileDatabaseHistory.getInstance(activity.applicationContext).deleteAll()
}
true
}
@@ -418,11 +400,11 @@ class NestedSettingsFragment : PreferenceFragmentCompat(), Preference.OnPreferen
.setMessage(message)
.create()
.apply {
setButton(AlertDialog.BUTTON_POSITIVE, getText(android.R.string.ok))
setButton(AlertDialog.BUTTON_POSITIVE, getText(R.string.enable))
{ dialog, _ ->
dialog.dismiss()
}
setButton(AlertDialog.BUTTON_NEGATIVE, getText(android.R.string.cancel))
setButton(AlertDialog.BUTTON_NEGATIVE, getText(R.string.disable))
{ dialog, _ ->
copyPasswordPreference.isChecked = false
dialog.dismiss()

View File

@@ -28,6 +28,11 @@ import java.util.*
object PreferencesUtil {
fun showReadOnlyWarning(context: Context): Boolean {
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
return prefs.getBoolean(context.getString(R.string.show_read_only_warning), true)
}
fun omitBackup(context: Context): Boolean {
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
return prefs.getBoolean(context.getString(R.string.omitbackup_key),

View File

@@ -25,8 +25,8 @@ import android.content.DialogInterface
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.support.v4.app.Fragment
import android.support.v7.widget.Toolbar
import androidx.fragment.app.Fragment
import androidx.appcompat.widget.Toolbar
import android.view.MenuItem
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.dialogs.AssignMasterKeyDialogFragment
@@ -38,8 +38,10 @@ import com.kunzisoft.keepass.database.action.ProgressDialogSaveDatabaseThread
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.timeout.TimeoutHelper
open class SettingsActivity : LockingActivity(), MainPreferenceFragment.Callback, AssignMasterKeyDialogFragment.AssignPasswordDialogListener {
open class SettingsActivity
: LockingActivity(),
MainPreferenceFragment.Callback,
AssignMasterKeyDialogFragment.AssignPasswordDialogListener {
private var backupManager: BackupManager? = null

View File

@@ -20,7 +20,7 @@
package com.kunzisoft.keepass.settings
import android.os.Bundle
import android.support.v4.app.Fragment
import androidx.fragment.app.Fragment
class SettingsAutofillActivity : SettingsActivity() {

View File

@@ -1,7 +1,7 @@
package com.kunzisoft.keepass.settings.preference
import android.content.Context
import android.support.v7.preference.DialogPreference
import androidx.preference.DialogPreference
import android.util.AttributeSet
import com.kunzisoft.keepass.R

View File

@@ -1,7 +1,7 @@
package com.kunzisoft.keepass.settings.preference
import android.content.Context
import android.support.v7.preference.ListPreference
import androidx.preference.ListPreference
import android.util.AttributeSet
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.icons.IconPackChooser

View File

@@ -1,7 +1,7 @@
package com.kunzisoft.keepass.settings.preference
import android.content.Context
import android.support.v7.preference.DialogPreference
import androidx.preference.DialogPreference
import android.util.AttributeSet
import com.kunzisoft.keepass.R

View File

@@ -1,7 +1,7 @@
package com.kunzisoft.keepass.settings.preference
import android.content.Context
import android.support.v7.preference.DialogPreference
import androidx.preference.DialogPreference
import android.util.AttributeSet
import com.kunzisoft.keepass.R

View File

@@ -20,15 +20,17 @@
package com.kunzisoft.keepass.settings.preferencedialogfragment
import android.os.Bundle
import android.support.v7.widget.LinearLayoutManager
import android.support.v7.widget.RecyclerView
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import android.view.View
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.element.PwEncryptionAlgorithm
import com.kunzisoft.keepass.settings.preferencedialogfragment.adapter.ListRadioItemAdapter
import com.kunzisoft.keepass.tasks.ActionRunnable
class DatabaseEncryptionAlgorithmPreferenceDialogFragmentCompat : DatabaseSavePreferenceDialogFragmentCompat(), ListRadioItemAdapter.RadioItemSelectedCallback<PwEncryptionAlgorithm> {
class DatabaseEncryptionAlgorithmPreferenceDialogFragmentCompat
: DatabaseSavePreferenceDialogFragmentCompat(),
ListRadioItemAdapter.RadioItemSelectedCallback<PwEncryptionAlgorithm> {
private var algorithmSelected: PwEncryptionAlgorithm? = null

View File

@@ -20,16 +20,18 @@
package com.kunzisoft.keepass.settings.preferencedialogfragment
import android.os.Bundle
import android.support.v7.preference.Preference
import android.support.v7.widget.LinearLayoutManager
import android.support.v7.widget.RecyclerView
import androidx.preference.Preference
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import android.view.View
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.crypto.keyDerivation.KdfEngine
import com.kunzisoft.keepass.settings.preferencedialogfragment.adapter.ListRadioItemAdapter
import com.kunzisoft.keepass.tasks.ActionRunnable
class DatabaseKeyDerivationPreferenceDialogFragmentCompat : DatabaseSavePreferenceDialogFragmentCompat(), ListRadioItemAdapter.RadioItemSelectedCallback<KdfEngine> {
class DatabaseKeyDerivationPreferenceDialogFragmentCompat
: DatabaseSavePreferenceDialogFragmentCompat(),
ListRadioItemAdapter.RadioItemSelectedCallback<KdfEngine> {
private var kdfEngineSelected: KdfEngine? = null
private var roundPreference: Preference? = null

View File

@@ -19,8 +19,8 @@
*/
package com.kunzisoft.keepass.settings.preferencedialogfragment
import android.support.annotation.StringRes
import android.support.v7.preference.PreferenceDialogFragmentCompat
import androidx.annotation.StringRes
import androidx.preference.PreferenceDialogFragmentCompat
import android.view.View
import android.widget.TextView

View File

@@ -20,7 +20,7 @@
package com.kunzisoft.keepass.settings.preferencedialogfragment.adapter
import android.content.Context
import android.support.v7.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup

View File

@@ -23,16 +23,16 @@ import android.annotation.SuppressLint
import android.app.Dialog
import android.content.DialogInterface
import android.os.Bundle
import android.support.annotation.StringRes
import android.support.v4.app.DialogFragment
import android.support.v4.app.FragmentActivity
import android.support.v7.app.AlertDialog
import androidx.annotation.StringRes
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.FragmentActivity
import androidx.appcompat.app.AlertDialog
import android.view.View
import android.widget.ProgressBar
import android.widget.TextView
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.utils.lockScreenOrientation
import com.kunzisoft.keepass.utils.unlockScreenOrientation
import com.kunzisoft.keepass.view.lockScreenOrientation
import com.kunzisoft.keepass.view.unlockScreenOrientation
open class ProgressTaskDialogFragment : DialogFragment(), ProgressTaskUpdater {

View File

@@ -19,7 +19,7 @@
*/
package com.kunzisoft.keepass.tasks
import android.support.annotation.StringRes
import androidx.annotation.StringRes
interface ProgressTaskUpdater {
fun updateMessage(@StringRes resId: Int)

View File

@@ -26,6 +26,7 @@ import android.content.ClipboardManager
import android.content.Context
import android.os.Handler
import android.preference.PreferenceManager
import androidx.annotation.IntegerRes
import android.text.SpannableString
import android.text.method.LinkMovementMethod
import android.text.util.Linkify
@@ -46,7 +47,7 @@ class ClipboardHelper(private val context: Context) {
@JvmOverloads
fun timeoutCopyToClipboard(text: String, toastString: String = "") {
if (!toastString.isEmpty())
if (toastString.isNotEmpty())
Toast.makeText(context, toastString, Toast.LENGTH_LONG).show()
try {
copyToClipboard(text)
@@ -107,38 +108,37 @@ class ClipboardHelper(private val context: Context) {
override fun run() {
val currentClip = getClipboard(mCtx).toString()
if (currentClip == mClearText) {
try {
@IntegerRes
val stringErrorId = try {
cleanClipboard()
uiThreadCallback.post {
Toast.makeText(mCtx,
R.string.clipboard_cleared,
Toast.LENGTH_LONG).show()
}
R.string.clipboard_cleared
} catch (e: SamsungClipboardException) {
uiThreadCallback.post {
Toast.makeText(mCtx,
R.string.clipboard_error_clear,
Toast.LENGTH_LONG).show()
}
R.string.clipboard_error_clear
}
uiThreadCallback.post {
Toast.makeText(mCtx, stringErrorId, Toast.LENGTH_LONG).show()
}
}
}
}
private fun showSamsungDialog() {
val text = context.getString(R.string.clipboard_error)+
val textDescription = context.getString(R.string.clipboard_error)+
System.getProperty("line.separator") +
context.getString(R.string.clipboard_error_url)
val s = SpannableString(text)
val tv = TextView(context)
tv.text = s
tv.autoLinkMask = Activity.RESULT_OK
tv.movementMethod = LinkMovementMethod.getInstance()
Linkify.addLinks(s, Linkify.WEB_URLS)
val builder = AlertDialog.Builder(context)
builder.setTitle(R.string.clipboard_error_title)
val spannableString = SpannableString(textDescription)
val textView = TextView(context).apply {
text = spannableString
autoLinkMask = Activity.RESULT_OK
movementMethod = LinkMovementMethod.getInstance()
}
Linkify.addLinks(spannableString, Linkify.WEB_URLS)
AlertDialog.Builder(context)
.setTitle(R.string.clipboard_error_title)
.setView(textView)
.setPositiveButton(android.R.string.ok) { dialog, _ -> dialog.dismiss() }
.setView(tv)
.show()
}
}

View File

@@ -17,7 +17,7 @@
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.activities.helpers;
package com.kunzisoft.keepass.utils;
import android.content.Intent;
import android.net.Uri;

View File

@@ -0,0 +1,31 @@
package com.kunzisoft.keepass.utils
import android.content.Context
import android.net.Uri
import com.kunzisoft.keepass.app.database.FileDatabaseHistory
import com.kunzisoft.keepass.settings.PreferencesUtil
class FileDatabaseInfo : FileInfo {
constructor(context: Context, fileUri: Uri): super(context, fileUri)
constructor(context: Context, filePath: String): super(context, filePath)
fun retrieveDatabaseAlias(alias: String): String {
return when {
alias.isNotEmpty() -> alias
PreferencesUtil.isFullFilePathEnable(context) -> filePath ?: ""
else -> fileName ?: ""
}
}
fun retrieveDatabaseTitle(titleCallback: (String)->Unit) {
FileDatabaseHistory.getInstance(context.applicationContext).getFileDatabaseHistory(fileUri) {
fileDatabaseHistoryEntity ->
titleCallback.invoke(retrieveDatabaseAlias(fileDatabaseHistoryEntity?.databaseAlias ?: ""))
}
}
}

View File

@@ -0,0 +1,92 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePass DX.
*
* KeePass DX 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.
*
* KeePass DX 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 KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.utils
import android.content.Context
import android.net.Uri
import androidx.documentfile.provider.DocumentFile
import com.kunzisoft.keepass.R
import java.io.File
import java.io.Serializable
import java.text.DateFormat
import java.util.*
open class FileInfo : Serializable {
var context: Context
var fileUri: Uri
var filePath: String? = null
var fileName: String? = ""
var lastModification = Date()
var size: Long = 0L
constructor(context: Context, fileUri: Uri) {
this.context = context
this.fileUri = fileUri
init()
}
constructor(context: Context, filePath: String) {
this.context = context
this.fileUri = Uri.parse(filePath)
init()
}
fun init() {
this.filePath = fileUri.path
if (EXTERNAL_STORAGE_AUTHORITY == fileUri.authority) {
DocumentFile.fromSingleUri(context, fileUri)?.let { file ->
size = file.length()
fileName = file.name
lastModification = Date(file.lastModified())
}
} else {
filePath?.let {
File(it).let { file ->
size = file.length()
fileName = file.name
lastModification = Date(file.lastModified())
}
}
}
if (fileName == null || fileName!!.isEmpty()) {
fileName = filePath
}
}
fun found(): Boolean {
return size != 0L
}
fun getModificationString(): String {
return DateFormat.getDateTimeInstance()
.format(lastModification)
}
fun getSizeString(): String {
return (size.toString() + " " + context.getString(R.string.bytes))
}
companion object {
private const val EXTERNAL_STORAGE_AUTHORITY = "com.android.externalstorage.documents"
}
}

View File

@@ -21,7 +21,7 @@ package com.kunzisoft.keepass.utils;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.annotation.NonNull;
import androidx.annotation.NonNull;
import android.util.Log;
import com.kunzisoft.keepass.stream.ActionReadBytes;
@@ -145,6 +145,7 @@ public class MemUtil {
return compressedDataStream;
}
// TODO Remove
// For writing to a Parcel
public static <K extends Parcelable,V extends Parcelable> void writeParcelableMap(

View File

@@ -19,18 +19,16 @@
*/
package com.kunzisoft.keepass.utils
import android.content.ActivityNotFoundException
import android.content.Intent
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.widget.Toast
import com.kunzisoft.keepass.BuildConfig
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.AboutActivity
import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper.READ_ONLY_DEFAULT
import com.kunzisoft.keepass.settings.SettingsActivity
import com.kunzisoft.keepass.activities.stylish.StylishActivity
import com.kunzisoft.keepass.settings.SettingsActivity
object MenuUtil {
@@ -45,15 +43,8 @@ object MenuUtil {
inflater.inflate(R.menu.default_menu, menu)
}
fun onContributionItemSelected(activity: StylishActivity): Boolean {
try {
Util.gotoUrl(activity, R.string.contribution_url)
} catch (e: ActivityNotFoundException) {
Toast.makeText(activity, R.string.error_failed_to_launch_link, Toast.LENGTH_LONG).show()
return false
}
return true
fun onContributionItemSelected(activity: StylishActivity) {
UriUtil.gotoUrl(activity, R.string.contribution_url)
}
/*
@@ -62,7 +53,10 @@ object MenuUtil {
@JvmOverloads
fun onDefaultMenuOptionsItemSelected(activity: StylishActivity, item: MenuItem, readOnly: Boolean = READ_ONLY_DEFAULT, timeoutEnable: Boolean = false): Boolean {
when (item.itemId) {
R.id.menu_contribute -> return onContributionItemSelected(activity)
R.id.menu_contribute -> {
onContributionItemSelected(activity)
return true
}
R.id.menu_app_settings -> {
// To avoid flickering when launch settings in a LockingActivity

Some files were not shown because too many files have changed in this diff Show More