title: How to check which StorageVolume we have access to, and which we don't?
body: <h2>Background</h2>
  
  <p>Google (sadly) <a href="https://www.xda-developers.com/android-q-storage-access-framework-scoped-storage/" rel="noreferrer"><strong>plans to ruin storage permission</strong></a> so that apps won't be able to access the file system using the standard File API (and file-paths). Many are <a href="https://issuetracker.google.com/issues/128591846" rel="noreferrer"><strong>against it</strong></a> as it changes the way apps can access the storage and in many ways it's a restricted and limited API.</p>
  
  <p>As a result, we will need to use SAF (storage access framework) entirely on some future Android version (on Android Q we can, at least temporarily, <a href="https://developer.android.com/preview/privacy/scoped-storage" rel="noreferrer"><strong>use a flag</strong></a> to use the normal storage permission), if we wish to deal with various storage volumes and reach all files there.</p>
  
  <p>So, for example, suppose you want to make a file manager and show all the storage volumes of the device, to show what the user can grant access to, and if you already have access to each, you just enter it. Such a thing seems very legitimate, but as I can't find a way to do it.</p>
  
  <h2>The problem</h2>
  
  <p>Starting from API 24 (<a href="https://developer.android.com/reference/android/os/storage/StorageManager#getStorageVolumes()" rel="noreferrer"><strong>here</strong></a>), we finally have the ability to list all of the storage volumes, as such:</p>
  
  <pre><code>    val storageManager = getSystemService(Context.STORAGE_SERVICE) as StorageManager
      val storageVolumes = storageManager.storageVolumes
  </code></pre>
  
  <p>And, for the first time ever, we can have an Intent to request access to a storageVolume (<a href="https://developer.android.com/reference/android/os/storage/StorageVolume.html#createOpenDocumentTreeIntent()" rel="noreferrer"><strong>here</strong></a>). So if we want, for example, to request the user to grant access to the primary one (which will just start from there, actually, and not really ask anything), we could use this:</p>
  
  <pre><code>startActivityForResult(storageManager.primaryStorageVolume.createOpenDocumentTreeIntent(), REQUEST_CODE__DIRECTORTY_PERMISSION)
  </code></pre>
  
  <p>Instead of <code>startActivityForResult(Intent(Intent.ACTION_OPEN_DOCUMENT_TREE), REQUEST_CODE__DIRECTORTY_PERMISSION)</code> , and hoping the user will choose the correct thing there.</p>
  
  <p>And to finally get the access to what the user chose, we have this:</p>
  
  <pre><code>@TargetApi(Build.VERSION_CODES.KITKAT)
  override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
      super.onActivityResult(requestCode, resultCode, data)
      if (requestCode == REQUEST_CODE__DIRECTORTY_PERMISSION &amp;&amp; resultCode == Activity.RESULT_OK &amp;&amp; data != null) {
          val treeUri = data.data ?: return
          contentResolver.takePersistableUriPermission(treeUri, Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
          val pickedDir = DocumentFile.fromTreeUri(this, treeUri)
          ...
  </code></pre>
  
  <p>So far we can request for permission on the various storage volumes...</p>
  
  <p>However, the problem arises if you want to know which you got permission to and which you haven't.</p>
  
  <h2>What I've found</h2>
  
  <ol>
  <li><p>There is a video about "Scoped Directory Access" by Google (<a href="https://www.youtube.com/watch?v=XBfQC4owUJg" rel="noreferrer"><strong>here</strong></a>), which they talk specifically about the StorageVolume class. They even give information about listening to mount-events of StorageVolume, but they don't tell anything about identifying those that we got access to.</p></li>
  <li><p>The only ID of StorageVolume class is <a href="https://developer.android.com/reference/android/os/storage/StorageVolume.html#getUuid()" rel="noreferrer"><strong>uuid</strong></a> , but it's not even guaranteed to return anything. And indeed it returns null in various cases. For example the case of the primary storage.</p></li>
  <li><p>When using the <code>createOpenDocumentTreeIntent</code> function, I've noticed there is a Uri hidden inside, probably telling which to start with. It's inside the extras, in a key called "android.provider.extra.INITIAL_URI". When checking its value on the primary storage, for example, I got this:</p>
  
  <p>content://com.android.externalstorage.documents/root/primary</p></li>
  <li><p>When I look at the Uri I get in return in the onActivityResult, I get something a bit similar to #2, but different for the <code>treeUri</code> variable I've shown :</p>
  
  <p>content://com.android.externalstorage.documents/tree/primary%3A</p></li>
  <li><p>In order to get the list of what you have access to so far, you can use <a href="https://developer.android.com/reference/android/content/ContentResolver#getPersistedUriPermissions()" rel="noreferrer"><strong>this</strong></a>:</p>
  
  <p>val persistedUriPermissions = contentResolver.persistedUriPermissions</p></li>
  </ol>
  
  <p>This returns you a list of <a href="https://developer.android.com/reference/android/content/UriPermission" rel="noreferrer"><strong>UriPermission</strong></a>, each has a Uri. Sadly, when I use it, I get the same as on #3, which I can't really compare to what I get from StorageVolume :</p>
  
  <pre><code>content://com.android.externalstorage.documents/tree/primary%3A
  </code></pre>
  
  <p>So as you can see, I can't find any kind of mapping between the list of storage volumes and what the user grants.</p>
  
  <p>I can't even know if the user has chosen a storage volume at all, because the function of <code>createOpenDocumentTreeIntent</code> only send the user to the StorageVolume, but it's still possible to select a folder instead.</p>
  
  <p>The only thing that I do have, is a chunk of workaround functions I've found on other questions here, and I don't think they are reliable, especially now that we don't really have access to File API and file-path.</p>
  
  <p>I've written them here, in case you think they are useful:</p>
  
  <pre><code>@TargetApi(VERSION_CODES.LOLLIPOP)
  private static String getVolumeIdFromTreeUri(final Uri treeUri) {
      final String docId = DocumentsContract.getTreeDocumentId(treeUri);
      final int end = docId.indexOf(':');
      String result = end == -1 ? null : docId.substring(0, end);
      return result;
  }
  
  private static String getDocumentPathFromTreeUri(final Uri treeUri) {
      final String docId = DocumentsContract.getTreeDocumentId(treeUri);
      //TODO avoid using spliting of a string (because it uses extra strings creation)
      final String[] split = docId.split(":");
      if ((split.length &gt;= 2) &amp;&amp; (split[1] != null))
          return split[1];
      else
          return File.separator;
  }
  
  public static String getFullPathOfDocumentFile(Context context, DocumentFile documentFile) {
      String volumePath = getVolumePath(context, getVolumeIdFromTreeUri(documentFile.getUri()));
      if (volumePath == null)
          return null;
      DocumentFile parent = documentFile.getParentFile();
      if (parent == null)
          return volumePath;
      final LinkedList&lt;String&gt; fileHierarchy = new LinkedList&lt;&gt;();
      while (true) {
          fileHierarchy.add(0, documentFile.getName());
          documentFile = parent;
          parent = documentFile.getParentFile();
          if (parent == null)
              break;
      }
      final StringBuilder sb = new StringBuilder(volumePath).append(File.separator);
      for (String fileName : fileHierarchy)
          sb.append(fileName).append(File.separator);
      return sb.toString();
  }
  
  /**
   * Get the full path of a document from its tree URI.
   *
   * @param treeUri The tree RI.
   * @return The path (without trailing file separator).
   */
  public static String getFullPathFromTreeUri(Context context, final Uri treeUri) {
      if (treeUri == null)
          return null;
      String volumePath = getVolumePath(context, getVolumeIdFromTreeUri(treeUri));
      if (volumePath == null)
          return File.separator;
      if (volumePath.endsWith(File.separator))
          volumePath = volumePath.substring(0, volumePath.length() - 1);
      String documentPath = getDocumentPathFromTreeUri(treeUri);
      if (documentPath.endsWith(File.separator))
          documentPath = documentPath.substring(0, documentPath.length() - 1);
      if (documentPath.length() &gt; 0)
          if (documentPath.startsWith(File.separator))
              return volumePath + documentPath;
          else return volumePath + File.separator + documentPath;
      return volumePath;
  }
  
  /**
   * Get the path of a certain volume.
   *
   * @param volumeId The volume id.
   * @return The path.
   */
  private static String getVolumePath(Context context, final String volumeId) {
      if (VERSION.SDK_INT &lt; VERSION_CODES.LOLLIPOP)
          return null;
      try {
          final StorageManager storageManager = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE);
          if (VERSION.SDK_INT &gt;= VERSION_CODES.N) {
              final Class&lt;?&gt; storageVolumeClazz = StorageVolume.class;
              final Method getPath = storageVolumeClazz.getMethod("getPath");
              final List&lt;StorageVolume&gt; storageVolumes = storageManager.getStorageVolumes();
              for (final StorageVolume storageVolume : storageVolumes) {
                  final String uuid = storageVolume.getUuid();
                  final boolean primary = storageVolume.isPrimary();
                  // primary volume?
                  if (primary &amp;&amp; PRIMARY_VOLUME_NAME.equals(volumeId)) {
                      return (String) getPath.invoke(storageVolume);
                  }
                  // other volumes?
                  if (uuid != null &amp;&amp; uuid.equals(volumeId))
                      return (String) getPath.invoke(storageVolume);
              }
              return null;
          }
          final Class&lt;?&gt; storageVolumeClazz = Class.forName("android.os.storage.StorageVolume");
          final Method getVolumeList = storageManager.getClass().getMethod("getVolumeList");
          final Method getUuid = storageVolumeClazz.getMethod("getUuid");
          //noinspection JavaReflectionMemberAccess
          final Method getPath = storageVolumeClazz.getMethod("getPath");
          final Method isPrimary = storageVolumeClazz.getMethod("isPrimary");
          final Object result = getVolumeList.invoke(storageManager);
          final int length = Array.getLength(result);
          for (int i = 0; i &lt; length; i++) {
              final Object storageVolumeElement = Array.get(result, i);
              final String uuid = (String) getUuid.invoke(storageVolumeElement);
              final Boolean primary = (Boolean) isPrimary.invoke(storageVolumeElement);
              // primary volume?
              if (primary &amp;&amp; PRIMARY_VOLUME_NAME.equals(volumeId)) {
                  return (String) getPath.invoke(storageVolumeElement);
              }
              // other volumes?
              if (uuid != null &amp;&amp; uuid.equals(volumeId))
                  return (String) getPath.invoke(storageVolumeElement);
          }
          // not found.
          return null;
      } catch (Exception ex) {
          return null;
      }
  }
  </code></pre>
  
  <h2>The question</h2>
  
  <p>How can I map between the list of StorageVolume and the list of granted UriPermission ? </p>
  
  <p>In other words, given a list of StorageVolume, how can I know to which I have access to and which I don't , and if I do have access, to open it and see what's inside?</p>