mirror of
https://github.com/Kunzisoft/KeePassDX.git
synced 2025-12-04 15:49:33 +01:00
Merge branch 'develop' of github.com:Kunzisoft/KeePassDX into develop
This commit is contained in:
@@ -1,3 +1,7 @@
|
||||
KeepassDX (2.5.0.0beta23)
|
||||
* New, more secure database creation workflow
|
||||
* Add alias for history files (WARNING: history is erased)
|
||||
|
||||
KeepassDX (2.5.0.0beta22)
|
||||
* Rebuild code for actions
|
||||
* Add UUID as entry view
|
||||
|
||||
1
app/.gitignore
vendored
1
app/.gitignore
vendored
@@ -1 +1,2 @@
|
||||
.cxx
|
||||
.externalNativeBuild
|
||||
|
||||
@@ -4,15 +4,15 @@ apply plugin: 'kotlin-android-extensions'
|
||||
apply plugin: 'kotlin-kapt'
|
||||
|
||||
android {
|
||||
compileSdkVersion 27
|
||||
compileSdkVersion 28
|
||||
buildToolsVersion '28.0.3'
|
||||
|
||||
defaultConfig {
|
||||
applicationId "com.kunzisoft.keepass"
|
||||
minSdkVersion 14
|
||||
targetSdkVersion 27
|
||||
versionCode = 22
|
||||
versionName = "2.5.0.0beta22"
|
||||
targetSdkVersion 28
|
||||
versionCode = 23
|
||||
versionName = "2.5.0.0beta23"
|
||||
multiDexEnabled true
|
||||
|
||||
testApplicationId = "com.kunzisoft.keepass.tests"
|
||||
@@ -79,42 +79,34 @@ android {
|
||||
}
|
||||
}
|
||||
|
||||
def supportVersion = "27.1.1"
|
||||
def spongycastleVersion = "1.58.0.0"
|
||||
def permissionDispatcherVersion = "3.3.1"
|
||||
def room_version = "2.1.0"
|
||||
|
||||
dependencies {
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||
implementation "com.android.support:appcompat-v7:$supportVersion"
|
||||
implementation "com.android.support:design:$supportVersion"
|
||||
implementation "com.android.support:preference-v7:$supportVersion"
|
||||
implementation "com.android.support:preference-v14:$supportVersion"
|
||||
implementation "com.android.support:cardview-v7:$supportVersion"
|
||||
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
|
||||
implementation 'androidx.appcompat:appcompat:1.0.2'
|
||||
implementation 'androidx.preference:preference:1.0.0'
|
||||
implementation 'androidx.legacy:legacy-preference-v14:1.0.0'
|
||||
implementation 'androidx.cardview:cardview:1.0.0'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
||||
implementation 'com.google.android.material:material:1.0.0'
|
||||
|
||||
implementation "androidx.room:room-runtime:$room_version"
|
||||
kapt "androidx.room:room-compiler:$room_version"
|
||||
|
||||
implementation "com.madgag.spongycastle:core:$spongycastleVersion"
|
||||
implementation "com.madgag.spongycastle:prov:$spongycastleVersion"
|
||||
// Expandable view
|
||||
implementation 'net.cachapa.expandablelayout:expandablelayout:2.9.2'
|
||||
// Time
|
||||
implementation 'joda-time:joda-time:2.9.9'
|
||||
implementation 'org.sufficientlysecure:html-textview:3.5'
|
||||
implementation 'com.nononsenseapps:filepicker:4.1.0'
|
||||
// Education
|
||||
implementation 'com.getkeepsafe.taptargetview:taptargetview:1.12.0'
|
||||
// Permissions
|
||||
implementation("com.github.hotchemi:permissionsdispatcher:$permissionDispatcherVersion") {
|
||||
// if you don't use android.app.Fragment you can exclude support for them
|
||||
exclude module: "support-v13"
|
||||
}
|
||||
kapt "com.github.hotchemi:permissionsdispatcher-processor:$permissionDispatcherVersion"
|
||||
// Apache Commons Collections
|
||||
implementation 'commons-collections:commons-collections:3.2.1'
|
||||
implementation 'org.apache.commons:commons-io:1.3.2'
|
||||
// Base64
|
||||
implementation 'biz.source_code:base64coder:2010-12-19'
|
||||
// IO-Extras
|
||||
implementation 'com.github.davidmoten:io-extras:0.1'
|
||||
implementation 'com.google.code.gson:gson:2.8.4'
|
||||
implementation 'com.google.guava:guava:23.0-android'
|
||||
// Icon pack
|
||||
implementation project(path: ':icon-pack-classic')
|
||||
implementation project(path: ':icon-pack-material')
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="com.kunzisoft.keepass"
|
||||
android:installLocation="auto">
|
||||
<supports-screens
|
||||
@@ -8,6 +7,7 @@
|
||||
android:normalScreens="true"
|
||||
android:largeScreens="true"
|
||||
android:anyDensity="true" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.USE_FINGERPRINT"/>
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
||||
<uses-permission android:name="android.permission.VIBRATE"/>
|
||||
@@ -48,7 +48,7 @@
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<data android:scheme="file" />
|
||||
<data android:scheme="content" />
|
||||
<data android:mimeType="application/octet-stream" />
|
||||
<data android:mimeType="*/*" />
|
||||
<data android:host="*" />
|
||||
<data android:pathPattern=".*\\.kdb" />
|
||||
<data android:pathPattern=".*\\..*\\.kdb" />
|
||||
@@ -71,29 +71,28 @@
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.kdbx" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.kdbx" />
|
||||
</intent-filter>
|
||||
<intent-filter tools:ignore="AppLinkUrlError">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<category android:name="android.intent.category.BROWSABLE"/>
|
||||
<data android:mimeType="application/octet-stream"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<!-- Folder picker -->
|
||||
<provider
|
||||
android:name="android.support.v4.content.FileProvider"
|
||||
android:authorities="${applicationId}.provider"
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="true">
|
||||
<meta-data
|
||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||
android:resource="@xml/nnf_provider_paths" />
|
||||
</provider>
|
||||
<activity
|
||||
android:name=".activities.stylish.FilePickerStylishActivity"
|
||||
android:label="@string/app_name">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.GET_CONTENT" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<data android:scheme="file" />
|
||||
<data android:scheme="content" />
|
||||
<data android:mimeType="application/octet-stream" />
|
||||
<data android:mimeType="application/x-kdb" />
|
||||
<data android:mimeType="application/x-kdbx" />
|
||||
<data android:mimeType="application/x-keepass" />
|
||||
<data android:host="*" />
|
||||
<data android:pathPattern=".*" />
|
||||
<data android:pathPattern=".*\\.*" />
|
||||
<data android:pathPattern=".*\\..*\\.*" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\.*" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\.*" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\.*" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\.*" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\.*" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.*" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.*" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.*" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<!-- Main Activity -->
|
||||
|
||||
@@ -21,7 +21,7 @@ package com.kunzisoft.keepass.activities
|
||||
|
||||
import android.content.pm.PackageManager.NameNotFoundException
|
||||
import android.os.Bundle
|
||||
import android.support.v7.widget.Toolbar
|
||||
import androidx.appcompat.widget.Toolbar
|
||||
import android.util.Log
|
||||
import android.view.MenuItem
|
||||
import android.widget.TextView
|
||||
|
||||
@@ -19,15 +19,14 @@
|
||||
package com.kunzisoft.keepass.activities
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.content.Intent
|
||||
import android.graphics.Color
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.support.design.widget.CollapsingToolbarLayout
|
||||
import android.support.v7.app.AlertDialog
|
||||
import android.support.v7.widget.Toolbar
|
||||
import com.google.android.material.appbar.CollapsingToolbarLayout
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.widget.Toolbar
|
||||
import android.util.Log
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
@@ -45,12 +44,11 @@ import com.kunzisoft.keepass.icons.assignDatabaseIcon
|
||||
import com.kunzisoft.keepass.magikeyboard.MagikIME
|
||||
import com.kunzisoft.keepass.notifications.ClipboardEntryNotificationService
|
||||
import com.kunzisoft.keepass.settings.PreferencesUtil
|
||||
import com.kunzisoft.keepass.settings.PreferencesUtil.isFirstTimeAskAllowCopyPasswordAndProtectedFields
|
||||
import com.kunzisoft.keepass.settings.SettingsAutofillActivity
|
||||
import com.kunzisoft.keepass.timeout.ClipboardHelper
|
||||
import com.kunzisoft.keepass.timeout.TimeoutHelper
|
||||
import com.kunzisoft.keepass.utils.MenuUtil
|
||||
import com.kunzisoft.keepass.utils.Util
|
||||
import com.kunzisoft.keepass.utils.UriUtil
|
||||
import com.kunzisoft.keepass.view.EntryContentsView
|
||||
|
||||
class EntryActivity : LockingHideActivity() {
|
||||
@@ -164,9 +162,33 @@ class EntryActivity : LockingHideActivity() {
|
||||
getString(R.string.entry_user_name)))
|
||||
})
|
||||
|
||||
val allowCopyPassword = PreferencesUtil.allowCopyPasswordAndProtectedFields(this)
|
||||
entryContentsView?.assignPassword(entry.password, allowCopyPassword)
|
||||
if (allowCopyPassword) {
|
||||
val isFirstTimeAskAllowCopyPasswordAndProtectedFields =
|
||||
PreferencesUtil.isFirstTimeAskAllowCopyPasswordAndProtectedFields(this)
|
||||
val allowCopyPasswordAndProtectedFields =
|
||||
PreferencesUtil.allowCopyPasswordAndProtectedFields(this)
|
||||
|
||||
val showWarningClipboardDialogOnClickListener = View.OnClickListener {
|
||||
AlertDialog.Builder(this@EntryActivity)
|
||||
.setMessage(getString(R.string.allow_copy_password_warning) +
|
||||
"\n\n" +
|
||||
getString(R.string.clipboard_warning))
|
||||
.create().apply {
|
||||
setButton(AlertDialog.BUTTON_POSITIVE, getText(R.string.enable)) {dialog, _ ->
|
||||
PreferencesUtil.setAllowCopyPasswordAndProtectedFields(this@EntryActivity, true)
|
||||
dialog.dismiss()
|
||||
fillEntryDataInContentsView(entry)
|
||||
}
|
||||
setButton(AlertDialog.BUTTON_NEGATIVE, getText(R.string.disable)) { dialog, _ ->
|
||||
PreferencesUtil.setAllowCopyPasswordAndProtectedFields(this@EntryActivity, false)
|
||||
dialog.dismiss()
|
||||
fillEntryDataInContentsView(entry)
|
||||
}
|
||||
show()
|
||||
}
|
||||
}
|
||||
|
||||
entryContentsView?.assignPassword(entry.password, allowCopyPasswordAndProtectedFields)
|
||||
if (allowCopyPasswordAndProtectedFields) {
|
||||
entryContentsView?.assignPasswordCopyListener(View.OnClickListener {
|
||||
clipboardHelper?.timeoutCopyToClipboard(entry.password,
|
||||
getString(R.string.copy_field,
|
||||
@@ -174,27 +196,8 @@ class EntryActivity : LockingHideActivity() {
|
||||
})
|
||||
} else {
|
||||
// If dialog not already shown
|
||||
if (isFirstTimeAskAllowCopyPasswordAndProtectedFields(this)) {
|
||||
entryContentsView?.assignPasswordCopyListener(View.OnClickListener {
|
||||
val message = getString(R.string.allow_copy_password_warning) +
|
||||
"\n\n" +
|
||||
getString(R.string.clipboard_warning)
|
||||
val warningDialog = AlertDialog.Builder(this@EntryActivity)
|
||||
.setMessage(message).create()
|
||||
warningDialog.setButton(AlertDialog.BUTTON_POSITIVE, getText(android.R.string.ok)
|
||||
) { dialog, _ ->
|
||||
PreferencesUtil.setAllowCopyPasswordAndProtectedFields(this@EntryActivity, true)
|
||||
dialog.dismiss()
|
||||
fillEntryDataInContentsView(entry)
|
||||
}
|
||||
warningDialog.setButton(AlertDialog.BUTTON_NEGATIVE, getText(android.R.string.cancel)
|
||||
) { dialog, _ ->
|
||||
PreferencesUtil.setAllowCopyPasswordAndProtectedFields(this@EntryActivity, false)
|
||||
dialog.dismiss()
|
||||
fillEntryDataInContentsView(entry)
|
||||
}
|
||||
warningDialog.show()
|
||||
})
|
||||
if (isFirstTimeAskAllowCopyPasswordAndProtectedFields) {
|
||||
entryContentsView?.assignPasswordCopyListener(showWarningClipboardDialogOnClickListener)
|
||||
} else {
|
||||
entryContentsView?.assignPasswordCopyListener(null)
|
||||
}
|
||||
@@ -209,13 +212,23 @@ class EntryActivity : LockingHideActivity() {
|
||||
entryContentsView?.clearExtraFields()
|
||||
|
||||
entry.fields.doActionToAllCustomProtectedField { label, value ->
|
||||
val showAction = !value.isProtected || PreferencesUtil.allowCopyPasswordAndProtectedFields(this@EntryActivity)
|
||||
entryContentsView?.addExtraField(label, value, showAction, View.OnClickListener {
|
||||
|
||||
val allowCopyProtectedField = !value.isProtected || allowCopyPasswordAndProtectedFields
|
||||
if (allowCopyProtectedField) {
|
||||
entryContentsView?.addExtraField(label, value, allowCopyProtectedField, View.OnClickListener {
|
||||
clipboardHelper?.timeoutCopyToClipboard(
|
||||
value.toString(),
|
||||
getString(R.string.copy_field, label)
|
||||
)
|
||||
})
|
||||
} else {
|
||||
// If dialog not already shown
|
||||
if (isFirstTimeAskAllowCopyPasswordAndProtectedFields) {
|
||||
entryContentsView?.addExtraField(label, value, allowCopyProtectedField, showWarningClipboardDialogOnClickListener)
|
||||
} else {
|
||||
entryContentsView?.addExtraField(label, value, allowCopyProtectedField, null)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -306,7 +319,7 @@ class EntryActivity : LockingHideActivity() {
|
||||
|
||||
private fun performedNextEducation(entryActivityEducation: EntryActivityEducation,
|
||||
menu: Menu) {
|
||||
if (entryContentsView?.isUserNamePresent == true
|
||||
val entryCopyEducationPerformed = entryContentsView?.isUserNamePresent == true
|
||||
&& entryActivityEducation.checkAndPerformedEntryCopyEducation(
|
||||
findViewById(R.id.entry_user_name_action_image),
|
||||
{
|
||||
@@ -317,8 +330,11 @@ class EntryActivity : LockingHideActivity() {
|
||||
{
|
||||
// Launch autofill settings
|
||||
startActivity(Intent(this@EntryActivity, SettingsAutofillActivity::class.java))
|
||||
}))
|
||||
else if (toolbar?.findViewById<View>(R.id.menu_edit) != null && entryActivityEducation.checkAndPerformedEntryEditEducation(
|
||||
})
|
||||
|
||||
if (!entryCopyEducationPerformed) {
|
||||
// entryEditEducationPerformed
|
||||
toolbar?.findViewById<View>(R.id.menu_edit) != null && entryActivityEducation.checkAndPerformedEntryEditEducation(
|
||||
toolbar!!.findViewById(R.id.menu_edit),
|
||||
{
|
||||
onOptionsItemSelected(menu.findItem(R.id.menu_edit))
|
||||
@@ -327,13 +343,16 @@ class EntryActivity : LockingHideActivity() {
|
||||
// Open Keepass doc to create field references
|
||||
startActivity(Intent(Intent.ACTION_VIEW,
|
||||
Uri.parse(getString(R.string.field_references_url))))
|
||||
}))
|
||||
;
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
when (item.itemId) {
|
||||
R.id.menu_contribute -> return MenuUtil.onContributionItemSelected(this)
|
||||
R.id.menu_contribute -> {
|
||||
MenuUtil.onContributionItemSelected(this)
|
||||
return true
|
||||
}
|
||||
|
||||
R.id.menu_toggle_pass -> {
|
||||
mShowPassword = !mShowPassword
|
||||
@@ -357,11 +376,7 @@ class EntryActivity : LockingHideActivity() {
|
||||
url = "http://$url"
|
||||
}
|
||||
|
||||
try {
|
||||
Util.gotoUrl(this, url)
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
Toast.makeText(this, R.string.no_url_handler, Toast.LENGTH_LONG).show()
|
||||
}
|
||||
UriUtil.gotoUrl(this, url)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.support.v7.widget.Toolbar
|
||||
import androidx.appcompat.widget.Toolbar
|
||||
import android.util.Log
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
@@ -45,7 +45,9 @@ import com.kunzisoft.keepass.timeout.TimeoutHelper
|
||||
import com.kunzisoft.keepass.utils.MenuUtil
|
||||
import com.kunzisoft.keepass.view.EntryEditContentsView
|
||||
|
||||
class EntryEditActivity : LockingHideActivity(), IconPickerDialogFragment.IconPickerListener, GeneratePasswordDialogFragment.GeneratePasswordListener {
|
||||
class EntryEditActivity : LockingHideActivity(),
|
||||
IconPickerDialogFragment.IconPickerListener,
|
||||
GeneratePasswordDialogFragment.GeneratePasswordListener {
|
||||
|
||||
private var mDatabase: Database? = null
|
||||
|
||||
@@ -154,33 +156,6 @@ class EntryEditActivity : LockingHideActivity(), IconPickerDialogFragment.IconPi
|
||||
|
||||
// Verify the education views
|
||||
entryEditActivityEducation = EntryEditActivityEducation(this)
|
||||
entryEditActivityEducation?.let {
|
||||
Handler().post { performedNextEducation(it) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun performedNextEducation(entryEditActivityEducation: EntryEditActivityEducation) {
|
||||
val passwordView = entryEditContentsView?.generatePasswordView
|
||||
val addNewFieldView = entryEditContentsView?.addNewFieldView
|
||||
|
||||
if (passwordView != null
|
||||
&& entryEditActivityEducation.checkAndPerformedGeneratePasswordEducation(
|
||||
passwordView,
|
||||
{
|
||||
openPasswordGenerator()
|
||||
},
|
||||
{
|
||||
performedNextEducation(entryEditActivityEducation)
|
||||
}
|
||||
))
|
||||
else if (mNewEntry != null && mNewEntry!!.allowExtraFields() && !mNewEntry!!.containsCustomFields()
|
||||
&& addNewFieldView != null && addNewFieldView.visibility == View.VISIBLE
|
||||
&& entryEditActivityEducation.checkAndPerformedEntryNewFieldEducation(
|
||||
addNewFieldView,
|
||||
{
|
||||
addNewCustomField()
|
||||
}))
|
||||
;
|
||||
}
|
||||
|
||||
private fun populateViewsWithEntry(newEntry: EntryVersioned) {
|
||||
@@ -309,9 +284,39 @@ class EntryEditActivity : LockingHideActivity(), IconPickerDialogFragment.IconPi
|
||||
inflater.inflate(R.menu.database_lock, menu)
|
||||
MenuUtil.contributionMenuInflater(inflater, menu)
|
||||
|
||||
entryEditActivityEducation?.let {
|
||||
Handler().post { performedNextEducation(it) }
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
private fun performedNextEducation(entryEditActivityEducation: EntryEditActivityEducation) {
|
||||
val passwordView = entryEditContentsView?.generatePasswordView
|
||||
val addNewFieldView = entryEditContentsView?.addNewFieldView
|
||||
|
||||
val generatePasswordEducationPerformed = passwordView != null
|
||||
&& entryEditActivityEducation.checkAndPerformedGeneratePasswordEducation(
|
||||
passwordView,
|
||||
{
|
||||
openPasswordGenerator()
|
||||
},
|
||||
{
|
||||
performedNextEducation(entryEditActivityEducation)
|
||||
}
|
||||
)
|
||||
if (!generatePasswordEducationPerformed) {
|
||||
// entryNewFieldEducationPerformed
|
||||
mNewEntry != null && mNewEntry!!.allowExtraFields() && !mNewEntry!!.containsCustomFields()
|
||||
&& addNewFieldView != null && addNewFieldView.visibility == View.VISIBLE
|
||||
&& entryEditActivityEducation.checkAndPerformedEntryNewFieldEducation(
|
||||
addNewFieldView,
|
||||
{
|
||||
addNewCustomField()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
when (item.itemId) {
|
||||
R.id.menu_lock -> {
|
||||
@@ -319,7 +324,10 @@ class EntryEditActivity : LockingHideActivity(), IconPickerDialogFragment.IconPi
|
||||
return true
|
||||
}
|
||||
|
||||
R.id.menu_contribute -> return MenuUtil.onContributionItemSelected(this)
|
||||
R.id.menu_contribute -> {
|
||||
MenuUtil.onContributionItemSelected(this)
|
||||
return true
|
||||
}
|
||||
|
||||
android.R.id.home -> finish()
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
*/
|
||||
package com.kunzisoft.keepass.activities
|
||||
|
||||
import android.Manifest
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.app.assist.AssistStructure
|
||||
import android.content.Intent
|
||||
@@ -29,56 +29,43 @@ import android.os.Bundle
|
||||
import android.os.Environment
|
||||
import android.os.Handler
|
||||
import android.preference.PreferenceManager
|
||||
import android.support.annotation.RequiresApi
|
||||
import android.support.v7.app.AlertDialog
|
||||
import android.support.v7.widget.LinearLayoutManager
|
||||
import android.support.v7.widget.RecyclerView
|
||||
import android.support.v7.widget.Toolbar
|
||||
import androidx.annotation.RequiresApi
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.recyclerview.widget.SimpleItemAnimator
|
||||
import androidx.appcompat.widget.Toolbar
|
||||
import android.util.Log
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.widget.EditText
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.activities.dialogs.AssignMasterKeyDialogFragment
|
||||
import com.kunzisoft.keepass.activities.dialogs.CreateFileDialogFragment
|
||||
import com.kunzisoft.keepass.activities.dialogs.FileInformationDialogFragment
|
||||
import com.kunzisoft.keepass.activities.dialogs.BrowserDialogFragment
|
||||
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
|
||||
import com.kunzisoft.keepass.activities.helpers.KeyFileHelper
|
||||
import com.kunzisoft.keepass.activities.helpers.OpenFileHelper
|
||||
import com.kunzisoft.keepass.activities.stylish.StylishActivity
|
||||
import com.kunzisoft.keepass.adapters.FileDatabaseHistoryAdapter
|
||||
import com.kunzisoft.keepass.app.database.FileDatabaseHistory
|
||||
import com.kunzisoft.keepass.autofill.AutofillHelper
|
||||
import com.kunzisoft.keepass.database.action.CreateDatabaseRunnable
|
||||
import com.kunzisoft.keepass.database.action.ProgressDialogThread
|
||||
import com.kunzisoft.keepass.database.element.Database
|
||||
import com.kunzisoft.keepass.education.FileDatabaseSelectActivityEducation
|
||||
import com.kunzisoft.keepass.fileselect.DeleteFileHistoryAsyncTask
|
||||
import com.kunzisoft.keepass.fileselect.FileDatabaseModel
|
||||
import com.kunzisoft.keepass.fileselect.OpenFileHistoryAsyncTask
|
||||
import com.kunzisoft.keepass.fileselect.database.FileDatabaseHistory
|
||||
import com.kunzisoft.keepass.magikeyboard.KeyboardHelper
|
||||
import com.kunzisoft.keepass.settings.PreferencesUtil
|
||||
import com.kunzisoft.keepass.tasks.ActionRunnable
|
||||
import com.kunzisoft.keepass.utils.MenuUtil
|
||||
import com.kunzisoft.keepass.utils.UriUtil
|
||||
import com.kunzisoft.keepass.view.asError
|
||||
import kotlinx.android.synthetic.main.activity_file_selection.*
|
||||
import net.cachapa.expandablelayout.ExpandableLayout
|
||||
import permissions.dispatcher.*
|
||||
import java.io.File
|
||||
import java.io.FileNotFoundException
|
||||
import java.io.IOException
|
||||
import java.lang.ref.WeakReference
|
||||
import java.net.URLDecoder
|
||||
import java.util.*
|
||||
|
||||
@RuntimePermissions
|
||||
class FileDatabaseSelectActivity : StylishActivity(),
|
||||
CreateFileDialogFragment.DefinePathDialogListener,
|
||||
AssignMasterKeyDialogFragment.AssignPasswordDialogListener,
|
||||
FileDatabaseHistoryAdapter.FileItemOpenListener,
|
||||
FileDatabaseHistoryAdapter.FileSelectClearListener,
|
||||
FileDatabaseHistoryAdapter.FileInformationShowListener {
|
||||
AssignMasterKeyDialogFragment.AssignPasswordDialogListener {
|
||||
|
||||
// Views
|
||||
private var fileListContainer: View? = null
|
||||
@@ -96,14 +83,14 @@ class FileDatabaseSelectActivity : StylishActivity(),
|
||||
|
||||
private var mDatabaseFileUri: Uri? = null
|
||||
|
||||
private var mKeyFileHelper: KeyFileHelper? = null
|
||||
private var mOpenFileHelper: OpenFileHelper? = null
|
||||
|
||||
private var mDefaultPath: String? = null
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
mFileDatabaseHistory = FileDatabaseHistory.getInstance(WeakReference(applicationContext))
|
||||
mFileDatabaseHistory = FileDatabaseHistory.getInstance(applicationContext)
|
||||
|
||||
setContentView(R.layout.activity_file_selection)
|
||||
fileListContainer = findViewById(R.id.container_file_list)
|
||||
@@ -131,10 +118,6 @@ class FileDatabaseSelectActivity : StylishActivity(),
|
||||
fileSelectExpandableLayout?.expand()
|
||||
}
|
||||
|
||||
// History list
|
||||
val databaseFileListView = findViewById<RecyclerView>(R.id.file_list)
|
||||
databaseFileListView.layoutManager = LinearLayoutManager(this)
|
||||
|
||||
// Open button
|
||||
openButtonView = findViewById(R.id.open_database)
|
||||
openButtonView?.setOnClickListener { _ ->
|
||||
@@ -148,21 +131,56 @@ class FileDatabaseSelectActivity : StylishActivity(),
|
||||
|
||||
// Create button
|
||||
createButtonView = findViewById(R.id.create_database)
|
||||
createButtonView?.setOnClickListener { openCreateFileDialogFragmentWithPermissionCheck() }
|
||||
if (Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
|
||||
addCategory(Intent.CATEGORY_OPENABLE)
|
||||
type = "application/x-keepass"
|
||||
}.resolveActivity(packageManager) == null) {
|
||||
// No Activity found that can handle this intent.
|
||||
createButtonView?.visibility = View.GONE
|
||||
}
|
||||
else{
|
||||
// There is an activity which can handle this intent.
|
||||
createButtonView?.visibility = View.VISIBLE
|
||||
}
|
||||
|
||||
mKeyFileHelper = KeyFileHelper(this)
|
||||
createButtonView?.setOnClickListener { createNewFile() }
|
||||
|
||||
mOpenFileHelper = OpenFileHelper(this)
|
||||
browseButtonView = findViewById(R.id.browse_button)
|
||||
browseButtonView?.setOnClickListener(mKeyFileHelper!!.getOpenFileOnClickViewListener {
|
||||
browseButtonView?.setOnClickListener(mOpenFileHelper!!.getOpenFileOnClickViewListener {
|
||||
Uri.parse("file://" + openFileNameView!!.text.toString())
|
||||
})
|
||||
|
||||
|
||||
// History list
|
||||
val fileDatabaseHistoryRecyclerView = findViewById<RecyclerView>(R.id.file_list)
|
||||
fileDatabaseHistoryRecyclerView.layoutManager = LinearLayoutManager(this, RecyclerView.VERTICAL, false)
|
||||
// Removes blinks
|
||||
(fileDatabaseHistoryRecyclerView.itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false
|
||||
// Construct adapter with listeners
|
||||
mAdapterDatabaseHistory = FileDatabaseHistoryAdapter(this@FileDatabaseSelectActivity,
|
||||
mFileDatabaseHistory?.databaseUriList ?: ArrayList())
|
||||
mAdapterDatabaseHistory?.setOnItemClickListener(this)
|
||||
mAdapterDatabaseHistory?.setFileSelectClearListener(this)
|
||||
mAdapterDatabaseHistory?.setFileInformationShowListener(this)
|
||||
databaseFileListView.adapter = mAdapterDatabaseHistory
|
||||
mAdapterDatabaseHistory = FileDatabaseHistoryAdapter(this)
|
||||
mAdapterDatabaseHistory?.setOnFileDatabaseHistoryOpenListener { fileDatabaseHistoryEntityToOpen ->
|
||||
launchPasswordActivity(
|
||||
fileDatabaseHistoryEntityToOpen.databaseUri,
|
||||
fileDatabaseHistoryEntityToOpen.keyFileUri)
|
||||
updateFileListVisibility()
|
||||
}
|
||||
mAdapterDatabaseHistory?.setOnFileDatabaseHistoryDeleteListener { fileDatabaseHistoryToDelete ->
|
||||
// Remove from app database
|
||||
mFileDatabaseHistory?.deleteFileDatabaseHistory(fileDatabaseHistoryToDelete) { fileHistoryDeleted ->
|
||||
// Remove from adapter
|
||||
fileHistoryDeleted?.let { databaseFileHistoryDeleted ->
|
||||
mAdapterDatabaseHistory?.deleteDatabaseFileHistory(databaseFileHistoryDeleted)
|
||||
mAdapterDatabaseHistory?.notifyDataSetChanged()
|
||||
updateFileListVisibility()
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
mAdapterDatabaseHistory?.setOnSaveAliasListener { fileDatabaseHistoryWithNewAlias ->
|
||||
mFileDatabaseHistory?.addOrUpdateFileDatabaseHistory(fileDatabaseHistoryWithNewAlias)
|
||||
}
|
||||
fileDatabaseHistoryRecyclerView.adapter = mAdapterDatabaseHistory
|
||||
|
||||
// Load default database if not an orientation change
|
||||
if (!(savedInstanceState != null
|
||||
@@ -171,68 +189,48 @@ class FileDatabaseSelectActivity : StylishActivity(),
|
||||
val prefs = PreferenceManager.getDefaultSharedPreferences(this)
|
||||
val fileName = prefs.getString(PasswordActivity.KEY_DEFAULT_FILENAME, "")
|
||||
|
||||
if (fileName != null && fileName.isNotEmpty()) {
|
||||
val dbUri = UriUtil.parseUriFile(fileName)
|
||||
var scheme: String? = null
|
||||
if (dbUri != null)
|
||||
scheme = dbUri.scheme
|
||||
|
||||
if (scheme != null && scheme.isNotEmpty() && scheme.equals("file", ignoreCase = true)) {
|
||||
val path = dbUri!!.path
|
||||
val db = File(path!!)
|
||||
|
||||
if (db.exists()) {
|
||||
try {
|
||||
UriUtil.verifyFilePath(fileName) { path ->
|
||||
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
|
||||
if (createButtonView != null
|
||||
&& mFileDatabaseHistory != null
|
||||
&& !mFileDatabaseHistory!!.hasRecentFiles() && fileDatabaseSelectActivityEducation.checkAndPerformedCreateDatabaseEducation(
|
||||
createButtonView!!,
|
||||
{
|
||||
openCreateFileDialogFragmentWithPermissionCheck()
|
||||
/**
|
||||
* Create a new file by calling the content provider
|
||||
*/
|
||||
@SuppressLint("InlinedApi")
|
||||
private fun createNewFile() {
|
||||
try {
|
||||
startActivityForResult(Intent(
|
||||
Intent.ACTION_CREATE_DOCUMENT).apply {
|
||||
addCategory(Intent.CATEGORY_OPENABLE)
|
||||
type = "application/x-keepass"
|
||||
putExtra(Intent.EXTRA_TITLE, getString(R.string.database_file_name_default) +
|
||||
getString(R.string.database_file_extension_default))
|
||||
},
|
||||
{
|
||||
// But if the user cancel, it can also select a database
|
||||
performedNextEducation(fileDatabaseSelectActivityEducation)
|
||||
}))
|
||||
else if (browseButtonView != null
|
||||
&& fileDatabaseSelectActivityEducation.checkAndPerformedSelectDatabaseEducation(
|
||||
browseButtonView!!,
|
||||
{tapTargetView ->
|
||||
tapTargetView?.let {
|
||||
mKeyFileHelper?.openFileOnClickViewListener?.onClick(it)
|
||||
CREATE_FILE_REQUEST_CODE)
|
||||
} catch (e: Exception) {
|
||||
BrowserDialogFragment().show(supportFragmentManager, "browserDialog")
|
||||
}
|
||||
},
|
||||
{
|
||||
fileSelectExpandableButtonView?.let {
|
||||
fileDatabaseSelectActivityEducation
|
||||
.checkAndPerformedOpenLinkDatabaseEducation(it)
|
||||
}
|
||||
}
|
||||
))
|
||||
;
|
||||
}
|
||||
|
||||
private fun fileNoFoundAction(e: FileNotFoundException) {
|
||||
val error = getString(R.string.file_not_found_content)
|
||||
Toast.makeText(this@FileDatabaseSelectActivity,
|
||||
error, Toast.LENGTH_LONG).show()
|
||||
Snackbar.make(activity_file_selection_coordinator_layout, error, Snackbar.LENGTH_LONG).asError().show()
|
||||
Log.e(TAG, error, e)
|
||||
}
|
||||
|
||||
private fun launchPasswordActivity(fileName: String, keyFile: String) {
|
||||
private fun launchPasswordActivity(fileName: String, keyFile: String?) {
|
||||
EntrySelectionHelper.doEntrySelectionAction(intent,
|
||||
{
|
||||
try {
|
||||
@@ -295,26 +293,23 @@ class FileDatabaseSelectActivity : StylishActivity(),
|
||||
super.onResume()
|
||||
|
||||
updateExternalStorageWarning()
|
||||
|
||||
// Construct adapter with listeners
|
||||
mFileDatabaseHistory?.getAllFileDatabaseHistories { databaseFileHistoryList ->
|
||||
databaseFileHistoryList?.let {
|
||||
mAdapterDatabaseHistory?.addDatabaseFileHistoryList(it)
|
||||
updateFileListVisibility()
|
||||
mAdapterDatabaseHistory!!.notifyDataSetChanged()
|
||||
mAdapterDatabaseHistory?.notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
super.onSaveInstanceState(outState)
|
||||
// only to keep the current activity
|
||||
outState.putBoolean(EXTRA_STAY, true)
|
||||
}
|
||||
|
||||
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||
// NOTE: delegate the permission handling to generated method
|
||||
onRequestPermissionsResult(requestCode, grantResults)
|
||||
}
|
||||
|
||||
@NeedsPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||
fun openCreateFileDialogFragment() {
|
||||
val createFileDialogFragment = CreateFileDialogFragment()
|
||||
createFileDialogFragment.show(supportFragmentManager, "createFileDialogFragment")
|
||||
// to retrieve the URI of a created database after an orientation change
|
||||
outState.putParcelable(EXTRA_DATABASE_URI, mDatabaseFileUri)
|
||||
}
|
||||
|
||||
private fun updateFileListVisibility() {
|
||||
@@ -324,82 +319,6 @@ class FileDatabaseSelectActivity : StylishActivity(),
|
||||
fileListContainer?.visibility = View.VISIBLE
|
||||
}
|
||||
|
||||
/**
|
||||
* Create file for database
|
||||
* @return If not created, return false
|
||||
*/
|
||||
private fun createDatabaseFile(path: Uri): Boolean {
|
||||
|
||||
val pathString = URLDecoder.decode(path.path, "UTF-8")
|
||||
// Make sure file name exists
|
||||
if (pathString.isEmpty()) {
|
||||
Log.e(TAG, getString(R.string.error_filename_required))
|
||||
Toast.makeText(this@FileDatabaseSelectActivity,
|
||||
R.string.error_filename_required,
|
||||
Toast.LENGTH_LONG).show()
|
||||
return false
|
||||
}
|
||||
|
||||
// Try to create the file
|
||||
val file = File(pathString)
|
||||
try {
|
||||
if (file.exists()) {
|
||||
Log.e(TAG, getString(R.string.error_database_exists) + " " + file)
|
||||
Toast.makeText(this@FileDatabaseSelectActivity,
|
||||
R.string.error_database_exists,
|
||||
Toast.LENGTH_LONG).show()
|
||||
return false
|
||||
}
|
||||
val parent = file.parentFile
|
||||
|
||||
if (parent == null || parent.exists() && !parent.isDirectory) {
|
||||
Log.e(TAG, getString(R.string.error_invalid_path) + " " + file)
|
||||
Toast.makeText(this@FileDatabaseSelectActivity,
|
||||
R.string.error_invalid_path,
|
||||
Toast.LENGTH_LONG).show()
|
||||
return false
|
||||
}
|
||||
|
||||
if (!parent.exists()) {
|
||||
// Create parent directory
|
||||
if (!parent.mkdirs()) {
|
||||
Log.e(TAG, getString(R.string.error_could_not_create_parent) + " " + parent)
|
||||
Toast.makeText(this@FileDatabaseSelectActivity,
|
||||
R.string.error_could_not_create_parent,
|
||||
Toast.LENGTH_LONG).show()
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return file.createNewFile()
|
||||
} catch (e: IOException) {
|
||||
Log.e(TAG, getString(R.string.error_could_not_create_parent) + " " + e.localizedMessage)
|
||||
e.printStackTrace()
|
||||
Toast.makeText(
|
||||
this@FileDatabaseSelectActivity,
|
||||
getText(R.string.error_file_not_create).toString() + " "
|
||||
+ e.localizedMessage,
|
||||
Toast.LENGTH_LONG).show()
|
||||
return false
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override fun onDefinePathDialogPositiveClick(pathFile: Uri?): Boolean {
|
||||
mDatabaseFileUri = pathFile
|
||||
if (pathFile == null)
|
||||
return false
|
||||
return if (createDatabaseFile(pathFile)) {
|
||||
AssignMasterKeyDialogFragment().show(supportFragmentManager, "passwordDialog")
|
||||
true
|
||||
} else
|
||||
false
|
||||
}
|
||||
|
||||
override fun onDefinePathDialogNegativeClick(pathFile: Uri?): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onAssignKeyDialogPositiveClick(
|
||||
masterPasswordChecked: Boolean, masterPassword: String?,
|
||||
keyFileChecked: Boolean, keyFile: Uri?) {
|
||||
@@ -418,22 +337,21 @@ class FileDatabaseSelectActivity : StylishActivity(),
|
||||
keyFileChecked,
|
||||
keyFile,
|
||||
true, // TODO get readonly
|
||||
LaunchGroupActivityFinish(databaseUri)
|
||||
LaunchGroupActivityFinish(databaseUri, keyFile)
|
||||
)
|
||||
},
|
||||
R.string.progress_create)
|
||||
.start()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
val error = "Unable to create database with this password and key file"
|
||||
Toast.makeText(this, error, Toast.LENGTH_LONG).show()
|
||||
Log.e(TAG, error + " " + e.message)
|
||||
// TODO remove
|
||||
e.printStackTrace()
|
||||
val error = getString(R.string.error_create_database_file)
|
||||
Snackbar.make(activity_file_selection_coordinator_layout, error, Snackbar.LENGTH_LONG).asError().show()
|
||||
Log.e(TAG, error, e)
|
||||
}
|
||||
}
|
||||
|
||||
private inner class LaunchGroupActivityFinish internal constructor(private val fileURI: Uri) : ActionRunnable() {
|
||||
private inner class LaunchGroupActivityFinish(private val databaseFileUri: Uri,
|
||||
private val keyFileUri: Uri?) : ActionRunnable() {
|
||||
|
||||
override fun run() {
|
||||
finishRun(true, null)
|
||||
@@ -443,7 +361,7 @@ class FileDatabaseSelectActivity : StylishActivity(),
|
||||
runOnUiThread {
|
||||
if (result.isSuccess) {
|
||||
// Add database to recent files
|
||||
mFileDatabaseHistory?.addDatabaseUri(fileURI)
|
||||
mFileDatabaseHistory?.addOrUpdateDatabaseUri(databaseFileUri, keyFileUri)
|
||||
mAdapterDatabaseHistory?.notifyDataSetChanged()
|
||||
updateFileListVisibility()
|
||||
GroupActivity.launch(this@FileDatabaseSelectActivity)
|
||||
@@ -460,29 +378,6 @@ class FileDatabaseSelectActivity : StylishActivity(),
|
||||
|
||||
}
|
||||
|
||||
override fun onFileItemOpenListener(itemPosition: Int) {
|
||||
OpenFileHistoryAsyncTask({ fileName, keyFile ->
|
||||
if (fileName != null && keyFile != null)
|
||||
launchPasswordActivity(fileName, keyFile)
|
||||
updateFileListVisibility()
|
||||
}, mFileDatabaseHistory).execute(itemPosition)
|
||||
}
|
||||
|
||||
override fun onClickFileInformation(fileDatabaseModel: FileDatabaseModel) {
|
||||
FileInformationDialogFragment.newInstance(fileDatabaseModel).show(supportFragmentManager, "fileInformation")
|
||||
}
|
||||
|
||||
override fun onFileSelectClearListener(fileDatabaseModel: FileDatabaseModel): Boolean {
|
||||
DeleteFileHistoryAsyncTask({
|
||||
fileDatabaseModel.fileUri?.let {
|
||||
mFileDatabaseHistory?.deleteDatabaseUri(it)
|
||||
}
|
||||
mAdapterDatabaseHistory?.notifyDataSetChanged()
|
||||
updateFileListVisibility()
|
||||
}, mFileDatabaseHistory, mAdapterDatabaseHistory).execute(fileDatabaseModel)
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
super.onActivityResult(requestCode, resultCode, data)
|
||||
|
||||
@@ -490,7 +385,7 @@ class FileDatabaseSelectActivity : StylishActivity(),
|
||||
AutofillHelper.onActivityResultSetResultAndFinish(this, requestCode, resultCode, data)
|
||||
}
|
||||
|
||||
mKeyFileHelper?.onActivityResultCallback(requestCode, resultCode, data
|
||||
mOpenFileHelper?.onActivityResultCallback(requestCode, resultCode, data
|
||||
) { uri ->
|
||||
if (uri != null) {
|
||||
if (PreferencesUtil.autoOpenSelectedFile(this@FileDatabaseSelectActivity)) {
|
||||
@@ -501,33 +396,62 @@ class FileDatabaseSelectActivity : StylishActivity(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OnShowRationale(Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||
internal fun showRationaleForExternalStorage(request: PermissionRequest) {
|
||||
AlertDialog.Builder(this)
|
||||
.setMessage(R.string.permission_external_storage_rationale_write_database)
|
||||
.setPositiveButton(R.string.allow) { _, _ -> request.proceed() }
|
||||
.setNegativeButton(R.string.cancel) { _, _ -> request.cancel() }
|
||||
.show()
|
||||
// Retrieve the created URI from the file manager
|
||||
if (requestCode == CREATE_FILE_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
|
||||
mDatabaseFileUri = data?.data
|
||||
if (mDatabaseFileUri != null) {
|
||||
AssignMasterKeyDialogFragment().show(supportFragmentManager, "passwordDialog")
|
||||
}
|
||||
|
||||
@OnPermissionDenied(Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||
internal fun showDeniedForExternalStorage() {
|
||||
Toast.makeText(this, R.string.permission_external_storage_denied, Toast.LENGTH_SHORT).show()
|
||||
// else {
|
||||
// TODO Show error
|
||||
// }
|
||||
}
|
||||
|
||||
@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 {
|
||||
super.onCreateOptionsMenu(menu)
|
||||
MenuUtil.defaultMenuInflater(menuInflater, menu)
|
||||
|
||||
Handler().post { performedNextEducation(FileDatabaseSelectActivityEducation(this)) }
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
private fun performedNextEducation(fileDatabaseSelectActivityEducation: FileDatabaseSelectActivityEducation) {
|
||||
// If no recent files
|
||||
val createDatabaseEducationPerformed = createButtonView != null && createButtonView!!.visibility == View.VISIBLE
|
||||
&& mAdapterDatabaseHistory != null
|
||||
&& mAdapterDatabaseHistory!!.itemCount > 0
|
||||
&& fileDatabaseSelectActivityEducation.checkAndPerformedCreateDatabaseEducation(
|
||||
createButtonView!!,
|
||||
{
|
||||
createNewFile()
|
||||
},
|
||||
{
|
||||
// But if the user cancel, it can also select a database
|
||||
performedNextEducation(fileDatabaseSelectActivityEducation)
|
||||
})
|
||||
if (!createDatabaseEducationPerformed) {
|
||||
// selectDatabaseEducationPerformed
|
||||
browseButtonView != null
|
||||
&& fileDatabaseSelectActivityEducation.checkAndPerformedSelectDatabaseEducation(
|
||||
browseButtonView!!,
|
||||
{tapTargetView ->
|
||||
tapTargetView?.let {
|
||||
mOpenFileHelper?.openFileOnClickViewListener?.onClick(it)
|
||||
}
|
||||
},
|
||||
{
|
||||
fileSelectExpandableButtonView?.let {
|
||||
fileDatabaseSelectActivityEducation
|
||||
.checkAndPerformedOpenLinkDatabaseEducation(it)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
return MenuUtil.onDefaultMenuOptionsItemSelected(this, item) && super.onOptionsItemSelected(item)
|
||||
}
|
||||
@@ -536,6 +460,9 @@ class FileDatabaseSelectActivity : StylishActivity(),
|
||||
|
||||
private const val TAG = "FileDbSelectActivity"
|
||||
private const val EXTRA_STAY = "EXTRA_STAY"
|
||||
private const val EXTRA_DATABASE_URI = "EXTRA_DATABASE_URI"
|
||||
|
||||
private const val CREATE_FILE_REQUEST_CODE = 3853
|
||||
|
||||
/*
|
||||
* -------------------------
|
||||
|
||||
@@ -29,11 +29,10 @@ import android.graphics.Color
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.preference.PreferenceManager
|
||||
import android.support.annotation.RequiresApi
|
||||
import android.support.v4.app.FragmentManager
|
||||
import android.support.v7.widget.SearchView
|
||||
import android.support.v7.widget.Toolbar
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import androidx.appcompat.widget.SearchView
|
||||
import androidx.appcompat.widget.Toolbar
|
||||
import android.util.Log
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
@@ -183,7 +182,7 @@ class GroupActivity : LockingActivity(),
|
||||
// Attach fragment to content view
|
||||
supportFragmentManager.beginTransaction().replace(
|
||||
R.id.nodes_list_fragment_container,
|
||||
mListNodesFragment,
|
||||
mListNodesFragment!!,
|
||||
fragmentTag)
|
||||
.commit()
|
||||
|
||||
@@ -628,8 +627,9 @@ class GroupActivity : LockingActivity(),
|
||||
|
||||
private fun performedNextEducation(groupActivityEducation: GroupActivityEducation,
|
||||
menu: Menu) {
|
||||
|
||||
// If no node, show education to add new one
|
||||
if (mListNodesFragment != null
|
||||
val addNodeButtonEducationPerformed = mListNodesFragment != null
|
||||
&& mListNodesFragment!!.isEmpty
|
||||
&& addNodeButtonView?.addButtonView != null
|
||||
&& addNodeButtonView!!.isEnable
|
||||
@@ -641,8 +641,10 @@ class GroupActivity : LockingActivity(),
|
||||
{
|
||||
performedNextEducation(groupActivityEducation, menu)
|
||||
}
|
||||
))
|
||||
else if (toolbar != null
|
||||
)
|
||||
if (!addNodeButtonEducationPerformed) {
|
||||
|
||||
val searchMenuEducationPerformed = toolbar != null
|
||||
&& toolbar!!.findViewById<View>(R.id.menu_search) != null
|
||||
&& groupActivityEducation.checkAndPerformedSearchMenuEducation(
|
||||
toolbar!!.findViewById(R.id.menu_search),
|
||||
@@ -651,8 +653,11 @@ class GroupActivity : LockingActivity(),
|
||||
},
|
||||
{
|
||||
performedNextEducation(groupActivityEducation, menu)
|
||||
}))
|
||||
else if (toolbar != null
|
||||
})
|
||||
|
||||
if (!searchMenuEducationPerformed) {
|
||||
|
||||
val sortMenuEducationPerformed = toolbar != null
|
||||
&& toolbar!!.findViewById<View>(R.id.menu_sort) != null
|
||||
&& groupActivityEducation.checkAndPerformedSortMenuEducation(
|
||||
toolbar!!.findViewById(R.id.menu_sort),
|
||||
@@ -661,8 +666,11 @@ class GroupActivity : LockingActivity(),
|
||||
},
|
||||
{
|
||||
performedNextEducation(groupActivityEducation, menu)
|
||||
}))
|
||||
else if (toolbar != null
|
||||
})
|
||||
|
||||
if (!sortMenuEducationPerformed) {
|
||||
// lockMenuEducationPerformed
|
||||
toolbar != null
|
||||
&& toolbar!!.findViewById<View>(R.id.menu_lock) != null
|
||||
&& groupActivityEducation.checkAndPerformedLockMenuEducation(
|
||||
toolbar!!.findViewById(R.id.menu_lock),
|
||||
@@ -671,8 +679,10 @@ class GroupActivity : LockingActivity(),
|
||||
},
|
||||
{
|
||||
performedNextEducation(groupActivityEducation, menu)
|
||||
}))
|
||||
;
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun startActivity(intent: Intent) {
|
||||
@@ -857,11 +867,9 @@ class GroupActivity : LockingActivity(),
|
||||
}
|
||||
|
||||
private fun showWarnings() {
|
||||
// TODO Preferences
|
||||
if (Database.getInstance().isReadOnly) {
|
||||
val prefs = PreferenceManager.getDefaultSharedPreferences(this)
|
||||
if (prefs.getBoolean(getString(R.string.show_read_only_warning), true)) {
|
||||
ReadOnlyDialog(this).show()
|
||||
if (PreferencesUtil.showReadOnlyWarning(this)) {
|
||||
ReadOnlyDialog().show(supportFragmentManager, "readOnlyDialog")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,8 +5,8 @@ import android.content.Intent
|
||||
import android.content.SharedPreferences
|
||||
import android.os.Bundle
|
||||
import android.preference.PreferenceManager
|
||||
import android.support.v7.widget.LinearLayoutManager
|
||||
import android.support.v7.widget.RecyclerView
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.Menu
|
||||
@@ -130,7 +130,7 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
|
||||
|
||||
onScrollListener?.let { onScrollListener ->
|
||||
listView?.addOnScrollListener(object : RecyclerView.OnScrollListener() {
|
||||
override fun onScrolled(recyclerView: RecyclerView?, dx: Int, dy: Int) {
|
||||
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
||||
super.onScrolled(recyclerView, dx, dy)
|
||||
onScrollListener.onScrolled(dy)
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
*/
|
||||
package com.kunzisoft.keepass.activities
|
||||
|
||||
import android.Manifest
|
||||
import android.app.Activity
|
||||
import android.app.assist.AssistStructure
|
||||
import android.app.backup.BackupManager
|
||||
@@ -32,11 +31,12 @@ import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.preference.PreferenceManager
|
||||
import android.support.annotation.RequiresApi
|
||||
import android.support.v7.app.AlertDialog
|
||||
import android.support.v7.widget.Toolbar
|
||||
import androidx.annotation.RequiresApi
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import androidx.appcompat.widget.Toolbar
|
||||
import android.text.Editable
|
||||
import android.text.TextWatcher
|
||||
import android.util.Log
|
||||
import android.view.KeyEvent
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
@@ -45,9 +45,14 @@ import android.view.inputmethod.EditorInfo.IME_ACTION_DONE
|
||||
import android.widget.*
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.activities.dialogs.PasswordEncodingDialogFragment
|
||||
import com.kunzisoft.keepass.activities.helpers.*
|
||||
import com.kunzisoft.keepass.utils.ClipDataCompat
|
||||
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
|
||||
import com.kunzisoft.keepass.activities.helpers.OpenFileHelper
|
||||
import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper
|
||||
import com.kunzisoft.keepass.activities.lock.LockingActivity
|
||||
import com.kunzisoft.keepass.activities.stylish.StylishActivity
|
||||
import com.kunzisoft.keepass.utils.FileDatabaseInfo
|
||||
import com.kunzisoft.keepass.app.database.FileDatabaseHistory
|
||||
import com.kunzisoft.keepass.autofill.AutofillHelper
|
||||
import com.kunzisoft.keepass.database.action.LoadDatabaseRunnable
|
||||
import com.kunzisoft.keepass.database.action.ProgressDialogThread
|
||||
@@ -61,14 +66,13 @@ import com.kunzisoft.keepass.tasks.ActionRunnable
|
||||
import com.kunzisoft.keepass.utils.MenuUtil
|
||||
import com.kunzisoft.keepass.utils.UriUtil
|
||||
import com.kunzisoft.keepass.view.FingerPrintInfoView
|
||||
import permissions.dispatcher.*
|
||||
import java.io.File
|
||||
import com.kunzisoft.keepass.view.asError
|
||||
import kotlinx.android.synthetic.main.activity_password.*
|
||||
import java.io.FileNotFoundException
|
||||
import java.lang.Exception
|
||||
import java.lang.ref.WeakReference
|
||||
|
||||
@RuntimePermissions
|
||||
class PasswordActivity : StylishActivity(),
|
||||
UriIntentInitTaskCallback {
|
||||
class PasswordActivity : StylishActivity() {
|
||||
|
||||
// Views
|
||||
private var toolbar: Toolbar? = null
|
||||
@@ -87,7 +91,7 @@ class PasswordActivity : StylishActivity(),
|
||||
private var prefs: SharedPreferences? = null
|
||||
|
||||
private var mRememberKeyFile: Boolean = false
|
||||
private var mKeyFileHelper: KeyFileHelper? = null
|
||||
private var mOpenFileHelper: OpenFileHelper? = null
|
||||
|
||||
private var readOnly: Boolean = false
|
||||
|
||||
@@ -121,8 +125,8 @@ class PasswordActivity : StylishActivity(),
|
||||
readOnly = ReadOnlyHelper.retrieveReadOnlyFromInstanceStateOrPreference(this, savedInstanceState)
|
||||
|
||||
val browseView = findViewById<View>(R.id.browse_button)
|
||||
mKeyFileHelper = KeyFileHelper(this@PasswordActivity)
|
||||
browseView.setOnClickListener(mKeyFileHelper!!.openFileOnClickViewListener)
|
||||
mOpenFileHelper = OpenFileHelper(this@PasswordActivity)
|
||||
browseView.setOnClickListener(mOpenFileHelper!!.openFileOnClickViewListener)
|
||||
|
||||
passwordView?.setOnEditorActionListener(onEditorActionListener)
|
||||
passwordView?.addTextChangedListener(object : TextWatcher {
|
||||
@@ -172,8 +176,7 @@ class PasswordActivity : StylishActivity(),
|
||||
// For check shutdown
|
||||
super.onResume()
|
||||
|
||||
UriIntentInitTask(WeakReference(this), this, mRememberKeyFile)
|
||||
.execute(intent)
|
||||
initUriFromIntent()
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
@@ -181,26 +184,56 @@ class PasswordActivity : StylishActivity(),
|
||||
super.onSaveInstanceState(outState)
|
||||
}
|
||||
|
||||
override fun onPostInitTask(databaseFileUri: Uri?, keyFileUri: Uri?, errorStringId: Int?) {
|
||||
mDatabaseFileUri = databaseFileUri
|
||||
private fun initUriFromIntent() {
|
||||
|
||||
if (errorStringId != null) {
|
||||
Toast.makeText(this@PasswordActivity, errorStringId, Toast.LENGTH_LONG).show()
|
||||
val databaseUri: Uri?
|
||||
val keyFileUri: Uri?
|
||||
|
||||
// If is a view intent
|
||||
val action = intent.action
|
||||
if (action != null && action == VIEW_INTENT) {
|
||||
|
||||
val databaseUriRetrieve = intent.data
|
||||
// Stop activity here if we can't verify database URI
|
||||
try {
|
||||
UriUtil.verifyFileUri(databaseUriRetrieve)
|
||||
} catch (e : Exception) {
|
||||
Log.e(TAG, "File URI not validate", e)
|
||||
Toast.makeText(this@PasswordActivity, e.message, Toast.LENGTH_LONG).show()
|
||||
finish()
|
||||
return
|
||||
}
|
||||
|
||||
// Verify permission to read file
|
||||
if (databaseFileUri != null && !databaseFileUri.scheme!!.contains("content"))
|
||||
doNothingWithPermissionCheck()
|
||||
databaseUri = databaseUriRetrieve
|
||||
keyFileUri = ClipDataCompat.getUriFromIntent(intent, KEY_KEYFILE)
|
||||
|
||||
} else {
|
||||
databaseUri = UriUtil.parseUriFile(intent.getStringExtra(KEY_FILENAME))
|
||||
keyFileUri = UriUtil.parseUriFile(intent.getStringExtra(KEY_KEYFILE))
|
||||
}
|
||||
|
||||
// Post init uri with KeyFile if needed
|
||||
if (mRememberKeyFile && (keyFileUri == null || keyFileUri.toString().isEmpty())) {
|
||||
// Retrieve KeyFile in a thread
|
||||
databaseUri?.let { databaseUriNotNull ->
|
||||
FileDatabaseHistory.getInstance(applicationContext)
|
||||
.getKeyFileUriByDatabaseUri(databaseUriNotNull) {
|
||||
onPostInitUri(databaseUri, it)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
onPostInitUri(databaseUri, keyFileUri)
|
||||
}
|
||||
}
|
||||
|
||||
private fun onPostInitUri(databaseFileUri: Uri?, keyFileUri: Uri?) {
|
||||
mDatabaseFileUri = databaseFileUri
|
||||
|
||||
// Define title
|
||||
val dbUriString = databaseFileUri?.toString() ?: ""
|
||||
if (dbUriString.isNotEmpty()) {
|
||||
if (PreferencesUtil.isFullFilePathEnable(this))
|
||||
filenameView?.text = dbUriString
|
||||
else
|
||||
filenameView?.text = File(databaseFileUri!!.path!!).name // TODO Encapsulate
|
||||
databaseFileUri?.let {
|
||||
FileDatabaseInfo(this, it).retrieveDatabaseTitle { title ->
|
||||
filenameView?.text = title
|
||||
}
|
||||
}
|
||||
|
||||
// Define Key File text
|
||||
@@ -216,7 +249,7 @@ class PasswordActivity : StylishActivity(),
|
||||
newDefaultFileName = databaseFileUri?.toString() ?: newDefaultFileName
|
||||
}
|
||||
|
||||
prefs?.edit()?.apply() {
|
||||
prefs?.edit()?.apply {
|
||||
putString(KEY_DEFAULT_FILENAME, newDefaultFileName)
|
||||
apply()
|
||||
}
|
||||
@@ -418,7 +451,7 @@ class PasswordActivity : StylishActivity(),
|
||||
}
|
||||
} else {
|
||||
if (result.message != null && result.message!!.isNotEmpty()) {
|
||||
Toast.makeText(this@PasswordActivity, result.message, Toast.LENGTH_LONG).show()
|
||||
Snackbar.make(activity_password_coordinator_layout, result.message!!, Snackbar.LENGTH_LONG).asError().show()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -465,16 +498,18 @@ class PasswordActivity : StylishActivity(),
|
||||
|
||||
private fun performedNextEducation(passwordActivityEducation: PasswordActivityEducation,
|
||||
menu: Menu) {
|
||||
if (toolbar != null
|
||||
&& passwordActivityEducation.checkAndPerformedFingerprintUnlockEducation(
|
||||
val unlockEducationPerformed = toolbar != null
|
||||
&& passwordActivityEducation.checkAndPerformedUnlockEducation(
|
||||
toolbar!!,
|
||||
{
|
||||
performedNextEducation(passwordActivityEducation, menu)
|
||||
},
|
||||
{
|
||||
performedNextEducation(passwordActivityEducation, menu)
|
||||
}))
|
||||
else if (toolbar != null
|
||||
})
|
||||
if (!unlockEducationPerformed) {
|
||||
|
||||
val readOnlyEducationPerformed = toolbar != null
|
||||
&& toolbar!!.findViewById<View>(R.id.menu_open_file_read_mode_key) != null
|
||||
&& passwordActivityEducation.checkAndPerformedReadOnlyEducation(
|
||||
toolbar!!.findViewById(R.id.menu_open_file_read_mode_key),
|
||||
@@ -484,13 +519,19 @@ class PasswordActivity : StylishActivity(),
|
||||
},
|
||||
{
|
||||
performedNextEducation(passwordActivityEducation, menu)
|
||||
}))
|
||||
else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
|
||||
})
|
||||
|
||||
if (!readOnlyEducationPerformed) {
|
||||
|
||||
// fingerprintEducationPerformed
|
||||
(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
|
||||
&& PreferencesUtil.isFingerprintEnable(applicationContext)
|
||||
&& FingerPrintHelper.isFingerprintSupported(getSystemService(FingerprintManager::class.java))
|
||||
&& fingerPrintInfoView != null && fingerPrintInfoView?.fingerPrintImageView != null
|
||||
&& passwordActivityEducation.checkAndPerformedFingerprintEducation(fingerPrintInfoView?.fingerPrintImageView!!))
|
||||
;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun changeOpenFileReadIcon(togglePassword: MenuItem) {
|
||||
@@ -520,12 +561,6 @@ class PasswordActivity : StylishActivity(),
|
||||
return super.onOptionsItemSelected(item)
|
||||
}
|
||||
|
||||
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||
// NOTE: delegate the permission handling to generated method
|
||||
onRequestPermissionsResult(requestCode, grantResults)
|
||||
}
|
||||
|
||||
override fun onActivityResult(
|
||||
requestCode: Int,
|
||||
resultCode: Int,
|
||||
@@ -538,7 +573,7 @@ class PasswordActivity : StylishActivity(),
|
||||
}
|
||||
|
||||
var keyFileResult = false
|
||||
mKeyFileHelper?.let {
|
||||
mOpenFileHelper?.let {
|
||||
keyFileResult = it.onActivityResultCallback(requestCode, resultCode, data
|
||||
) { uri ->
|
||||
if (uri != null) {
|
||||
@@ -557,64 +592,28 @@ class PasswordActivity : StylishActivity(),
|
||||
}
|
||||
}
|
||||
|
||||
@NeedsPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||
fun doNothing() {
|
||||
}
|
||||
|
||||
@OnShowRationale(Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||
internal fun showRationaleForExternalStorage(request: PermissionRequest) {
|
||||
AlertDialog.Builder(this)
|
||||
.setMessage(R.string.permission_external_storage_rationale_read_database)
|
||||
.setPositiveButton(R.string.allow) { _, _ -> request.proceed() }
|
||||
.setNegativeButton(R.string.cancel) { _, _ -> request.cancel() }
|
||||
.show()
|
||||
}
|
||||
|
||||
@OnPermissionDenied(Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||
internal fun showDeniedForExternalStorage() {
|
||||
Toast.makeText(this, R.string.permission_external_storage_denied, Toast.LENGTH_SHORT).show()
|
||||
finish()
|
||||
}
|
||||
|
||||
@OnNeverAskAgain(Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||
internal fun showNeverAskForExternalStorage() {
|
||||
Toast.makeText(this, R.string.permission_external_storage_never_ask, Toast.LENGTH_SHORT).show()
|
||||
finish()
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private val TAG = PasswordActivity::class.java.name
|
||||
|
||||
const val KEY_DEFAULT_FILENAME = "defaultFileName"
|
||||
|
||||
private const val KEY_FILENAME = "fileName"
|
||||
private const val KEY_KEYFILE = "keyFile"
|
||||
private const val VIEW_INTENT = "android.intent.action.VIEW"
|
||||
|
||||
private const val KEY_PASSWORD = "password"
|
||||
private const val KEY_LAUNCH_IMMEDIATELY = "launchImmediately"
|
||||
|
||||
private fun buildAndLaunchIntent(activity: Activity, fileName: String, keyFile: String,
|
||||
private fun buildAndLaunchIntent(activity: Activity, fileName: String, keyFile: String?,
|
||||
intentBuildLauncher: (Intent) -> Unit) {
|
||||
val intent = Intent(activity, PasswordActivity::class.java)
|
||||
intent.putExtra(UriIntentInitTask.KEY_FILENAME, fileName)
|
||||
intent.putExtra(UriIntentInitTask.KEY_KEYFILE, keyFile)
|
||||
intent.putExtra(KEY_FILENAME, fileName)
|
||||
if (keyFile != null)
|
||||
intent.putExtra(KEY_KEYFILE, keyFile)
|
||||
intentBuildLauncher.invoke(intent)
|
||||
}
|
||||
|
||||
@Throws(FileNotFoundException::class)
|
||||
private fun verifyFileNameUriFromLaunch(fileName: String) {
|
||||
if (fileName.isEmpty()) {
|
||||
throw FileNotFoundException()
|
||||
}
|
||||
|
||||
val uri = UriUtil.parseUriFile(fileName)
|
||||
val scheme = uri?.scheme
|
||||
if (scheme != null && scheme.isNotEmpty() && scheme.equals("file", ignoreCase = true)) {
|
||||
val dbFile = File(uri.path!!)
|
||||
if (!dbFile.exists()) {
|
||||
throw FileNotFoundException()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* -------------------------
|
||||
* Standard Launch
|
||||
@@ -625,8 +624,8 @@ class PasswordActivity : StylishActivity(),
|
||||
fun launch(
|
||||
activity: Activity,
|
||||
fileName: String,
|
||||
keyFile: String) {
|
||||
verifyFileNameUriFromLaunch(fileName)
|
||||
keyFile: String?) {
|
||||
UriUtil.verifyFilePath(fileName)
|
||||
buildAndLaunchIntent(activity, fileName, keyFile) { intent ->
|
||||
activity.startActivity(intent)
|
||||
}
|
||||
@@ -642,8 +641,8 @@ class PasswordActivity : StylishActivity(),
|
||||
fun launchForKeyboardResult(
|
||||
activity: Activity,
|
||||
fileName: String,
|
||||
keyFile: String) {
|
||||
verifyFileNameUriFromLaunch(fileName)
|
||||
keyFile: String?) {
|
||||
UriUtil.verifyFilePath(fileName)
|
||||
|
||||
buildAndLaunchIntent(activity, fileName, keyFile) { intent ->
|
||||
KeyboardHelper.startActivityForKeyboardSelection(activity, intent)
|
||||
@@ -661,9 +660,9 @@ class PasswordActivity : StylishActivity(),
|
||||
fun launchForAutofillResult(
|
||||
activity: Activity,
|
||||
fileName: String,
|
||||
keyFile: String,
|
||||
keyFile: String?,
|
||||
assistStructure: AssistStructure?) {
|
||||
verifyFileNameUriFromLaunch(fileName)
|
||||
UriUtil.verifyFilePath(fileName)
|
||||
|
||||
if (assistStructure != null) {
|
||||
buildAndLaunchIntent(activity, fileName, keyFile) { intent ->
|
||||
|
||||
@@ -25,16 +25,16 @@ import android.content.DialogInterface
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.support.v4.app.DialogFragment
|
||||
import android.support.v7.app.AlertDialog
|
||||
import com.google.android.material.textfield.TextInputLayout
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import android.text.Editable
|
||||
import android.text.TextWatcher
|
||||
import android.view.View
|
||||
import android.widget.CompoundButton
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.activities.helpers.KeyFileHelper
|
||||
import com.kunzisoft.keepass.activities.helpers.OpenFileHelper
|
||||
import com.kunzisoft.keepass.utils.UriUtil
|
||||
|
||||
class AssignMasterKeyDialogFragment : DialogFragment() {
|
||||
@@ -43,15 +43,39 @@ class AssignMasterKeyDialogFragment : DialogFragment() {
|
||||
private var mKeyFile: Uri? = null
|
||||
|
||||
private var rootView: View? = null
|
||||
|
||||
private var passwordCheckBox: CompoundButton? = null
|
||||
private var passView: TextView? = null
|
||||
private var passConfView: TextView? = null
|
||||
private var passwordView: TextView? = null
|
||||
private var passwordRepeatTextInputLayout: TextInputLayout? = null
|
||||
private var passwordRepeatView: TextView? = null
|
||||
|
||||
private var keyFileTextInputLayout: TextInputLayout? = null
|
||||
private var keyFileCheckBox: CompoundButton? = null
|
||||
private var keyFileView: TextView? = null
|
||||
|
||||
private var mListener: AssignPasswordDialogListener? = null
|
||||
|
||||
private var mKeyFileHelper: KeyFileHelper? = null
|
||||
private var mOpenFileHelper: OpenFileHelper? = null
|
||||
|
||||
private val passwordTextWatcher = object : TextWatcher {
|
||||
override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
|
||||
|
||||
override fun onTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
|
||||
|
||||
override fun afterTextChanged(editable: Editable) {
|
||||
passwordCheckBox?.isChecked = true
|
||||
}
|
||||
}
|
||||
|
||||
private val keyFileTextWatcher = object : TextWatcher {
|
||||
override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
|
||||
|
||||
override fun onTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
|
||||
|
||||
override fun afterTextChanged(editable: Editable) {
|
||||
keyFileCheckBox?.isChecked = true
|
||||
}
|
||||
}
|
||||
|
||||
interface AssignPasswordDialogListener {
|
||||
fun onAssignKeyDialogPositiveClick(masterPasswordChecked: Boolean, masterPassword: String?,
|
||||
@@ -83,33 +107,17 @@ class AssignMasterKeyDialogFragment : DialogFragment() {
|
||||
.setNegativeButton(R.string.cancel) { _, _ -> }
|
||||
|
||||
passwordCheckBox = rootView?.findViewById(R.id.password_checkbox)
|
||||
passView = rootView?.findViewById(R.id.pass_password)
|
||||
passView?.addTextChangedListener(object : TextWatcher {
|
||||
override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
|
||||
|
||||
override fun onTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
|
||||
|
||||
override fun afterTextChanged(editable: Editable) {
|
||||
passwordCheckBox?.isChecked = true
|
||||
}
|
||||
})
|
||||
passConfView = rootView?.findViewById(R.id.pass_conf_password)
|
||||
passwordView = rootView?.findViewById(R.id.pass_password)
|
||||
passwordRepeatTextInputLayout = rootView?.findViewById(R.id.password_repeat_input_layout)
|
||||
passwordRepeatView = rootView?.findViewById(R.id.pass_conf_password)
|
||||
|
||||
keyFileTextInputLayout = rootView?.findViewById(R.id.keyfile_input_layout)
|
||||
keyFileCheckBox = rootView?.findViewById(R.id.keyfile_checkox)
|
||||
keyFileView = rootView?.findViewById(R.id.pass_keyfile)
|
||||
keyFileView?.addTextChangedListener(object : TextWatcher {
|
||||
override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
|
||||
|
||||
override fun onTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
|
||||
|
||||
override fun afterTextChanged(editable: Editable) {
|
||||
keyFileCheckBox?.isChecked = true
|
||||
}
|
||||
})
|
||||
|
||||
mKeyFileHelper = KeyFileHelper(this)
|
||||
mOpenFileHelper = OpenFileHelper(this)
|
||||
rootView?.findViewById<View>(R.id.browse_button)?.setOnClickListener { view ->
|
||||
mKeyFileHelper?.openFileOnClickViewListener?.onClick(view) }
|
||||
mOpenFileHelper?.openFileOnClickViewListener?.onClick(view) }
|
||||
|
||||
val dialog = builder.create()
|
||||
|
||||
@@ -149,20 +157,35 @@ class AssignMasterKeyDialogFragment : DialogFragment() {
|
||||
return super.onCreateDialog(savedInstanceState)
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
|
||||
// To check checkboxes if a text is present
|
||||
passwordView?.addTextChangedListener(passwordTextWatcher)
|
||||
keyFileView?.addTextChangedListener(keyFileTextWatcher)
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
|
||||
passwordView?.removeTextChangedListener(passwordTextWatcher)
|
||||
keyFileView?.removeTextChangedListener(keyFileTextWatcher)
|
||||
}
|
||||
|
||||
private fun verifyPassword(): Boolean {
|
||||
var error = false
|
||||
if (passwordCheckBox != null
|
||||
&& passwordCheckBox!!.isChecked
|
||||
&& passView != null
|
||||
&& passConfView != null) {
|
||||
mMasterPassword = passView!!.text.toString()
|
||||
val confPassword = passConfView!!.text.toString()
|
||||
&& passwordView != null
|
||||
&& passwordRepeatView != null) {
|
||||
mMasterPassword = passwordView!!.text.toString()
|
||||
val confPassword = passwordRepeatView!!.text.toString()
|
||||
|
||||
// Verify that passwords match
|
||||
if (mMasterPassword != confPassword) {
|
||||
error = true
|
||||
// Passwords do not match
|
||||
Toast.makeText(context, R.string.error_pass_match, Toast.LENGTH_LONG).show()
|
||||
passwordRepeatTextInputLayout?.error = getString(R.string.error_pass_match)
|
||||
}
|
||||
|
||||
if (mMasterPassword == null || mMasterPassword!!.isEmpty()) {
|
||||
@@ -183,7 +206,8 @@ class AssignMasterKeyDialogFragment : DialogFragment() {
|
||||
// Verify that a keyfile is set
|
||||
if (keyFile == null || keyFile.toString().isEmpty()) {
|
||||
error = true
|
||||
Toast.makeText(context, R.string.error_nokeyfile, Toast.LENGTH_LONG).show()
|
||||
// TODO better keyfile check
|
||||
keyFileTextInputLayout?.error = getString(R.string.error_nokeyfile)
|
||||
}
|
||||
}
|
||||
return error
|
||||
@@ -224,7 +248,7 @@ class AssignMasterKeyDialogFragment : DialogFragment() {
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
super.onActivityResult(requestCode, resultCode, data)
|
||||
|
||||
mKeyFileHelper?.onActivityResultCallback(requestCode, resultCode, data
|
||||
mOpenFileHelper?.onActivityResultCallback(requestCode, resultCode, data
|
||||
) { uri ->
|
||||
UriUtil.parseUriFile(uri)?.let { pathUri ->
|
||||
keyFileCheckBox?.isChecked = true
|
||||
|
||||
@@ -21,11 +21,12 @@ package com.kunzisoft.keepass.activities.dialogs
|
||||
|
||||
import android.app.Dialog
|
||||
import android.os.Bundle
|
||||
import android.support.v4.app.DialogFragment
|
||||
import android.support.v7.app.AlertDialog
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import android.widget.Button
|
||||
import android.widget.TextView
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.utils.Util
|
||||
import com.kunzisoft.keepass.utils.UriUtil
|
||||
|
||||
class BrowserDialogFragment : DialogFragment() {
|
||||
|
||||
@@ -37,15 +38,18 @@ class BrowserDialogFragment : DialogFragment() {
|
||||
builder.setView(root)
|
||||
.setNegativeButton(R.string.cancel) { _, _ -> }
|
||||
|
||||
val market = root.findViewById<Button>(R.id.install_market)
|
||||
val textDescription = root.findViewById<TextView>(R.id.file_manager_install_description)
|
||||
textDescription.text = getString(R.string.file_manager_install_description)
|
||||
|
||||
val market = root.findViewById<Button>(R.id.file_manager_install_play_store)
|
||||
market.setOnClickListener {
|
||||
Util.gotoUrl(context!!, R.string.filemanager_play_store)
|
||||
UriUtil.gotoUrl(context!!, R.string.filemanager_play_store)
|
||||
dismiss()
|
||||
}
|
||||
|
||||
val web = root.findViewById<Button>(R.id.install_web)
|
||||
val web = root.findViewById<Button>(R.id.file_manager_install_f_droid)
|
||||
web.setOnClickListener {
|
||||
Util.gotoUrl(context!!, R.string.filemanager_f_droid)
|
||||
UriUtil.gotoUrl(context!!, R.string.filemanager_f_droid)
|
||||
dismiss()
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,14 +22,18 @@ package com.kunzisoft.keepass.activities.dialogs
|
||||
import android.app.Dialog
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.support.v4.app.DialogFragment
|
||||
import android.support.v7.app.AlertDialog
|
||||
import com.google.android.material.textfield.TextInputLayout
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import android.view.View
|
||||
import android.widget.*
|
||||
import android.widget.Button
|
||||
import android.widget.CompoundButton
|
||||
import android.widget.EditText
|
||||
import android.widget.SeekBar
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.password.PasswordGenerator
|
||||
import com.kunzisoft.keepass.settings.PreferencesUtil
|
||||
import com.kunzisoft.keepass.utils.applyFontVisibility
|
||||
import com.kunzisoft.keepass.view.applyFontVisibility
|
||||
|
||||
class GeneratePasswordDialogFragment : DialogFragment() {
|
||||
|
||||
@@ -37,6 +41,7 @@ class GeneratePasswordDialogFragment : DialogFragment() {
|
||||
|
||||
private var root: View? = null
|
||||
private var lengthTextView: EditText? = null
|
||||
private var passwordInputLayoutView: TextInputLayout? = null
|
||||
private var passwordView: EditText? = null
|
||||
|
||||
private var uppercaseBox: CompoundButton? = null
|
||||
@@ -65,6 +70,7 @@ class GeneratePasswordDialogFragment : DialogFragment() {
|
||||
val inflater = activity.layoutInflater
|
||||
root = inflater.inflate(R.layout.fragment_generate_password, null)
|
||||
|
||||
passwordInputLayoutView = root?.findViewById(R.id.password_input_layout)
|
||||
passwordView = root?.findViewById(R.id.password)
|
||||
passwordView?.applyFontVisibility()
|
||||
|
||||
@@ -162,8 +168,7 @@ class GeneratePasswordDialogFragment : DialogFragment() {
|
||||
try {
|
||||
val length = Integer.valueOf(root?.findViewById<EditText>(R.id.length)?.text.toString())
|
||||
|
||||
val generator = PasswordGenerator(resources)
|
||||
password = generator.generatePassword(length,
|
||||
password = PasswordGenerator(resources).generatePassword(length,
|
||||
uppercaseBox?.isChecked == true,
|
||||
lowercaseBox?.isChecked == true,
|
||||
digitsBox?.isChecked == true,
|
||||
@@ -174,9 +179,9 @@ class GeneratePasswordDialogFragment : DialogFragment() {
|
||||
bracketsBox?.isChecked == true,
|
||||
extendedBox?.isChecked == true)
|
||||
} catch (e: NumberFormatException) {
|
||||
Toast.makeText(context, R.string.error_wrong_length, Toast.LENGTH_LONG).show()
|
||||
passwordInputLayoutView?.error = getString(R.string.error_wrong_length)
|
||||
} catch (e: IllegalArgumentException) {
|
||||
Toast.makeText(context, e.message, Toast.LENGTH_LONG).show()
|
||||
passwordInputLayoutView?.error = e.message
|
||||
}
|
||||
|
||||
return password
|
||||
|
||||
@@ -23,9 +23,9 @@ import android.app.Dialog
|
||||
import android.content.Context
|
||||
import android.graphics.Color
|
||||
import android.os.Bundle
|
||||
import android.support.design.widget.TextInputLayout
|
||||
import android.support.v4.app.DialogFragment
|
||||
import android.support.v7.app.AlertDialog
|
||||
import com.google.android.material.textfield.TextInputLayout
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import android.widget.Button
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
|
||||
@@ -24,9 +24,9 @@ import android.content.Context
|
||||
import android.content.res.ColorStateList
|
||||
import android.graphics.Color
|
||||
import android.os.Bundle
|
||||
import android.support.v4.app.DialogFragment
|
||||
import android.support.v4.widget.ImageViewCompat
|
||||
import android.support.v7.app.AlertDialog
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.core.widget.ImageViewCompat
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
|
||||
@@ -24,12 +24,12 @@ import android.content.Intent
|
||||
import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
import android.os.Bundle
|
||||
import android.provider.Settings
|
||||
import android.support.v4.app.DialogFragment
|
||||
import android.support.v7.app.AlertDialog
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import android.view.View
|
||||
import com.kunzisoft.keepass.BuildConfig
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.utils.Util
|
||||
import com.kunzisoft.keepass.utils.UriUtil
|
||||
|
||||
class KeyboardExplanationDialogFragment : DialogFragment() {
|
||||
|
||||
@@ -47,9 +47,9 @@ class KeyboardExplanationDialogFragment : DialogFragment() {
|
||||
|
||||
val containerKeyboardSwitcher = rootView.findViewById<View>(R.id.container_keyboard_switcher)
|
||||
if (BuildConfig.CLOSED_STORE) {
|
||||
containerKeyboardSwitcher.setOnClickListener { Util.gotoUrl(context!!, R.string.keyboard_switcher_play_store) }
|
||||
containerKeyboardSwitcher.setOnClickListener { UriUtil.gotoUrl(context!!, R.string.keyboard_switcher_play_store) }
|
||||
} else {
|
||||
containerKeyboardSwitcher.setOnClickListener { Util.gotoUrl(context!!, R.string.keyboard_switcher_f_droid) }
|
||||
containerKeyboardSwitcher.setOnClickListener { UriUtil.gotoUrl(context!!, R.string.keyboard_switcher_f_droid) }
|
||||
}
|
||||
|
||||
builder.setView(rootView)
|
||||
|
||||
@@ -23,7 +23,7 @@ import android.app.AlertDialog
|
||||
import android.app.Dialog
|
||||
import android.content.DialogInterface
|
||||
import android.os.Bundle
|
||||
import android.support.v4.app.DialogFragment
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import com.kunzisoft.keepass.R
|
||||
|
||||
class PasswordEncodingDialogFragment : DialogFragment() {
|
||||
|
||||
@@ -20,17 +20,15 @@
|
||||
package com.kunzisoft.keepass.activities.dialogs
|
||||
|
||||
import android.app.Dialog
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.os.Bundle
|
||||
import android.support.v4.app.DialogFragment
|
||||
import android.support.v7.app.AlertDialog
|
||||
import android.text.Html
|
||||
import android.text.SpannableStringBuilder
|
||||
import android.widget.Toast
|
||||
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.core.text.HtmlCompat
|
||||
import androidx.core.text.HtmlCompat.FROM_HTML_MODE_LEGACY
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import com.kunzisoft.keepass.BuildConfig
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.utils.Util
|
||||
import com.kunzisoft.keepass.utils.UriUtil
|
||||
|
||||
/**
|
||||
* Custom Dialog that asks the user to download the pro version or make a donation.
|
||||
@@ -44,25 +42,16 @@ class ProFeatureDialogFragment : DialogFragment() {
|
||||
|
||||
val stringBuilder = SpannableStringBuilder()
|
||||
if (BuildConfig.CLOSED_STORE) {
|
||||
// TODO HtmlCompat with androidX
|
||||
stringBuilder.append(Html.fromHtml(getString(R.string.html_text_ad_free))).append("\n\n")
|
||||
stringBuilder.append(Html.fromHtml(getString(R.string.html_text_buy_pro)))
|
||||
stringBuilder.append(HtmlCompat.fromHtml(getString(R.string.html_text_ad_free), FROM_HTML_MODE_LEGACY)).append("\n\n")
|
||||
stringBuilder.append(HtmlCompat.fromHtml(getString(R.string.html_text_buy_pro), FROM_HTML_MODE_LEGACY))
|
||||
builder.setPositiveButton(R.string.download) { _, _ ->
|
||||
try {
|
||||
Util.gotoUrl(context!!, R.string.app_pro_url)
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
Toast.makeText(context, R.string.error_failed_to_launch_link, Toast.LENGTH_LONG).show()
|
||||
}
|
||||
UriUtil.gotoUrl(context!!, R.string.app_pro_url)
|
||||
}
|
||||
} else {
|
||||
stringBuilder.append(Html.fromHtml(getString(R.string.html_text_feature_generosity))).append("\n\n")
|
||||
stringBuilder.append(Html.fromHtml(getString(R.string.html_text_donation)))
|
||||
stringBuilder.append(HtmlCompat.fromHtml(getString(R.string.html_text_feature_generosity), FROM_HTML_MODE_LEGACY)).append("\n\n")
|
||||
stringBuilder.append(HtmlCompat.fromHtml(getString(R.string.html_text_donation), FROM_HTML_MODE_LEGACY))
|
||||
builder.setPositiveButton(R.string.contribute) { _, _ ->
|
||||
try {
|
||||
Util.gotoUrl(context!!, R.string.contribution_url)
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
Toast.makeText(context, R.string.error_failed_to_launch_link, Toast.LENGTH_LONG).show()
|
||||
}
|
||||
UriUtil.gotoUrl(context!!, R.string.contribution_url)
|
||||
}
|
||||
}
|
||||
builder.setMessage(stringBuilder)
|
||||
|
||||
@@ -19,33 +19,38 @@
|
||||
*/
|
||||
package com.kunzisoft.keepass.activities.dialogs
|
||||
|
||||
import android.app.AlertDialog
|
||||
import android.content.Context
|
||||
import android.app.Dialog
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.preference.PreferenceManager
|
||||
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import com.kunzisoft.keepass.R
|
||||
|
||||
class ReadOnlyDialog(context: Context) : AlertDialog(context) {
|
||||
class ReadOnlyDialog : DialogFragment() {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle) {
|
||||
val ctx = context
|
||||
var warning = ctx.getString(R.string.read_only_warning)
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
activity?.let { activity ->
|
||||
// Use the Builder class for convenient dialog construction
|
||||
val builder = androidx.appcompat.app.AlertDialog.Builder(activity)
|
||||
|
||||
var warning = getString(R.string.read_only_warning)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||
warning = warning + "\n\n" + context.getString(R.string.read_only_kitkat_warning)
|
||||
warning = warning + "\n\n" + getString(R.string.read_only_kitkat_warning)
|
||||
}
|
||||
setMessage(warning)
|
||||
builder.setMessage(warning)
|
||||
|
||||
setButton(BUTTON_POSITIVE, ctx.getText(android.R.string.ok)) { _, _ -> dismiss() }
|
||||
setButton(BUTTON_NEGATIVE, ctx.getText(R.string.beta_dontask)) { _, _ ->
|
||||
val prefs = PreferenceManager.getDefaultSharedPreferences(ctx)
|
||||
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(ctx.getString(R.string.show_read_only_warning), false)
|
||||
edit.putBoolean(getString(R.string.show_read_only_warning), false)
|
||||
edit.apply()
|
||||
dismiss()
|
||||
}
|
||||
|
||||
super.onCreate(savedInstanceState)
|
||||
// Create the AlertDialog object and return it
|
||||
return builder.create()
|
||||
}
|
||||
return super.onCreateDialog(savedInstanceState)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,9 +22,9 @@ package com.kunzisoft.keepass.activities.dialogs
|
||||
import android.app.Dialog
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.support.annotation.IdRes
|
||||
import android.support.v4.app.DialogFragment
|
||||
import android.support.v7.app.AlertDialog
|
||||
import androidx.annotation.IdRes
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import android.view.View
|
||||
import android.widget.CompoundButton
|
||||
import android.widget.RadioGroup
|
||||
|
||||
@@ -22,12 +22,13 @@ package com.kunzisoft.keepass.activities.dialogs
|
||||
import android.app.Dialog
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.support.v4.app.DialogFragment
|
||||
import android.support.v7.app.AlertDialog
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import android.text.Html
|
||||
import android.text.SpannableStringBuilder
|
||||
import android.text.method.LinkMovementMethod
|
||||
import android.widget.TextView
|
||||
import androidx.core.text.HtmlCompat
|
||||
import com.kunzisoft.keepass.R
|
||||
|
||||
class UnavailableFeatureDialogFragment : DialogFragment() {
|
||||
@@ -53,7 +54,7 @@ class UnavailableFeatureDialogFragment : DialogFragment() {
|
||||
androidNameFromApiNumber(Build.VERSION.SDK_INT, Build.VERSION.RELEASE),
|
||||
androidNameFromApiNumber(minVersionRequired)))
|
||||
message.append("\n\n")
|
||||
.append(Html.fromHtml("<a href=\"https://source.android.com/setup/build-numbers\">CodeNames</a>"))
|
||||
.append(HtmlCompat.fromHtml("<a href=\"https://source.android.com/setup/build-numbers\">CodeNames</a>", HtmlCompat.FROM_HTML_MODE_LEGACY))
|
||||
} else
|
||||
message.append(getString(R.string.unavailable_feature_hardware))
|
||||
|
||||
|
||||
@@ -20,17 +20,14 @@
|
||||
package com.kunzisoft.keepass.activities.dialogs
|
||||
|
||||
import android.app.Dialog
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.os.Bundle
|
||||
import android.support.v4.app.DialogFragment
|
||||
import android.support.v7.app.AlertDialog
|
||||
import android.text.Html
|
||||
import android.text.SpannableStringBuilder
|
||||
import android.widget.Toast
|
||||
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.core.text.HtmlCompat
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import com.kunzisoft.keepass.BuildConfig
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.utils.Util
|
||||
import com.kunzisoft.keepass.utils.UriUtil
|
||||
|
||||
/**
|
||||
* Custom Dialog that asks the user to download the pro version or make a donation.
|
||||
@@ -45,34 +42,26 @@ class UnderDevelopmentFeatureDialogFragment : DialogFragment() {
|
||||
val stringBuilder = SpannableStringBuilder()
|
||||
if (BuildConfig.CLOSED_STORE) {
|
||||
if (BuildConfig.FULL_VERSION) {
|
||||
stringBuilder.append(Html.fromHtml(getString(R.string.html_text_dev_feature_thanks))).append("\n\n")
|
||||
.append(Html.fromHtml(getString(R.string.html_rose))).append("\n\n")
|
||||
.append(Html.fromHtml(getString(R.string.html_text_dev_feature_work_hard))).append("\n")
|
||||
.append(Html.fromHtml(getString(R.string.html_text_dev_feature_upgrade))).append(" ")
|
||||
stringBuilder.append(HtmlCompat.fromHtml(getString(R.string.html_text_dev_feature_thanks), HtmlCompat.FROM_HTML_MODE_LEGACY)).append("\n\n")
|
||||
.append(HtmlCompat.fromHtml(getString(R.string.html_rose), HtmlCompat.FROM_HTML_MODE_LEGACY)).append("\n\n")
|
||||
.append(HtmlCompat.fromHtml(getString(R.string.html_text_dev_feature_work_hard), HtmlCompat.FROM_HTML_MODE_LEGACY)).append("\n")
|
||||
.append(HtmlCompat.fromHtml(getString(R.string.html_text_dev_feature_upgrade), HtmlCompat.FROM_HTML_MODE_LEGACY)).append(" ")
|
||||
builder.setPositiveButton(android.R.string.ok) { _, _ -> dismiss() }
|
||||
} else {
|
||||
stringBuilder.append(Html.fromHtml(getString(R.string.html_text_dev_feature))).append("\n\n")
|
||||
.append(Html.fromHtml(getString(R.string.html_text_dev_feature_buy_pro))).append("\n")
|
||||
.append(Html.fromHtml(getString(R.string.html_text_dev_feature_encourage)))
|
||||
stringBuilder.append(HtmlCompat.fromHtml(getString(R.string.html_text_dev_feature), HtmlCompat.FROM_HTML_MODE_LEGACY)).append("\n\n")
|
||||
.append(HtmlCompat.fromHtml(getString(R.string.html_text_dev_feature_buy_pro), HtmlCompat.FROM_HTML_MODE_LEGACY)).append("\n")
|
||||
.append(HtmlCompat.fromHtml(getString(R.string.html_text_dev_feature_encourage), HtmlCompat.FROM_HTML_MODE_LEGACY))
|
||||
builder.setPositiveButton(R.string.download) { _, _ ->
|
||||
try {
|
||||
Util.gotoUrl(context!!, R.string.app_pro_url)
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
Toast.makeText(context, R.string.error_failed_to_launch_link, Toast.LENGTH_LONG).show()
|
||||
}
|
||||
UriUtil.gotoUrl(context!!, R.string.app_pro_url)
|
||||
}
|
||||
builder.setNegativeButton(android.R.string.cancel) { _, _ -> dismiss() }
|
||||
}
|
||||
} else {
|
||||
stringBuilder.append(Html.fromHtml(getString(R.string.html_text_dev_feature))).append("\n\n")
|
||||
.append(Html.fromHtml(getString(R.string.html_text_dev_feature_contibute))).append(" ")
|
||||
.append(Html.fromHtml(getString(R.string.html_text_dev_feature_encourage)))
|
||||
stringBuilder.append(HtmlCompat.fromHtml(getString(R.string.html_text_dev_feature), HtmlCompat.FROM_HTML_MODE_LEGACY)).append("\n\n")
|
||||
.append(HtmlCompat.fromHtml(getString(R.string.html_text_dev_feature_contibute), HtmlCompat.FROM_HTML_MODE_LEGACY)).append(" ")
|
||||
.append(HtmlCompat.fromHtml(getString(R.string.html_text_dev_feature_encourage), HtmlCompat.FROM_HTML_MODE_LEGACY))
|
||||
builder.setPositiveButton(R.string.contribute) { _, _ ->
|
||||
try {
|
||||
Util.gotoUrl(context!!, R.string.contribution_url)
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
Toast.makeText(context, R.string.error_failed_to_launch_link, Toast.LENGTH_LONG).show()
|
||||
}
|
||||
UriUtil.gotoUrl(context!!, R.string.contribution_url)
|
||||
}
|
||||
builder.setNegativeButton(android.R.string.cancel) { _, _ -> dismiss() }
|
||||
}
|
||||
|
||||
@@ -26,15 +26,14 @@ import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.support.v4.app.Fragment
|
||||
import android.support.v4.app.FragmentActivity
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import android.util.Log
|
||||
import android.view.View
|
||||
import com.kunzisoft.keepass.activities.dialogs.BrowserDialogFragment
|
||||
import com.kunzisoft.keepass.fileselect.StorageAF
|
||||
import com.kunzisoft.keepass.utils.UriUtil
|
||||
|
||||
class KeyFileHelper {
|
||||
class OpenFileHelper {
|
||||
|
||||
private var activity: Activity? = null
|
||||
private var fragment: Fragment? = null
|
||||
@@ -56,9 +55,9 @@ class KeyFileHelper {
|
||||
|
||||
override fun onClick(v: View) {
|
||||
try {
|
||||
if (activity != null && StorageAF.useStorageFramework(activity!!)) {
|
||||
try {
|
||||
openActivityWithActionOpenDocument()
|
||||
} else {
|
||||
} catch(e: Exception) {
|
||||
openActivityWithActionGetContent()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
@@ -72,7 +71,7 @@ class KeyFileHelper {
|
||||
}
|
||||
|
||||
private fun openActivityWithActionOpenDocument() {
|
||||
val i = Intent(StorageAF.ACTION_OPEN_DOCUMENT)
|
||||
val i = Intent(ACTION_OPEN_DOCUMENT)
|
||||
i.addCategory(Intent.CATEGORY_OPENABLE)
|
||||
i.type = "*/*"
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||
@@ -158,7 +157,7 @@ class KeyFileHelper {
|
||||
val browserDialogFragment = BrowserDialogFragment()
|
||||
if (fragment != null && fragment!!.fragmentManager != null)
|
||||
browserDialogFragment.show(fragment!!.fragmentManager!!, "browserDialog")
|
||||
else if (activity!!.fragmentManager != null)
|
||||
else if ((activity as FragmentActivity).supportFragmentManager != null)
|
||||
browserDialogFragment.show((activity as FragmentActivity).supportFragmentManager, "browserDialog")
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Can't open BrowserDialog", e)
|
||||
@@ -193,7 +192,6 @@ class KeyFileHelper {
|
||||
if (data != null) {
|
||||
var uri = data.data
|
||||
if (uri != null) {
|
||||
if (StorageAF.useStorageFramework(activity!!)) {
|
||||
try {
|
||||
// try to persist read and write permissions
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||
@@ -205,7 +203,6 @@ class KeyFileHelper {
|
||||
} catch (e: Exception) {
|
||||
// nop
|
||||
}
|
||||
}
|
||||
if (requestCode == GET_CONTENT) {
|
||||
uri = UriUtil.translateUri(activity!!, uri)
|
||||
}
|
||||
@@ -221,7 +218,18 @@ class KeyFileHelper {
|
||||
|
||||
companion object {
|
||||
|
||||
private const val TAG = "KeyFileHelper"
|
||||
private const val TAG = "OpenFileHelper"
|
||||
|
||||
private var ACTION_OPEN_DOCUMENT: String
|
||||
|
||||
init {
|
||||
ACTION_OPEN_DOCUMENT = try {
|
||||
val openDocument = Intent::class.java.getField("ACTION_OPEN_DOCUMENT")
|
||||
openDocument.get(null) as String
|
||||
} catch (e: Exception) {
|
||||
"android.intent.action.OPEN_DOCUMENT"
|
||||
}
|
||||
}
|
||||
|
||||
const val OPEN_INTENTS_FILE_BROWSE = "org.openintents.action.PICK_FILE"
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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?)
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,8 +20,8 @@
|
||||
package com.kunzisoft.keepass.activities.stylish
|
||||
|
||||
import android.content.Context
|
||||
import android.support.annotation.StyleRes
|
||||
import android.support.v7.preference.PreferenceManager
|
||||
import androidx.annotation.StyleRes
|
||||
import androidx.preference.PreferenceManager
|
||||
import android.util.Log
|
||||
|
||||
import com.kunzisoft.keepass.R
|
||||
@@ -68,15 +68,4 @@ object Stylish {
|
||||
else -> R.style.KeepassDXStyle_Light
|
||||
}
|
||||
}
|
||||
|
||||
@StyleRes
|
||||
fun getFilePickerThemeId(context: Context): Int {
|
||||
return when {
|
||||
themeString.equals(context.getString(R.string.list_style_name_dark)) -> R.style.KeepassDXStyle_FilePickerStyle_Dark
|
||||
themeString.equals(context.getString(R.string.list_style_name_blue)) -> R.style.KeepassDXStyle_FilePickerStyle_Blue
|
||||
themeString.equals(context.getString(R.string.list_style_name_red)) -> R.style.KeepassDXStyle_FilePickerStyle_Red
|
||||
themeString.equals(context.getString(R.string.list_style_name_purple)) -> R.style.KeepassDXStyle_FilePickerStyle_Purple
|
||||
else -> R.style.KeepassDXStyle_FilePickerStyle
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,8 +20,8 @@
|
||||
package com.kunzisoft.keepass.activities.stylish
|
||||
|
||||
import android.os.Bundle
|
||||
import android.support.annotation.StyleRes
|
||||
import android.support.v7.app.AppCompatActivity
|
||||
import androidx.annotation.StyleRes
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import android.util.Log
|
||||
|
||||
abstract class StylishActivity : AppCompatActivity() {
|
||||
|
||||
@@ -23,9 +23,9 @@ import android.content.Context
|
||||
import android.graphics.Color
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.support.annotation.StyleRes
|
||||
import android.support.v4.app.Fragment
|
||||
import android.support.v7.view.ContextThemeWrapper
|
||||
import androidx.annotation.StyleRes
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.appcompat.view.ContextThemeWrapper
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.kunzisoft.keepass.magikeyboard.adapter
|
||||
package com.kunzisoft.keepass.adapters
|
||||
|
||||
import android.content.Context
|
||||
import android.support.v7.widget.RecyclerView
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
@@ -21,23 +21,30 @@ package com.kunzisoft.keepass.adapters
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.support.annotation.ColorInt
|
||||
import android.support.v7.widget.RecyclerView
|
||||
import androidx.annotation.ColorInt
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import android.util.TypedValue
|
||||
import android.view.*
|
||||
import android.widget.EditText
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import android.widget.ViewSwitcher
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.fileselect.FileDatabaseModel
|
||||
import com.kunzisoft.keepass.settings.PreferencesUtil
|
||||
import com.kunzisoft.keepass.app.database.FileDatabaseHistoryEntity
|
||||
import com.kunzisoft.keepass.utils.FileDatabaseInfo
|
||||
|
||||
class FileDatabaseHistoryAdapter(private val context: Context, private val listFiles: List<String>)
|
||||
class FileDatabaseHistoryAdapter(private val context: Context)
|
||||
: RecyclerView.Adapter<FileDatabaseHistoryAdapter.FileDatabaseHistoryViewHolder>() {
|
||||
|
||||
private val inflater: LayoutInflater = LayoutInflater.from(context)
|
||||
private var fileItemOpenListener: FileItemOpenListener? = null
|
||||
private var fileSelectClearListener: FileSelectClearListener? = null
|
||||
private var fileInformationShowListener: FileInformationShowListener? = null
|
||||
private var fileItemOpenListener: ((FileDatabaseHistoryEntity)->Unit)? = null
|
||||
private var fileSelectClearListener: ((FileDatabaseHistoryEntity)->Boolean)? = null
|
||||
private var saveAliasListener: ((FileDatabaseHistoryEntity)->Unit)? = null
|
||||
|
||||
private val listDatabaseFiles = ArrayList<FileDatabaseHistoryEntity>()
|
||||
|
||||
private var mExpandedPosition = -1
|
||||
private var mPreviousExpandedPosition = -1
|
||||
|
||||
@ColorInt
|
||||
private val defaultColor: Int
|
||||
@@ -45,7 +52,6 @@ class FileDatabaseHistoryAdapter(private val context: Context, private val listF
|
||||
private val warningColor: Int
|
||||
|
||||
init {
|
||||
|
||||
val typedValue = TypedValue()
|
||||
val theme = context.theme
|
||||
theme.resolveAttribute(R.attr.colorAccent, typedValue, true)
|
||||
@@ -60,91 +66,120 @@ class FileDatabaseHistoryAdapter(private val context: Context, private val listF
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: FileDatabaseHistoryViewHolder, position: Int) {
|
||||
val fileDatabaseModel = FileDatabaseModel(context, listFiles[position])
|
||||
// Context menu creation
|
||||
holder.fileContainer.setOnCreateContextMenuListener(ContextMenuBuilder(fileDatabaseModel))
|
||||
// Get info from position
|
||||
val fileHistoryEntity = listDatabaseFiles[position]
|
||||
val fileDatabaseInfo = FileDatabaseInfo(context, fileHistoryEntity.databaseUri)
|
||||
|
||||
// Click item to open file
|
||||
if (fileItemOpenListener != null)
|
||||
holder.fileContainer.setOnClickListener(FileItemClickListener(position))
|
||||
// Assign file name
|
||||
if (PreferencesUtil.isFullFilePathEnable(context))
|
||||
holder.fileName.text = Uri.decode(fileDatabaseModel.fileUri.toString())
|
||||
else
|
||||
holder.fileName.text = fileDatabaseModel.fileName
|
||||
holder.fileName.textSize = PreferencesUtil.getListTextSize(context)
|
||||
holder.fileContainer.setOnClickListener {
|
||||
fileItemOpenListener?.invoke(fileHistoryEntity)
|
||||
}
|
||||
|
||||
// File alias
|
||||
holder.fileAlias.text = fileDatabaseInfo.retrieveDatabaseAlias(fileHistoryEntity.databaseAlias)
|
||||
|
||||
// File path
|
||||
holder.filePath.text = Uri.decode(fileDatabaseInfo.fileUri.toString())
|
||||
|
||||
holder.filePreciseInfoContainer.visibility = if (fileDatabaseInfo.found()) {
|
||||
// Modification
|
||||
holder.fileModification.text = fileDatabaseInfo.getModificationString()
|
||||
// Size
|
||||
holder.fileSize.text = fileDatabaseInfo.getSizeString()
|
||||
|
||||
View.VISIBLE
|
||||
} else
|
||||
View.GONE
|
||||
|
||||
// Click on information
|
||||
if (fileInformationShowListener != null)
|
||||
holder.fileInformation.setOnClickListener(FileInformationClickListener(fileDatabaseModel))
|
||||
val isExpanded = position == mExpandedPosition
|
||||
//This line hides or shows the layout in question
|
||||
holder.fileExpandContainer.visibility = if (isExpanded) View.VISIBLE else View.GONE
|
||||
|
||||
// Save alias modification
|
||||
holder.fileAliasCloseButton.setOnClickListener {
|
||||
// Change the alias
|
||||
fileHistoryEntity.databaseAlias = holder.fileAliasEdit.text.toString()
|
||||
saveAliasListener?.invoke(fileHistoryEntity)
|
||||
|
||||
// Finish save mode
|
||||
holder.fileMainSwitcher.showPrevious()
|
||||
// Refresh current position to show alias
|
||||
notifyItemChanged(position)
|
||||
}
|
||||
|
||||
// Open alias modification
|
||||
holder.fileModifyButton.setOnClickListener {
|
||||
holder.fileAliasEdit.setText(holder.fileAlias.text)
|
||||
holder.fileMainSwitcher.showNext()
|
||||
}
|
||||
|
||||
holder.fileDeleteButton.setOnClickListener {
|
||||
fileSelectClearListener?.invoke(fileHistoryEntity)
|
||||
}
|
||||
|
||||
if (isExpanded) {
|
||||
mPreviousExpandedPosition = position
|
||||
}
|
||||
|
||||
holder.fileInformation.setOnClickListener {
|
||||
mExpandedPosition = if (isExpanded) -1 else position
|
||||
|
||||
// Notify change
|
||||
if (mPreviousExpandedPosition < itemCount)
|
||||
notifyItemChanged(mPreviousExpandedPosition)
|
||||
notifyItemChanged(position)
|
||||
}
|
||||
|
||||
// Refresh View / Close alias modification if not contains fileAlias
|
||||
if (holder.fileMainSwitcher.currentView.findViewById<View>(R.id.file_alias)
|
||||
!= holder.fileAlias)
|
||||
holder.fileMainSwitcher.showPrevious()
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
return listFiles.size
|
||||
return listDatabaseFiles.size
|
||||
}
|
||||
|
||||
fun setOnItemClickListener(fileItemOpenListener: FileItemOpenListener) {
|
||||
this.fileItemOpenListener = fileItemOpenListener
|
||||
fun addDatabaseFileHistoryList(listFileDatabaseHistoryToAdd: List<FileDatabaseHistoryEntity>) {
|
||||
listDatabaseFiles.clear()
|
||||
listDatabaseFiles.addAll(listFileDatabaseHistoryToAdd)
|
||||
}
|
||||
|
||||
fun setFileSelectClearListener(fileSelectClearListener: FileSelectClearListener) {
|
||||
this.fileSelectClearListener = fileSelectClearListener
|
||||
fun deleteDatabaseFileHistory(fileDatabaseHistoryToDelete: FileDatabaseHistoryEntity) {
|
||||
listDatabaseFiles.remove(fileDatabaseHistoryToDelete)
|
||||
}
|
||||
|
||||
fun setFileInformationShowListener(fileInformationShowListener: FileInformationShowListener) {
|
||||
this.fileInformationShowListener = fileInformationShowListener
|
||||
fun setOnFileDatabaseHistoryOpenListener(listener : ((FileDatabaseHistoryEntity)->Unit)?) {
|
||||
this.fileItemOpenListener = listener
|
||||
}
|
||||
|
||||
interface FileItemOpenListener {
|
||||
fun onFileItemOpenListener(itemPosition: Int)
|
||||
fun setOnFileDatabaseHistoryDeleteListener(listener : ((FileDatabaseHistoryEntity)->Boolean)?) {
|
||||
this.fileSelectClearListener = listener
|
||||
}
|
||||
|
||||
interface FileSelectClearListener {
|
||||
fun onFileSelectClearListener(fileDatabaseModel: FileDatabaseModel): Boolean
|
||||
}
|
||||
|
||||
interface FileInformationShowListener {
|
||||
fun onClickFileInformation(fileDatabaseModel: FileDatabaseModel)
|
||||
}
|
||||
|
||||
private inner class FileItemClickListener(private val position: Int) : View.OnClickListener {
|
||||
|
||||
override fun onClick(v: View) {
|
||||
fileItemOpenListener?.onFileItemOpenListener(position)
|
||||
}
|
||||
}
|
||||
|
||||
private inner class FileInformationClickListener(private val fileDatabaseModel: FileDatabaseModel) : View.OnClickListener {
|
||||
|
||||
override fun onClick(view: View) {
|
||||
fileInformationShowListener?.onClickFileInformation(fileDatabaseModel)
|
||||
}
|
||||
}
|
||||
|
||||
private inner class ContextMenuBuilder(private val fileDatabaseModel: FileDatabaseModel) : View.OnCreateContextMenuListener {
|
||||
|
||||
private val mOnMyActionClickListener = MenuItem.OnMenuItemClickListener { item ->
|
||||
if (fileSelectClearListener == null)
|
||||
return@OnMenuItemClickListener false
|
||||
when (item.itemId) {
|
||||
MENU_CLEAR -> fileSelectClearListener!!.onFileSelectClearListener(fileDatabaseModel)
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateContextMenu(contextMenu: ContextMenu?, view: View?, contextMenuInfo: ContextMenu.ContextMenuInfo?) {
|
||||
contextMenu?.add(Menu.NONE, MENU_CLEAR, Menu.NONE, R.string.remove_from_filelist)
|
||||
?.setOnMenuItemClickListener(mOnMyActionClickListener)
|
||||
}
|
||||
fun setOnSaveAliasListener(listener : ((FileDatabaseHistoryEntity)->Unit)?) {
|
||||
this.saveAliasListener = listener
|
||||
}
|
||||
|
||||
inner class FileDatabaseHistoryViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
||||
|
||||
var fileContainer: View = itemView.findViewById(R.id.file_container)
|
||||
var fileName: TextView = itemView.findViewById(R.id.file_filename)
|
||||
var fileContainer: ViewGroup = itemView.findViewById(R.id.file_container_basic_info)
|
||||
|
||||
var fileAlias: TextView = itemView.findViewById(R.id.file_alias)
|
||||
var fileInformation: ImageView = itemView.findViewById(R.id.file_information)
|
||||
}
|
||||
|
||||
companion object {
|
||||
var fileMainSwitcher: ViewSwitcher = itemView.findViewById(R.id.file_main_switcher)
|
||||
var fileAliasEdit: EditText = itemView.findViewById(R.id.file_alias_edit)
|
||||
var fileAliasCloseButton: ImageView = itemView.findViewById(R.id.file_alias_save)
|
||||
|
||||
private const val MENU_CLEAR = 1
|
||||
var fileExpandContainer: ViewGroup = itemView.findViewById(R.id.file_expand_container)
|
||||
var fileModifyButton: ImageView = itemView.findViewById(R.id.file_modify_button)
|
||||
var fileDeleteButton: ImageView = itemView.findViewById(R.id.file_delete_button)
|
||||
var filePath: TextView = itemView.findViewById(R.id.file_path)
|
||||
var filePreciseInfoContainer: ViewGroup = itemView.findViewById(R.id.file_precise_info_container)
|
||||
var fileModification: TextView = itemView.findViewById(R.id.file_modification)
|
||||
var fileSize: TextView = itemView.findViewById(R.id.file_size)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,9 +21,9 @@ package com.kunzisoft.keepass.adapters
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Color
|
||||
import android.support.v7.util.SortedList
|
||||
import android.support.v7.widget.RecyclerView
|
||||
import android.support.v7.widget.util.SortedListAdapterCallback
|
||||
import androidx.recyclerview.widget.SortedList
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.recyclerview.widget.SortedListAdapterCallback
|
||||
import android.util.Log
|
||||
import android.view.*
|
||||
import android.widget.ImageView
|
||||
@@ -45,10 +45,13 @@ class NodeAdapter
|
||||
|
||||
private val nodeSortedList: SortedList<NodeVersioned>
|
||||
private val inflater: LayoutInflater = LayoutInflater.from(context)
|
||||
private var textSize: Float = 0.toFloat()
|
||||
private var subtextSize: Float = 0.toFloat()
|
||||
private var infoTextSize: Float = 0.toFloat()
|
||||
private var iconSize: Float = 0.toFloat()
|
||||
|
||||
private var calculateViewTypeTextSize = Array(2) { true} // number of view type
|
||||
private var prefTextSize: Float = 0F
|
||||
private var subtextSize: Float = 0F
|
||||
private var infoTextSize: Float = 0F
|
||||
private var numberChildrenTextSize: Float = 0F
|
||||
private var iconSize: Float = 0F
|
||||
private var listSort: SortNodeEnum = SortNodeEnum.DB
|
||||
private var ascendingSort: Boolean = true
|
||||
private var groupsBeforeSort: Boolean = true
|
||||
@@ -122,19 +125,16 @@ class NodeAdapter
|
||||
}
|
||||
|
||||
private fun assignPreferences() {
|
||||
val textSizeDefault = java.lang.Float.parseFloat(context.getString(R.string.list_size_default))
|
||||
this.textSize = PreferencesUtil.getListTextSize(context)
|
||||
this.subtextSize = context.resources.getInteger(R.integer.list_small_size_default) * textSize / textSizeDefault
|
||||
this.infoTextSize = context.resources.getInteger(R.integer.list_tiny_size_default) * textSize / textSizeDefault
|
||||
// Retrieve the icon size
|
||||
val iconDefaultSize = context.resources.getDimension(R.dimen.list_icon_size_default)
|
||||
this.iconSize = iconDefaultSize * textSize / textSizeDefault
|
||||
this.prefTextSize = PreferencesUtil.getListTextSize(context) / java.lang.Float.parseFloat(context.getString(R.string.list_size_default))
|
||||
this.listSort = PreferencesUtil.getListSort(context)
|
||||
this.ascendingSort = PreferencesUtil.getAscendingSort(context)
|
||||
this.groupsBeforeSort = PreferencesUtil.getGroupsBeforeSort(context)
|
||||
this.recycleBinBottomSort = PreferencesUtil.getRecycleBinBottomSort(context)
|
||||
this.showUserNames = PreferencesUtil.showUsernamesListEntries(context)
|
||||
this.showNumberEntries = PreferencesUtil.showNumberEntries(context)
|
||||
|
||||
// Reinit textSize for all view type
|
||||
calculateViewTypeTextSize.forEachIndexed { index, _ -> calculateViewTypeTextSize[index] = true }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -212,16 +212,41 @@ class NodeAdapter
|
||||
return NodeViewHolder(view)
|
||||
}
|
||||
|
||||
private fun calculateTextSize(holder: NodeViewHolder, viewType: Int) {
|
||||
if (calculateViewTypeTextSize[viewType]) {
|
||||
this.subtextSize = holder.subText.textSize * prefTextSize
|
||||
this.infoTextSize = holder.text.textSize * prefTextSize
|
||||
holder.numberChildren?.let {
|
||||
this.numberChildrenTextSize = it.textSize * prefTextSize
|
||||
}
|
||||
this.iconSize = context.resources.getDimension(R.dimen.list_icon_size_default) * prefTextSize
|
||||
calculateViewTypeTextSize[viewType] = false
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: NodeViewHolder, position: Int) {
|
||||
val subNode = nodeSortedList.get(position)
|
||||
|
||||
calculateTextSize(holder, getItemViewType(position))
|
||||
|
||||
// Assign image
|
||||
val iconColor = when (subNode.type) {
|
||||
Type.GROUP -> iconGroupColor
|
||||
Type.ENTRY -> iconEntryColor
|
||||
}
|
||||
holder.icon.assignDatabaseIcon(mDatabase.drawFactory, subNode.icon, iconColor)
|
||||
holder.icon.apply {
|
||||
assignDatabaseIcon(mDatabase.drawFactory, subNode.icon, iconColor)
|
||||
// Relative size of the icon
|
||||
layoutParams?.apply {
|
||||
height = iconSize.toInt()
|
||||
width = iconSize.toInt()
|
||||
}
|
||||
}
|
||||
// Assign text
|
||||
holder.text.text = subNode.title
|
||||
holder.text.apply {
|
||||
text = subNode.title
|
||||
textSize = infoTextSize
|
||||
}
|
||||
// Assign click
|
||||
holder.container.setOnClickListener { nodeClickCallback?.onNodeClick(subNode) }
|
||||
// Context menu
|
||||
@@ -230,9 +255,10 @@ class NodeAdapter
|
||||
ContextMenuBuilder(menuInflater, subNode, readOnly, isASearchResult, nodeMenuListener))
|
||||
}
|
||||
|
||||
// Add username
|
||||
holder.subText.text = ""
|
||||
holder.subText.visibility = View.GONE
|
||||
// Add subText with username
|
||||
holder.subText.apply {
|
||||
text = ""
|
||||
visibility = View.GONE
|
||||
if (subNode.type == Type.ENTRY) {
|
||||
val entry = subNode as EntryVersioned
|
||||
|
||||
@@ -242,24 +268,21 @@ class NodeAdapter
|
||||
|
||||
val username = entry.username
|
||||
if (showUserNames && username.isNotEmpty()) {
|
||||
holder.subText.visibility = View.VISIBLE
|
||||
holder.subText.text = username
|
||||
visibility = View.VISIBLE
|
||||
text = username
|
||||
}
|
||||
|
||||
mDatabase.stopManageEntry(entry)
|
||||
}
|
||||
textSize = subtextSize
|
||||
}
|
||||
|
||||
// Assign image and text size
|
||||
// Relative size of the icon
|
||||
holder.icon.layoutParams?.height = iconSize.toInt()
|
||||
holder.icon.layoutParams?.width = iconSize.toInt()
|
||||
holder.text.textSize = textSize
|
||||
holder.subText.textSize = subtextSize
|
||||
// Add number of entries in groups
|
||||
if (subNode.type == Type.GROUP) {
|
||||
if (showNumberEntries) {
|
||||
holder.numberChildren?.apply {
|
||||
text = (subNode as GroupVersioned).getChildEntries(true).size.toString()
|
||||
textSize = infoTextSize
|
||||
textSize = numberChildrenTextSize
|
||||
visibility = View.VISIBLE
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -22,7 +22,7 @@ package com.kunzisoft.keepass.adapters
|
||||
import android.content.Context
|
||||
import android.database.Cursor
|
||||
import android.graphics.Color
|
||||
import android.support.v4.widget.CursorAdapter
|
||||
import androidx.cursoradapter.widget.CursorAdapter
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
@@ -38,7 +38,7 @@ import com.kunzisoft.keepass.settings.PreferencesUtil
|
||||
import java.util.*
|
||||
|
||||
class SearchEntryCursorAdapter(context: Context, private val database: Database)
|
||||
: CursorAdapter(context, null, FLAG_REGISTER_CONTENT_OBSERVER) {
|
||||
: androidx.cursoradapter.widget.CursorAdapter(context, null, FLAG_REGISTER_CONTENT_OBSERVER) {
|
||||
|
||||
private val cursorInflater: LayoutInflater = context.getSystemService(
|
||||
Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
*/
|
||||
package com.kunzisoft.keepass.app
|
||||
|
||||
import android.support.multidex.MultiDexApplication
|
||||
import androidx.multidex.MultiDexApplication
|
||||
import com.kunzisoft.keepass.activities.stylish.Stylish
|
||||
import com.kunzisoft.keepass.database.element.Database
|
||||
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,7 @@ import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.service.autofill.Dataset
|
||||
import android.service.autofill.FillResponse
|
||||
import android.support.annotation.RequiresApi
|
||||
import androidx.annotation.RequiresApi
|
||||
import android.util.Log
|
||||
import android.view.autofill.AutofillManager
|
||||
import android.view.autofill.AutofillValue
|
||||
|
||||
@@ -26,8 +26,8 @@ import android.content.Intent
|
||||
import android.content.IntentSender
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.support.annotation.RequiresApi
|
||||
import android.support.v7.app.AppCompatActivity
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import com.kunzisoft.keepass.activities.FileDatabaseSelectActivity
|
||||
import com.kunzisoft.keepass.activities.GroupActivity
|
||||
import com.kunzisoft.keepass.database.element.Database
|
||||
|
||||
@@ -22,7 +22,7 @@ package com.kunzisoft.keepass.autofill
|
||||
import android.os.Build
|
||||
import android.os.CancellationSignal
|
||||
import android.service.autofill.*
|
||||
import android.support.annotation.RequiresApi
|
||||
import androidx.annotation.RequiresApi
|
||||
import android.util.Log
|
||||
import android.widget.RemoteViews
|
||||
import com.kunzisoft.keepass.R
|
||||
|
||||
@@ -20,7 +20,7 @@ package com.kunzisoft.keepass.autofill
|
||||
|
||||
import android.app.assist.AssistStructure
|
||||
import android.os.Build
|
||||
import android.support.annotation.RequiresApi
|
||||
import androidx.annotation.RequiresApi
|
||||
import android.text.InputType
|
||||
import android.util.Log
|
||||
import android.view.View
|
||||
|
||||
@@ -26,6 +26,8 @@ import android.app.backup.SharedPreferencesBackupHelper
|
||||
@SuppressLint("NewApi")
|
||||
class SettingsBackupAgent : BackupAgentHelper() {
|
||||
|
||||
//TODO Backup
|
||||
|
||||
override fun onCreate() {
|
||||
val defaultPrefs = this.packageName + "_preferences"
|
||||
val prefHelper = SharedPreferencesBackupHelper(this, defaultPrefs)
|
||||
|
||||
@@ -22,12 +22,12 @@ package com.kunzisoft.keepass.database.action
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.preference.PreferenceManager
|
||||
import android.support.annotation.StringRes
|
||||
import androidx.annotation.StringRes
|
||||
import android.util.Log
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.database.element.Database
|
||||
import com.kunzisoft.keepass.database.exception.*
|
||||
import com.kunzisoft.keepass.fileselect.database.FileDatabaseHistory
|
||||
import com.kunzisoft.keepass.app.database.FileDatabaseHistory
|
||||
import com.kunzisoft.keepass.tasks.ActionRunnable
|
||||
import com.kunzisoft.keepass.tasks.ProgressTaskUpdater
|
||||
import java.io.FileNotFoundException
|
||||
@@ -119,7 +119,9 @@ class LoadDatabaseRunnable(private val mWeakContext: WeakReference<Context>,
|
||||
if (!mRememberKeyFile) {
|
||||
keyFileUri = null
|
||||
}
|
||||
FileDatabaseHistory.getInstance(mWeakContext).addDatabaseUri(uri, keyFileUri)
|
||||
mWeakContext.get()?.let {
|
||||
FileDatabaseHistory.getInstance(it).addOrUpdateDatabaseUri(uri, keyFileUri)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFinishRun(result: Result) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package com.kunzisoft.keepass.database.action
|
||||
|
||||
import android.support.v4.app.FragmentActivity
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.tasks.ActionRunnable
|
||||
import com.kunzisoft.keepass.tasks.ProgressTaskUpdater
|
||||
|
||||
@@ -3,8 +3,8 @@ package com.kunzisoft.keepass.database.action
|
||||
import android.content.Intent
|
||||
import android.os.AsyncTask
|
||||
import android.os.Build
|
||||
import android.support.annotation.StringRes
|
||||
import android.support.v4.app.FragmentActivity
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService
|
||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.DATABASE_TASK_TITLE_KEY
|
||||
import com.kunzisoft.keepass.tasks.ActionRunnable
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package com.kunzisoft.keepass.database.action.node
|
||||
|
||||
import android.support.v4.app.FragmentActivity
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import android.util.Log
|
||||
import com.kunzisoft.keepass.database.action.SaveDatabaseRunnable
|
||||
import com.kunzisoft.keepass.database.element.Database
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
*/
|
||||
package com.kunzisoft.keepass.database.action.node
|
||||
|
||||
import android.support.v4.app.FragmentActivity
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import com.kunzisoft.keepass.database.element.Database
|
||||
import com.kunzisoft.keepass.database.element.EntryVersioned
|
||||
import com.kunzisoft.keepass.database.element.GroupVersioned
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
*/
|
||||
package com.kunzisoft.keepass.database.action.node
|
||||
|
||||
import android.support.v4.app.FragmentActivity
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import com.kunzisoft.keepass.database.element.Database
|
||||
import com.kunzisoft.keepass.database.element.GroupVersioned
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
*/
|
||||
package com.kunzisoft.keepass.database.action.node
|
||||
|
||||
import android.support.v4.app.FragmentActivity
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import android.util.Log
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.database.element.Database
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
package com.kunzisoft.keepass.database.action.node
|
||||
|
||||
import android.os.Bundle
|
||||
import android.support.v4.app.FragmentActivity
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import com.kunzisoft.keepass.database.element.Database
|
||||
import com.kunzisoft.keepass.database.element.EntryVersioned
|
||||
import com.kunzisoft.keepass.database.element.GroupVersioned
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
package com.kunzisoft.keepass.database.action.node
|
||||
|
||||
import android.os.Bundle
|
||||
import android.support.v4.app.FragmentActivity
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
|
||||
import com.kunzisoft.keepass.database.element.Database
|
||||
import com.kunzisoft.keepass.database.element.GroupVersioned
|
||||
@@ -61,10 +61,11 @@ class DeleteGroupRunnable(context: FragmentActivity,
|
||||
mParent?.let {
|
||||
database.undoRecycle(mGroupToDelete, it)
|
||||
}
|
||||
} else {
|
||||
}
|
||||
// else {
|
||||
// Let's not bother recovering from a failure to save a deleted tree. It is too much work.
|
||||
// TODO database.undoDeleteGroupFrom(mGroup, mParent);
|
||||
}
|
||||
// }
|
||||
}
|
||||
|
||||
// Add position in bundle to delete the node in view
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
*/
|
||||
package com.kunzisoft.keepass.database.action.node
|
||||
|
||||
import android.support.v4.app.FragmentActivity
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import android.util.Log
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.database.element.Database
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
*/
|
||||
package com.kunzisoft.keepass.database.action.node
|
||||
|
||||
import android.support.v4.app.FragmentActivity
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import android.util.Log
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.database.element.Database
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
*/
|
||||
package com.kunzisoft.keepass.database.action.node
|
||||
|
||||
import android.support.v4.app.FragmentActivity
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import com.kunzisoft.keepass.database.element.Database
|
||||
import com.kunzisoft.keepass.database.element.EntryVersioned
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
*/
|
||||
package com.kunzisoft.keepass.database.action.node
|
||||
|
||||
import android.support.v4.app.FragmentActivity
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import com.kunzisoft.keepass.database.element.Database
|
||||
import com.kunzisoft.keepass.database.element.GroupVersioned
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ package com.kunzisoft.keepass.education
|
||||
|
||||
import android.app.Activity
|
||||
import android.graphics.Color
|
||||
import android.support.v4.content.ContextCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import android.view.View
|
||||
import com.getkeepsafe.taptargetview.TapTarget
|
||||
import com.getkeepsafe.taptargetview.TapTargetView
|
||||
|
||||
@@ -2,7 +2,7 @@ package com.kunzisoft.keepass.education
|
||||
|
||||
import android.app.Activity
|
||||
import android.graphics.Color
|
||||
import android.support.v4.content.ContextCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import android.view.View
|
||||
import com.getkeepsafe.taptargetview.TapTarget
|
||||
import com.getkeepsafe.taptargetview.TapTargetView
|
||||
@@ -11,7 +11,7 @@ import com.kunzisoft.keepass.R
|
||||
class PasswordActivityEducation(activity: Activity)
|
||||
: Education(activity) {
|
||||
|
||||
fun checkAndPerformedFingerprintUnlockEducation(educationView: View,
|
||||
fun checkAndPerformedUnlockEducation(educationView: View,
|
||||
onEducationViewClick: ((TapTargetView?) -> Unit)? = null,
|
||||
onOuterViewClick: ((TapTargetView?) -> Unit)? = null): Boolean {
|
||||
return checkAndPerformedEducation(!isEducationUnlockPerformed(activity),
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,7 +24,7 @@ import android.graphics.drawable.Animatable2
|
||||
import android.graphics.drawable.AnimatedVectorDrawable
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.os.Build
|
||||
import android.support.annotation.RequiresApi
|
||||
import androidx.annotation.RequiresApi
|
||||
import android.widget.ImageView
|
||||
|
||||
import com.kunzisoft.keepass.R
|
||||
|
||||
@@ -23,9 +23,9 @@ import android.app.Dialog
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.support.annotation.RequiresApi
|
||||
import android.support.v4.app.DialogFragment
|
||||
import android.support.v7.app.AlertDialog
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import android.view.View
|
||||
import com.kunzisoft.keepass.R
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ import android.os.CancellationSignal
|
||||
import android.security.keystore.KeyGenParameterSpec
|
||||
import android.security.keystore.KeyPermanentlyInvalidatedException
|
||||
import android.security.keystore.KeyProperties
|
||||
import android.support.annotation.RequiresApi
|
||||
import androidx.annotation.RequiresApi
|
||||
import android.util.Base64
|
||||
import android.util.Log
|
||||
|
||||
@@ -289,7 +289,7 @@ class FingerPrintHelper(context: Context, private val fingerPrintCallback: Finge
|
||||
// fingerprint hardware supported and api level OK
|
||||
return (isFingerprintSupported(fingerprintManager)
|
||||
// fingerprints enrolled
|
||||
&& fingerprintManager != null && fingerprintManager!!.hasEnrolledFingerprints()
|
||||
&& fingerprintManager != null && fingerprintManager.hasEnrolledFingerprints()
|
||||
// and lockscreen configured
|
||||
&& keyguardManager != null && keyguardManager!!.isKeyguardSecure)
|
||||
}
|
||||
|
||||
@@ -6,8 +6,8 @@ import android.hardware.fingerprint.FingerprintManager
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Handler
|
||||
import android.support.annotation.RequiresApi
|
||||
import android.support.v7.app.AppCompatActivity
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import android.util.Log
|
||||
import android.view.Menu
|
||||
import android.view.MenuInflater
|
||||
|
||||
@@ -28,8 +28,8 @@ import android.graphics.Color
|
||||
import android.graphics.drawable.BitmapDrawable
|
||||
import android.graphics.drawable.ColorDrawable
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.support.v4.content.res.ResourcesCompat
|
||||
import android.support.v4.widget.ImageViewCompat
|
||||
import androidx.core.content.res.ResourcesCompat
|
||||
import androidx.core.widget.ImageViewCompat
|
||||
import android.util.Log
|
||||
import android.widget.ImageView
|
||||
import com.kunzisoft.keepass.R
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.kunzisoft.keepass.magikeyboard
|
||||
|
||||
import android.os.Bundle
|
||||
import android.support.v7.app.AppCompatActivity
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import com.kunzisoft.keepass.activities.FileDatabaseSelectActivity
|
||||
import com.kunzisoft.keepass.activities.GroupActivity
|
||||
import com.kunzisoft.keepass.database.element.Database
|
||||
@@ -10,10 +10,6 @@ import com.kunzisoft.keepass.timeout.TimeoutHelper
|
||||
|
||||
class KeyboardLauncherActivity : AppCompatActivity() {
|
||||
|
||||
companion object {
|
||||
val TAG = KeyboardLauncherActivity::class.java.name!!
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
if (Database.getInstance().loaded && TimeoutHelper.checkTime(this))
|
||||
GroupActivity.launchForKeyboardSelection(this, PreferencesUtil.enableReadOnlyDatabase(this))
|
||||
|
||||
@@ -27,8 +27,6 @@ import android.inputmethodservice.InputMethodService
|
||||
import android.inputmethodservice.Keyboard
|
||||
import android.inputmethodservice.KeyboardView
|
||||
import android.media.AudioManager
|
||||
import android.support.v7.widget.LinearLayoutManager
|
||||
import android.support.v7.widget.RecyclerView
|
||||
import android.util.Log
|
||||
import android.view.*
|
||||
import android.view.inputmethod.EditorInfo
|
||||
@@ -37,7 +35,7 @@ import android.widget.FrameLayout
|
||||
import android.widget.PopupWindow
|
||||
import android.widget.TextView
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.magikeyboard.adapter.FieldsAdapter
|
||||
import com.kunzisoft.keepass.adapters.FieldsAdapter
|
||||
import com.kunzisoft.keepass.model.EntryInfo
|
||||
import com.kunzisoft.keepass.model.Field
|
||||
import com.kunzisoft.keepass.notifications.KeyboardEntryNotificationService
|
||||
@@ -107,14 +105,14 @@ class MagikIME : InputMethodService(), KeyboardView.OnKeyboardActionListener {
|
||||
popupCustomKeys?.inputMethodMode = PopupWindow.INPUT_METHOD_NEEDED
|
||||
popupCustomKeys?.contentView = popupFieldsView
|
||||
|
||||
val recyclerView = popupFieldsView.findViewById<RecyclerView>(R.id.keyboard_popup_fields_list)
|
||||
val recyclerView = popupFieldsView.findViewById<androidx.recyclerview.widget.RecyclerView>(R.id.keyboard_popup_fields_list)
|
||||
fieldsAdapter = FieldsAdapter(this)
|
||||
fieldsAdapter?.onItemClickListener = object : FieldsAdapter.OnItemClickListener {
|
||||
override fun onItemClick(item: Field) {
|
||||
currentInputConnection.commitText(item.protectedValue.toString(), 1)
|
||||
}
|
||||
}
|
||||
recyclerView.layoutManager = LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, true)
|
||||
recyclerView.layoutManager = androidx.recyclerview.widget.LinearLayoutManager(this, androidx.recyclerview.widget.LinearLayoutManager.HORIZONTAL, true)
|
||||
recyclerView.adapter = fieldsAdapter
|
||||
|
||||
val closeView = popupFieldsView.findViewById<View>(R.id.keyboard_popup_close)
|
||||
@@ -194,7 +192,7 @@ class MagikIME : InputMethodService(), KeyboardView.OnKeyboardActionListener {
|
||||
KEY_BACK_KEYBOARD -> try {
|
||||
val imeManager = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||
if (window.window != null)
|
||||
imeManager.switchToLastInputMethod(window.window!!.attributes.token)
|
||||
imeManager.switchToLastInputMethod(window.window!!.attributes.token) // TODO Deprecated
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Unable to switch to the previous IME", e)
|
||||
val imeManager = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||
|
||||
@@ -5,7 +5,7 @@ import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.support.v7.preference.PreferenceManager
|
||||
import androidx.preference.PreferenceManager
|
||||
import android.util.Log
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.magikeyboard.MagikIME
|
||||
@@ -103,7 +103,9 @@ class KeyboardEntryNotificationService : NotificationService() {
|
||||
val keyboardTimeout = sharedPreferences.getString(getString(R.string.keyboard_entry_timeout_key),
|
||||
getString(R.string.timeout_default))
|
||||
notificationTimeoutMilliSecs = try {
|
||||
keyboardTimeout?.let {
|
||||
java.lang.Long.parseLong(keyboardTimeout)
|
||||
} ?: 0
|
||||
} catch (e: NumberFormatException) {
|
||||
TimeoutHelper.DEFAULT_TIMEOUT
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.os.IBinder
|
||||
import android.support.v4.app.NotificationCompat
|
||||
import androidx.core.app.NotificationCompat
|
||||
import android.util.TypedValue
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.activities.stylish.Stylish
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
package com.kunzisoft.keepass.settings
|
||||
|
||||
import android.os.Bundle
|
||||
import android.support.v7.widget.Toolbar
|
||||
import androidx.appcompat.widget.Toolbar
|
||||
import android.view.MenuItem
|
||||
|
||||
import com.kunzisoft.keepass.R
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
package com.kunzisoft.keepass.settings
|
||||
|
||||
import android.os.Bundle
|
||||
import android.support.v7.preference.PreferenceFragmentCompat
|
||||
import androidx.preference.PreferenceFragmentCompat
|
||||
|
||||
import com.kunzisoft.keepass.R
|
||||
|
||||
|
||||
@@ -21,8 +21,8 @@ package com.kunzisoft.keepass.settings
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.support.v7.preference.Preference
|
||||
import android.support.v7.preference.PreferenceFragmentCompat
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceFragmentCompat
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.activities.dialogs.AssignMasterKeyDialogFragment
|
||||
import com.kunzisoft.keepass.database.element.Database
|
||||
|
||||
@@ -27,13 +27,13 @@ import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.provider.Settings
|
||||
import android.support.annotation.RequiresApi
|
||||
import android.support.v14.preference.SwitchPreference
|
||||
import android.support.v4.app.DialogFragment
|
||||
import android.support.v7.app.AlertDialog
|
||||
import android.support.v7.preference.Preference
|
||||
import android.support.v7.preference.PreferenceCategory
|
||||
import android.support.v7.preference.PreferenceFragmentCompat
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.preference.SwitchPreference
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceCategory
|
||||
import androidx.preference.PreferenceFragmentCompat
|
||||
import android.util.Log
|
||||
import android.view.autofill.AutofillManager
|
||||
import android.widget.Toast
|
||||
@@ -45,14 +45,13 @@ import com.kunzisoft.keepass.activities.dialogs.UnavailableFeatureDialogFragment
|
||||
import com.kunzisoft.keepass.activities.dialogs.UnderDevelopmentFeatureDialogFragment
|
||||
import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper
|
||||
import com.kunzisoft.keepass.activities.stylish.Stylish
|
||||
import com.kunzisoft.keepass.app.database.FileDatabaseHistory
|
||||
import com.kunzisoft.keepass.database.element.Database
|
||||
import com.kunzisoft.keepass.education.Education
|
||||
import com.kunzisoft.keepass.fileselect.database.FileDatabaseHistory
|
||||
import com.kunzisoft.keepass.fingerprint.FingerPrintHelper
|
||||
import com.kunzisoft.keepass.fingerprint.FingerPrintViewsManager
|
||||
import com.kunzisoft.keepass.icons.IconPackChooser
|
||||
import com.kunzisoft.keepass.settings.preferencedialogfragment.*
|
||||
import java.lang.ref.WeakReference
|
||||
|
||||
class NestedSettingsFragment : PreferenceFragmentCompat(), Preference.OnPreferenceClickListener {
|
||||
|
||||
@@ -119,7 +118,7 @@ class NestedSettingsFragment : PreferenceFragmentCompat(), Preference.OnPreferen
|
||||
val keyFile = findPreference(getString(R.string.keyfile_key))
|
||||
keyFile.setOnPreferenceChangeListener { _, newValue ->
|
||||
if (!(newValue as Boolean)) {
|
||||
FileDatabaseHistory.getInstance(WeakReference(activity.applicationContext)).deleteAllKeys()
|
||||
FileDatabaseHistory.getInstance(activity.applicationContext).deleteAllKeyFiles()
|
||||
}
|
||||
true
|
||||
}
|
||||
@@ -127,24 +126,7 @@ class NestedSettingsFragment : PreferenceFragmentCompat(), Preference.OnPreferen
|
||||
val recentHistory = findPreference(getString(R.string.recentfile_key))
|
||||
recentHistory.setOnPreferenceChangeListener { _, newValue ->
|
||||
if (!(newValue as Boolean)) {
|
||||
FileDatabaseHistory.getInstance(WeakReference(activity.applicationContext)).deleteAll()
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
val storageAccessFramework = findPreference(getString(R.string.saf_key)) as SwitchPreference
|
||||
storageAccessFramework.setOnPreferenceChangeListener { _, newValue ->
|
||||
if (!(newValue as Boolean) && context != null) {
|
||||
val alertDialog = AlertDialog.Builder(context!!)
|
||||
.setMessage(getString(R.string.warning_disabling_storage_access_framework)).create()
|
||||
alertDialog.setButton(AlertDialog.BUTTON_POSITIVE, getText(android.R.string.ok)
|
||||
) { dialog, _ -> dialog.dismiss() }
|
||||
alertDialog.setButton(AlertDialog.BUTTON_NEGATIVE, getText(android.R.string.cancel)
|
||||
) { dialog, _ ->
|
||||
storageAccessFramework.isChecked = true
|
||||
dialog.dismiss()
|
||||
}
|
||||
alertDialog.show()
|
||||
FileDatabaseHistory.getInstance(activity.applicationContext).deleteAll()
|
||||
}
|
||||
true
|
||||
}
|
||||
@@ -418,11 +400,11 @@ class NestedSettingsFragment : PreferenceFragmentCompat(), Preference.OnPreferen
|
||||
.setMessage(message)
|
||||
.create()
|
||||
.apply {
|
||||
setButton(AlertDialog.BUTTON_POSITIVE, getText(android.R.string.ok))
|
||||
setButton(AlertDialog.BUTTON_POSITIVE, getText(R.string.enable))
|
||||
{ dialog, _ ->
|
||||
dialog.dismiss()
|
||||
}
|
||||
setButton(AlertDialog.BUTTON_NEGATIVE, getText(android.R.string.cancel))
|
||||
setButton(AlertDialog.BUTTON_NEGATIVE, getText(R.string.disable))
|
||||
{ dialog, _ ->
|
||||
copyPasswordPreference.isChecked = false
|
||||
dialog.dismiss()
|
||||
|
||||
@@ -28,6 +28,11 @@ import java.util.*
|
||||
|
||||
object PreferencesUtil {
|
||||
|
||||
fun showReadOnlyWarning(context: Context): Boolean {
|
||||
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
return prefs.getBoolean(context.getString(R.string.show_read_only_warning), true)
|
||||
}
|
||||
|
||||
fun omitBackup(context: Context): Boolean {
|
||||
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
return prefs.getBoolean(context.getString(R.string.omitbackup_key),
|
||||
|
||||
@@ -25,8 +25,8 @@ import android.content.DialogInterface
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.support.v4.app.Fragment
|
||||
import android.support.v7.widget.Toolbar
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.appcompat.widget.Toolbar
|
||||
import android.view.MenuItem
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.activities.dialogs.AssignMasterKeyDialogFragment
|
||||
@@ -38,8 +38,10 @@ import com.kunzisoft.keepass.database.action.ProgressDialogSaveDatabaseThread
|
||||
import com.kunzisoft.keepass.database.element.Database
|
||||
import com.kunzisoft.keepass.timeout.TimeoutHelper
|
||||
|
||||
|
||||
open class SettingsActivity : LockingActivity(), MainPreferenceFragment.Callback, AssignMasterKeyDialogFragment.AssignPasswordDialogListener {
|
||||
open class SettingsActivity
|
||||
: LockingActivity(),
|
||||
MainPreferenceFragment.Callback,
|
||||
AssignMasterKeyDialogFragment.AssignPasswordDialogListener {
|
||||
|
||||
private var backupManager: BackupManager? = null
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
package com.kunzisoft.keepass.settings
|
||||
|
||||
import android.os.Bundle
|
||||
import android.support.v4.app.Fragment
|
||||
import androidx.fragment.app.Fragment
|
||||
|
||||
class SettingsAutofillActivity : SettingsActivity() {
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.kunzisoft.keepass.settings.preference
|
||||
|
||||
import android.content.Context
|
||||
import android.support.v7.preference.DialogPreference
|
||||
import androidx.preference.DialogPreference
|
||||
import android.util.AttributeSet
|
||||
|
||||
import com.kunzisoft.keepass.R
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.kunzisoft.keepass.settings.preference
|
||||
|
||||
import android.content.Context
|
||||
import android.support.v7.preference.ListPreference
|
||||
import androidx.preference.ListPreference
|
||||
import android.util.AttributeSet
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.icons.IconPackChooser
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.kunzisoft.keepass.settings.preference
|
||||
|
||||
import android.content.Context
|
||||
import android.support.v7.preference.DialogPreference
|
||||
import androidx.preference.DialogPreference
|
||||
import android.util.AttributeSet
|
||||
|
||||
import com.kunzisoft.keepass.R
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.kunzisoft.keepass.settings.preference
|
||||
|
||||
import android.content.Context
|
||||
import android.support.v7.preference.DialogPreference
|
||||
import androidx.preference.DialogPreference
|
||||
import android.util.AttributeSet
|
||||
|
||||
import com.kunzisoft.keepass.R
|
||||
|
||||
@@ -20,15 +20,17 @@
|
||||
package com.kunzisoft.keepass.settings.preferencedialogfragment
|
||||
|
||||
import android.os.Bundle
|
||||
import android.support.v7.widget.LinearLayoutManager
|
||||
import android.support.v7.widget.RecyclerView
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import android.view.View
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.database.element.PwEncryptionAlgorithm
|
||||
import com.kunzisoft.keepass.settings.preferencedialogfragment.adapter.ListRadioItemAdapter
|
||||
import com.kunzisoft.keepass.tasks.ActionRunnable
|
||||
|
||||
class DatabaseEncryptionAlgorithmPreferenceDialogFragmentCompat : DatabaseSavePreferenceDialogFragmentCompat(), ListRadioItemAdapter.RadioItemSelectedCallback<PwEncryptionAlgorithm> {
|
||||
class DatabaseEncryptionAlgorithmPreferenceDialogFragmentCompat
|
||||
: DatabaseSavePreferenceDialogFragmentCompat(),
|
||||
ListRadioItemAdapter.RadioItemSelectedCallback<PwEncryptionAlgorithm> {
|
||||
|
||||
private var algorithmSelected: PwEncryptionAlgorithm? = null
|
||||
|
||||
|
||||
@@ -20,16 +20,18 @@
|
||||
package com.kunzisoft.keepass.settings.preferencedialogfragment
|
||||
|
||||
import android.os.Bundle
|
||||
import android.support.v7.preference.Preference
|
||||
import android.support.v7.widget.LinearLayoutManager
|
||||
import android.support.v7.widget.RecyclerView
|
||||
import androidx.preference.Preference
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import android.view.View
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.crypto.keyDerivation.KdfEngine
|
||||
import com.kunzisoft.keepass.settings.preferencedialogfragment.adapter.ListRadioItemAdapter
|
||||
import com.kunzisoft.keepass.tasks.ActionRunnable
|
||||
|
||||
class DatabaseKeyDerivationPreferenceDialogFragmentCompat : DatabaseSavePreferenceDialogFragmentCompat(), ListRadioItemAdapter.RadioItemSelectedCallback<KdfEngine> {
|
||||
class DatabaseKeyDerivationPreferenceDialogFragmentCompat
|
||||
: DatabaseSavePreferenceDialogFragmentCompat(),
|
||||
ListRadioItemAdapter.RadioItemSelectedCallback<KdfEngine> {
|
||||
|
||||
private var kdfEngineSelected: KdfEngine? = null
|
||||
private var roundPreference: Preference? = null
|
||||
|
||||
@@ -19,8 +19,8 @@
|
||||
*/
|
||||
package com.kunzisoft.keepass.settings.preferencedialogfragment
|
||||
|
||||
import android.support.annotation.StringRes
|
||||
import android.support.v7.preference.PreferenceDialogFragmentCompat
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.preference.PreferenceDialogFragmentCompat
|
||||
import android.view.View
|
||||
import android.widget.TextView
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
package com.kunzisoft.keepass.settings.preferencedialogfragment.adapter
|
||||
|
||||
import android.content.Context
|
||||
import android.support.v7.widget.RecyclerView
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
|
||||
@@ -23,16 +23,16 @@ import android.annotation.SuppressLint
|
||||
import android.app.Dialog
|
||||
import android.content.DialogInterface
|
||||
import android.os.Bundle
|
||||
import android.support.annotation.StringRes
|
||||
import android.support.v4.app.DialogFragment
|
||||
import android.support.v4.app.FragmentActivity
|
||||
import android.support.v7.app.AlertDialog
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import android.view.View
|
||||
import android.widget.ProgressBar
|
||||
import android.widget.TextView
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.utils.lockScreenOrientation
|
||||
import com.kunzisoft.keepass.utils.unlockScreenOrientation
|
||||
import com.kunzisoft.keepass.view.lockScreenOrientation
|
||||
import com.kunzisoft.keepass.view.unlockScreenOrientation
|
||||
|
||||
open class ProgressTaskDialogFragment : DialogFragment(), ProgressTaskUpdater {
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
*/
|
||||
package com.kunzisoft.keepass.tasks
|
||||
|
||||
import android.support.annotation.StringRes
|
||||
import androidx.annotation.StringRes
|
||||
|
||||
interface ProgressTaskUpdater {
|
||||
fun updateMessage(@StringRes resId: Int)
|
||||
|
||||
@@ -26,6 +26,7 @@ import android.content.ClipboardManager
|
||||
import android.content.Context
|
||||
import android.os.Handler
|
||||
import android.preference.PreferenceManager
|
||||
import androidx.annotation.IntegerRes
|
||||
import android.text.SpannableString
|
||||
import android.text.method.LinkMovementMethod
|
||||
import android.text.util.Linkify
|
||||
@@ -46,7 +47,7 @@ class ClipboardHelper(private val context: Context) {
|
||||
|
||||
@JvmOverloads
|
||||
fun timeoutCopyToClipboard(text: String, toastString: String = "") {
|
||||
if (!toastString.isEmpty())
|
||||
if (toastString.isNotEmpty())
|
||||
Toast.makeText(context, toastString, Toast.LENGTH_LONG).show()
|
||||
try {
|
||||
copyToClipboard(text)
|
||||
@@ -107,38 +108,37 @@ class ClipboardHelper(private val context: Context) {
|
||||
override fun run() {
|
||||
val currentClip = getClipboard(mCtx).toString()
|
||||
if (currentClip == mClearText) {
|
||||
try {
|
||||
|
||||
@IntegerRes
|
||||
val stringErrorId = try {
|
||||
cleanClipboard()
|
||||
uiThreadCallback.post {
|
||||
Toast.makeText(mCtx,
|
||||
R.string.clipboard_cleared,
|
||||
Toast.LENGTH_LONG).show()
|
||||
}
|
||||
R.string.clipboard_cleared
|
||||
} catch (e: SamsungClipboardException) {
|
||||
uiThreadCallback.post {
|
||||
Toast.makeText(mCtx,
|
||||
R.string.clipboard_error_clear,
|
||||
Toast.LENGTH_LONG).show()
|
||||
R.string.clipboard_error_clear
|
||||
}
|
||||
uiThreadCallback.post {
|
||||
Toast.makeText(mCtx, stringErrorId, Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun showSamsungDialog() {
|
||||
val text = context.getString(R.string.clipboard_error)+
|
||||
val textDescription = context.getString(R.string.clipboard_error)+
|
||||
System.getProperty("line.separator") +
|
||||
context.getString(R.string.clipboard_error_url)
|
||||
val s = SpannableString(text)
|
||||
val tv = TextView(context)
|
||||
tv.text = s
|
||||
tv.autoLinkMask = Activity.RESULT_OK
|
||||
tv.movementMethod = LinkMovementMethod.getInstance()
|
||||
Linkify.addLinks(s, Linkify.WEB_URLS)
|
||||
val builder = AlertDialog.Builder(context)
|
||||
builder.setTitle(R.string.clipboard_error_title)
|
||||
val spannableString = SpannableString(textDescription)
|
||||
val textView = TextView(context).apply {
|
||||
text = spannableString
|
||||
autoLinkMask = Activity.RESULT_OK
|
||||
movementMethod = LinkMovementMethod.getInstance()
|
||||
}
|
||||
|
||||
Linkify.addLinks(spannableString, Linkify.WEB_URLS)
|
||||
AlertDialog.Builder(context)
|
||||
.setTitle(R.string.clipboard_error_title)
|
||||
.setView(textView)
|
||||
.setPositiveButton(android.R.string.ok) { dialog, _ -> dialog.dismiss() }
|
||||
.setView(tv)
|
||||
.show()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.activities.helpers;
|
||||
package com.kunzisoft.keepass.utils;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
@@ -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 ?: ""))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
92
app/src/main/java/com/kunzisoft/keepass/utils/FileInfo.kt
Normal file
92
app/src/main/java/com/kunzisoft/keepass/utils/FileInfo.kt
Normal 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"
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,7 @@ package com.kunzisoft.keepass.utils;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.support.annotation.NonNull;
|
||||
import androidx.annotation.NonNull;
|
||||
import android.util.Log;
|
||||
|
||||
import com.kunzisoft.keepass.stream.ActionReadBytes;
|
||||
@@ -145,6 +145,7 @@ public class MemUtil {
|
||||
return compressedDataStream;
|
||||
}
|
||||
|
||||
// TODO Remove
|
||||
|
||||
// For writing to a Parcel
|
||||
public static <K extends Parcelable,V extends Parcelable> void writeParcelableMap(
|
||||
|
||||
@@ -19,18 +19,16 @@
|
||||
*/
|
||||
package com.kunzisoft.keepass.utils
|
||||
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.content.Intent
|
||||
import android.view.Menu
|
||||
import android.view.MenuInflater
|
||||
import android.view.MenuItem
|
||||
import android.widget.Toast
|
||||
import com.kunzisoft.keepass.BuildConfig
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.activities.AboutActivity
|
||||
import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper.READ_ONLY_DEFAULT
|
||||
import com.kunzisoft.keepass.settings.SettingsActivity
|
||||
import com.kunzisoft.keepass.activities.stylish.StylishActivity
|
||||
import com.kunzisoft.keepass.settings.SettingsActivity
|
||||
|
||||
|
||||
object MenuUtil {
|
||||
@@ -45,15 +43,8 @@ object MenuUtil {
|
||||
inflater.inflate(R.menu.default_menu, menu)
|
||||
}
|
||||
|
||||
fun onContributionItemSelected(activity: StylishActivity): Boolean {
|
||||
try {
|
||||
Util.gotoUrl(activity, R.string.contribution_url)
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
Toast.makeText(activity, R.string.error_failed_to_launch_link, Toast.LENGTH_LONG).show()
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
fun onContributionItemSelected(activity: StylishActivity) {
|
||||
UriUtil.gotoUrl(activity, R.string.contribution_url)
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -62,7 +53,10 @@ object MenuUtil {
|
||||
@JvmOverloads
|
||||
fun onDefaultMenuOptionsItemSelected(activity: StylishActivity, item: MenuItem, readOnly: Boolean = READ_ONLY_DEFAULT, timeoutEnable: Boolean = false): Boolean {
|
||||
when (item.itemId) {
|
||||
R.id.menu_contribute -> return onContributionItemSelected(activity)
|
||||
R.id.menu_contribute -> {
|
||||
onContributionItemSelected(activity)
|
||||
return true
|
||||
}
|
||||
|
||||
R.id.menu_app_settings -> {
|
||||
// To avoid flickering when launch settings in a LockingActivity
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user