{"record":{"id":"d27bcc3d6d466f92","repo":"MuntashirAkon/AppManager","slug":"could-not-fetch-attributes-for-tree-documenturi","errorCode":null,"errorMessage":"Could not fetch attributes for tree ${documentUri}","messagePattern":"Could not fetch attributes for tree (.+?)","errorType":"exception","errorClass":"IOException","httpStatus":null,"severity":"error","filePath":"app/src/main/java/io/github/muntashirakon/io/PathAttributesImpl.java","lineNumber":47,"sourceCode":"        return new PathAttributesImpl(f.getName(), file.getType(), f.lastModified(), f.lastAccess(), f.creationTime(),\n                OsConstants.S_ISREG(mode), OsConstants.S_ISDIR(mode), OsConstants.S_ISLNK(mode), f.length());\n    }\n\n    @NonNull\n    public static PathAttributesImpl fromVirtual(@NonNull VirtualDocumentFile file) {\n        int mode = file.getMode();\n        return new PathAttributesImpl(file.getName(), file.getType(), file.lastModified(), file.lastAccess(), file.creationTime(),\n                OsConstants.S_ISREG(mode), OsConstants.S_ISDIR(mode), OsConstants.S_ISLNK(mode), file.length());\n    }\n\n    @NonNull\n    public static PathAttributesImpl fromSaf(@NonNull Context context, @NonNull DocumentFile safDocumentFile)\n            throws IOException {\n        Uri documentUri = safDocumentFile.getUri();\n        ContentResolver resolver = context.getContentResolver();\n        try (Cursor c = resolver.query(documentUri, null, null, null, null)) {\n            if (!c.moveToFirst()) {\n                throw new IOException(\"Could not fetch attributes for tree \" + documentUri);\n            }\n            String[] columns = c.getColumnNames();\n            String name = null;\n            String type = null;\n            long lastModified = 0;\n            long size = 0;\n            for (int i = 0; i < columns.length; ++i) {\n                switch (columns[i]) {\n                    case DocumentsContract.Document.COLUMN_DISPLAY_NAME:\n                        name = c.getString(i);\n                        break;\n                    case DocumentsContract.Document.COLUMN_MIME_TYPE:\n                        type = c.getString(i);\n                        break;\n                    case DocumentsContract.Document.COLUMN_LAST_MODIFIED:\n                        lastModified = c.getLong(i);\n                        break;\n                    case DocumentsContract.Document.COLUMN_SIZE:","sourceCodeStart":29,"sourceCodeEnd":65,"githubUrl":"https://github.com/MuntashirAkon/AppManager/blob/0152f468fc9463ee02dc2ca83f6fe4989a2c4ca5/app/src/main/java/io/github/muntashirakon/io/PathAttributesImpl.java#L29-L65","documentation":"PathAttributesImpl.fromSaf queries the DocumentsProvider backing the given SAF DocumentFile for its metadata columns. If the returned cursor is empty (no first row), the provider has no record for this tree/document URI, so the library throws this IOException instead of returning partial attributes. It signals that the URI is unresolvable — usually deleted, revoked, or malformed.","triggerScenarios":"Calling Path.getAttributes()/fromSaf with a DocumentFile whose tree URI no longer resolves: the document was deleted or moved, the persisted URI permission was revoked (e.g. after app data clear or reboot on some devices), or the provider returned a valid-but-empty cursor.","commonSituations":"Persisted ACTION_OPEN_DOCUMENT_TREE URIs pointing at storage roots that were re-mounted or wiped; third-party DocumentsProviders (cloud providers) being offline; testing with a fabricated Uri that was never returned by the SAF picker.","solutions":["Re-acquire the tree/document via the Storage Access Framework picker (ACTION_OPEN_DOCUMENT_TREE / ACTION_OPEN_DOCUMENT) and rebuild the DocumentFile","Check that you still hold the URI permission (context.contentResolver.persistedUriPermissions) and re-take it if revoked","Verify the document still exists via DocumentFile.exists() before querying attributes","Wrap the call in try-catch for IOException and treat it as a 'missing document' case rather than retrying with the same stale URI"],"exampleFix":"// before\nPathAttributesImpl attrs = PathAttributesImpl.fromSaf(context, staleDocFile);\n// after\nif (!staleDocFile.exists() || !hasUriPermission(context, staleDocFile.getUri())) {\n    staleDocFile = rePickTreeViaSaf(); // ACTION_OPEN_DOCUMENT_TREE\n}\nPathAttributesImpl attrs = PathAttributesImpl.fromSaf(context, staleDocFile);","handlingStrategy":"try-catch","validationCode":"if (docFile == null || !docFile.exists() || !hasPersistedPermission(context, docFile.getUri())) { reAcquireTree(); }","typeGuard":"boolean isResolvableTree(Context ctx, DocumentFile f) {\n    return f != null && f.getUri() != null\n        && ctx.getContentResolver().getPersistedUriPermissions().stream()\n            .anyMatch(p -> p.getUri().equals(f.getUri()));\n}","tryCatchPattern":"try {\n    PathAttributesImpl attrs = PathAttributesImpl.fromSaf(context, docFile);\n} catch (IOException e) {\n    if (e.getMessage() != null && e.getMessage().startsWith(\"Could not fetch attributes for tree\")) {\n        docFile = rePickTreeViaSaf(); // URI dead or revoked\n    } else throw e;\n}","preventionTips":["Persist URI permissions with takePersistableUriPermission immediately after the SAF picker returns","Check DocumentFile.exists() before querying metadata","Treat any stored tree URI as volatile and re-validate on app start","Handle provider availability for third-party/cloud DocumentsProviders"],"tags":["android","saf","storage-access-framework"],"backgroundTag":"empty-result-set","analyzedSha":"0152f468fc9463ee02dc2ca83f6fe4989a2c4ca5","analyzedAt":"2026-09-12T14:03:37.243Z","contentChangedAt":"2026-09-12T14:03:37.243Z","schemaVersion":2},"datasetVersion":"2026-09-14T11:17:12.474Z"}