aboutsummaryrefslogtreecommitdiff
path: root/app/src/main/java
diff options
context:
space:
mode:
authorHarsh Shandilya <msfjarvis@gmail.com>2020-07-09 14:00:24 +0530
committerGitHub <noreply@github.com>2020-07-09 14:00:24 +0530
commitfc00de61dcc53f8378e332f7db1d5c58e3308aa7 (patch)
treee303a6b77f72ae4cbe7d0d16141fe07f822b97a9 /app/src/main/java
parent0ead6b2a4dfc409ffdffd5ef34c860aaa7f378ae (diff)
Move password export to the IO dispatcher (#918)
* Move password export to the IO dispatcher Signed-off-by: Harsh Shandilya <me@msfjarvis.dev> * Simplify snackbars and disable exit operations during export Signed-off-by: Harsh Shandilya <me@msfjarvis.dev> * Move export password logic to service Signed-off-by: Aditya Wasan <adityawasan55@gmail.com> * Reformat Signed-off-by: Harsh Shandilya <me@msfjarvis.dev> * Use explicit null check Signed-off-by: Harsh Shandilya <me@msfjarvis.dev> * Remove unneeded hack Signed-off-by: Harsh Shandilya <me@msfjarvis.dev> * Fixup strings Signed-off-by: Harsh Shandilya <me@msfjarvis.dev> * Don't use coroutines in a service Signed-off-by: Aditya Wasan <adityawasan55@gmail.com> * Update notification icon Signed-off-by: Aditya Wasan <adityawasan55@gmail.com> * Rollback unwanted formatting Signed-off-by: Harsh Shandilya <me@msfjarvis.dev> Co-authored-by: Aditya Wasan <adityawasan55@gmail.com>
Diffstat (limited to 'app/src/main/java')
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/PasswordExportService.kt152
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/UserPreference.kt95
2 files changed, 170 insertions, 77 deletions
diff --git a/app/src/main/java/com/zeapo/pwdstore/PasswordExportService.kt b/app/src/main/java/com/zeapo/pwdstore/PasswordExportService.kt
new file mode 100644
index 00000000..e877a77a
--- /dev/null
+++ b/app/src/main/java/com/zeapo/pwdstore/PasswordExportService.kt
@@ -0,0 +1,152 @@
+package com.zeapo.pwdstore
+
+import android.app.NotificationChannel
+import android.app.NotificationManager
+import android.app.Service
+import android.content.Intent
+import android.net.Uri
+import android.os.Build
+import android.os.IBinder
+import androidx.core.app.NotificationCompat
+import androidx.core.content.getSystemService
+import androidx.documentfile.provider.DocumentFile
+import com.github.ajalt.timberkt.d
+import com.zeapo.pwdstore.utils.PasswordRepository
+import java.time.LocalDateTime
+import java.time.format.DateTimeFormatter
+import java.util.Calendar
+import java.util.TimeZone
+
+class PasswordExportService : Service() {
+
+ override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
+ if (intent != null) {
+ when (intent.action) {
+ ACTION_EXPORT_PASSWORD -> {
+ val uri = intent.getParcelableExtra<Uri>("uri")
+ if (uri != null) {
+ val targetDirectory = DocumentFile.fromTreeUri(applicationContext, uri)
+
+ if (targetDirectory != null) {
+ createNotification()
+ exportPasswords(targetDirectory)
+ stopSelf()
+ return START_NOT_STICKY
+ }
+ }
+ }
+ }
+ }
+ return super.onStartCommand(intent, flags, startId)
+ }
+
+ override fun onBind(intent: Intent?): IBinder? {
+ return null
+ }
+
+ /**
+ * Exports passwords to the given directory.
+ *
+ * Recursively copies the existing password store to an external directory.
+ *
+ * @param targetDirectory directory to copy password directory to.
+ */
+ private fun exportPasswords(targetDirectory: DocumentFile) {
+
+ val repositoryDirectory = requireNotNull(PasswordRepository.getRepositoryDirectory(applicationContext))
+ val sourcePassDir = DocumentFile.fromFile(repositoryDirectory)
+
+ d { "Copying ${repositoryDirectory.path} to $targetDirectory" }
+
+ val dateString = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ LocalDateTime
+ .now()
+ .format(DateTimeFormatter.ISO_DATE_TIME)
+ } else {
+ String.format("%tFT%<tRZ", Calendar.getInstance(TimeZone.getTimeZone("Z")))
+ }
+
+ val passDir = targetDirectory.createDirectory("password_store_$dateString")
+
+ if (passDir != null) {
+ copyDirToDir(sourcePassDir, passDir)
+ }
+ }
+
+ /**
+ * Copies a password file to a given directory.
+ *
+ * Note: this does not preserve last modified time.
+ *
+ * @param passwordFile password file to copy.
+ * @param targetDirectory target directory to copy password.
+ */
+ private fun copyFileToDir(passwordFile: DocumentFile, targetDirectory: DocumentFile) {
+ val sourceInputStream = contentResolver.openInputStream(passwordFile.uri)
+ val name = passwordFile.name
+ val targetPasswordFile = targetDirectory.createFile("application/octet-stream", name!!)
+ if (targetPasswordFile?.exists() == true) {
+ val destOutputStream = contentResolver.openOutputStream(targetPasswordFile.uri)
+
+ if (destOutputStream != null && sourceInputStream != null) {
+ sourceInputStream.copyTo(destOutputStream, 1024)
+
+ sourceInputStream.close()
+ destOutputStream.close()
+ }
+ }
+ }
+
+ /**
+ * Recursively copies a directory to a destination.
+ *
+ * @param sourceDirectory directory to copy from.
+ * @param targetDirectory directory to copy to.
+ */
+ private fun copyDirToDir(sourceDirectory: DocumentFile, targetDirectory: DocumentFile) {
+ sourceDirectory.listFiles().forEach { file ->
+ if (file.isDirectory) {
+ // Create new directory and recurse
+ val newDir = targetDirectory.createDirectory(file.name!!)
+ copyDirToDir(file, newDir!!)
+ } else {
+ copyFileToDir(file, targetDirectory)
+ }
+ }
+ }
+
+ private fun createNotification() {
+ createNotificationChannel()
+
+ val notification = NotificationCompat.Builder(this, CHANNEL_ID)
+ .setContentTitle(getString(R.string.app_name))
+ .setContentText(getString(R.string.exporting_passwords))
+ .setSmallIcon(R.drawable.ic_round_import_export)
+ .setPriority(NotificationCompat.PRIORITY_LOW)
+ .build()
+
+ startForeground(2, notification)
+ }
+
+ private fun createNotificationChannel() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ val serviceChannel = NotificationChannel(
+ CHANNEL_ID,
+ getString(R.string.app_name),
+ NotificationManager.IMPORTANCE_LOW
+ )
+ val manager = getSystemService<NotificationManager>()
+ if (manager != null) {
+ manager.createNotificationChannel(serviceChannel)
+ } else {
+ d { "Failed to create notification channel" }
+ }
+ }
+ }
+
+ companion object {
+
+ const val ACTION_EXPORT_PASSWORD = "ACTION_EXPORT_PASSWORD"
+ private const val CHANNEL_ID = "NotificationService"
+ }
+}
diff --git a/app/src/main/java/com/zeapo/pwdstore/UserPreference.kt b/app/src/main/java/com/zeapo/pwdstore/UserPreference.kt
index 9f444d45..950b6523 100644
--- a/app/src/main/java/com/zeapo/pwdstore/UserPreference.kt
+++ b/app/src/main/java/com/zeapo/pwdstore/UserPreference.kt
@@ -59,11 +59,7 @@ import com.zeapo.pwdstore.utils.autofillManager
import com.zeapo.pwdstore.utils.getEncryptedPrefs
import java.io.File
import java.io.IOException
-import java.time.LocalDateTime
-import java.time.format.DateTimeFormatter
-import java.util.Calendar
import java.util.HashSet
-import java.util.TimeZone
import me.msfjarvis.openpgpktx.util.OpenPgpUtils
typealias ClickListener = Preference.OnPreferenceClickListener
@@ -643,6 +639,13 @@ class UserPreference : AppCompatActivity() {
* Exports the passwords
*/
private fun exportPasswords() {
+ val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).apply {
+ flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or
+ Intent.FLAG_GRANT_WRITE_URI_PERMISSION or
+ Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION or
+ Intent.FLAG_GRANT_PREFIX_URI_PERMISSION
+ }
+
registerForActivityResult(StartActivityForResult()) { result ->
if (!validateResult(result)) return@registerForActivityResult
val uri = result.data?.data
@@ -651,10 +654,19 @@ class UserPreference : AppCompatActivity() {
val targetDirectory = DocumentFile.fromTreeUri(applicationContext, uri)
if (targetDirectory != null) {
- exportPasswords(targetDirectory)
+ val service = Intent(applicationContext, PasswordExportService::class.java).apply {
+ action = PasswordExportService.ACTION_EXPORT_PASSWORD
+ putExtra("uri", uri)
+ }
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ startForegroundService(service)
+ } else {
+ startService(service)
+ }
}
}
- }.launch(Intent(Intent.ACTION_OPEN_DOCUMENT_TREE))
+ }.launch(intent)
}
/**
@@ -772,77 +784,6 @@ class UserPreference : AppCompatActivity() {
return autofillManager?.hasEnabledAutofillServices() == true
}
- /**
- * Exports passwords to the given directory.
- *
- * Recursively copies the existing password store to an external directory.
- *
- * @param targetDirectory directory to copy password directory to.
- */
- private fun exportPasswords(targetDirectory: DocumentFile) {
-
- val repositoryDirectory = requireNotNull(PasswordRepository.getRepositoryDirectory(applicationContext))
- val sourcePassDir = DocumentFile.fromFile(repositoryDirectory)
-
- tag(TAG).d { "Copying ${repositoryDirectory.path} to $targetDirectory" }
-
- val dateString = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
- LocalDateTime
- .now()
- .format(DateTimeFormatter.ISO_DATE_TIME)
- } else {
- String.format("%tFT%<tRZ", Calendar.getInstance(TimeZone.getTimeZone("Z")))
- }
-
- val passDir = targetDirectory.createDirectory("password_store_$dateString")
-
- if (passDir != null) {
- copyDirToDir(sourcePassDir, passDir)
- }
- }
-
- /**
- * Copies a password file to a given directory.
- *
- * Note: this does not preserve last modified time.
- *
- * @param passwordFile password file to copy.
- * @param targetDirectory target directory to copy password.
- */
- private fun copyFileToDir(passwordFile: DocumentFile, targetDirectory: DocumentFile) {
- val sourceInputStream = contentResolver.openInputStream(passwordFile.uri)
- val name = passwordFile.name
- val targetPasswordFile = targetDirectory.createFile("application/octet-stream", name!!)
- if (targetPasswordFile?.exists() == true) {
- val destOutputStream = contentResolver.openOutputStream(targetPasswordFile.uri)
-
- if (destOutputStream != null && sourceInputStream != null) {
- sourceInputStream.copyTo(destOutputStream, 1024)
-
- sourceInputStream.close()
- destOutputStream.close()
- }
- }
- }
-
- /**
- * Recursively copies a directory to a destination.
- *
- * @param sourceDirectory directory to copy from.
- * @param sourceDirectory directory to copy to.
- */
- private fun copyDirToDir(sourceDirectory: DocumentFile, targetDirectory: DocumentFile) {
- sourceDirectory.listFiles().forEach { file ->
- if (file.isDirectory) {
- // Create new directory and recurse
- val newDir = targetDirectory.createDirectory(file.name!!)
- copyDirToDir(file, newDir!!)
- } else {
- copyFileToDir(file, targetDirectory)
- }
- }
- }
-
companion object {
private const val TAG = "UserPreference"