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) KeepassDX (2.5.0.0beta22)
* Rebuild code for actions * Rebuild code for actions
* Add UUID as entry view * Add UUID as entry view

1
app/.gitignore vendored
View File

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

View File

@@ -4,15 +4,15 @@ apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt' apply plugin: 'kotlin-kapt'
android { android {
compileSdkVersion 27 compileSdkVersion 28
buildToolsVersion '28.0.3' buildToolsVersion '28.0.3'
defaultConfig { defaultConfig {
applicationId "com.kunzisoft.keepass" applicationId "com.kunzisoft.keepass"
minSdkVersion 14 minSdkVersion 14
targetSdkVersion 27 targetSdkVersion 28
versionCode = 22 versionCode = 23
versionName = "2.5.0.0beta22" versionName = "2.5.0.0beta23"
multiDexEnabled true multiDexEnabled true
testApplicationId = "com.kunzisoft.keepass.tests" testApplicationId = "com.kunzisoft.keepass.tests"
@@ -79,42 +79,34 @@ android {
} }
} }
def supportVersion = "27.1.1"
def spongycastleVersion = "1.58.0.0" def spongycastleVersion = "1.58.0.0"
def permissionDispatcherVersion = "3.3.1" def room_version = "2.1.0"
dependencies { dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation "com.android.support:appcompat-v7:$supportVersion" implementation 'androidx.appcompat:appcompat:1.0.2'
implementation "com.android.support:design:$supportVersion" implementation 'androidx.preference:preference:1.0.0'
implementation "com.android.support:preference-v7:$supportVersion" implementation 'androidx.legacy:legacy-preference-v14:1.0.0'
implementation "com.android.support:preference-v14:$supportVersion" implementation 'androidx.cardview:cardview:1.0.0'
implementation "com.android.support:cardview-v7:$supportVersion" implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'com.android.support.constraint:constraint-layout: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:core:$spongycastleVersion"
implementation "com.madgag.spongycastle:prov:$spongycastleVersion" implementation "com.madgag.spongycastle:prov:$spongycastleVersion"
// Expandable view // Expandable view
implementation 'net.cachapa.expandablelayout:expandablelayout:2.9.2' implementation 'net.cachapa.expandablelayout:expandablelayout:2.9.2'
// Time // Time
implementation 'joda-time:joda-time:2.9.9' implementation 'joda-time:joda-time:2.9.9'
implementation 'org.sufficientlysecure:html-textview:3.5' // Education
implementation 'com.nononsenseapps:filepicker:4.1.0'
implementation 'com.getkeepsafe.taptargetview:taptargetview:1.12.0' 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 // Apache Commons Collections
implementation 'commons-collections:commons-collections:3.2.1' implementation 'commons-collections:commons-collections:3.2.1'
implementation 'org.apache.commons:commons-io:1.3.2' implementation 'org.apache.commons:commons-io:1.3.2'
// Base64 // Base64
implementation 'biz.source_code:base64coder:2010-12-19' 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 // Icon pack
implementation project(path: ':icon-pack-classic') implementation project(path: ':icon-pack-classic')
implementation project(path: ':icon-pack-material') implementation project(path: ':icon-pack-material')

View File

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

View File

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

View File

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

View File

@@ -22,7 +22,7 @@ import android.app.Activity
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.os.Handler import android.os.Handler
import android.support.v7.widget.Toolbar import androidx.appcompat.widget.Toolbar
import android.util.Log import android.util.Log
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
@@ -45,7 +45,9 @@ import com.kunzisoft.keepass.timeout.TimeoutHelper
import com.kunzisoft.keepass.utils.MenuUtil import com.kunzisoft.keepass.utils.MenuUtil
import com.kunzisoft.keepass.view.EntryEditContentsView 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 private var mDatabase: Database? = null
@@ -154,33 +156,6 @@ class EntryEditActivity : LockingHideActivity(), IconPickerDialogFragment.IconPi
// Verify the education views // Verify the education views
entryEditActivityEducation = EntryEditActivityEducation(this) 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) { private fun populateViewsWithEntry(newEntry: EntryVersioned) {
@@ -309,9 +284,39 @@ class EntryEditActivity : LockingHideActivity(), IconPickerDialogFragment.IconPi
inflater.inflate(R.menu.database_lock, menu) inflater.inflate(R.menu.database_lock, menu)
MenuUtil.contributionMenuInflater(inflater, menu) MenuUtil.contributionMenuInflater(inflater, menu)
entryEditActivityEducation?.let {
Handler().post { performedNextEducation(it) }
}
return true 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 { override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) { when (item.itemId) {
R.id.menu_lock -> { R.id.menu_lock -> {
@@ -319,7 +324,10 @@ class EntryEditActivity : LockingHideActivity(), IconPickerDialogFragment.IconPi
return true return true
} }
R.id.menu_contribute -> return MenuUtil.onContributionItemSelected(this) R.id.menu_contribute -> {
MenuUtil.onContributionItemSelected(this)
return true
}
android.R.id.home -> finish() android.R.id.home -> finish()
} }

View File

@@ -19,7 +19,7 @@
*/ */
package com.kunzisoft.keepass.activities package com.kunzisoft.keepass.activities
import android.Manifest import android.annotation.SuppressLint
import android.app.Activity import android.app.Activity
import android.app.assist.AssistStructure import android.app.assist.AssistStructure
import android.content.Intent import android.content.Intent
@@ -29,56 +29,43 @@ import android.os.Bundle
import android.os.Environment import android.os.Environment
import android.os.Handler import android.os.Handler
import android.preference.PreferenceManager import android.preference.PreferenceManager
import android.support.annotation.RequiresApi import androidx.annotation.RequiresApi
import android.support.v7.app.AlertDialog import com.google.android.material.snackbar.Snackbar
import android.support.v7.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import android.support.v7.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import android.support.v7.widget.Toolbar import androidx.recyclerview.widget.SimpleItemAnimator
import androidx.appcompat.widget.Toolbar
import android.util.Log import android.util.Log
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import android.widget.EditText import android.widget.EditText
import android.widget.TextView import android.widget.TextView
import android.widget.Toast
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.dialogs.AssignMasterKeyDialogFragment import com.kunzisoft.keepass.activities.dialogs.AssignMasterKeyDialogFragment
import com.kunzisoft.keepass.activities.dialogs.CreateFileDialogFragment import com.kunzisoft.keepass.activities.dialogs.BrowserDialogFragment
import com.kunzisoft.keepass.activities.dialogs.FileInformationDialogFragment
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper 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.activities.stylish.StylishActivity
import com.kunzisoft.keepass.adapters.FileDatabaseHistoryAdapter import com.kunzisoft.keepass.adapters.FileDatabaseHistoryAdapter
import com.kunzisoft.keepass.app.database.FileDatabaseHistory
import com.kunzisoft.keepass.autofill.AutofillHelper import com.kunzisoft.keepass.autofill.AutofillHelper
import com.kunzisoft.keepass.database.action.CreateDatabaseRunnable import com.kunzisoft.keepass.database.action.CreateDatabaseRunnable
import com.kunzisoft.keepass.database.action.ProgressDialogThread import com.kunzisoft.keepass.database.action.ProgressDialogThread
import com.kunzisoft.keepass.database.element.Database import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.education.FileDatabaseSelectActivityEducation 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.magikeyboard.KeyboardHelper
import com.kunzisoft.keepass.settings.PreferencesUtil import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.tasks.ActionRunnable import com.kunzisoft.keepass.tasks.ActionRunnable
import com.kunzisoft.keepass.utils.MenuUtil import com.kunzisoft.keepass.utils.MenuUtil
import com.kunzisoft.keepass.utils.UriUtil 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 net.cachapa.expandablelayout.ExpandableLayout
import permissions.dispatcher.*
import java.io.File
import java.io.FileNotFoundException import java.io.FileNotFoundException
import java.io.IOException
import java.lang.ref.WeakReference
import java.net.URLDecoder
import java.util.*
@RuntimePermissions
class FileDatabaseSelectActivity : StylishActivity(), class FileDatabaseSelectActivity : StylishActivity(),
CreateFileDialogFragment.DefinePathDialogListener, AssignMasterKeyDialogFragment.AssignPasswordDialogListener {
AssignMasterKeyDialogFragment.AssignPasswordDialogListener,
FileDatabaseHistoryAdapter.FileItemOpenListener,
FileDatabaseHistoryAdapter.FileSelectClearListener,
FileDatabaseHistoryAdapter.FileInformationShowListener {
// Views // Views
private var fileListContainer: View? = null private var fileListContainer: View? = null
@@ -96,14 +83,14 @@ class FileDatabaseSelectActivity : StylishActivity(),
private var mDatabaseFileUri: Uri? = null private var mDatabaseFileUri: Uri? = null
private var mKeyFileHelper: KeyFileHelper? = null private var mOpenFileHelper: OpenFileHelper? = null
private var mDefaultPath: String? = null private var mDefaultPath: String? = null
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
mFileDatabaseHistory = FileDatabaseHistory.getInstance(WeakReference(applicationContext)) mFileDatabaseHistory = FileDatabaseHistory.getInstance(applicationContext)
setContentView(R.layout.activity_file_selection) setContentView(R.layout.activity_file_selection)
fileListContainer = findViewById(R.id.container_file_list) fileListContainer = findViewById(R.id.container_file_list)
@@ -131,10 +118,6 @@ class FileDatabaseSelectActivity : StylishActivity(),
fileSelectExpandableLayout?.expand() fileSelectExpandableLayout?.expand()
} }
// History list
val databaseFileListView = findViewById<RecyclerView>(R.id.file_list)
databaseFileListView.layoutManager = LinearLayoutManager(this)
// Open button // Open button
openButtonView = findViewById(R.id.open_database) openButtonView = findViewById(R.id.open_database)
openButtonView?.setOnClickListener { _ -> openButtonView?.setOnClickListener { _ ->
@@ -148,21 +131,56 @@ class FileDatabaseSelectActivity : StylishActivity(),
// Create button // Create button
createButtonView = findViewById(R.id.create_database) 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 = findViewById(R.id.browse_button)
browseButtonView?.setOnClickListener(mKeyFileHelper!!.getOpenFileOnClickViewListener { browseButtonView?.setOnClickListener(mOpenFileHelper!!.getOpenFileOnClickViewListener {
Uri.parse("file://" + openFileNameView!!.text.toString()) 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 // Construct adapter with listeners
mAdapterDatabaseHistory = FileDatabaseHistoryAdapter(this@FileDatabaseSelectActivity, mAdapterDatabaseHistory = FileDatabaseHistoryAdapter(this)
mFileDatabaseHistory?.databaseUriList ?: ArrayList()) mAdapterDatabaseHistory?.setOnFileDatabaseHistoryOpenListener { fileDatabaseHistoryEntityToOpen ->
mAdapterDatabaseHistory?.setOnItemClickListener(this) launchPasswordActivity(
mAdapterDatabaseHistory?.setFileSelectClearListener(this) fileDatabaseHistoryEntityToOpen.databaseUri,
mAdapterDatabaseHistory?.setFileInformationShowListener(this) fileDatabaseHistoryEntityToOpen.keyFileUri)
databaseFileListView.adapter = mAdapterDatabaseHistory 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 // Load default database if not an orientation change
if (!(savedInstanceState != null if (!(savedInstanceState != null
@@ -171,68 +189,48 @@ class FileDatabaseSelectActivity : StylishActivity(),
val prefs = PreferenceManager.getDefaultSharedPreferences(this) val prefs = PreferenceManager.getDefaultSharedPreferences(this)
val fileName = prefs.getString(PasswordActivity.KEY_DEFAULT_FILENAME, "") val fileName = prefs.getString(PasswordActivity.KEY_DEFAULT_FILENAME, "")
if (fileName != null && fileName.isNotEmpty()) { try {
val dbUri = UriUtil.parseUriFile(fileName) UriUtil.verifyFilePath(fileName) { path ->
var scheme: String? = null launchPasswordActivityWithPath(path)
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())
} }
} 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 * Create a new file by calling the content provider
if (createButtonView != null */
&& mFileDatabaseHistory != null @SuppressLint("InlinedApi")
&& !mFileDatabaseHistory!!.hasRecentFiles() && fileDatabaseSelectActivityEducation.checkAndPerformedCreateDatabaseEducation( private fun createNewFile() {
createButtonView!!, try {
{ startActivityForResult(Intent(
openCreateFileDialogFragmentWithPermissionCheck() Intent.ACTION_CREATE_DOCUMENT).apply {
}, addCategory(Intent.CATEGORY_OPENABLE)
{ type = "application/x-keepass"
// But if the user cancel, it can also select a database putExtra(Intent.EXTRA_TITLE, getString(R.string.database_file_name_default) +
performedNextEducation(fileDatabaseSelectActivityEducation) getString(R.string.database_file_extension_default))
})) },
else if (browseButtonView != null CREATE_FILE_REQUEST_CODE)
&& fileDatabaseSelectActivityEducation.checkAndPerformedSelectDatabaseEducation( } catch (e: Exception) {
browseButtonView!!, BrowserDialogFragment().show(supportFragmentManager, "browserDialog")
{tapTargetView -> }
tapTargetView?.let {
mKeyFileHelper?.openFileOnClickViewListener?.onClick(it)
}
},
{
fileSelectExpandableButtonView?.let {
fileDatabaseSelectActivityEducation
.checkAndPerformedOpenLinkDatabaseEducation(it)
}
}
))
;
} }
private fun fileNoFoundAction(e: FileNotFoundException) { private fun fileNoFoundAction(e: FileNotFoundException) {
val error = getString(R.string.file_not_found_content) val error = getString(R.string.file_not_found_content)
Toast.makeText(this@FileDatabaseSelectActivity, Snackbar.make(activity_file_selection_coordinator_layout, error, Snackbar.LENGTH_LONG).asError().show()
error, Toast.LENGTH_LONG).show()
Log.e(TAG, error, e) Log.e(TAG, error, e)
} }
private fun launchPasswordActivity(fileName: String, keyFile: String) { private fun launchPasswordActivity(fileName: String, keyFile: String?) {
EntrySelectionHelper.doEntrySelectionAction(intent, EntrySelectionHelper.doEntrySelectionAction(intent,
{ {
try { try {
@@ -295,26 +293,23 @@ class FileDatabaseSelectActivity : StylishActivity(),
super.onResume() super.onResume()
updateExternalStorageWarning() updateExternalStorageWarning()
updateFileListVisibility()
mAdapterDatabaseHistory!!.notifyDataSetChanged() // Construct adapter with listeners
mFileDatabaseHistory?.getAllFileDatabaseHistories { databaseFileHistoryList ->
databaseFileHistoryList?.let {
mAdapterDatabaseHistory?.addDatabaseFileHistoryList(it)
updateFileListVisibility()
mAdapterDatabaseHistory?.notifyDataSetChanged()
}
}
} }
override fun onSaveInstanceState(outState: Bundle) { override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState) super.onSaveInstanceState(outState)
// only to keep the current activity // only to keep the current activity
outState.putBoolean(EXTRA_STAY, true) outState.putBoolean(EXTRA_STAY, true)
} // to retrieve the URI of a created database after an orientation change
outState.putParcelable(EXTRA_DATABASE_URI, mDatabaseFileUri)
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")
} }
private fun updateFileListVisibility() { private fun updateFileListVisibility() {
@@ -324,82 +319,6 @@ class FileDatabaseSelectActivity : StylishActivity(),
fileListContainer?.visibility = View.VISIBLE 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( override fun onAssignKeyDialogPositiveClick(
masterPasswordChecked: Boolean, masterPassword: String?, masterPasswordChecked: Boolean, masterPassword: String?,
keyFileChecked: Boolean, keyFile: Uri?) { keyFileChecked: Boolean, keyFile: Uri?) {
@@ -418,22 +337,21 @@ class FileDatabaseSelectActivity : StylishActivity(),
keyFileChecked, keyFileChecked,
keyFile, keyFile,
true, // TODO get readonly true, // TODO get readonly
LaunchGroupActivityFinish(databaseUri) LaunchGroupActivityFinish(databaseUri, keyFile)
) )
}, },
R.string.progress_create) R.string.progress_create)
.start() .start()
} }
} catch (e: Exception) { } catch (e: Exception) {
val error = "Unable to create database with this password and key file" val error = getString(R.string.error_create_database_file)
Toast.makeText(this, error, Toast.LENGTH_LONG).show() Snackbar.make(activity_file_selection_coordinator_layout, error, Snackbar.LENGTH_LONG).asError().show()
Log.e(TAG, error + " " + e.message) Log.e(TAG, error, e)
// TODO remove
e.printStackTrace()
} }
} }
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() { override fun run() {
finishRun(true, null) finishRun(true, null)
@@ -443,7 +361,7 @@ class FileDatabaseSelectActivity : StylishActivity(),
runOnUiThread { runOnUiThread {
if (result.isSuccess) { if (result.isSuccess) {
// Add database to recent files // Add database to recent files
mFileDatabaseHistory?.addDatabaseUri(fileURI) mFileDatabaseHistory?.addOrUpdateDatabaseUri(databaseFileUri, keyFileUri)
mAdapterDatabaseHistory?.notifyDataSetChanged() mAdapterDatabaseHistory?.notifyDataSetChanged()
updateFileListVisibility() updateFileListVisibility()
GroupActivity.launch(this@FileDatabaseSelectActivity) 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?) { override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data) super.onActivityResult(requestCode, resultCode, data)
@@ -490,7 +385,7 @@ class FileDatabaseSelectActivity : StylishActivity(),
AutofillHelper.onActivityResultSetResultAndFinish(this, requestCode, resultCode, data) AutofillHelper.onActivityResultSetResultAndFinish(this, requestCode, resultCode, data)
} }
mKeyFileHelper?.onActivityResultCallback(requestCode, resultCode, data mOpenFileHelper?.onActivityResultCallback(requestCode, resultCode, data
) { uri -> ) { uri ->
if (uri != null) { if (uri != null) {
if (PreferencesUtil.autoOpenSelectedFile(this@FileDatabaseSelectActivity)) { if (PreferencesUtil.autoOpenSelectedFile(this@FileDatabaseSelectActivity)) {
@@ -501,33 +396,62 @@ class FileDatabaseSelectActivity : StylishActivity(),
} }
} }
} }
}
@OnShowRationale(Manifest.permission.WRITE_EXTERNAL_STORAGE) // Retrieve the created URI from the file manager
internal fun showRationaleForExternalStorage(request: PermissionRequest) { if (requestCode == CREATE_FILE_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
AlertDialog.Builder(this) mDatabaseFileUri = data?.data
.setMessage(R.string.permission_external_storage_rationale_write_database) if (mDatabaseFileUri != null) {
.setPositiveButton(R.string.allow) { _, _ -> request.proceed() } AssignMasterKeyDialogFragment().show(supportFragmentManager, "passwordDialog")
.setNegativeButton(R.string.cancel) { _, _ -> request.cancel() } }
.show() // else {
} // TODO Show error
// }
@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()
} }
override fun onCreateOptionsMenu(menu: Menu): Boolean { override fun onCreateOptionsMenu(menu: Menu): Boolean {
super.onCreateOptionsMenu(menu) super.onCreateOptionsMenu(menu)
MenuUtil.defaultMenuInflater(menuInflater, menu) MenuUtil.defaultMenuInflater(menuInflater, menu)
Handler().post { performedNextEducation(FileDatabaseSelectActivityEducation(this)) }
return true 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 { override fun onOptionsItemSelected(item: MenuItem): Boolean {
return MenuUtil.onDefaultMenuOptionsItemSelected(this, item) && super.onOptionsItemSelected(item) return MenuUtil.onDefaultMenuOptionsItemSelected(this, item) && super.onOptionsItemSelected(item)
} }
@@ -536,6 +460,9 @@ class FileDatabaseSelectActivity : StylishActivity(),
private const val TAG = "FileDbSelectActivity" private const val TAG = "FileDbSelectActivity"
private const val EXTRA_STAY = "EXTRA_STAY" 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.Build
import android.os.Bundle import android.os.Bundle
import android.os.Handler import android.os.Handler
import android.preference.PreferenceManager import androidx.annotation.RequiresApi
import android.support.annotation.RequiresApi import androidx.fragment.app.FragmentManager
import android.support.v4.app.FragmentManager import androidx.appcompat.widget.SearchView
import android.support.v7.widget.SearchView import androidx.appcompat.widget.Toolbar
import android.support.v7.widget.Toolbar
import android.util.Log import android.util.Log
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
@@ -183,7 +182,7 @@ class GroupActivity : LockingActivity(),
// Attach fragment to content view // Attach fragment to content view
supportFragmentManager.beginTransaction().replace( supportFragmentManager.beginTransaction().replace(
R.id.nodes_list_fragment_container, R.id.nodes_list_fragment_container,
mListNodesFragment, mListNodesFragment!!,
fragmentTag) fragmentTag)
.commit() .commit()
@@ -628,8 +627,9 @@ class GroupActivity : LockingActivity(),
private fun performedNextEducation(groupActivityEducation: GroupActivityEducation, private fun performedNextEducation(groupActivityEducation: GroupActivityEducation,
menu: Menu) { menu: Menu) {
// If no node, show education to add new one // If no node, show education to add new one
if (mListNodesFragment != null val addNodeButtonEducationPerformed = mListNodesFragment != null
&& mListNodesFragment!!.isEmpty && mListNodesFragment!!.isEmpty
&& addNodeButtonView?.addButtonView != null && addNodeButtonView?.addButtonView != null
&& addNodeButtonView!!.isEnable && addNodeButtonView!!.isEnable
@@ -641,38 +641,48 @@ class GroupActivity : LockingActivity(),
{ {
performedNextEducation(groupActivityEducation, menu) performedNextEducation(groupActivityEducation, menu)
} }
)) )
else if (toolbar != null if (!addNodeButtonEducationPerformed) {
&& toolbar!!.findViewById<View>(R.id.menu_search) != null
&& groupActivityEducation.checkAndPerformedSearchMenuEducation( val searchMenuEducationPerformed = toolbar != null
toolbar!!.findViewById(R.id.menu_search), && toolbar!!.findViewById<View>(R.id.menu_search) != null
{ && groupActivityEducation.checkAndPerformedSearchMenuEducation(
menu.findItem(R.id.menu_search).expandActionView() toolbar!!.findViewById(R.id.menu_search),
}, {
{ menu.findItem(R.id.menu_search).expandActionView()
performedNextEducation(groupActivityEducation, menu) },
})) {
else if (toolbar != null performedNextEducation(groupActivityEducation, menu)
&& toolbar!!.findViewById<View>(R.id.menu_sort) != null })
&& groupActivityEducation.checkAndPerformedSortMenuEducation(
if (!searchMenuEducationPerformed) {
val sortMenuEducationPerformed = toolbar != null
&& toolbar!!.findViewById<View>(R.id.menu_sort) != null
&& groupActivityEducation.checkAndPerformedSortMenuEducation(
toolbar!!.findViewById(R.id.menu_sort), toolbar!!.findViewById(R.id.menu_sort),
{ {
onOptionsItemSelected(menu.findItem(R.id.menu_sort)) onOptionsItemSelected(menu.findItem(R.id.menu_sort))
}, },
{ {
performedNextEducation(groupActivityEducation, menu) performedNextEducation(groupActivityEducation, menu)
})) })
else if (toolbar != null
&& toolbar!!.findViewById<View>(R.id.menu_lock) != null if (!sortMenuEducationPerformed) {
&& groupActivityEducation.checkAndPerformedLockMenuEducation( // lockMenuEducationPerformed
toolbar!!.findViewById(R.id.menu_lock), toolbar != null
{ && toolbar!!.findViewById<View>(R.id.menu_lock) != null
onOptionsItemSelected(menu.findItem(R.id.menu_lock)) && groupActivityEducation.checkAndPerformedLockMenuEducation(
}, toolbar!!.findViewById(R.id.menu_lock),
{ {
performedNextEducation(groupActivityEducation, menu) onOptionsItemSelected(menu.findItem(R.id.menu_lock))
})) },
; {
performedNextEducation(groupActivityEducation, menu)
})
}
}
}
} }
override fun startActivity(intent: Intent) { override fun startActivity(intent: Intent) {
@@ -857,11 +867,9 @@ class GroupActivity : LockingActivity(),
} }
private fun showWarnings() { private fun showWarnings() {
// TODO Preferences
if (Database.getInstance().isReadOnly) { if (Database.getInstance().isReadOnly) {
val prefs = PreferenceManager.getDefaultSharedPreferences(this) if (PreferencesUtil.showReadOnlyWarning(this)) {
if (prefs.getBoolean(getString(R.string.show_read_only_warning), true)) { ReadOnlyDialog().show(supportFragmentManager, "readOnlyDialog")
ReadOnlyDialog(this).show()
} }
} }
} }

View File

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

View File

@@ -19,7 +19,6 @@
*/ */
package com.kunzisoft.keepass.activities package com.kunzisoft.keepass.activities
import android.Manifest
import android.app.Activity import android.app.Activity
import android.app.assist.AssistStructure import android.app.assist.AssistStructure
import android.app.backup.BackupManager import android.app.backup.BackupManager
@@ -32,11 +31,12 @@ import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.os.Handler import android.os.Handler
import android.preference.PreferenceManager import android.preference.PreferenceManager
import android.support.annotation.RequiresApi import androidx.annotation.RequiresApi
import android.support.v7.app.AlertDialog import com.google.android.material.snackbar.Snackbar
import android.support.v7.widget.Toolbar import androidx.appcompat.widget.Toolbar
import android.text.Editable import android.text.Editable
import android.text.TextWatcher import android.text.TextWatcher
import android.util.Log
import android.view.KeyEvent import android.view.KeyEvent
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
@@ -45,9 +45,14 @@ import android.view.inputmethod.EditorInfo.IME_ACTION_DONE
import android.widget.* import android.widget.*
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.dialogs.PasswordEncodingDialogFragment 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.lock.LockingActivity
import com.kunzisoft.keepass.activities.stylish.StylishActivity 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.autofill.AutofillHelper
import com.kunzisoft.keepass.database.action.LoadDatabaseRunnable import com.kunzisoft.keepass.database.action.LoadDatabaseRunnable
import com.kunzisoft.keepass.database.action.ProgressDialogThread 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.MenuUtil
import com.kunzisoft.keepass.utils.UriUtil import com.kunzisoft.keepass.utils.UriUtil
import com.kunzisoft.keepass.view.FingerPrintInfoView import com.kunzisoft.keepass.view.FingerPrintInfoView
import permissions.dispatcher.* import com.kunzisoft.keepass.view.asError
import java.io.File import kotlinx.android.synthetic.main.activity_password.*
import java.io.FileNotFoundException import java.io.FileNotFoundException
import java.lang.Exception
import java.lang.ref.WeakReference import java.lang.ref.WeakReference
@RuntimePermissions class PasswordActivity : StylishActivity() {
class PasswordActivity : StylishActivity(),
UriIntentInitTaskCallback {
// Views // Views
private var toolbar: Toolbar? = null private var toolbar: Toolbar? = null
@@ -87,7 +91,7 @@ class PasswordActivity : StylishActivity(),
private var prefs: SharedPreferences? = null private var prefs: SharedPreferences? = null
private var mRememberKeyFile: Boolean = false private var mRememberKeyFile: Boolean = false
private var mKeyFileHelper: KeyFileHelper? = null private var mOpenFileHelper: OpenFileHelper? = null
private var readOnly: Boolean = false private var readOnly: Boolean = false
@@ -121,8 +125,8 @@ class PasswordActivity : StylishActivity(),
readOnly = ReadOnlyHelper.retrieveReadOnlyFromInstanceStateOrPreference(this, savedInstanceState) readOnly = ReadOnlyHelper.retrieveReadOnlyFromInstanceStateOrPreference(this, savedInstanceState)
val browseView = findViewById<View>(R.id.browse_button) val browseView = findViewById<View>(R.id.browse_button)
mKeyFileHelper = KeyFileHelper(this@PasswordActivity) mOpenFileHelper = OpenFileHelper(this@PasswordActivity)
browseView.setOnClickListener(mKeyFileHelper!!.openFileOnClickViewListener) browseView.setOnClickListener(mOpenFileHelper!!.openFileOnClickViewListener)
passwordView?.setOnEditorActionListener(onEditorActionListener) passwordView?.setOnEditorActionListener(onEditorActionListener)
passwordView?.addTextChangedListener(object : TextWatcher { passwordView?.addTextChangedListener(object : TextWatcher {
@@ -172,8 +176,7 @@ class PasswordActivity : StylishActivity(),
// For check shutdown // For check shutdown
super.onResume() super.onResume()
UriIntentInitTask(WeakReference(this), this, mRememberKeyFile) initUriFromIntent()
.execute(intent)
} }
override fun onSaveInstanceState(outState: Bundle) { override fun onSaveInstanceState(outState: Bundle) {
@@ -181,26 +184,56 @@ class PasswordActivity : StylishActivity(),
super.onSaveInstanceState(outState) super.onSaveInstanceState(outState)
} }
override fun onPostInitTask(databaseFileUri: Uri?, keyFileUri: Uri?, errorStringId: Int?) { private fun initUriFromIntent() {
mDatabaseFileUri = databaseFileUri
if (errorStringId != null) { val databaseUri: Uri?
Toast.makeText(this@PasswordActivity, errorStringId, Toast.LENGTH_LONG).show() val keyFileUri: Uri?
finish()
return // 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 // Post init uri with KeyFile if needed
if (databaseFileUri != null && !databaseFileUri.scheme!!.contains("content")) if (mRememberKeyFile && (keyFileUri == null || keyFileUri.toString().isEmpty())) {
doNothingWithPermissionCheck() // 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 // Define title
val dbUriString = databaseFileUri?.toString() ?: "" databaseFileUri?.let {
if (dbUriString.isNotEmpty()) { FileDatabaseInfo(this, it).retrieveDatabaseTitle { title ->
if (PreferencesUtil.isFullFilePathEnable(this)) filenameView?.text = title
filenameView?.text = dbUriString }
else
filenameView?.text = File(databaseFileUri!!.path!!).name // TODO Encapsulate
} }
// Define Key File text // Define Key File text
@@ -216,7 +249,7 @@ class PasswordActivity : StylishActivity(),
newDefaultFileName = databaseFileUri?.toString() ?: newDefaultFileName newDefaultFileName = databaseFileUri?.toString() ?: newDefaultFileName
} }
prefs?.edit()?.apply() { prefs?.edit()?.apply {
putString(KEY_DEFAULT_FILENAME, newDefaultFileName) putString(KEY_DEFAULT_FILENAME, newDefaultFileName)
apply() apply()
} }
@@ -418,7 +451,7 @@ class PasswordActivity : StylishActivity(),
} }
} else { } else {
if (result.message != null && result.message!!.isNotEmpty()) { 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, private fun performedNextEducation(passwordActivityEducation: PasswordActivityEducation,
menu: Menu) { menu: Menu) {
if (toolbar != null val unlockEducationPerformed = toolbar != null
&& passwordActivityEducation.checkAndPerformedFingerprintUnlockEducation( && passwordActivityEducation.checkAndPerformedUnlockEducation(
toolbar!!, toolbar!!,
{ {
performedNextEducation(passwordActivityEducation, menu) performedNextEducation(passwordActivityEducation, menu)
}, },
{ {
performedNextEducation(passwordActivityEducation, menu) performedNextEducation(passwordActivityEducation, menu)
})) })
else if (toolbar != null if (!unlockEducationPerformed) {
&& toolbar!!.findViewById<View>(R.id.menu_open_file_read_mode_key) != null
&& passwordActivityEducation.checkAndPerformedReadOnlyEducation( val readOnlyEducationPerformed = toolbar != null
toolbar!!.findViewById(R.id.menu_open_file_read_mode_key), && toolbar!!.findViewById<View>(R.id.menu_open_file_read_mode_key) != null
{ && passwordActivityEducation.checkAndPerformedReadOnlyEducation(
onOptionsItemSelected(menu.findItem(R.id.menu_open_file_read_mode_key)) toolbar!!.findViewById(R.id.menu_open_file_read_mode_key),
performedNextEducation(passwordActivityEducation, menu) {
}, 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 performedNextEducation(passwordActivityEducation, menu)
&& PreferencesUtil.isFingerprintEnable(applicationContext) })
&& FingerPrintHelper.isFingerprintSupported(getSystemService(FingerprintManager::class.java))
&& fingerPrintInfoView != null && fingerPrintInfoView?.fingerPrintImageView != null if (!readOnlyEducationPerformed) {
&& passwordActivityEducation.checkAndPerformedFingerprintEducation(fingerPrintInfoView?.fingerPrintImageView!!))
; // 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) { private fun changeOpenFileReadIcon(togglePassword: MenuItem) {
@@ -520,12 +561,6 @@ class PasswordActivity : StylishActivity(),
return super.onOptionsItemSelected(item) 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( override fun onActivityResult(
requestCode: Int, requestCode: Int,
resultCode: Int, resultCode: Int,
@@ -538,7 +573,7 @@ class PasswordActivity : StylishActivity(),
} }
var keyFileResult = false var keyFileResult = false
mKeyFileHelper?.let { mOpenFileHelper?.let {
keyFileResult = it.onActivityResultCallback(requestCode, resultCode, data keyFileResult = it.onActivityResultCallback(requestCode, resultCode, data
) { uri -> ) { uri ->
if (uri != null) { 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 { companion object {
private val TAG = PasswordActivity::class.java.name private val TAG = PasswordActivity::class.java.name
const val KEY_DEFAULT_FILENAME = "defaultFileName" 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_PASSWORD = "password"
private const val KEY_LAUNCH_IMMEDIATELY = "launchImmediately" 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) { intentBuildLauncher: (Intent) -> Unit) {
val intent = Intent(activity, PasswordActivity::class.java) val intent = Intent(activity, PasswordActivity::class.java)
intent.putExtra(UriIntentInitTask.KEY_FILENAME, fileName) intent.putExtra(KEY_FILENAME, fileName)
intent.putExtra(UriIntentInitTask.KEY_KEYFILE, keyFile) if (keyFile != null)
intent.putExtra(KEY_KEYFILE, keyFile)
intentBuildLauncher.invoke(intent) 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 * Standard Launch
@@ -625,8 +624,8 @@ class PasswordActivity : StylishActivity(),
fun launch( fun launch(
activity: Activity, activity: Activity,
fileName: String, fileName: String,
keyFile: String) { keyFile: String?) {
verifyFileNameUriFromLaunch(fileName) UriUtil.verifyFilePath(fileName)
buildAndLaunchIntent(activity, fileName, keyFile) { intent -> buildAndLaunchIntent(activity, fileName, keyFile) { intent ->
activity.startActivity(intent) activity.startActivity(intent)
} }
@@ -642,8 +641,8 @@ class PasswordActivity : StylishActivity(),
fun launchForKeyboardResult( fun launchForKeyboardResult(
activity: Activity, activity: Activity,
fileName: String, fileName: String,
keyFile: String) { keyFile: String?) {
verifyFileNameUriFromLaunch(fileName) UriUtil.verifyFilePath(fileName)
buildAndLaunchIntent(activity, fileName, keyFile) { intent -> buildAndLaunchIntent(activity, fileName, keyFile) { intent ->
KeyboardHelper.startActivityForKeyboardSelection(activity, intent) KeyboardHelper.startActivityForKeyboardSelection(activity, intent)
@@ -661,9 +660,9 @@ class PasswordActivity : StylishActivity(),
fun launchForAutofillResult( fun launchForAutofillResult(
activity: Activity, activity: Activity,
fileName: String, fileName: String,
keyFile: String, keyFile: String?,
assistStructure: AssistStructure?) { assistStructure: AssistStructure?) {
verifyFileNameUriFromLaunch(fileName) UriUtil.verifyFilePath(fileName)
if (assistStructure != null) { if (assistStructure != null) {
buildAndLaunchIntent(activity, fileName, keyFile) { intent -> buildAndLaunchIntent(activity, fileName, keyFile) { intent ->

View File

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

View File

@@ -21,11 +21,12 @@ package com.kunzisoft.keepass.activities.dialogs
import android.app.Dialog import android.app.Dialog
import android.os.Bundle import android.os.Bundle
import android.support.v4.app.DialogFragment import androidx.fragment.app.DialogFragment
import android.support.v7.app.AlertDialog import androidx.appcompat.app.AlertDialog
import android.widget.Button import android.widget.Button
import android.widget.TextView
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.utils.Util import com.kunzisoft.keepass.utils.UriUtil
class BrowserDialogFragment : DialogFragment() { class BrowserDialogFragment : DialogFragment() {
@@ -37,15 +38,18 @@ class BrowserDialogFragment : DialogFragment() {
builder.setView(root) builder.setView(root)
.setNegativeButton(R.string.cancel) { _, _ -> } .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 { market.setOnClickListener {
Util.gotoUrl(context!!, R.string.filemanager_play_store) UriUtil.gotoUrl(context!!, R.string.filemanager_play_store)
dismiss() dismiss()
} }
val web = root.findViewById<Button>(R.id.install_web) val web = root.findViewById<Button>(R.id.file_manager_install_f_droid)
web.setOnClickListener { web.setOnClickListener {
Util.gotoUrl(context!!, R.string.filemanager_f_droid) UriUtil.gotoUrl(context!!, R.string.filemanager_f_droid)
dismiss() 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.app.Dialog
import android.content.Context import android.content.Context
import android.os.Bundle import android.os.Bundle
import android.support.v4.app.DialogFragment import com.google.android.material.textfield.TextInputLayout
import android.support.v7.app.AlertDialog import androidx.fragment.app.DialogFragment
import androidx.appcompat.app.AlertDialog
import android.view.View 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.R
import com.kunzisoft.keepass.password.PasswordGenerator import com.kunzisoft.keepass.password.PasswordGenerator
import com.kunzisoft.keepass.settings.PreferencesUtil import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.utils.applyFontVisibility import com.kunzisoft.keepass.view.applyFontVisibility
class GeneratePasswordDialogFragment : DialogFragment() { class GeneratePasswordDialogFragment : DialogFragment() {
@@ -37,6 +41,7 @@ class GeneratePasswordDialogFragment : DialogFragment() {
private var root: View? = null private var root: View? = null
private var lengthTextView: EditText? = null private var lengthTextView: EditText? = null
private var passwordInputLayoutView: TextInputLayout? = null
private var passwordView: EditText? = null private var passwordView: EditText? = null
private var uppercaseBox: CompoundButton? = null private var uppercaseBox: CompoundButton? = null
@@ -65,6 +70,7 @@ class GeneratePasswordDialogFragment : DialogFragment() {
val inflater = activity.layoutInflater val inflater = activity.layoutInflater
root = inflater.inflate(R.layout.fragment_generate_password, null) root = inflater.inflate(R.layout.fragment_generate_password, null)
passwordInputLayoutView = root?.findViewById(R.id.password_input_layout)
passwordView = root?.findViewById(R.id.password) passwordView = root?.findViewById(R.id.password)
passwordView?.applyFontVisibility() passwordView?.applyFontVisibility()
@@ -162,8 +168,7 @@ class GeneratePasswordDialogFragment : DialogFragment() {
try { try {
val length = Integer.valueOf(root?.findViewById<EditText>(R.id.length)?.text.toString()) val length = Integer.valueOf(root?.findViewById<EditText>(R.id.length)?.text.toString())
val generator = PasswordGenerator(resources) password = PasswordGenerator(resources).generatePassword(length,
password = generator.generatePassword(length,
uppercaseBox?.isChecked == true, uppercaseBox?.isChecked == true,
lowercaseBox?.isChecked == true, lowercaseBox?.isChecked == true,
digitsBox?.isChecked == true, digitsBox?.isChecked == true,
@@ -174,9 +179,9 @@ class GeneratePasswordDialogFragment : DialogFragment() {
bracketsBox?.isChecked == true, bracketsBox?.isChecked == true,
extendedBox?.isChecked == true) extendedBox?.isChecked == true)
} catch (e: NumberFormatException) { } 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) { } catch (e: IllegalArgumentException) {
Toast.makeText(context, e.message, Toast.LENGTH_LONG).show() passwordInputLayoutView?.error = e.message
} }
return password return password

View File

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

View File

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

View File

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

View File

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

View File

@@ -20,17 +20,15 @@
package com.kunzisoft.keepass.activities.dialogs package com.kunzisoft.keepass.activities.dialogs
import android.app.Dialog import android.app.Dialog
import android.content.ActivityNotFoundException
import android.os.Bundle 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.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.BuildConfig
import com.kunzisoft.keepass.R 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. * 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() val stringBuilder = SpannableStringBuilder()
if (BuildConfig.CLOSED_STORE) { if (BuildConfig.CLOSED_STORE) {
// TODO HtmlCompat with androidX stringBuilder.append(HtmlCompat.fromHtml(getString(R.string.html_text_ad_free), FROM_HTML_MODE_LEGACY)).append("\n\n")
stringBuilder.append(Html.fromHtml(getString(R.string.html_text_ad_free))).append("\n\n") stringBuilder.append(HtmlCompat.fromHtml(getString(R.string.html_text_buy_pro), FROM_HTML_MODE_LEGACY))
stringBuilder.append(Html.fromHtml(getString(R.string.html_text_buy_pro)))
builder.setPositiveButton(R.string.download) { _, _ -> builder.setPositiveButton(R.string.download) { _, _ ->
try { UriUtil.gotoUrl(context!!, R.string.app_pro_url)
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()
}
} }
} else { } else {
stringBuilder.append(Html.fromHtml(getString(R.string.html_text_feature_generosity))).append("\n\n") stringBuilder.append(HtmlCompat.fromHtml(getString(R.string.html_text_feature_generosity), FROM_HTML_MODE_LEGACY)).append("\n\n")
stringBuilder.append(Html.fromHtml(getString(R.string.html_text_donation))) stringBuilder.append(HtmlCompat.fromHtml(getString(R.string.html_text_donation), FROM_HTML_MODE_LEGACY))
builder.setPositiveButton(R.string.contribute) { _, _ -> builder.setPositiveButton(R.string.contribute) { _, _ ->
try { UriUtil.gotoUrl(context!!, R.string.contribution_url)
Util.gotoUrl(context!!, R.string.contribution_url)
} catch (e: ActivityNotFoundException) {
Toast.makeText(context, R.string.error_failed_to_launch_link, Toast.LENGTH_LONG).show()
}
} }
} }
builder.setMessage(stringBuilder) builder.setMessage(stringBuilder)

View File

@@ -19,33 +19,38 @@
*/ */
package com.kunzisoft.keepass.activities.dialogs package com.kunzisoft.keepass.activities.dialogs
import android.app.AlertDialog import android.app.Dialog
import android.content.Context
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.preference.PreferenceManager import android.preference.PreferenceManager
import androidx.fragment.app.DialogFragment
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
class ReadOnlyDialog(context: Context) : AlertDialog(context) { class ReadOnlyDialog : DialogFragment() {
override fun onCreate(savedInstanceState: Bundle) { override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val ctx = context activity?.let { activity ->
var warning = ctx.getString(R.string.read_only_warning) // Use the Builder class for convenient dialog construction
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { val builder = androidx.appcompat.app.AlertDialog.Builder(activity)
warning = warning + "\n\n" + context.getString(R.string.read_only_kitkat_warning)
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) return super.onCreateDialog(savedInstanceState)
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)
} }
} }

View File

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

View File

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

View File

@@ -20,17 +20,14 @@
package com.kunzisoft.keepass.activities.dialogs package com.kunzisoft.keepass.activities.dialogs
import android.app.Dialog import android.app.Dialog
import android.content.ActivityNotFoundException
import android.os.Bundle 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.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.BuildConfig
import com.kunzisoft.keepass.R 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. * 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() val stringBuilder = SpannableStringBuilder()
if (BuildConfig.CLOSED_STORE) { if (BuildConfig.CLOSED_STORE) {
if (BuildConfig.FULL_VERSION) { if (BuildConfig.FULL_VERSION) {
stringBuilder.append(Html.fromHtml(getString(R.string.html_text_dev_feature_thanks))).append("\n\n") stringBuilder.append(HtmlCompat.fromHtml(getString(R.string.html_text_dev_feature_thanks), HtmlCompat.FROM_HTML_MODE_LEGACY)).append("\n\n")
.append(Html.fromHtml(getString(R.string.html_rose))).append("\n\n") .append(HtmlCompat.fromHtml(getString(R.string.html_rose), HtmlCompat.FROM_HTML_MODE_LEGACY)).append("\n\n")
.append(Html.fromHtml(getString(R.string.html_text_dev_feature_work_hard))).append("\n") .append(HtmlCompat.fromHtml(getString(R.string.html_text_dev_feature_work_hard), HtmlCompat.FROM_HTML_MODE_LEGACY)).append("\n")
.append(Html.fromHtml(getString(R.string.html_text_dev_feature_upgrade))).append(" ") .append(HtmlCompat.fromHtml(getString(R.string.html_text_dev_feature_upgrade), HtmlCompat.FROM_HTML_MODE_LEGACY)).append(" ")
builder.setPositiveButton(android.R.string.ok) { _, _ -> dismiss() } builder.setPositiveButton(android.R.string.ok) { _, _ -> dismiss() }
} else { } else {
stringBuilder.append(Html.fromHtml(getString(R.string.html_text_dev_feature))).append("\n\n") stringBuilder.append(HtmlCompat.fromHtml(getString(R.string.html_text_dev_feature), HtmlCompat.FROM_HTML_MODE_LEGACY)).append("\n\n")
.append(Html.fromHtml(getString(R.string.html_text_dev_feature_buy_pro))).append("\n") .append(HtmlCompat.fromHtml(getString(R.string.html_text_dev_feature_buy_pro), HtmlCompat.FROM_HTML_MODE_LEGACY)).append("\n")
.append(Html.fromHtml(getString(R.string.html_text_dev_feature_encourage))) .append(HtmlCompat.fromHtml(getString(R.string.html_text_dev_feature_encourage), HtmlCompat.FROM_HTML_MODE_LEGACY))
builder.setPositiveButton(R.string.download) { _, _ -> builder.setPositiveButton(R.string.download) { _, _ ->
try { UriUtil.gotoUrl(context!!, R.string.app_pro_url)
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()
}
} }
builder.setNegativeButton(android.R.string.cancel) { _, _ -> dismiss() } builder.setNegativeButton(android.R.string.cancel) { _, _ -> dismiss() }
} }
} else { } else {
stringBuilder.append(Html.fromHtml(getString(R.string.html_text_dev_feature))).append("\n\n") stringBuilder.append(HtmlCompat.fromHtml(getString(R.string.html_text_dev_feature), HtmlCompat.FROM_HTML_MODE_LEGACY)).append("\n\n")
.append(Html.fromHtml(getString(R.string.html_text_dev_feature_contibute))).append(" ") .append(HtmlCompat.fromHtml(getString(R.string.html_text_dev_feature_contibute), HtmlCompat.FROM_HTML_MODE_LEGACY)).append(" ")
.append(Html.fromHtml(getString(R.string.html_text_dev_feature_encourage))) .append(HtmlCompat.fromHtml(getString(R.string.html_text_dev_feature_encourage), HtmlCompat.FROM_HTML_MODE_LEGACY))
builder.setPositiveButton(R.string.contribute) { _, _ -> builder.setPositiveButton(R.string.contribute) { _, _ ->
try { UriUtil.gotoUrl(context!!, R.string.contribution_url)
Util.gotoUrl(context!!, R.string.contribution_url)
} catch (e: ActivityNotFoundException) {
Toast.makeText(context, R.string.error_failed_to_launch_link, Toast.LENGTH_LONG).show()
}
} }
builder.setNegativeButton(android.R.string.cancel) { _, _ -> dismiss() } builder.setNegativeButton(android.R.string.cancel) { _, _ -> dismiss() }
} }

View File

@@ -26,15 +26,14 @@ import android.content.Intent
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.net.Uri import android.net.Uri
import android.os.Build import android.os.Build
import android.support.v4.app.Fragment import androidx.fragment.app.Fragment
import android.support.v4.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import android.util.Log import android.util.Log
import android.view.View import android.view.View
import com.kunzisoft.keepass.activities.dialogs.BrowserDialogFragment import com.kunzisoft.keepass.activities.dialogs.BrowserDialogFragment
import com.kunzisoft.keepass.fileselect.StorageAF
import com.kunzisoft.keepass.utils.UriUtil import com.kunzisoft.keepass.utils.UriUtil
class KeyFileHelper { class OpenFileHelper {
private var activity: Activity? = null private var activity: Activity? = null
private var fragment: Fragment? = null private var fragment: Fragment? = null
@@ -56,9 +55,9 @@ class KeyFileHelper {
override fun onClick(v: View) { override fun onClick(v: View) {
try { try {
if (activity != null && StorageAF.useStorageFramework(activity!!)) { try {
openActivityWithActionOpenDocument() openActivityWithActionOpenDocument()
} else { } catch(e: Exception) {
openActivityWithActionGetContent() openActivityWithActionGetContent()
} }
} catch (e: Exception) { } catch (e: Exception) {
@@ -72,7 +71,7 @@ class KeyFileHelper {
} }
private fun openActivityWithActionOpenDocument() { private fun openActivityWithActionOpenDocument() {
val i = Intent(StorageAF.ACTION_OPEN_DOCUMENT) val i = Intent(ACTION_OPEN_DOCUMENT)
i.addCategory(Intent.CATEGORY_OPENABLE) i.addCategory(Intent.CATEGORY_OPENABLE)
i.type = "*/*" i.type = "*/*"
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
@@ -158,7 +157,7 @@ class KeyFileHelper {
val browserDialogFragment = BrowserDialogFragment() val browserDialogFragment = BrowserDialogFragment()
if (fragment != null && fragment!!.fragmentManager != null) if (fragment != null && fragment!!.fragmentManager != null)
browserDialogFragment.show(fragment!!.fragmentManager!!, "browserDialog") browserDialogFragment.show(fragment!!.fragmentManager!!, "browserDialog")
else if (activity!!.fragmentManager != null) else if ((activity as FragmentActivity).supportFragmentManager != null)
browserDialogFragment.show((activity as FragmentActivity).supportFragmentManager, "browserDialog") browserDialogFragment.show((activity as FragmentActivity).supportFragmentManager, "browserDialog")
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "Can't open BrowserDialog", e) Log.e(TAG, "Can't open BrowserDialog", e)
@@ -193,18 +192,16 @@ class KeyFileHelper {
if (data != null) { if (data != null) {
var uri = data.data var uri = data.data
if (uri != null) { if (uri != null) {
if (StorageAF.useStorageFramework(activity!!)) { try {
try { // try to persist read and write permissions
// try to persist read and write permissions if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { activity?.contentResolver?.apply {
activity?.contentResolver?.apply { takePersistableUriPermission(uri!!, Intent.FLAG_GRANT_READ_URI_PERMISSION)
takePersistableUriPermission(uri!!, Intent.FLAG_GRANT_READ_URI_PERMISSION) takePersistableUriPermission(uri!!, Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
takePersistableUriPermission(uri!!, Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
}
} }
} catch (e: Exception) {
// nop
} }
} catch (e: Exception) {
// nop
} }
if (requestCode == GET_CONTENT) { if (requestCode == GET_CONTENT) {
uri = UriUtil.translateUri(activity!!, uri) uri = UriUtil.translateUri(activity!!, uri)
@@ -221,7 +218,18 @@ class KeyFileHelper {
companion object { 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" 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 package com.kunzisoft.keepass.activities.stylish
import android.content.Context import android.content.Context
import android.support.annotation.StyleRes import androidx.annotation.StyleRes
import android.support.v7.preference.PreferenceManager import androidx.preference.PreferenceManager
import android.util.Log import android.util.Log
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
@@ -68,15 +68,4 @@ object Stylish {
else -> R.style.KeepassDXStyle_Light 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 package com.kunzisoft.keepass.activities.stylish
import android.os.Bundle import android.os.Bundle
import android.support.annotation.StyleRes import androidx.annotation.StyleRes
import android.support.v7.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import android.util.Log import android.util.Log
abstract class StylishActivity : AppCompatActivity() { abstract class StylishActivity : AppCompatActivity() {

View File

@@ -23,9 +23,9 @@ import android.content.Context
import android.graphics.Color import android.graphics.Color
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.support.annotation.StyleRes import androidx.annotation.StyleRes
import android.support.v4.app.Fragment import androidx.fragment.app.Fragment
import android.support.v7.view.ContextThemeWrapper import androidx.appcompat.view.ContextThemeWrapper
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup 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.content.Context
import android.support.v7.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup

View File

@@ -21,23 +21,30 @@ package com.kunzisoft.keepass.adapters
import android.content.Context import android.content.Context
import android.net.Uri import android.net.Uri
import android.support.annotation.ColorInt import androidx.annotation.ColorInt
import android.support.v7.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import android.util.TypedValue import android.util.TypedValue
import android.view.* import android.view.*
import android.widget.EditText
import android.widget.ImageView import android.widget.ImageView
import android.widget.TextView import android.widget.TextView
import android.widget.ViewSwitcher
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.fileselect.FileDatabaseModel import com.kunzisoft.keepass.app.database.FileDatabaseHistoryEntity
import com.kunzisoft.keepass.settings.PreferencesUtil 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>() { : RecyclerView.Adapter<FileDatabaseHistoryAdapter.FileDatabaseHistoryViewHolder>() {
private val inflater: LayoutInflater = LayoutInflater.from(context) private val inflater: LayoutInflater = LayoutInflater.from(context)
private var fileItemOpenListener: FileItemOpenListener? = null private var fileItemOpenListener: ((FileDatabaseHistoryEntity)->Unit)? = null
private var fileSelectClearListener: FileSelectClearListener? = null private var fileSelectClearListener: ((FileDatabaseHistoryEntity)->Boolean)? = null
private var fileInformationShowListener: FileInformationShowListener? = null private var saveAliasListener: ((FileDatabaseHistoryEntity)->Unit)? = null
private val listDatabaseFiles = ArrayList<FileDatabaseHistoryEntity>()
private var mExpandedPosition = -1
private var mPreviousExpandedPosition = -1
@ColorInt @ColorInt
private val defaultColor: Int private val defaultColor: Int
@@ -45,7 +52,6 @@ class FileDatabaseHistoryAdapter(private val context: Context, private val listF
private val warningColor: Int private val warningColor: Int
init { init {
val typedValue = TypedValue() val typedValue = TypedValue()
val theme = context.theme val theme = context.theme
theme.resolveAttribute(R.attr.colorAccent, typedValue, true) 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) { override fun onBindViewHolder(holder: FileDatabaseHistoryViewHolder, position: Int) {
val fileDatabaseModel = FileDatabaseModel(context, listFiles[position]) // Get info from position
// Context menu creation val fileHistoryEntity = listDatabaseFiles[position]
holder.fileContainer.setOnCreateContextMenuListener(ContextMenuBuilder(fileDatabaseModel)) val fileDatabaseInfo = FileDatabaseInfo(context, fileHistoryEntity.databaseUri)
// Click item to open file // Click item to open file
if (fileItemOpenListener != null) if (fileItemOpenListener != null)
holder.fileContainer.setOnClickListener(FileItemClickListener(position)) holder.fileContainer.setOnClickListener {
// Assign file name fileItemOpenListener?.invoke(fileHistoryEntity)
if (PreferencesUtil.isFullFilePathEnable(context)) }
holder.fileName.text = Uri.decode(fileDatabaseModel.fileUri.toString())
else // File alias
holder.fileName.text = fileDatabaseModel.fileName holder.fileAlias.text = fileDatabaseInfo.retrieveDatabaseAlias(fileHistoryEntity.databaseAlias)
holder.fileName.textSize = PreferencesUtil.getListTextSize(context)
// 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 // Click on information
if (fileInformationShowListener != null) val isExpanded = position == mExpandedPosition
holder.fileInformation.setOnClickListener(FileInformationClickListener(fileDatabaseModel)) //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 { override fun getItemCount(): Int {
return listFiles.size return listDatabaseFiles.size
} }
fun setOnItemClickListener(fileItemOpenListener: FileItemOpenListener) { fun addDatabaseFileHistoryList(listFileDatabaseHistoryToAdd: List<FileDatabaseHistoryEntity>) {
this.fileItemOpenListener = fileItemOpenListener listDatabaseFiles.clear()
listDatabaseFiles.addAll(listFileDatabaseHistoryToAdd)
} }
fun setFileSelectClearListener(fileSelectClearListener: FileSelectClearListener) { fun deleteDatabaseFileHistory(fileDatabaseHistoryToDelete: FileDatabaseHistoryEntity) {
this.fileSelectClearListener = fileSelectClearListener listDatabaseFiles.remove(fileDatabaseHistoryToDelete)
} }
fun setFileInformationShowListener(fileInformationShowListener: FileInformationShowListener) { fun setOnFileDatabaseHistoryOpenListener(listener : ((FileDatabaseHistoryEntity)->Unit)?) {
this.fileInformationShowListener = fileInformationShowListener this.fileItemOpenListener = listener
} }
interface FileItemOpenListener { fun setOnFileDatabaseHistoryDeleteListener(listener : ((FileDatabaseHistoryEntity)->Boolean)?) {
fun onFileItemOpenListener(itemPosition: Int) this.fileSelectClearListener = listener
} }
interface FileSelectClearListener { fun setOnSaveAliasListener(listener : ((FileDatabaseHistoryEntity)->Unit)?) {
fun onFileSelectClearListener(fileDatabaseModel: FileDatabaseModel): Boolean this.saveAliasListener = listener
}
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)
}
} }
inner class FileDatabaseHistoryViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { inner class FileDatabaseHistoryViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var fileContainer: View = itemView.findViewById(R.id.file_container) var fileContainer: ViewGroup = itemView.findViewById(R.id.file_container_basic_info)
var fileName: TextView = itemView.findViewById(R.id.file_filename)
var fileAlias: TextView = itemView.findViewById(R.id.file_alias)
var fileInformation: ImageView = itemView.findViewById(R.id.file_information) 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.content.Context
import android.graphics.Color import android.graphics.Color
import android.support.v7.util.SortedList import androidx.recyclerview.widget.SortedList
import android.support.v7.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import android.support.v7.widget.util.SortedListAdapterCallback import androidx.recyclerview.widget.SortedListAdapterCallback
import android.util.Log import android.util.Log
import android.view.* import android.view.*
import android.widget.ImageView import android.widget.ImageView
@@ -45,10 +45,13 @@ class NodeAdapter
private val nodeSortedList: SortedList<NodeVersioned> private val nodeSortedList: SortedList<NodeVersioned>
private val inflater: LayoutInflater = LayoutInflater.from(context) private val inflater: LayoutInflater = LayoutInflater.from(context)
private var textSize: Float = 0.toFloat()
private var subtextSize: Float = 0.toFloat() private var calculateViewTypeTextSize = Array(2) { true} // number of view type
private var infoTextSize: Float = 0.toFloat() private var prefTextSize: Float = 0F
private var iconSize: Float = 0.toFloat() 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 listSort: SortNodeEnum = SortNodeEnum.DB
private var ascendingSort: Boolean = true private var ascendingSort: Boolean = true
private var groupsBeforeSort: Boolean = true private var groupsBeforeSort: Boolean = true
@@ -122,19 +125,16 @@ class NodeAdapter
} }
private fun assignPreferences() { private fun assignPreferences() {
val textSizeDefault = java.lang.Float.parseFloat(context.getString(R.string.list_size_default)) this.prefTextSize = PreferencesUtil.getListTextSize(context) / 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.listSort = PreferencesUtil.getListSort(context) this.listSort = PreferencesUtil.getListSort(context)
this.ascendingSort = PreferencesUtil.getAscendingSort(context) this.ascendingSort = PreferencesUtil.getAscendingSort(context)
this.groupsBeforeSort = PreferencesUtil.getGroupsBeforeSort(context) this.groupsBeforeSort = PreferencesUtil.getGroupsBeforeSort(context)
this.recycleBinBottomSort = PreferencesUtil.getRecycleBinBottomSort(context) this.recycleBinBottomSort = PreferencesUtil.getRecycleBinBottomSort(context)
this.showUserNames = PreferencesUtil.showUsernamesListEntries(context) this.showUserNames = PreferencesUtil.showUsernamesListEntries(context)
this.showNumberEntries = PreferencesUtil.showNumberEntries(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) 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) { override fun onBindViewHolder(holder: NodeViewHolder, position: Int) {
val subNode = nodeSortedList.get(position) val subNode = nodeSortedList.get(position)
calculateTextSize(holder, getItemViewType(position))
// Assign image // Assign image
val iconColor = when (subNode.type) { val iconColor = when (subNode.type) {
Type.GROUP -> iconGroupColor Type.GROUP -> iconGroupColor
Type.ENTRY -> iconEntryColor 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 // Assign text
holder.text.text = subNode.title holder.text.apply {
text = subNode.title
textSize = infoTextSize
}
// Assign click // Assign click
holder.container.setOnClickListener { nodeClickCallback?.onNodeClick(subNode) } holder.container.setOnClickListener { nodeClickCallback?.onNodeClick(subNode) }
// Context menu // Context menu
@@ -230,36 +255,34 @@ class NodeAdapter
ContextMenuBuilder(menuInflater, subNode, readOnly, isASearchResult, nodeMenuListener)) ContextMenuBuilder(menuInflater, subNode, readOnly, isASearchResult, nodeMenuListener))
} }
// Add username // Add subText with username
holder.subText.text = "" holder.subText.apply {
holder.subText.visibility = View.GONE text = ""
if (subNode.type == Type.ENTRY) { visibility = View.GONE
val entry = subNode as EntryVersioned 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 val username = entry.username
if (showUserNames && username.isNotEmpty()) { if (showUserNames && username.isNotEmpty()) {
holder.subText.visibility = View.VISIBLE visibility = View.VISIBLE
holder.subText.text = username text = username
}
mDatabase.stopManageEntry(entry)
} }
textSize = subtextSize
mDatabase.stopManageEntry(entry)
} }
// Assign image and text size // Add number of entries in groups
// 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
if (subNode.type == Type.GROUP) { if (subNode.type == Type.GROUP) {
if (showNumberEntries) { if (showNumberEntries) {
holder.numberChildren?.apply { holder.numberChildren?.apply {
text = (subNode as GroupVersioned).getChildEntries(true).size.toString() text = (subNode as GroupVersioned).getChildEntries(true).size.toString()
textSize = infoTextSize textSize = numberChildrenTextSize
visibility = View.VISIBLE visibility = View.VISIBLE
} }
} else { } else {

View File

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

View File

@@ -19,7 +19,7 @@
*/ */
package com.kunzisoft.keepass.app 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.activities.stylish.Stylish
import com.kunzisoft.keepass.database.element.Database 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.os.Build
import android.service.autofill.Dataset import android.service.autofill.Dataset
import android.service.autofill.FillResponse import android.service.autofill.FillResponse
import android.support.annotation.RequiresApi import androidx.annotation.RequiresApi
import android.util.Log import android.util.Log
import android.view.autofill.AutofillManager import android.view.autofill.AutofillManager
import android.view.autofill.AutofillValue import android.view.autofill.AutofillValue

View File

@@ -26,8 +26,8 @@ import android.content.Intent
import android.content.IntentSender import android.content.IntentSender
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.support.annotation.RequiresApi import androidx.annotation.RequiresApi
import android.support.v7.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import com.kunzisoft.keepass.activities.FileDatabaseSelectActivity import com.kunzisoft.keepass.activities.FileDatabaseSelectActivity
import com.kunzisoft.keepass.activities.GroupActivity import com.kunzisoft.keepass.activities.GroupActivity
import com.kunzisoft.keepass.database.element.Database 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.Build
import android.os.CancellationSignal import android.os.CancellationSignal
import android.service.autofill.* import android.service.autofill.*
import android.support.annotation.RequiresApi import androidx.annotation.RequiresApi
import android.util.Log import android.util.Log
import android.widget.RemoteViews import android.widget.RemoteViews
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
package com.kunzisoft.keepass.database.action 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.R
import com.kunzisoft.keepass.tasks.ActionRunnable import com.kunzisoft.keepass.tasks.ActionRunnable
import com.kunzisoft.keepass.tasks.ProgressTaskUpdater import com.kunzisoft.keepass.tasks.ProgressTaskUpdater

View File

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

View File

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

View File

@@ -19,7 +19,7 @@
*/ */
package com.kunzisoft.keepass.database.action.node 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.Database
import com.kunzisoft.keepass.database.element.EntryVersioned import com.kunzisoft.keepass.database.element.EntryVersioned
import com.kunzisoft.keepass.database.element.GroupVersioned import com.kunzisoft.keepass.database.element.GroupVersioned

View File

@@ -19,7 +19,7 @@
*/ */
package com.kunzisoft.keepass.database.action.node 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.Database
import com.kunzisoft.keepass.database.element.GroupVersioned import com.kunzisoft.keepass.database.element.GroupVersioned

View File

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

View File

@@ -20,7 +20,7 @@
package com.kunzisoft.keepass.database.action.node package com.kunzisoft.keepass.database.action.node
import android.os.Bundle 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.Database
import com.kunzisoft.keepass.database.element.EntryVersioned import com.kunzisoft.keepass.database.element.EntryVersioned
import com.kunzisoft.keepass.database.element.GroupVersioned import com.kunzisoft.keepass.database.element.GroupVersioned

View File

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

View File

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

View File

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

View File

@@ -19,7 +19,7 @@
*/ */
package com.kunzisoft.keepass.database.action.node 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.Database
import com.kunzisoft.keepass.database.element.EntryVersioned import com.kunzisoft.keepass.database.element.EntryVersioned

View File

@@ -19,7 +19,7 @@
*/ */
package com.kunzisoft.keepass.database.action.node 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.Database
import com.kunzisoft.keepass.database.element.GroupVersioned import com.kunzisoft.keepass.database.element.GroupVersioned

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -27,8 +27,6 @@ import android.inputmethodservice.InputMethodService
import android.inputmethodservice.Keyboard import android.inputmethodservice.Keyboard
import android.inputmethodservice.KeyboardView import android.inputmethodservice.KeyboardView
import android.media.AudioManager import android.media.AudioManager
import android.support.v7.widget.LinearLayoutManager
import android.support.v7.widget.RecyclerView
import android.util.Log import android.util.Log
import android.view.* import android.view.*
import android.view.inputmethod.EditorInfo import android.view.inputmethod.EditorInfo
@@ -37,7 +35,7 @@ import android.widget.FrameLayout
import android.widget.PopupWindow import android.widget.PopupWindow
import android.widget.TextView import android.widget.TextView
import com.kunzisoft.keepass.R 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.EntryInfo
import com.kunzisoft.keepass.model.Field import com.kunzisoft.keepass.model.Field
import com.kunzisoft.keepass.notifications.KeyboardEntryNotificationService import com.kunzisoft.keepass.notifications.KeyboardEntryNotificationService
@@ -107,14 +105,14 @@ class MagikIME : InputMethodService(), KeyboardView.OnKeyboardActionListener {
popupCustomKeys?.inputMethodMode = PopupWindow.INPUT_METHOD_NEEDED popupCustomKeys?.inputMethodMode = PopupWindow.INPUT_METHOD_NEEDED
popupCustomKeys?.contentView = popupFieldsView 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 = FieldsAdapter(this)
fieldsAdapter?.onItemClickListener = object : FieldsAdapter.OnItemClickListener { fieldsAdapter?.onItemClickListener = object : FieldsAdapter.OnItemClickListener {
override fun onItemClick(item: Field) { override fun onItemClick(item: Field) {
currentInputConnection.commitText(item.protectedValue.toString(), 1) 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 recyclerView.adapter = fieldsAdapter
val closeView = popupFieldsView.findViewById<View>(R.id.keyboard_popup_close) val closeView = popupFieldsView.findViewById<View>(R.id.keyboard_popup_close)
@@ -194,7 +192,7 @@ class MagikIME : InputMethodService(), KeyboardView.OnKeyboardActionListener {
KEY_BACK_KEYBOARD -> try { KEY_BACK_KEYBOARD -> try {
val imeManager = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager val imeManager = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
if (window.window != null) if (window.window != null)
imeManager.switchToLastInputMethod(window.window!!.attributes.token) imeManager.switchToLastInputMethod(window.window!!.attributes.token) // TODO Deprecated
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "Unable to switch to the previous IME", e) Log.e(TAG, "Unable to switch to the previous IME", e)
val imeManager = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager 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.Context
import android.content.Intent import android.content.Intent
import android.content.IntentFilter import android.content.IntentFilter
import android.support.v7.preference.PreferenceManager import androidx.preference.PreferenceManager
import android.util.Log import android.util.Log
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.magikeyboard.MagikIME import com.kunzisoft.keepass.magikeyboard.MagikIME
@@ -103,7 +103,9 @@ class KeyboardEntryNotificationService : NotificationService() {
val keyboardTimeout = sharedPreferences.getString(getString(R.string.keyboard_entry_timeout_key), val keyboardTimeout = sharedPreferences.getString(getString(R.string.keyboard_entry_timeout_key),
getString(R.string.timeout_default)) getString(R.string.timeout_default))
notificationTimeoutMilliSecs = try { notificationTimeoutMilliSecs = try {
java.lang.Long.parseLong(keyboardTimeout) keyboardTimeout?.let {
java.lang.Long.parseLong(keyboardTimeout)
} ?: 0
} catch (e: NumberFormatException) { } catch (e: NumberFormatException) {
TimeoutHelper.DEFAULT_TIMEOUT TimeoutHelper.DEFAULT_TIMEOUT
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -28,6 +28,11 @@ import java.util.*
object PreferencesUtil { 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 { fun omitBackup(context: Context): Boolean {
val prefs = PreferenceManager.getDefaultSharedPreferences(context) val prefs = PreferenceManager.getDefaultSharedPreferences(context)
return prefs.getBoolean(context.getString(R.string.omitbackup_key), 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.content.Intent
import android.net.Uri import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.support.v4.app.Fragment import androidx.fragment.app.Fragment
import android.support.v7.widget.Toolbar import androidx.appcompat.widget.Toolbar
import android.view.MenuItem import android.view.MenuItem
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.dialogs.AssignMasterKeyDialogFragment 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.database.element.Database
import com.kunzisoft.keepass.timeout.TimeoutHelper import com.kunzisoft.keepass.timeout.TimeoutHelper
open class SettingsActivity
open class SettingsActivity : LockingActivity(), MainPreferenceFragment.Callback, AssignMasterKeyDialogFragment.AssignPasswordDialogListener { : LockingActivity(),
MainPreferenceFragment.Callback,
AssignMasterKeyDialogFragment.AssignPasswordDialogListener {
private var backupManager: BackupManager? = null private var backupManager: BackupManager? = null

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -20,16 +20,18 @@
package com.kunzisoft.keepass.settings.preferencedialogfragment package com.kunzisoft.keepass.settings.preferencedialogfragment
import android.os.Bundle import android.os.Bundle
import android.support.v7.preference.Preference import androidx.preference.Preference
import android.support.v7.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import android.support.v7.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import android.view.View import android.view.View
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.crypto.keyDerivation.KdfEngine import com.kunzisoft.keepass.crypto.keyDerivation.KdfEngine
import com.kunzisoft.keepass.settings.preferencedialogfragment.adapter.ListRadioItemAdapter import com.kunzisoft.keepass.settings.preferencedialogfragment.adapter.ListRadioItemAdapter
import com.kunzisoft.keepass.tasks.ActionRunnable 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 kdfEngineSelected: KdfEngine? = null
private var roundPreference: Preference? = null private var roundPreference: Preference? = null

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -17,7 +17,7 @@
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>. * 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.content.Intent;
import android.net.Uri; 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.Parcel;
import android.os.Parcelable; import android.os.Parcelable;
import android.support.annotation.NonNull; import androidx.annotation.NonNull;
import android.util.Log; import android.util.Log;
import com.kunzisoft.keepass.stream.ActionReadBytes; import com.kunzisoft.keepass.stream.ActionReadBytes;
@@ -145,6 +145,7 @@ public class MemUtil {
return compressedDataStream; return compressedDataStream;
} }
// TODO Remove
// For writing to a Parcel // For writing to a Parcel
public static <K extends Parcelable,V extends Parcelable> void writeParcelableMap( public static <K extends Parcelable,V extends Parcelable> void writeParcelableMap(

View File

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

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