New Feature -> Auto backup whenever there is changes in accounts :D

Added Chinese (Simplified)
This commit is contained in:
Yogesh Choudhary Paliyal
2021-09-03 22:39:20 +05:30
parent eb5c25a707
commit b26b5dc1c3
19 changed files with 283 additions and 94 deletions

View File

@@ -20,8 +20,8 @@ android {
applicationId appPackageId
minSdkVersion 22
targetSdkVersion 30
versionCode 1308
versionName "1.3.8"
versionCode 1309
versionName "1.3.9"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
@@ -79,7 +79,7 @@ dependencies {
implementation 'com.google.android.material:material:1.4.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.0'
implementation 'androidx.preference:preference-ktx:1.1.1'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
@@ -112,15 +112,23 @@ dependencies {
implementation "androidx.preference:preference-ktx:1.1.1"
implementation "androidx.work:work-runtime-ktx:2.6.0"
//Androidx Security
implementation "androidx.security:security-crypto:1.1.0-alpha03"
implementation 'com.google.code.gson:gson:2.8.6'
implementation 'com.google.code.gson:gson:2.8.8'
// dependency injection
implementation("com.google.dagger:hilt-android:$hilt_version")
kapt("com.google.dagger:hilt-android-compiler:$hilt_version")
implementation("androidx.hilt:hilt-work:1.0.0")
// When using Kotlin.
kapt("androidx.hilt:hilt-compiler:1.0.0")
}

View File

@@ -1,5 +1,6 @@
<?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.yogeshpaliyal.keypass">
<application
@@ -10,8 +11,7 @@
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.KeyPass">
<activity
android:name=".ui.backup.BackupActivity"/>
<activity android:name=".ui.backup.BackupActivity" />
<activity android:name=".ui.generate.GeneratePasswordActivity" />
<activity android:name=".ui.auth.AuthenticationActivity">
<intent-filter>
@@ -34,6 +34,15 @@
<meta-data
android:name="preloaded_fonts"
android:resource="@array/preloaded_fonts" />
<!-- If you want to disable android.startup completely. -->
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
tools:node="remove">
</provider>
</application>
</manifest>

View File

@@ -1,7 +1,10 @@
package com.yogeshpaliyal.keypass
import android.app.Application
import androidx.hilt.work.HiltWorkerFactory
import androidx.work.Configuration
import dagger.hilt.android.HiltAndroidApp
import javax.inject.Inject
/*
* @author Yogesh Paliyal
@@ -10,14 +13,25 @@ import dagger.hilt.android.HiltAndroidApp
* created on 22-01-2021 22:41
*/
@HiltAndroidApp
class MyApplication : Application() {
class MyApplication : Application(), Configuration.Provider {
companion object {
lateinit var instance: MyApplication
}
@Inject
lateinit var workerFactory: HiltWorkerFactory
override fun onCreate() {
super.onCreate()
instance = this
}
override fun getWorkManagerConfiguration(): Configuration {
return Configuration.Builder()
.setMinimumLoggingLevel(android.util.Log.INFO)
.setWorkerFactory(workerFactory)
.build()
}
}

View File

@@ -1,10 +1,7 @@
package com.yogeshpaliyal.keypass.db
import androidx.lifecycle.LiveData
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.*
import com.yogeshpaliyal.keypass.data.AccountModel
import kotlinx.coroutines.flow.Flow
@@ -41,4 +38,7 @@ abstract class DbDao {
@Query("DELETE from account WHERE id = :id")
abstract fun deleteAccount(id: Long?)
@Delete
abstract fun deleteAccount(accountModel: AccountModel)
}

View File

@@ -10,6 +10,7 @@ import androidx.core.content.ContextCompat
import androidx.documentfile.provider.DocumentFile
import androidx.lifecycle.lifecycleScope
import androidx.preference.Preference
import androidx.preference.PreferenceCategory
import androidx.preference.PreferenceFragmentCompat
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.yogeshpaliyal.keypass.AppDatabase
@@ -80,6 +81,7 @@ class BackupActivity : AppCompatActivity() {
if (it.canUserAccessBackupDirectory(sp)) {
val selectedDirectory = Uri.parse(getBackupDirectory(sp))
backup(selectedDirectory)
}
}
}
@@ -98,20 +100,60 @@ class BackupActivity : AppCompatActivity() {
}
updateItems()
}
getString(R.string.settings_override_auto_backup) -> {
sp.apply {
setOverrideAutoBackup(overrideAutoBackup().not())
}
updateItems()
}
}
return super.onPreferenceTreeClick(preference)
}
fun backup(selectedDirectory: Uri){
lifecycleScope.launch {
context.backupAccounts(sp, appDb,selectedDirectory)?.let { keyPair ->
if (keyPair.first) {
val binding = LayoutBackupKeypharseBinding.inflate(layoutInflater)
binding.txtCode.text = getOrCreateBackupKey(sp).second
binding.txtCode.setOnClickListener {
val clipboard =
ContextCompat.getSystemService(
requireContext(),
ClipboardManager::class.java
)
val clip = ClipData.newPlainText("KeyPass", binding.txtCode.text)
clipboard?.setPrimaryClip(clip)
Toast.makeText(context, getString(R.string.copied_to_clipboard), Toast.LENGTH_SHORT).show()
}
MaterialAlertDialogBuilder(requireContext()).setView(binding.root)
.setPositiveButton(
getString(R.string.yes)
) { dialog, which ->
dialog?.dismiss()
}.show()
} else {
Toast.makeText(context, getString(R.string.backup_completed), Toast.LENGTH_SHORT).show()
}
}
}
updateItems()
}
private fun updateItems() {
val isBackupEnabled = context.canUserAccessBackupDirectory(sp)
val isAutoBackupEnabled = sp.isAutoBackupEnabled()
findPreference<Preference>(getString(R.string.settings_start_backup))?.isVisible = isBackupEnabled.not()
findPreference<Preference>(getString(R.string.settings_stop_backup))?.isVisible = isBackupEnabled
findPreference<Preference>(getString(R.string.settings_auto_backup))?.isVisible = isBackupEnabled
findPreference<Preference>(getString(R.string.settings_auto_backup))?.summary = if(isAutoBackupEnabled) getString(R.string.enabled) else getString(R.string.disabled)
findPreference<PreferenceCategory>(getString(R.string.settings_cat_auto_backup))?.isVisible = isBackupEnabled && isAutoBackupEnabled
findPreference<Preference>(getString(R.string.settings_override_auto_backup))?.summary = if(sp.overrideAutoBackup()) getString(R.string.enabled) else getString(R.string.disabled)
findPreference<Preference>(getString(R.string.settings_create_backup))?.isVisible = isBackupEnabled
findPreference<Preference>(getString(R.string.settings_create_backup))?.summary = getString(R.string.last_backup_date, getBackupTime(sp).formatCalendar("dd MMM yyyy hh:mm aa"))
@@ -156,50 +198,7 @@ class BackupActivity : AppCompatActivity() {
}
}
private fun backup(selectedDirectory: Uri) {
val keyPair = getOrCreateBackupKey(sp)
val tempFile = DocumentFile.fromTreeUri(requireContext(), selectedDirectory)?.createFile(
"*/*",
"key_pass_backup_${System.currentTimeMillis()}.keypass"
)
lifecycleScope.launch {
context?.contentResolver?.let {
appDb.createBackup(
keyPair.second,
it,
tempFile?.uri
)
setBackupTime(sp, System.currentTimeMillis())
if (keyPair.first) {
val binding = LayoutBackupKeypharseBinding.inflate(layoutInflater)
binding.txtCode.text = getOrCreateBackupKey(sp).second
binding.txtCode.setOnClickListener {
val clipboard =
ContextCompat.getSystemService(
requireContext(),
ClipboardManager::class.java
)
val clip = ClipData.newPlainText("KeyPass", binding.txtCode.text)
clipboard?.setPrimaryClip(clip)
Toast.makeText(context, getString(R.string.copied_to_clipboard), Toast.LENGTH_SHORT).show()
}
MaterialAlertDialogBuilder(requireContext()).setView(binding.root)
.setPositiveButton(
getString(R.string.yes)
) { dialog, which ->
dialog?.dismiss()
}.show()
} else {
Toast.makeText(context, getString(R.string.backup_completed), Toast.LENGTH_SHORT).show()
}
}
updateItems()
}
}
private fun changeBackupFolder() {
startBackup()
@@ -213,6 +212,10 @@ class BackupActivity : AppCompatActivity() {
clearBackupKey(sp)
setBackupDirectory(sp, "")
setBackupTime(sp, -1)
sp.apply {
setOverrideAutoBackup(false)
setAutoBackupEnabled(false)
}
updateItems()
}
}

View File

@@ -7,17 +7,11 @@ import android.view.Menu
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.Observer
import androidx.lifecycle.lifecycleScope
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.yogeshpaliyal.keypass.AppDatabase
import com.yogeshpaliyal.keypass.R
import com.yogeshpaliyal.keypass.databinding.FragmentDetailBinding
import com.yogeshpaliyal.keypass.utils.PasswordGenerator
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import javax.inject.Inject
/*
* @author Yogesh Paliyal
@@ -30,9 +24,6 @@ class DetailActivity : AppCompatActivity() {
lateinit var binding: FragmentDetailBinding
@Inject
lateinit var appDb: AppDatabase
companion object {
private const val ARG_ACCOUNT_ID = "ARG_ACCOUNT_ID"
@@ -90,14 +81,8 @@ class DetailActivity : AppCompatActivity() {
}
binding.btnSave.setOnClickListener {
lifecycleScope.launch(Dispatchers.IO) {
val model = mViewModel.accountModel.value
if (model != null) {
appDb.getDao().insertOrUpdateAccount(model)
}
withContext(Dispatchers.Main) {
onBackPressed()
}
mViewModel.insertOrUpdate {
onBackPressed()
}
}
}
@@ -110,11 +95,9 @@ class DetailActivity : AppCompatActivity() {
getString(R.string.delete)
) { dialog, which ->
dialog?.dismiss()
lifecycleScope.launch(Dispatchers.IO) {
if (accountId > 0L) {
appDb.getDao().deleteAccount(accountId)
}
withContext(Dispatchers.Main) {
if (accountId > 0L) {
mViewModel.deleteAccount {
onBackPressed()
}
}

View File

@@ -1,14 +1,18 @@
package com.yogeshpaliyal.keypass.ui.detail
import android.app.Application
import android.content.SharedPreferences
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import com.yogeshpaliyal.keypass.AppDatabase
import com.yogeshpaliyal.keypass.data.AccountModel
import com.yogeshpaliyal.keypass.worker.executeAutoBackup
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import javax.inject.Inject
/*
@@ -18,16 +22,44 @@ import javax.inject.Inject
* created on 31-01-2021 11:52
*/
@HiltViewModel
class DetailViewModel @Inject constructor(application: Application, val appDb: AppDatabase) : AndroidViewModel(application) {
class DetailViewModel @Inject constructor(val app: Application, val appDb: AppDatabase,val sp: SharedPreferences) : AndroidViewModel(app) {
val accountModel by lazy { MutableLiveData<AccountModel>() }
private val _accountModel by lazy { MutableLiveData<AccountModel>() }
val accountModel : LiveData<AccountModel> = _accountModel
fun loadAccount(accountId: Long?) {
viewModelScope.launch(Dispatchers.IO) {
accountModel.postValue(
_accountModel.postValue(
appDb.getDao().getAccount(accountId) ?: AccountModel()
)
}
}
fun deleteAccount(onExecCompleted : ()->Unit){
viewModelScope.launch {
accountModel.value?.let {
withContext(Dispatchers.IO) {
appDb.getDao().deleteAccount(it)
}
autoBackup()
onExecCompleted()
}
}
}
fun insertOrUpdate(onExecCompleted : ()->Unit){
viewModelScope.launch {
accountModel.value?.let {
withContext(Dispatchers.IO) {
appDb.getDao().insertOrUpdateAccount(it)
autoBackup()
}
}
onExecCompleted()
}
}
private fun autoBackup(){
app.executeAutoBackup(sp)
}
}

View File

@@ -5,6 +5,8 @@ import android.content.SharedPreferences
import android.net.Uri
import android.text.TextUtils
import androidx.documentfile.provider.DocumentFile
import com.yogeshpaliyal.keypass.AppDatabase
import com.yogeshpaliyal.keypass.db_helper.createBackup
import java.security.SecureRandom
/*
@@ -21,9 +23,9 @@ fun getRandomString(sizeOfRandomString: Int): String {
val sb = StringBuilder(sizeOfRandomString)
for (i in 0 until sizeOfRandomString) sb.append(
ALLOWED_CHARACTERS[
random.nextInt(
ALLOWED_CHARACTERS.length
)
random.nextInt(
ALLOWED_CHARACTERS.length
)
]
)
return sb.toString()
@@ -36,6 +38,42 @@ fun Context?.canUserAccessBackupDirectory(sp: SharedPreferences): Boolean {
return backupDirectory != null && backupDirectory.exists() && backupDirectory.canRead() && backupDirectory.canWrite()
}
/**
* @return Pair (Boolean to check if backup is for first time, is backup is for first time show user alert to save encryption key)
* Second Value contains the encryption key
*/
suspend fun Context?.backupAccounts(
sp: SharedPreferences,
appDb: AppDatabase,
selectedDirectory: Uri, fileName: String? = null
): Pair<Boolean, String>? {
this ?: return null
val keyPair = getOrCreateBackupKey(sp)
val fileName = (fileName ?: "key_pass_backup_${System.currentTimeMillis()}")+".keypass"
val directory = DocumentFile.fromTreeUri(this, selectedDirectory)
var docFile = directory?.findFile(fileName)
if (docFile == null)
docFile = DocumentFile.fromTreeUri(this, selectedDirectory)?.createFile(
"*/*",
fileName)
val response = appDb.createBackup(
keyPair.second,
contentResolver,
docFile?.uri
)
setBackupTime(sp, System.currentTimeMillis())
return keyPair
}
private fun getUri(string: String?): Uri? {
val uri = string
return if (TextUtils.isEmpty(uri)) {

View File

@@ -55,6 +55,16 @@ fun SharedPreferences?.isAutoBackupEnabled(): Boolean {
return this?.getBoolean(AUTO_BACKUP, false) ?: false
}
fun SharedPreferences?.overrideAutoBackup(): Boolean {
return this?.getBoolean(OVERRIDE_AUTO_BACKUP, false) ?: false
}
fun SharedPreferences?.setOverrideAutoBackup(value: Boolean) {
this?.edit {
putBoolean(OVERRIDE_AUTO_BACKUP, value)
}
}
fun SharedPreferences?.setAutoBackupEnabled(value: Boolean) {
this?.edit {
putBoolean(AUTO_BACKUP, value)
@@ -69,3 +79,4 @@ private const val BACKUP_KEY = "backup_key"
private const val BACKUP_DIRECTORY = "backup_directory"
private const val BACKUP_DATE_TIME = "backup_date_time"
private const val AUTO_BACKUP = "auto_backup"
private const val OVERRIDE_AUTO_BACKUP = "override_auto_backup"

View File

@@ -0,0 +1,42 @@
package com.yogeshpaliyal.keypass.worker
import android.content.Context
import android.content.SharedPreferences
import android.net.Uri
import androidx.hilt.work.HiltWorker
import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters
import com.yogeshpaliyal.keypass.AppDatabase
import com.yogeshpaliyal.keypass.utils.backupAccounts
import com.yogeshpaliyal.keypass.utils.canUserAccessBackupDirectory
import com.yogeshpaliyal.keypass.utils.getBackupDirectory
import com.yogeshpaliyal.keypass.utils.overrideAutoBackup
import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
@HiltWorker
class AutoBackupWorker @AssistedInject constructor(
@Assisted val appContext: Context, @Assisted params: WorkerParameters,
val appDatabase: AppDatabase,
val sp: SharedPreferences
) :
CoroutineWorker(appContext, params) {
override suspend fun doWork(): Result {
return withContext(Dispatchers.IO) {
if (appContext.canUserAccessBackupDirectory(sp)) {
val selectedDirectory = Uri.parse(getBackupDirectory(sp))
appContext.backupAccounts(
sp,
appDatabase,
selectedDirectory,
if (sp.overrideAutoBackup()) "key_pass_auto_backup" else null
)
}
Result.success()
}
}
}

View File

@@ -0,0 +1,19 @@
package com.yogeshpaliyal.keypass.worker
import android.content.Context
import android.content.SharedPreferences
import androidx.work.ExistingWorkPolicy
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkManager
import com.yogeshpaliyal.keypass.utils.isAutoBackupEnabled
fun Context?.executeAutoBackup(sp: SharedPreferences){
this ?: return
if (sp.isAutoBackupEnabled()) {
val work = OneTimeWorkRequestBuilder<AutoBackupWorker>().build()
WorkManager.getInstance(this.applicationContext)
.enqueueUniqueWork("AutoBackupWorker", ExistingWorkPolicy.KEEP, work)
}
}

View File

@@ -59,4 +59,9 @@
<string name="tags_comma_separated_optional">टैग अल्पविराम से अलग (वैकल्पिक)</string>
<string name="website_url_optional">वेबसाइट यूआरएल (वैकल्पिक)</string>
<string name="notes_optional">नोट्स (वैकल्पिक)</string>
<string name="auto_backup">ऑटो बैकअप</string>
<string name="disabled">अक्षम</string>
<string name="enabled">सक्रिय</string>
<string name="override_auto_backup_file">ऑटो बैकअप फ़ाइल को ओवरराइड करें</string>
</resources>

View File

@@ -59,4 +59,9 @@
<string name="tags_comma_separated_optional">标签(选填)</string>
<string name="website_url_optional">网站(选填)</string>
<string name="notes_optional">附注(选填)</string>
<string name="auto_backup">自动备份</string>
<string name="disabled">已禁用</string>
<string name="enabled">启用</string>
<string name="override_auto_backup_file">覆盖自动备份文件</string>
</resources>

View File

@@ -62,4 +62,5 @@
<string name="auto_backup">Auto Backup</string>
<string name="disabled">Disabled</string>
<string name="enabled">Enabled</string>
<string name="override_auto_backup_file">Override Auto Backup File</string>
</resources>

View File

@@ -10,5 +10,7 @@
<string name="settings_backup_folder" translatable="false">backup_folder</string>
<string name="settings_stop_backup" translatable="false">stop_backup</string>
<string name="settings_auto_backup" translatable="false">auto_backup</string>
<string name="settings_cat_auto_backup" translatable="false">cat_auto_backup</string>
<string name="settings_override_auto_backup" translatable="false">override_auto_backup</string>
</resources>

View File

@@ -3,39 +3,52 @@
<PreferenceCategory>
<Preference
app:summary="@string/backup_desc"/>
<Preference app:summary="@string/backup_desc" />
<Preference
app:key="@string/settings_start_backup"
app:title="@string/turn_on_backup"/>
app:title="@string/turn_on_backup" />
<Preference
app:key="@string/settings_create_backup"
app:title="@string/create_backup"
tools:summary="Last backup : 7m"/>
tools:summary="Last backup : 7m" />
<Preference
app:key="@string/settings_backup_folder"
app:title="@string/backup_folder"
tools:summary="@string/backup"/>
tools:summary="@string/backup" />
<Preference
app:key="@string/settings_auto_backup"
app:title="@string/auto_backup"
tools:summary="@string/disabled"/>
tools:summary="@string/disabled" />
<PreferenceCategory
app:key="@string/settings_cat_auto_backup"
app:title="@string/auto_backup"
app:allowDividerBelow="true">
<Preference app:summary="Your accounts will be backed up whenever account is added or modified" />
<Preference
app:key="@string/settings_override_auto_backup"
app:title="@string/override_auto_backup_file"
tools:summary="@string/disabled" />
</PreferenceCategory>
<Preference
app:key="@string/settings_verify_key_phrase"
app:title="@string/verify_keyphrase"
app:summary="@string/verify_keyphrase_message"/>
app:summary="@string/verify_keyphrase_message"
app:title="@string/verify_keyphrase" />
<Preference
app:key="@string/settings_stop_backup"
app:isPreferenceVisible="false"
app:title="@string/turn_off_backup"/>
app:key="@string/settings_stop_backup"
app:title="@string/turn_off_backup" />
</PreferenceCategory>
</PreferenceScreen>

View File

@@ -0,0 +1 @@
Fixed crash for some devices when creating backup

View File

@@ -0,0 +1,2 @@
New Feature -> Auto backup whenever there is changes in accounts :D
Added Chinese (Simplified)

View File

@@ -1 +1,2 @@
Fixed crash for some devices when creating backup
New Feature -> Auto backup whenever there is changes in accounts :D
Added Chinese (Simplified)