diff --git a/app/src/main/java/com/yogeshpaliyal/keypass/ui/backup/BackupActivity.kt b/app/src/main/java/com/yogeshpaliyal/keypass/ui/backup/BackupActivity.kt index 8b238de0..c0963eab 100644 --- a/app/src/main/java/com/yogeshpaliyal/keypass/ui/backup/BackupActivity.kt +++ b/app/src/main/java/com/yogeshpaliyal/keypass/ui/backup/BackupActivity.kt @@ -1,15 +1,26 @@ package com.yogeshpaliyal.keypass.ui.backup +import android.app.Activity +import android.content.ClipData +import android.content.ClipboardManager import android.content.Context import android.content.Intent import android.net.Uri import android.os.Bundle +import android.widget.Toast import androidx.appcompat.app.AppCompatActivity +import androidx.core.content.ContextCompat +import androidx.documentfile.provider.DocumentFile +import androidx.lifecycle.lifecycleScope import androidx.preference.Preference import androidx.preference.PreferenceFragmentCompat +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import com.yogeshpaliyal.keypass.AppDatabase import com.yogeshpaliyal.keypass.R -import com.yogeshpaliyal.keypass.utils.canUserAccessBackupDirectory -import com.yogeshpaliyal.keypass.utils.getBackupDirectory +import com.yogeshpaliyal.keypass.databinding.LayoutBackupKeypharseBinding +import com.yogeshpaliyal.keypass.db_helper.createBackup +import com.yogeshpaliyal.keypass.utils.* +import kotlinx.coroutines.launch class BackupActivity : AppCompatActivity() { @@ -34,24 +45,144 @@ class BackupActivity : AppCompatActivity() { } class SettingsFragment : PreferenceFragmentCompat() { + + private val CHOOSE_BACKUPS_LOCATION_REQUEST_CODE = 26212 + override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { setPreferencesFromResource(R.xml.backup_preferences, rootKey) val selectedDirectory = Uri.parse(getBackupDirectory()) - if (context?.canUserAccessBackupDirectory() == true) { - // already backup on - findPreference("create_backup")?.isVisible = true - findPreference("backup_folder")?.isVisible = true - findPreference("settings_verify_key_phrase")?.isVisible = true - findPreference("start_backup")?.isVisible = false - } else { - // backup is off - findPreference("create_backup")?.isVisible = false - findPreference("backup_folder")?.isVisible = false - findPreference("settings_verify_key_phrase")?.isVisible = false - findPreference("start_backup")?.isVisible = true - } - + updateItems() } + + override fun onPreferenceTreeClick(preference: Preference?): Boolean { + when (preference?.key) { + "start_backup" -> { + startBackup() + } + "create_backup" -> { + context?.let { + if (it.canUserAccessBackupDirectory()) { + val selectedDirectory = Uri.parse(getBackupDirectory()) + backup(selectedDirectory) + } + } + } + "backup_folder" -> { + changeBackupFolder() + } + getString(R.string.settings_verify_key_phrase) -> { + verifyKeyPhrase() + } + "stop_backup" -> { + stopBackup() + } + } + return super.onPreferenceTreeClick(preference) + } + + private fun updateItems() { + val isBackupEnabled = context?.canUserAccessBackupDirectory() ?: false + + findPreference("start_backup")?.isVisible = isBackupEnabled.not() + + findPreference("create_backup")?.isVisible = isBackupEnabled + findPreference("create_backup")?.setSummary("Last backup : ${getBackupTime().formatCalendar("dd MMM yyyy hh:mm aa")}") + findPreference("backup_folder")?.isVisible = isBackupEnabled + findPreference("settings_verify_key_phrase")?.isVisible = isBackupEnabled + findPreference("stop_backup")?.isVisible = isBackupEnabled + } + + private fun startBackup(){ + val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE) + + intent.addFlags( + Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION or + Intent.FLAG_GRANT_WRITE_URI_PERMISSION or + Intent.FLAG_GRANT_READ_URI_PERMISSION + ) + + try { + startActivityForResult(intent, CHOOSE_BACKUPS_LOCATION_REQUEST_CODE) + } catch (e: Exception) { + e.printStackTrace() + } + } + + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + super.onActivityResult(requestCode, resultCode, data) + if (requestCode == CHOOSE_BACKUPS_LOCATION_REQUEST_CODE && resultCode == Activity.RESULT_OK){ + val contentResolver = context?.contentResolver + val selectedDirectory = data?.data + if (contentResolver != null && selectedDirectory != null) { + contentResolver.takePersistableUriPermission( + selectedDirectory, + Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION + ) + + setBackupDirectory(selectedDirectory.toString()) + backup(selectedDirectory) + } + } + } + + private fun backup(selectedDirectory: Uri){ + + val keyPair = getOrCreateBackupKey() + + val tempFile = DocumentFile.fromTreeUri(requireContext(), selectedDirectory)?.createFile( + "*/*", + "key_pass_backup_${System.currentTimeMillis()}.keypass" + ) + + lifecycleScope.launch { + context?.contentResolver?.let { + AppDatabase.getInstance().createBackup(keyPair.second, + it, + tempFile?.uri + ) + setBackupTime(System.currentTimeMillis()) + if (keyPair.first) { + val binding = LayoutBackupKeypharseBinding.inflate(layoutInflater) + binding.txtCode.text = getOrCreateBackupKey().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, "Copied to clipboard", Toast.LENGTH_SHORT).show() + } + MaterialAlertDialogBuilder(requireContext()).setView(binding.root) + .setPositiveButton( + "Yes" + ) { dialog, which -> dialog?.dismiss() + }.show() + }else{ + Toast.makeText(context, getString(R.string.backup_completed), Toast.LENGTH_SHORT).show() + } + } + + updateItems() + } + } + + private fun changeBackupFolder(){ + startBackup() + } + + private fun verifyKeyPhrase(){ + Toast.makeText(context, "Under Development", Toast.LENGTH_SHORT).show() + } + + private fun stopBackup(){ + clearBackupKey() + setBackupDirectory("") + setBackupTime(-1) + updateItems() + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/yogeshpaliyal/keypass/utils/DateTimeHelper.kt b/app/src/main/java/com/yogeshpaliyal/keypass/utils/DateTimeHelper.kt new file mode 100644 index 00000000..fbace541 --- /dev/null +++ b/app/src/main/java/com/yogeshpaliyal/keypass/utils/DateTimeHelper.kt @@ -0,0 +1,21 @@ +package com.yogeshpaliyal.keypass.utils + +import java.text.SimpleDateFormat +import java.util.* + + +/* +* @author Yogesh Paliyal +* techpaliyal@gmail.com +* https://techpaliyal.com +* created on 23-03-2021 22:30 +*/ + + + +fun Long.formatCalendar(dateTimeFormat: String?): String? { + val calendar: Calendar = Calendar.getInstance() + calendar.timeInMillis = this + val simpleDateFormat = SimpleDateFormat(dateTimeFormat, Locale.US) + return simpleDateFormat.format(calendar.getTime()) +} \ No newline at end of file diff --git a/app/src/main/java/com/yogeshpaliyal/keypass/utils/SharedPreferenceUtils.kt b/app/src/main/java/com/yogeshpaliyal/keypass/utils/SharedPreferenceUtils.kt index 994d9079..1c12aae6 100644 --- a/app/src/main/java/com/yogeshpaliyal/keypass/utils/SharedPreferenceUtils.kt +++ b/app/src/main/java/com/yogeshpaliyal/keypass/utils/SharedPreferenceUtils.kt @@ -7,7 +7,6 @@ import androidx.core.content.edit import androidx.security.crypto.EncryptedSharedPreferences import androidx.security.crypto.MasterKey import com.yogeshpaliyal.keypass.MyApplication -import java.util.* /* @@ -17,27 +16,28 @@ import java.util.* * created on 21-02-2021 11:18 */ -fun getSharedPreferences() : SharedPreferences{ +fun getSharedPreferences(): SharedPreferences { val masterKeyAlias = MasterKey.Builder(MyApplication.instance).also { - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) { - // this is equivalent to using deprecated MasterKeys.AES256_GCM_SPEC + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) { + // this is equivalent to using deprecated MasterKeys.AES256_GCM_SPEC - // this is equivalent to using deprecated MasterKeys.AES256_GCM_SPEC - val spec = KeyGenParameterSpec.Builder( - MasterKey.DEFAULT_MASTER_KEY_ALIAS, - KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT - ) - .setBlockModes(KeyProperties.BLOCK_MODE_GCM) - .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE) - .setKeySize(256) - .build() - it.setKeyGenParameterSpec(spec) - } + // this is equivalent to using deprecated MasterKeys.AES256_GCM_SPEC + val spec = KeyGenParameterSpec.Builder( + MasterKey.DEFAULT_MASTER_KEY_ALIAS, + KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT + ) + .setBlockModes(KeyProperties.BLOCK_MODE_GCM) + .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE) + .setKeySize(256) + .build() + it.setKeyGenParameterSpec(spec) + } //it.setUserAuthenticationRequired(true) } .build() - return EncryptedSharedPreferences.create(MyApplication.instance, + return EncryptedSharedPreferences.create( + MyApplication.instance, "secret_shared_prefs", masterKeyAlias, EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, @@ -51,32 +51,51 @@ fun getSharedPreferences() : SharedPreferences{ * 1st => true if key is created now & false if key is created previously * */ -fun getOrCreateBackupKey(reset: Boolean = false): Pair{ +fun getOrCreateBackupKey(reset: Boolean = false): Pair { val sp = getSharedPreferences() - return if (sp.contains(BACKUP_KEY) && reset.not()){ - Pair(false,sp.getString(BACKUP_KEY,"") ?: "") - }else{ + return if (sp.contains(BACKUP_KEY) && reset.not()) { + Pair(false, sp.getString(BACKUP_KEY, "") ?: "") + } else { val randomKey = getRandomString(16) sp.edit { putString(BACKUP_KEY, randomKey) } - Pair(true,randomKey) + Pair(true, randomKey) } } +fun clearBackupKey() { + val sp = getSharedPreferences() + sp.edit { + putString(BACKUP_KEY, "") + } -fun setBackupDirectory(string: String){ +} + + +fun setBackupDirectory(string: String) { getSharedPreferences().edit { putString(BACKUP_DIRECTORY, string) } - } -fun getBackupDirectory(): String{ +fun setBackupTime(time: Long) { + getSharedPreferences().edit { + putLong(BACKUP_DATE_TIME, time) + } +} + +fun getBackupDirectory(): String { val sp = getSharedPreferences() - return sp.getString(BACKUP_DIRECTORY,"") ?: "" + return sp.getString(BACKUP_DIRECTORY, "") ?: "" +} + +fun getBackupTime(): Long { + val sp = getSharedPreferences() + return sp.getLong(BACKUP_DATE_TIME, -1) ?: -1L } private const val BACKUP_KEY = "backup_key" -private const val BACKUP_DIRECTORY = "backup_directory" \ No newline at end of file +private const val BACKUP_DIRECTORY = "backup_directory" +private const val BACKUP_DATE_TIME = "backup_date_time" \ No newline at end of file diff --git a/app/src/main/res/xml/backup_preferences.xml b/app/src/main/res/xml/backup_preferences.xml index 24f2ee21..d13a7e16 100644 --- a/app/src/main/res/xml/backup_preferences.xml +++ b/app/src/main/res/xml/backup_preferences.xml @@ -27,6 +27,10 @@ app:summary="Test your backup passphrase and verify that it matches"/> + \ No newline at end of file