From 493e86902277d9e8b7996308aa589013b4418dd6 Mon Sep 17 00:00:00 2001 From: Harsh Shandilya Date: Thu, 21 Apr 2022 23:34:14 +0530 Subject: Remove support for external storage and raise target SDK to 31 (#1863) --- .../aps/ui/onboarding/fragments/CloneFragment.kt | 30 +++- .../onboarding/fragments/RepoLocationFragment.kt | 198 --------------------- .../msfjarvis/aps/ui/passwords/PasswordStore.kt | 49 +---- .../aps/ui/settings/DirectorySelectionActivity.kt | 59 ------ .../aps/ui/settings/RepositorySettings.kt | 93 +++------- 5 files changed, 56 insertions(+), 373 deletions(-) delete mode 100644 app/src/main/java/dev/msfjarvis/aps/ui/onboarding/fragments/RepoLocationFragment.kt delete mode 100644 app/src/main/java/dev/msfjarvis/aps/ui/settings/DirectorySelectionActivity.kt (limited to 'app/src/main/java/dev/msfjarvis/aps/ui') diff --git a/app/src/main/java/dev/msfjarvis/aps/ui/onboarding/fragments/CloneFragment.kt b/app/src/main/java/dev/msfjarvis/aps/ui/onboarding/fragments/CloneFragment.kt index 603c016b..16c1a784 100644 --- a/app/src/main/java/dev/msfjarvis/aps/ui/onboarding/fragments/CloneFragment.kt +++ b/app/src/main/java/dev/msfjarvis/aps/ui/onboarding/fragments/CloneFragment.kt @@ -11,7 +11,10 @@ import androidx.activity.result.contract.ActivityResultContracts import androidx.appcompat.app.AppCompatActivity import androidx.core.content.edit import androidx.fragment.app.Fragment +import com.github.michaelbull.result.onFailure +import com.github.michaelbull.result.runCatching import dev.msfjarvis.aps.R +import dev.msfjarvis.aps.data.repo.PasswordRepository import dev.msfjarvis.aps.databinding.FragmentCloneBinding import dev.msfjarvis.aps.ui.git.config.GitServerConfigActivity import dev.msfjarvis.aps.util.extensions.finish @@ -20,6 +23,9 @@ import dev.msfjarvis.aps.util.extensions.sharedPrefs import dev.msfjarvis.aps.util.extensions.unsafeLazy import dev.msfjarvis.aps.util.extensions.viewBinding import dev.msfjarvis.aps.util.settings.PreferenceKeys +import logcat.LogPriority.ERROR +import logcat.asLog +import logcat.logcat class CloneFragment : Fragment(R.layout.fragment_clone) { @@ -38,17 +44,33 @@ class CloneFragment : Fragment(R.layout.fragment_clone) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) binding.cloneRemote.setOnClickListener { cloneToHiddenDir() } - binding.createLocal.setOnClickListener { - parentFragmentManager.performTransactionWithBackStack(RepoLocationFragment.newInstance()) - } + binding.createLocal.setOnClickListener { createRepository() } } /** Clones a remote Git repository to the app's private directory */ private fun cloneToHiddenDir() { - settings.edit { putBoolean(PreferenceKeys.GIT_EXTERNAL, false) } cloneAction.launch(GitServerConfigActivity.createCloneIntent(requireContext())) } + private fun createRepository() { + val localDir = PasswordRepository.getRepositoryDirectory() + runCatching { + check(localDir.exists() || localDir.mkdir()) { "Failed to create directory!" } + PasswordRepository.createRepository(localDir) + if (!PasswordRepository.isInitialized) { + PasswordRepository.initialize() + } + parentFragmentManager.performTransactionWithBackStack(KeySelectionFragment.newInstance()) + } + .onFailure { e -> + logcat(ERROR) { e.asLog() } + if (!localDir.delete()) { + logcat { "Failed to delete local repository: $localDir" } + } + finish() + } + } + companion object { fun newInstance(): CloneFragment = CloneFragment() diff --git a/app/src/main/java/dev/msfjarvis/aps/ui/onboarding/fragments/RepoLocationFragment.kt b/app/src/main/java/dev/msfjarvis/aps/ui/onboarding/fragments/RepoLocationFragment.kt deleted file mode 100644 index befe44f1..00000000 --- a/app/src/main/java/dev/msfjarvis/aps/ui/onboarding/fragments/RepoLocationFragment.kt +++ /dev/null @@ -1,198 +0,0 @@ -/* - * Copyright © 2014-2021 The Android Password Store Authors. All Rights Reserved. - * SPDX-License-Identifier: GPL-3.0-only - */ - -package dev.msfjarvis.aps.ui.onboarding.fragments - -import android.Manifest -import android.content.Intent -import android.os.Bundle -import android.view.View -import androidx.activity.result.contract.ActivityResultContracts -import androidx.appcompat.app.AppCompatActivity -import androidx.core.content.edit -import androidx.fragment.app.Fragment -import com.github.michaelbull.result.onFailure -import com.github.michaelbull.result.runCatching -import com.google.android.material.dialog.MaterialAlertDialogBuilder -import dev.msfjarvis.aps.R -import dev.msfjarvis.aps.data.repo.PasswordRepository -import dev.msfjarvis.aps.databinding.FragmentRepoLocationBinding -import dev.msfjarvis.aps.ui.settings.DirectorySelectionActivity -import dev.msfjarvis.aps.util.extensions.finish -import dev.msfjarvis.aps.util.extensions.getString -import dev.msfjarvis.aps.util.extensions.isPermissionGranted -import dev.msfjarvis.aps.util.extensions.listFilesRecursively -import dev.msfjarvis.aps.util.extensions.performTransactionWithBackStack -import dev.msfjarvis.aps.util.extensions.sharedPrefs -import dev.msfjarvis.aps.util.extensions.unsafeLazy -import dev.msfjarvis.aps.util.extensions.viewBinding -import dev.msfjarvis.aps.util.settings.PasswordSortOrder -import dev.msfjarvis.aps.util.settings.PreferenceKeys -import java.io.File -import logcat.LogPriority.ERROR -import logcat.asLog -import logcat.logcat - -class RepoLocationFragment : Fragment(R.layout.fragment_repo_location) { - - private val settings by unsafeLazy { requireActivity().applicationContext.sharedPrefs } - private val directorySelectIntent by unsafeLazy { - Intent(requireContext(), DirectorySelectionActivity::class.java) - } - private val binding by viewBinding(FragmentRepoLocationBinding::bind) - private val sortOrder: PasswordSortOrder - get() = PasswordSortOrder.getSortOrder(settings) - - private val repositoryInitAction = - registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> - if (result.resultCode == AppCompatActivity.RESULT_OK) { - initializeRepositoryInfo() - } - } - - private val externalDirectorySelectAction = - registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> - if (result.resultCode == AppCompatActivity.RESULT_OK) { - if (checkExternalDirectory()) { - finish() - } else { - createRepository() - } - } - } - - private val externalDirPermGrantedAction = createPermGrantedAction { - externalDirectorySelectAction.launch(directorySelectIntent) - } - - private val repositoryUsePermGrantedAction = createPermGrantedAction { - initializeRepositoryInfo() - } - - private val repositoryChangePermGrantedAction = createPermGrantedAction { - repositoryInitAction.launch(directorySelectIntent) - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - binding.hidden.setOnClickListener { createRepoInHiddenDir() } - - binding.sdcard.setOnClickListener { createRepoFromExternalDir() } - } - - /** Initializes an empty repository in the app's private directory */ - private fun createRepoInHiddenDir() { - settings.edit { - putBoolean(PreferenceKeys.GIT_EXTERNAL, false) - remove(PreferenceKeys.GIT_EXTERNAL_REPO) - } - initializeRepositoryInfo() - } - - /** Initializes an empty repository in a selected directory if one does not already exist */ - private fun createRepoFromExternalDir() { - settings.edit { putBoolean(PreferenceKeys.GIT_EXTERNAL, true) } - val externalRepo = settings.getString(PreferenceKeys.GIT_EXTERNAL_REPO) - if (externalRepo == null) { - if (!isPermissionGranted(Manifest.permission.WRITE_EXTERNAL_STORAGE)) { - externalDirPermGrantedAction.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE) - } else { - // Unlikely we have storage permissions without user ever selecting a directory, - // but let's not assume. - externalDirectorySelectAction.launch(directorySelectIntent) - } - } else { - MaterialAlertDialogBuilder(requireActivity()) - .setTitle(resources.getString(R.string.directory_selected_title)) - .setMessage(resources.getString(R.string.directory_selected_message, externalRepo)) - .setPositiveButton(resources.getString(R.string.use)) { _, _ -> - if (!isPermissionGranted(Manifest.permission.WRITE_EXTERNAL_STORAGE)) { - repositoryUsePermGrantedAction.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE) - } else { - initializeRepositoryInfo() - } - } - .setNegativeButton(resources.getString(R.string.change)) { _, _ -> - if (!isPermissionGranted(Manifest.permission.WRITE_EXTERNAL_STORAGE)) { - repositoryChangePermGrantedAction.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE) - } else { - repositoryInitAction.launch(directorySelectIntent) - } - } - .show() - } - } - - private fun checkExternalDirectory(): Boolean { - if (settings.getBoolean(PreferenceKeys.GIT_EXTERNAL, false) && - settings.getString(PreferenceKeys.GIT_EXTERNAL_REPO) != null - ) { - val externalRepoPath = settings.getString(PreferenceKeys.GIT_EXTERNAL_REPO) - val dir = externalRepoPath?.let { File(it) } - if (dir != null && // The directory could be opened - dir.exists() && // The directory exists - dir.isDirectory && // The directory, is really a directory - dir.listFilesRecursively().isNotEmpty() && // The directory contains files - // The directory contains a non-zero number of password files - PasswordRepository.getPasswords( - dir, - PasswordRepository.getRepositoryDirectory(), - sortOrder - ) - .isNotEmpty() - ) { - PasswordRepository.closeRepository() - return true - } - } - return false - } - - private fun createRepository() { - val localDir = PasswordRepository.getRepositoryDirectory() - runCatching { - check(localDir.exists() || localDir.mkdir()) { "Failed to create directory!" } - PasswordRepository.createRepository(localDir) - if (!PasswordRepository.isInitialized) { - PasswordRepository.initialize() - } - parentFragmentManager.performTransactionWithBackStack(KeySelectionFragment.newInstance()) - } - .onFailure { e -> - logcat(ERROR) { e.asLog() } - if (!localDir.delete()) { - logcat { "Failed to delete local repository: $localDir" } - } - finish() - } - } - - private fun initializeRepositoryInfo() { - val externalRepo = settings.getBoolean(PreferenceKeys.GIT_EXTERNAL, false) - val externalRepoPath = settings.getString(PreferenceKeys.GIT_EXTERNAL_REPO) - if (externalRepo && !isPermissionGranted(Manifest.permission.WRITE_EXTERNAL_STORAGE)) { - return - } - if (externalRepo && externalRepoPath != null) { - if (checkExternalDirectory()) { - finish() - return - } - } - createRepository() - } - - private fun createPermGrantedAction(block: () -> Unit) = - registerForActivityResult(ActivityResultContracts.RequestPermission()) { granted -> - if (granted) { - block.invoke() - } - } - - companion object { - - fun newInstance(): RepoLocationFragment = RepoLocationFragment() - } -} diff --git a/app/src/main/java/dev/msfjarvis/aps/ui/passwords/PasswordStore.kt b/app/src/main/java/dev/msfjarvis/aps/ui/passwords/PasswordStore.kt index d80959b8..e4ec2359 100644 --- a/app/src/main/java/dev/msfjarvis/aps/ui/passwords/PasswordStore.kt +++ b/app/src/main/java/dev/msfjarvis/aps/ui/passwords/PasswordStore.kt @@ -4,7 +4,6 @@ */ package dev.msfjarvis.aps.ui.passwords -import android.Manifest import android.annotation.SuppressLint import android.content.ComponentName import android.content.Context @@ -40,12 +39,10 @@ import dev.msfjarvis.aps.ui.crypto.DecryptActivity import dev.msfjarvis.aps.ui.crypto.DecryptActivityV2 import dev.msfjarvis.aps.ui.crypto.PasswordCreationActivity import dev.msfjarvis.aps.ui.crypto.PasswordCreationActivityV2 -import dev.msfjarvis.aps.ui.dialogs.BasicBottomSheet import dev.msfjarvis.aps.ui.dialogs.FolderCreationDialogFragment import dev.msfjarvis.aps.ui.folderselect.SelectFolderActivity import dev.msfjarvis.aps.ui.git.base.BaseGitActivity import dev.msfjarvis.aps.ui.onboarding.activity.OnboardingActivity -import dev.msfjarvis.aps.ui.settings.DirectorySelectionActivity import dev.msfjarvis.aps.ui.settings.SettingsActivity import dev.msfjarvis.aps.util.autofill.AutofillMatcher import dev.msfjarvis.aps.util.extensions.base64 @@ -53,7 +50,6 @@ import dev.msfjarvis.aps.util.extensions.commitChange import dev.msfjarvis.aps.util.extensions.contains import dev.msfjarvis.aps.util.extensions.getString import dev.msfjarvis.aps.util.extensions.isInsideRepository -import dev.msfjarvis.aps.util.extensions.isPermissionGranted import dev.msfjarvis.aps.util.extensions.listFilesRecursively import dev.msfjarvis.aps.util.extensions.sharedPrefs import dev.msfjarvis.aps.util.features.Feature @@ -206,16 +202,7 @@ class PasswordStore : BaseGitActivity() { @SuppressLint("NewApi") override fun onCreate(savedInstanceState: Bundle?) { - // If user opens app with permission granted then revokes and returns, - // prevent attempt to create password list fragment - var savedInstance = savedInstanceState - if (savedInstanceState != null && - (!settings.getBoolean(PreferenceKeys.GIT_EXTERNAL, false) || - !isPermissionGranted(Manifest.permission.WRITE_EXTERNAL_STORAGE)) - ) { - savedInstance = null - } - super.onCreate(savedInstance) + super.onCreate(savedInstanceState) setContentView(R.layout.activity_pwdstore) model.currentDir.observe(this) { dir -> @@ -233,11 +220,7 @@ class PasswordStore : BaseGitActivity() { override fun onResume() { super.onResume() - if (settings.getBoolean(PreferenceKeys.GIT_EXTERNAL, false)) { - hasRequiredStoragePermissions() - } else { - checkLocalRepository() - } + checkLocalRepository() if (settings.getBoolean(PreferenceKeys.SEARCH_ON_START, false) && ::searchItem.isInitialized) { if (!searchItem.isActionViewExpanded) { searchItem.expandActionView() @@ -362,33 +345,9 @@ class PasswordStore : BaseGitActivity() { ) } - /** - * Validates if storage permission is granted, and requests for it if not. The return value is - * true if the permission has been granted. - */ - private fun hasRequiredStoragePermissions(): Boolean { - return if (!isPermissionGranted(Manifest.permission.WRITE_EXTERNAL_STORAGE)) { - BasicBottomSheet.Builder(this) - .setMessageRes(R.string.access_sdcard_text) - .setPositiveButtonClickListener(getString(R.string.snackbar_action_grant)) { - storagePermissionRequest.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE) - } - .build() - .show(supportFragmentManager, "STORAGE_PERMISSION_MISSING") - false - } else { - checkLocalRepository() - true - } - } - private fun checkLocalRepository() { - val repo = PasswordRepository.initialize() - if (repo == null) { - directorySelectAction.launch(Intent(this, DirectorySelectionActivity::class.java)) - } else { - checkLocalRepository(PasswordRepository.getRepositoryDirectory()) - } + PasswordRepository.initialize() + checkLocalRepository(PasswordRepository.getRepositoryDirectory()) } private fun checkLocalRepository(localDir: File?) { diff --git a/app/src/main/java/dev/msfjarvis/aps/ui/settings/DirectorySelectionActivity.kt b/app/src/main/java/dev/msfjarvis/aps/ui/settings/DirectorySelectionActivity.kt deleted file mode 100644 index 1f8873c0..00000000 --- a/app/src/main/java/dev/msfjarvis/aps/ui/settings/DirectorySelectionActivity.kt +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright © 2014-2021 The Android Password Store Authors. All Rights Reserved. - * SPDX-License-Identifier: GPL-3.0-only - */ - -package dev.msfjarvis.aps.ui.settings - -import android.net.Uri -import android.os.Bundle -import android.os.Environment -import android.provider.DocumentsContract -import androidx.activity.result.contract.ActivityResultContracts -import androidx.appcompat.app.AppCompatActivity -import androidx.core.content.edit -import com.google.android.material.dialog.MaterialAlertDialogBuilder -import dev.msfjarvis.aps.R -import dev.msfjarvis.aps.util.extensions.sharedPrefs -import dev.msfjarvis.aps.util.settings.PreferenceKeys -import logcat.logcat - -class DirectorySelectionActivity : AppCompatActivity() { - - @Suppress("DEPRECATION") - private val directorySelectAction = - registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { uri: Uri? -> - if (uri == null) return@registerForActivityResult - - logcat { "Selected repository URI is $uri" } - // TODO: This is fragile. Workaround until PasswordItem is backed by DocumentFile - val docId = DocumentsContract.getTreeDocumentId(uri) - val split = docId.split(":".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() - val path = if (split.size > 1) split[1] else split[0] - val repoPath = "${Environment.getExternalStorageDirectory()}/$path" - val prefs = sharedPrefs - - logcat { "Selected repository path is $repoPath" } - - if (Environment.getExternalStorageDirectory().path == repoPath) { - MaterialAlertDialogBuilder(this) - .setTitle(resources.getString(R.string.sdcard_root_warning_title)) - .setMessage(resources.getString(R.string.sdcard_root_warning_message)) - .setPositiveButton(resources.getString(R.string.sdcard_root_warning_remove_everything)) { - _, - _ -> - prefs.edit { putString(PreferenceKeys.GIT_EXTERNAL_REPO, uri.path) } - } - .setNegativeButton(R.string.dialog_cancel, null) - .show() - } - prefs.edit { putString(PreferenceKeys.GIT_EXTERNAL_REPO, repoPath) } - setResult(RESULT_OK) - finish() - } - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - directorySelectAction.launch(null) - } -} diff --git a/app/src/main/java/dev/msfjarvis/aps/ui/settings/RepositorySettings.kt b/app/src/main/java/dev/msfjarvis/aps/ui/settings/RepositorySettings.kt index cbd9c18f..1c43127a 100644 --- a/app/src/main/java/dev/msfjarvis/aps/ui/settings/RepositorySettings.kt +++ b/app/src/main/java/dev/msfjarvis/aps/ui/settings/RepositorySettings.kt @@ -23,7 +23,6 @@ import dagger.hilt.components.SingletonComponent import de.Maxr1998.modernpreferences.Preference import de.Maxr1998.modernpreferences.PreferenceScreen import de.Maxr1998.modernpreferences.helpers.checkBox -import de.Maxr1998.modernpreferences.helpers.onCheckedChange import de.Maxr1998.modernpreferences.helpers.onClick import de.Maxr1998.modernpreferences.helpers.pref import dev.msfjarvis.aps.R @@ -60,17 +59,6 @@ class RepositorySettings(private val activity: FragmentActivity) : SettingsProvi private var showSshKeyPref: Preference? = null - private fun selectExternalGitRepository() { - MaterialAlertDialogBuilder(activity) - .setTitle(activity.resources.getString(R.string.external_repository_dialog_title)) - .setMessage(activity.resources.getString(R.string.external_repository_dialog_text)) - .setPositiveButton(R.string.dialog_ok) { _, _ -> - activity.launchActivity(DirectorySelectionActivity::class.java) - } - .setNegativeButton(R.string.dialog_cancel, null) - .show() - } - override fun provideSettings(builder: PreferenceScreen.Builder) { val encryptedPreferences = hiltEntryPoint.encryptedPreferences() val gitSettings = hiltEntryPoint.gitSettings() @@ -162,64 +150,35 @@ class RepositorySettings(private val activity: FragmentActivity) : SettingsProvi true } } - val deleteRepoPref = - pref(PreferenceKeys.GIT_DELETE_REPO) { - titleRes = R.string.pref_git_delete_repo_title - summaryRes = R.string.pref_git_delete_repo_summary - visible = !activity.sharedPrefs.getBoolean(PreferenceKeys.GIT_EXTERNAL, false) - onClick { - val repoDir = PasswordRepository.getRepositoryDirectory() - MaterialAlertDialogBuilder(activity) - .setTitle(R.string.pref_dialog_delete_title) - .setMessage(activity.getString(R.string.dialog_delete_msg, repoDir)) - .setCancelable(false) - .setPositiveButton(R.string.dialog_delete) { dialogInterface, _ -> - runCatching { - PasswordRepository.getRepositoryDirectory().deleteRecursively() - PasswordRepository.closeRepository() - } - .onFailure { it.message?.let { message -> activity.snackbar(message = message) } } + pref(PreferenceKeys.GIT_DELETE_REPO) { + titleRes = R.string.pref_git_delete_repo_title + summaryRes = R.string.pref_git_delete_repo_summary + onClick { + val repoDir = PasswordRepository.getRepositoryDirectory() + MaterialAlertDialogBuilder(activity) + .setTitle(R.string.pref_dialog_delete_title) + .setMessage(activity.getString(R.string.dialog_delete_msg, repoDir)) + .setCancelable(false) + .setPositiveButton(R.string.dialog_delete) { dialogInterface, _ -> + runCatching { + PasswordRepository.getRepositoryDirectory().deleteRecursively() + PasswordRepository.closeRepository() + } + .onFailure { it.message?.let { message -> activity.snackbar(message = message) } } - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) { - activity.getSystemService()?.apply { - removeDynamicShortcuts(dynamicShortcuts.map { it.id }.toMutableList()) - } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) { + activity.getSystemService()?.apply { + removeDynamicShortcuts(dynamicShortcuts.map { it.id }.toMutableList()) } - activity.sharedPrefs.edit { - putBoolean(PreferenceKeys.REPOSITORY_INITIALIZED, false) - } - dialogInterface.cancel() - activity.finish() - } - .setNegativeButton(R.string.dialog_do_not_delete) { dialogInterface, _ -> - run { dialogInterface.cancel() } } - .show() - true - } - } - checkBox(PreferenceKeys.GIT_EXTERNAL) { - titleRes = R.string.pref_external_repository_title - summaryRes = R.string.pref_external_repository_summary - onCheckedChange { checked -> - deleteRepoPref.visible = !checked - deleteRepoPref.requestRebind() - PasswordRepository.closeRepository() - activity.sharedPrefs.edit { putBoolean(PreferenceKeys.REPO_CHANGED, true) } - true - } - } - pref(PreferenceKeys.GIT_EXTERNAL_REPO) { - val externalRepo = activity.sharedPrefs.getString(PreferenceKeys.GIT_EXTERNAL_REPO) - if (externalRepo != null) { - summary = externalRepo - } else { - summaryRes = R.string.pref_select_external_repository_summary_no_repo_selected - } - titleRes = R.string.pref_select_external_repository_title - dependency = PreferenceKeys.GIT_EXTERNAL - onClick { - selectExternalGitRepository() + activity.sharedPrefs.edit { putBoolean(PreferenceKeys.REPOSITORY_INITIALIZED, false) } + dialogInterface.cancel() + activity.finish() + } + .setNegativeButton(R.string.dialog_do_not_delete) { dialogInterface, _ -> + run { dialogInterface.cancel() } + } + .show() true } } -- cgit v1.2.3