2

I am working on a music player and want to use Storage access framework to access the files in the storage. For that I used Intent.ACTION_OPEN_DOCUMENT_TREE and contentResolver.takePersistableUriPermission(...). But once I've got the permissions, I have to store the allowed path so I am using SharedPreferences for that.

When I convert the URI I got from the Intent.ACTION_OPEN_DOCUMENT_TREE to string to reuse it, it gives me a nullPointerException(It says the URI I got from converting the string from preferences is null).

While if I use the same URI without going through saving to the SharedPreferences, It works just fine.

Here's the code:

package com.gaurav712.music

import android.content.Context
import android.content.Intent
import android.content.SharedPreferences
import android.net.Uri
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import androidx.documentfile.provider.DocumentFile

class MainActivity : AppCompatActivity() {

    private val openDocumentTreeRequestCode: Int = 40
    private lateinit var setPreferences: SharedPreferences
    private lateinit var rootUri: Uri

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        setPreferences = getPreferences(Context.MODE_PRIVATE)

        // Check if access to storage is permitted
        checkAccess()

        // Now that we have access to storage, set the root Uri
        rootUri = Uri.parse(setPreferences.getString("allowed_path", ""))
        Log.i("rootUri", rootUri.toString())

//        listFiles()
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        if(requestCode == openDocumentTreeRequestCode && resultCode == RESULT_OK) {

            val treeUri = data?.data    // get data

            if (treeUri != null) {
                contentResolver.takePersistableUriPermission(treeUri,
                    Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
            }

            val editor = setPreferences.edit()
            editor.putBoolean("got_permissions", true)
            editor.putString("allowed_path", treeUri?.toString())
            editor.apply()

            Log.i("treeUri", treeUri.toString())

            val documentFile = treeUri?.let { DocumentFile.fromTreeUri(this, it) }

            for (file in documentFile?.listFiles()!!) {
                file.name?.let { Log.i("file: ", it) }
            }
        }
    }

    private fun checkAccess() {
        val gotPermission: Boolean = setPreferences.getBoolean("got_permissions", false)
        if (!gotPermission)
            getAccess()
        else
            Log.i("got_permissions",
                gotPermission.toString()
                        + " : "
                        + setPreferences.getString("allowed_path", ""))
    }

    private fun getAccess() {
        val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
        startActivityForResult(intent, openDocumentTreeRequestCode)
    }

    private fun listFiles() {

        val documentFile: DocumentFile = DocumentFile.fromTreeUri(this, rootUri)!!

        for (file in documentFile.listFiles()) {
            Log.i("file: ", file.name.toString())
        }
    }
}

In onCreate() if I uncomment listFiles(), It gives nullPointerException but using the same chunk of code as you can see above in onActivityResult(), it all works fine. The chunk I am talking about:

            val documentFile = treeUri?.let { DocumentFile.fromTreeUri(this, it) }

            for (file in documentFile?.listFiles()!!) {
                file.name?.let { Log.i("file: ", it) }
            }

I can't figure out why it says rootUri is null.

I looked at all the similar questions asked like this one. I am using the same functions suggested (toString() and Uri.parse) for conversion but it doesn't seem to work with Storage access framework.

Gaurav
  • 145
  • 5
  • If you look at the actual `SharedPreferences` XML file (e.g., via Device File Explorer in Android Studio), or you look at the value returned by `setPreferences.getString("allowed_path", "")`, does it look OK? – CommonsWare Nov 01 '20 at 14:12
  • Yes I confirmed the values. Both are exactly same. The Uri I get on runtime and the one I am retrieving from the `SharedPreferences` (after `toString` is applied to the `Uri` I got from `Intent.ACTION_OPEN_DOCUMENT_TREE`). – Gaurav Nov 01 '20 at 14:15
  • Can you paste the value here? If it is the same `Uri`, I cannot understand why `Uri.parse()` would be unable to parse it. – CommonsWare Nov 01 '20 at 14:20
  • `content://com.android.externalstorage.documents/tree/primary%3A` when I choose the root of my Internal Storage. – Gaurav Nov 01 '20 at 15:47
  • I just put `val rootUri = Uri.parse("content://com.android.externalstorage.documents/tree/primary%3A")` into a scrap project, and `rootUri` is a valid `Uri`, not `null`. – CommonsWare Nov 01 '20 at 15:53
  • So you are saying my code should work? – Gaurav Nov 01 '20 at 15:59
  • I am saying that I cannot explain why `rootUri` is `null`. You may wish to double-check your results and confirm that is really the case. Also, you might consider posting the stack trace of your `NullPointerException`. – CommonsWare Nov 01 '20 at 16:12
  • I've been testing it for last 2 days. No luck! – Gaurav Nov 01 '20 at 16:14
  • Have you checked if uri.toString() delivers same value after shared preferences storage? – blackapps Nov 01 '20 at 16:55
  • There is a function to print all the uries for which your app has a permanent permission. Print that collection to see if your uri is still valid. That is a better method then your checkAccess. – blackapps Nov 01 '20 at 16:57
  • @blackapps yes I have confirmed the values. I quit the app and then re-run it and then the retrieved value from the storage is the same as the first time, the one I got while asking permission. And regarding the access, I confirmed that too from the App Info > Storage & Cache. That `checkAccess()` is just a dummy function to make the code minimal and functional for the post. – Gaurav Nov 02 '20 at 03:08
  • Why are you not mentioning if your uri is in the permanet permissions collection? Did you check? U have no idea what you checked using app info storage and cache. – blackapps Nov 02 '20 at 08:37

1 Answers1

0

I solved it! the function getAccess() should be:

    private fun getAccess() {
        val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).apply {
            addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION)
        }
        startActivityForResult(intent, openDocumentTreeRequestCode)
    }

I was missing Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION flag in the intent.

Gaurav
  • 145
  • 5