Anki-Android icon indicating copy to clipboard operation
Anki-Android copied to clipboard

Don't force `full` (F-Droid/GitHub) releases users to grant the `All files access` permission

Open BrayanDSO opened this issue 2 years ago • 9 comments

They may not want to give the permission, which I think is a understandable desire.

I believe that a dialog explaining why it is good to give the permission after the user has refused to grant it would be good. It should have an option to try granting the permission again and one to work the same way as play builds do.

BrayanDSO avatar Apr 08 '23 15:04 BrayanDSO

For those stumbling across this: i gave scoped access to the Ankidroid folder and it seems to work properly.

mainrs avatar Apr 10 '23 19:04 mainrs

Hello 👋, this issue has been opened for more than 3 months with no activity on it. If the issue is still here, please keep in mind that we need community support and help to fix it! Just comment something like still searching for solutions and if you found one, please open a pull request! You have 7 days until this gets closed automatically

github-actions[bot] avatar Jul 09 '23 20:07 github-actions[bot]

+1, would love to see this happen 😁

dogception avatar Aug 05 '23 04:08 dogception

I've made a merge request for f-droid to use the full build flavor, the storage access permission should be optional for f-droid users... any idea what we need to do specifically to make this happen?

mikehardy avatar Aug 28 '23 22:08 mikehardy

100% agree here.

  • I don't care about "saving my collection in a safe place that will not be deleted". My collection is already synced using AnkiWeb (and backuped in Desktop apps)
  • Even if I did care about that, it should just ask for some folder, not all files permission

This permission should be optional.

matejdro avatar Oct 29 '23 07:10 matejdro

any idea what we need to do specifically to make this happen?

I made a "short" dive into the code base and from what I’ve learned, some parts of the check for the "permissions required screen" ignore the fact that the data is already migrated into the app private directory. In TL;DR and IMO, the selectAnkiDroidFolder needs to have a check here which returns AppPrivateFolder when the folder was already migrated into private storage. (Further reasoning collapsed below.)

In-Depth Explanation of Code

The check works as follows:

  • at launch, AnkiDroid checks if it has all "required permissions" https://github.com/ankidroid/Anki-Android/blob/978aaf4b6010e6b00bdb2e4a19abb8e32e9319c1/AnkiDroid/src/main/java/com/ichi2/anki/introduction/CollectionPermissionScreenLauncher.kt#L42-L54
  • the "required permissions" are inferred based on the "AnkiDroid folder location"
  • the "folder location" is selected based on the Android OS version running, current permissions and, the location which is currently used (if data is not migrated): https://github.com/ankidroid/Anki-Android/blob/978aaf4b6010e6b00bdb2e4a19abb8e32e9319c1/AnkiDroid/src/main/java/com/ichi2/anki/InitialActivity.kt#L175-L207
    • following AnkiDroid methods is called in that process: https://github.com/ankidroid/Anki-Android/blob/978aaf4b6010e6b00bdb2e4a19abb8e32e9319c1/AnkiDroid/src/main/java/com/ichi2/utils/Permissions.kt#L164-L171
    • and following OS method is called: Environment.isExternalStorageManager(), [Environment.]

So to skip the screen, some of that code needs to change. Going through this code exemplary in the case of my phone:

  • Android 13 (Lineage OS 20; Tiramisu)
  • AnkiDroid 2.17.1, full build installed from F-Droid
    • not the most recent, but affected code, which is linked above, hasn’t changed since
  • data directory already migrated to private app directory

Manually evaluting the selectAnkiDroidFolder gets me to this result (code rolled out):

// I’m running Tiramisu, T not < Q, so first statement should be false
val canAccessLegacyStorage = Build.VERSION.SDK_INT < Build.VERSION_CODES.Q
// result of Environment.isExternalStorageLegacy() is unknown
    || Environment.isExternalStorageLegacy()
// so this may be true (however unimportant for conclusion as you will see below).
val currentFolderIsAccessibleAndLegacy = canAccessLegacyStorage
// but storage is migrated, so isLegacyStorage should return false
    && isLegacyStorage(context, setCollectionPath = false) == true
// Tiramisu >= R
val canManageExternalStorage = Build.VERSION.SDK_INT >= Build.VERSION_CODES.R &&
// this permission is defined in the manifest for F-Droid
    context.arePermissionsDefinedInAnkiDroidManifest(MANAGE_EXTERNAL_STORAGE)
// so canManageExternalStorage = true.
// Tiramisu not <= Q AND currentFolderIsAccessibleAndLegacy assumed false
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q || currentFolderIsAccessibleAndLegacy) {
    return AnkiDroidFolder.PublicFolder(PermissionSet.LEGACY_ACCESS)
}
// so continue here.
// but canManageExternalStorage is true, 
return if (canManageExternalStorage) {
    // so it jumps into here
    // without looking into where the current files are actually stored
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
        AnkiDroidFolder.PublicFolder(PermissionSet.TIRAMISU_EXTERNAL_MANAGER)
    } else {
        AnkiDroidFolder.PublicFolder(PermissionSet.EXTERNAL_MANAGER)
    }
} else {
    // btw. this is our goal
    return AnkiDroidFolder.AppPrivateFolder
}

Zocker1999NET avatar Mar 07 '24 12:03 Zocker1999NET

Actually, a dialog or some kind of screen should be shown with the two options 1. Safe storage, but with permission, 2. Storage that will be gone after uninstall, no permissions required, so the collection path is set correctly.

BrayanDSO avatar Mar 08 '24 18:03 BrayanDSO

For those stumbling across this: i gave scoped access to the Ankidroid folder and it seems to work properly.

How did you do that?

kaoneko avatar Apr 24 '24 19:04 kaoneko

For those stumbling across this: i gave scoped access to the Ankidroid folder and it seems to work properly.

How did you do that?

Apparently the feature is tied to the Android OS version I use: GrapheneOS. Here is the relevant documentation about it: https://grapheneos.org/usage#storage-scopes. Basically, I can select a specific folder for any given app inside the app's OS settings page. The app thinks it has full access, but actually it does not. Only to the folder I select.

mainrs avatar Apr 24 '24 21:04 mainrs