Skip to content

Commit 51b427b

Browse files
committed
feat: make all files access permission optional on full builds
1 parent c99b6b5 commit 51b427b

8 files changed

Lines changed: 55 additions & 13 deletions

File tree

AnkiDroid/src/main/java/com/ichi2/anki/InitialActivity.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import com.ichi2.anki.exception.StorageAccessException
3535
import com.ichi2.anki.servicelayer.PreferenceUpgradeService
3636
import com.ichi2.anki.servicelayer.PreferenceUpgradeService.setPreferencesUpToDate
3737
import com.ichi2.anki.servicelayer.ScopedStorageService.isLegacyStorage
38+
import com.ichi2.anki.settings.Prefs
3839
import com.ichi2.anki.ui.windows.permissions.InternetPermissionFragment
3940
import com.ichi2.anki.ui.windows.permissions.NotificationsPermissionFragment
4041
import com.ichi2.anki.ui.windows.permissions.PermissionsFragment
@@ -257,6 +258,9 @@ internal fun selectAnkiDroidFolder(
257258
}
258259

259260
fun selectAnkiDroidFolder(context: Context): AnkiDroidFolder {
261+
if (Prefs.usePrivateStorage) {
262+
return AnkiDroidFolder.AppPrivateFolder
263+
}
260264
val canAccessLegacyStorage = Build.VERSION.SDK_INT < Build.VERSION_CODES.Q || Environment.isExternalStorageLegacy()
261265
val currentFolderIsAccessibleAndLegacy = canAccessLegacyStorage && isLegacyStorage(context, setCollectionPath = false) == true
262266

AnkiDroid/src/main/java/com/ichi2/anki/IntentHandler.kt

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -349,14 +349,18 @@ class IntentHandler : AbstractIntentHandler() {
349349
*
350350
* @throws SystemStorageException if `getExternalFilesDir` returns null
351351
*/
352+
353+
fun hasRequiredPermissions(context: Context): Boolean {
354+
if (Prefs.usePrivateStorage) return true
355+
return !ScopedStorageService.isLegacyStorage(context) || hasLegacyStorageAccessPermission(context) ||
356+
Permissions.isExternalStorageManagerCompat()
357+
}
358+
352359
fun grantedStoragePermissions(
353360
context: Context,
354361
showToast: Boolean,
355362
): Boolean {
356-
val granted =
357-
!ScopedStorageService.isLegacyStorage(context) ||
358-
hasLegacyStorageAccessPermission(context) ||
359-
Permissions.isExternalStorageManagerCompat()
363+
val granted = hasRequiredPermissions(context)
360364

361365
if (!granted && showToast) {
362366
showThemedToast(context, context.getString(R.string.intent_handler_failed_no_storage_permission), false)

AnkiDroid/src/main/java/com/ichi2/anki/settings/Prefs.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,8 @@ open class PrefsRepository(
339339

340340
var internetPermissionRequested by booleanPref(R.string.internet_permission_requested_key, false)
341341

342+
var usePrivateStorage by booleanPref(R.string.use_private_storage_key, false)
343+
342344
// **************************************** Reviewer **************************************** //
343345

344346
val ignoreDisplayCutout by booleanPref(R.string.ignore_display_cutout_key, false)

AnkiDroid/src/main/java/com/ichi2/anki/ui/windows/permissions/PermissionsActivity.kt

Lines changed: 34 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,17 @@ import android.os.Bundle
2121
import android.os.Parcelable
2222
import androidx.activity.addCallback
2323
import androidx.core.content.IntentCompat
24+
import androidx.core.content.edit
2425
import androidx.fragment.app.commit
26+
import com.google.android.material.dialog.MaterialAlertDialogBuilder
2527
import com.ichi2.anki.AnkiActivity
28+
import com.ichi2.anki.AnkiDroidApp
29+
import com.ichi2.anki.CollectionHelper
2630
import com.ichi2.anki.PermissionSet
2731
import com.ichi2.anki.R
2832
import com.ichi2.anki.databinding.ActivityPermissionsBinding
33+
import com.ichi2.anki.introduction.hasCollectionStoragePermissions
34+
import com.ichi2.anki.settings.Prefs
2935
import com.ichi2.anki.showThemedToast
3036
import com.ichi2.anki.ui.windows.permissions.PermissionsFragment.Companion.HAS_ALL_PERMISSIONS_KEY
3137
import com.ichi2.anki.ui.windows.permissions.PermissionsFragment.Companion.PERMISSIONS_FRAGMENT_RESULT_KEY
@@ -34,6 +40,7 @@ import com.ichi2.themes.Themes
3440
import com.ichi2.themes.setTransparentStatusBar
3541
import dev.androidbroadcast.vbpd.viewBinding
3642
import timber.log.Timber
43+
import java.io.File
3744

3845
/**
3946
* Screen responsible for getting permissions from the user.
@@ -60,7 +67,13 @@ class PermissionsActivity : AnkiActivity(R.layout.activity_permissions) {
6067
Themes.setTheme(this)
6168
setTransparentStatusBar()
6269

63-
binding.continueButton.setOnClickListener { finish() }
70+
binding.continueButton.setOnClickListener {
71+
if (!hasCollectionStoragePermissions()) {
72+
showPrivateStorageWarningDialog()
73+
} else {
74+
finish()
75+
}
76+
}
6477

6578
// #20881: Activity should not be launchd without extras
6679
val permissionSet = IntentCompat.getParcelableExtra(intent, PERMISSIONS_SET_EXTRA, PermissionSet::class.java)
@@ -75,10 +88,6 @@ class PermissionsActivity : AnkiActivity(R.layout.activity_permissions) {
7588
requireNotNull(permissionSet.permissionsFragment?.getDeclaredConstructor()?.newInstance()) {
7689
"invalid permissionsFragment"
7790
}
78-
setFragmentResultListener(PERMISSIONS_FRAGMENT_RESULT_KEY) { _, bundle ->
79-
val hasAllPermissions = bundle.getBoolean(HAS_ALL_PERMISSIONS_KEY)
80-
setContinueButtonEnabled(hasAllPermissions)
81-
}
8291

8392
supportFragmentManager.commit {
8493
replace(R.id.fragment_container, permissionsFragment)
@@ -87,8 +96,26 @@ class PermissionsActivity : AnkiActivity(R.layout.activity_permissions) {
8796
onBackPressedDispatcher.addCallback {}
8897
}
8998

90-
fun setContinueButtonEnabled(isEnabled: Boolean) {
91-
binding.continueButton.isEnabled = isEnabled
99+
// TODO Rethink the UI - selection UI is preferred over a warning dialog: #21049
100+
private fun showPrivateStorageWarningDialog() {
101+
MaterialAlertDialogBuilder(this)
102+
.setTitle(R.string.private_storage_warning_title)
103+
.setMessage(R.string.private_storage_warning)
104+
.setPositiveButton(R.string.dialog_continue) { _, _ ->
105+
Prefs.usePrivateStorage = true
106+
val externalFilesDir = getExternalFilesDir(null) ?: filesDir
107+
val privateDir = File(externalFilesDir, "AnkiDroid")
108+
if (!privateDir.mkdirs() && !privateDir.exists()) {
109+
Timber.w("Failed to create AnkiDroid directory: ${privateDir.absolutePath}")
110+
showThemedToast(this, R.string.something_wrong, false)
111+
return@setPositiveButton
112+
}
113+
AnkiDroidApp.sharedPrefs().edit(commit = true) {
114+
putString(CollectionHelper.PREF_COLLECTION_PATH, privateDir.absolutePath)
115+
}
116+
finish()
117+
}.setNegativeButton(R.string.dialog_cancel, null)
118+
.show()
92119
}
93120

94121
companion object {

AnkiDroid/src/main/res/layout/activity_permissions.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@
4545
android:layout_height="wrap_content"
4646
android:layout_margin="16dp"
4747
android:paddingVertical="12dp"
48-
android:text="@string/dialog_continue"
48+
android:text="@string/dialog_skip"
4949
app:layout_constraintBottom_toBottomOf="parent"
5050
/>
5151

AnkiDroid/src/main/res/values/01-core.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@
190190
<string name="new_unsafe_collection">The new collection will be deleted from your phone if you uninstall AnkiDroid</string>
191191

192192
<!-- Permissions screen -->
193-
<string name="permissions_screen_headline">AnkiDroid needs some permissions to work</string>
193+
<string name="permissions_screen_headline">AnkiDroid works better with these permissions</string>
194194
<string name="permissions_screen_optional_headline" comment="Explains that there are optional permissions that can be granted to AnkiDroid.">AnkiDroid works best with these permissions</string>
195195
<string name="storage_access_title">Storage access</string>
196196
<string name="storage_access_summary">Saves your collection in a safe place that will not be deleted if the app is uninstalled</string>

AnkiDroid/src/main/res/values/03-dialogs.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@
6969
<string name="dialog_remove">Remove</string>
7070
<string name="dialog_exit" comment="Label for a button which will exit/finish the current screen">Exit</string>
7171
<string name="dialog_continue">Continue</string>
72+
<string name="dialog_skip">Skip</string>
7273
<string name="dialog_processing">Processing…</string>
7374
<string name="dialog_positive_create" comment="Create a new collection, probably erasing the previous one, may be necessary in case of error.">Create</string>
7475
<string name="dialog_positive_delete" maxLength="28">Delete</string>
@@ -217,6 +218,9 @@
217218
<string name="dismiss_backup_warning_new_user">Due to Android privacy changes, your data and automated backups will be deleted from your phone if the app is uninstalled</string>
218219
<string name="dismiss_backup_warning_upgrade">Due to Android privacy changes, your data and automated backups will be inaccessible if the app is uninstalled</string>
219220

221+
<string name="private_storage_warning_title">Limited storage access</string>
222+
<string name="private_storage_warning">Without this permission, your AnkiDroid collection will be stored in app private storage and will be permanently deleted if you uninstall AnkiDroid.</string>
223+
220224
<string name="create_deck_numeric_hint">If you have deck ordering issues (e.g. ‘10’ appears before ‘2’), replace ‘2’ with ‘02’</string>
221225

222226
<!-- Set Due Date -->

AnkiDroid/src/main/res/values/preferences.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,4 +269,5 @@
269269

270270
<!-- Onboarding -->
271271
<string name="internet_permission_requested_key">internetPermissionRequested</string>
272+
<string name="use_private_storage_key">use_private_storage</string>
272273
</resources>

0 commit comments

Comments
 (0)