File Permissions Extension Development Request

Dear Kodular Devlopers

Reason - Extension Development Request
Budget - 10$ - If work high then we increase budget.

I want a extension that can access Android/data Folder Permissions in Android 11 to 15 version, we can execute delete, copy, move, create folder in this permission using SAF Method.

I want access to Android/data/my.target.apk/

If anyone interested to make it please DM me.

Please Only Serious Person Help me No Time waste Please​:folded_hands:.

Did you already read this thread?

Taifun

I tried Claude and here is the answer
Taifun


I’ll provide a comprehensive guide on using Storage Access Framework (SAF) to access /Android/data/my.target.apk. Here’s a complete implementation:

// MainActivity.java
import android.app.Activity;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.Bundle;
import android.provider.DocumentsContract;
import android.util.Log;
import android.widget.Button;
import android.widget.Toast;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.appcompat.app.AppCompatActivity;
import androidx.documentfile.provider.DocumentFile;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "SAFAccess";
    private static final String PREF_NAME = "saf_prefs";
    private static final String PREF_URI_KEY = "android_data_uri";
    private static final String TARGET_PACKAGE = "my.target.apk";
    
    private SharedPreferences prefs;
    private Uri grantedUri;
    
    // Activity result launcher for directory picker
    private final ActivityResultLauncher<Intent> directoryPickerLauncher = 
        registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> {
            if (result.getResultCode() == Activity.RESULT_OK && result.getData() != null) {
                Uri uri = result.getData().getData();
                if (uri != null) {
                    handleDirectorySelection(uri);
                }
            }
        });

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        prefs = getSharedPreferences(PREF_NAME, MODE_PRIVATE);
        
        Button requestAccessBtn = findViewById(R.id.btnRequestAccess);
        Button listFilesBtn = findViewById(R.id.btnListFiles);
        Button readFileBtn = findViewById(R.id.btnReadFile);
        Button writeFileBtn = findViewById(R.id.btnWriteFile);
        
        requestAccessBtn.setOnClickListener(v -> requestAndroidDataAccess());
        listFilesBtn.setOnClickListener(v -> listTargetAppFiles());
        readFileBtn.setOnClickListener(v -> readSampleFile());
        writeFileBtn.setOnClickListener(v -> writeSampleFile());
        
        // Load previously granted URI
        loadSavedUri();
    }
    
    /**
     * Request access to Android/data directory using SAF
     */
    private void requestAndroidDataAccess() {
        Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
        
        // Try to open directly to Android/data if possible
        try {
            Uri androidDataUri = Uri.parse("content://com.android.externalstorage.documents/tree/primary%3AAndroid%2Fdata");
            intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, androidDataUri);
        } catch (Exception e) {
            Log.w(TAG, "Could not set initial URI", e);
        }
        
        // Add flags to persist permissions
        intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | 
                       Intent.FLAG_GRANT_WRITE_URI_PERMISSION |
                       Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION |
                       Intent.FLAG_GRANT_PREFIX_URI_PERMISSION);
        
        directoryPickerLauncher.launch(intent);
    }
    
    /**
     * Handle the selected directory URI
     */
    private void handleDirectorySelection(Uri uri) {
        try {
            // Take persistent permissions
            getContentResolver().takePersistableUriPermission(uri,
                Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
            
            // Save the URI for future use
            prefs.edit().putString(PREF_URI_KEY, uri.toString()).apply();
            grantedUri = uri;
            
            // Verify this is the Android/data directory
            DocumentFile docFile = DocumentFile.fromTreeUri(this, uri);
            if (docFile != null && docFile.getName() != null) {
                Log.d(TAG, "Granted access to: " + docFile.getName());
                Toast.makeText(this, "Access granted to: " + docFile.getName(), Toast.LENGTH_SHORT).show();
                
                // Check if target app directory exists
                checkTargetAppDirectory();
            }
            
        } catch (SecurityException e) {
            Log.e(TAG, "Failed to take persistent permission", e);
            Toast.makeText(this, "Failed to grant persistent access", Toast.LENGTH_SHORT).show();
        }
    }
    
    /**
     * Load previously saved URI
     */
    private void loadSavedUri() {
        String uriString = prefs.getString(PREF_URI_KEY, null);
        if (uriString != null) {
            grantedUri = Uri.parse(uriString);
            Log.d(TAG, "Loaded saved URI: " + uriString);
        }
    }
    
    /**
     * Check if target app directory exists
     */
    private void checkTargetAppDirectory() {
        if (grantedUri == null) {
            Toast.makeText(this, "No access granted", Toast.LENGTH_SHORT).show();
            return;
        }
        
        DocumentFile androidDataDir = DocumentFile.fromTreeUri(this, grantedUri);
        if (androidDataDir != null) {
            DocumentFile targetAppDir = androidDataDir.findFile(TARGET_PACKAGE);
            if (targetAppDir != null && targetAppDir.isDirectory()) {
                Log.d(TAG, "Target app directory found: " + TARGET_PACKAGE);
                Toast.makeText(this, "Target app directory found!", Toast.LENGTH_SHORT).show();
            } else {
                Log.w(TAG, "Target app directory not found: " + TARGET_PACKAGE);
                Toast.makeText(this, "Target app directory not found", Toast.LENGTH_SHORT).show();
            }
        }
    }
    
    /**
     * List files in the target app's directory
     */
    private void listTargetAppFiles() {
        if (grantedUri == null) {
            Toast.makeText(this, "Please grant access first", Toast.LENGTH_SHORT).show();
            return;
        }
        
        try {
            DocumentFile androidDataDir = DocumentFile.fromTreeUri(this, grantedUri);
            if (androidDataDir == null) return;
            
            DocumentFile targetAppDir = androidDataDir.findFile(TARGET_PACKAGE);
            if (targetAppDir == null || !targetAppDir.isDirectory()) {
                Toast.makeText(this, "Target app directory not found", Toast.LENGTH_SHORT).show();
                return;
            }
            
            Log.d(TAG, "Listing files in: " + TARGET_PACKAGE);
            listDirectoryContents(targetAppDir, 0);
            
        } catch (Exception e) {
            Log.e(TAG, "Error listing files", e);
            Toast.makeText(this, "Error listing files: " + e.getMessage(), Toast.LENGTH_SHORT).show();
        }
    }
    
    /**
     * Recursively list directory contents
     */
    private void listDirectoryContents(DocumentFile dir, int depth) {
        if (dir == null || !dir.isDirectory()) return;
        
        String indent = "  ".repeat(depth);
        DocumentFile[] files = dir.listFiles();
        
        for (DocumentFile file : files) {
            if (file.isDirectory()) {
                Log.d(TAG, indent + "[DIR] " + file.getName());
                // Recursively list subdirectories (be careful with depth)
                if (depth < 3) {
                    listDirectoryContents(file, depth + 1);
                }
            } else {
                Log.d(TAG, indent + "[FILE] " + file.getName() + " (" + file.length() + " bytes)");
            }
        }
    }
    
    /**
     * Read a sample file from the target app directory
     */
    private void readSampleFile() {
        if (grantedUri == null) {
            Toast.makeText(this, "Please grant access first", Toast.LENGTH_SHORT).show();
            return;
        }
        
        try {
            DocumentFile androidDataDir = DocumentFile.fromTreeUri(this, grantedUri);
            if (androidDataDir == null) return;
            
            DocumentFile targetAppDir = androidDataDir.findFile(TARGET_PACKAGE);
            if (targetAppDir == null) {
                Toast.makeText(this, "Target app directory not found", Toast.LENGTH_SHORT).show();
                return;
            }
            
            // Look for a specific file (e.g., shared_prefs or files subdirectory)
            DocumentFile filesDir = targetAppDir.findFile("files");
            if (filesDir != null && filesDir.isDirectory()) {
                DocumentFile[] files = filesDir.listFiles();
                for (DocumentFile file : files) {
                    if (file.isFile() && file.canRead()) {
                        readFileContent(file);
                        break; // Read first readable file
                    }
                }
            }
            
        } catch (Exception e) {
            Log.e(TAG, "Error reading file", e);
            Toast.makeText(this, "Error reading file: " + e.getMessage(), Toast.LENGTH_SHORT).show();
        }
    }
    
    /**
     * Read content from a DocumentFile
     */
    private void readFileContent(DocumentFile file) {
        try (InputStream inputStream = getContentResolver().openInputStream(file.getUri());
             BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) {
            
            StringBuilder content = new StringBuilder();
            String line;
            int lineCount = 0;
            
            while ((line = reader.readLine()) != null && lineCount < 10) { // Limit to first 10 lines
                content.append(line).append("\n");
                lineCount++;
            }
            
            Log.d(TAG, "File content from " + file.getName() + ":\n" + content.toString());
            Toast.makeText(this, "Read " + lineCount + " lines from " + file.getName(), Toast.LENGTH_SHORT).show();
            
        } catch (IOException e) {
            Log.e(TAG, "Error reading file content", e);
        }
    }
    
    /**
     * Write a sample file to the target app directory (if writable)
     */
    private void writeSampleFile() {
        if (grantedUri == null) {
            Toast.makeText(this, "Please grant access first", Toast.LENGTH_SHORT).show();
            return;
        }
        
        try {
            DocumentFile androidDataDir = DocumentFile.fromTreeUri(this, grantedUri);
            if (androidDataDir == null) return;
            
            DocumentFile targetAppDir = androidDataDir.findFile(TARGET_PACKAGE);
            if (targetAppDir == null) {
                Toast.makeText(this, "Target app directory not found", Toast.LENGTH_SHORT).show();
                return;
            }
            
            // Try to create a file in the files subdirectory
            DocumentFile filesDir = targetAppDir.findFile("files");
            if (filesDir == null) {
                filesDir = targetAppDir.createDirectory("files");
            }
            
            if (filesDir != null && filesDir.canWrite()) {
                DocumentFile newFile = filesDir.createFile("text/plain", "saf_test.txt");
                if (newFile != null) {
                    writeToFile(newFile, "Hello from SAF!\nTimestamp: " + System.currentTimeMillis());
                }
            }
            
        } catch (Exception e) {
            Log.e(TAG, "Error writing file", e);
            Toast.makeText(this, "Error writing file: " + e.getMessage(), Toast.LENGTH_SHORT).show();
        }
    }
    
    /**
     * Write content to a DocumentFile
     */
    private void writeToFile(DocumentFile file, String content) {
        try (OutputStream outputStream = getContentResolver().openOutputStream(file.getUri())) {
            if (outputStream != null) {
                outputStream.write(content.getBytes());
                Log.d(TAG, "Successfully wrote to: " + file.getName());
                Toast.makeText(this, "File written: " + file.getName(), Toast.LENGTH_SHORT).show();
            }
        } catch (IOException e) {
            Log.e(TAG, "Error writing to file", e);
        }
    }
    
    /**
     * Helper method to navigate to a specific subdirectory
     */
    private DocumentFile navigateToSubdirectory(DocumentFile parentDir, String... pathSegments) {
        DocumentFile currentDir = parentDir;
        
        for (String segment : pathSegments) {
            if (currentDir == null) break;
            currentDir = currentDir.findFile(segment);
            if (currentDir == null || !currentDir.isDirectory()) {
                return null;
            }
        }
        
        return currentDir;
    }
    
    /**
     * Get specific app subdirectories (cache, files, shared_prefs, etc.)
     */
    private void accessAppSubdirectories() {
        if (grantedUri == null) return;
        
        DocumentFile androidDataDir = DocumentFile.fromTreeUri(this, grantedUri);
        if (androidDataDir == null) return;
        
        DocumentFile targetAppDir = androidDataDir.findFile(TARGET_PACKAGE);
        if (targetAppDir == null) return;
        
        // Access common subdirectories
        String[] commonDirs = {"cache", "files", "shared_prefs", "databases", "code_cache"};
        
        for (String dirName : commonDirs) {
            DocumentFile subDir = targetAppDir.findFile(dirName);
            if (subDir != null && subDir.isDirectory()) {
                Log.d(TAG, "Found subdirectory: " + dirName);
                // Process files in this subdirectory
                DocumentFile[] files = subDir.listFiles();
                Log.d(TAG, "  Contains " + files.length + " items");
            }
        }
    }
}

Now let me also provide the required layout file and manifest permissions:

// MainActivity.java
import android.app.Activity;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.Bundle;
import android.provider.DocumentsContract;
import android.util.Log;
import android.widget.Button;
import android.widget.Toast;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.appcompat.app.AppCompatActivity;
import androidx.documentfile.provider.DocumentFile;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "SAFAccess";
    private static final String PREF_NAME = "saf_prefs";
    private static final String PREF_URI_KEY = "android_data_uri";
    private static final String TARGET_PACKAGE = "my.target.apk";
    
    private SharedPreferences prefs;
    private Uri grantedUri;
    
    // Activity result launcher for directory picker
    private final ActivityResultLauncher<Intent> directoryPickerLauncher = 
        registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> {
            if (result.getResultCode() == Activity.RESULT_OK && result.getData() != null) {
                Uri uri = result.getData().getData();
                if (uri != null) {
                    handleDirectorySelection(uri);
                }

// activity_main.xml
/*
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="16dp">

    <Button
        android:id="@+id/btnRequestAccess"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Request Android/data Access"
        android:layout_marginBottom="16dp" />

    <Button
        android:id="@+id/btnListFiles"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="List Target App Files"
        android:layout_marginBottom="16dp" />

    <Button
        android:id="@+id/btnReadFile"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Read Sample File"
        android:layout_marginBottom="16dp" />

    <Button
        android:id="@+id/btnWriteFile"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Write Sample File"
        android:layout_marginBottom="16dp" />

</LinearLayout>
*/

// AndroidManifest.xml additions
/*
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

<!-- For DocumentFile operations -->
<application
    android:requestLegacyExternalStorage="true"
    ... >
    
    <!-- Your activities here -->
    
</application>
*/

// build.gradle (Module: app) dependencies
/*
dependencies {
    implementation 'androidx.documentfile:documentfile:1.0.1'
    implementation 'androidx.activity:activity:1.7.2'
    // ... other dependencies
}
*/
            }
        });

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        prefs = getSharedPreferences(PREF_NAME, MODE_PRIVATE);
        
        Button requestAccessBtn = findViewById(R.id.btnRequestAccess);
        Button listFilesBtn = findViewById(R.id.btnListFiles);
        Button readFileBtn = findViewById(R.id.btnReadFile);
        Button writeFileBtn = findViewById(R.id.btnWriteFile);
        
        requestAccessBtn.setOnClickListener(v -> requestAndroidDataAccess());
        listFilesBtn.setOnClickListener(v -> listTargetAppFiles());
        readFileBtn.setOnClickListener(v -> readSampleFile());
        writeFileBtn.setOnClickListener(v -> writeSampleFile());
        
        // Load previously granted URI
        loadSavedUri();
    }
    
    /**
     * Request access to Android/data directory using SAF
     */
    private void requestAndroidDataAccess() {
        Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
        
        // Try to open directly to Android/data if possible
        try {
            Uri androidDataUri = Uri.parse("content://com.android.externalstorage.documents/tree/primary%3AAndroid%2Fdata");
            intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, androidDataUri);
        } catch (Exception e) {
            Log.w(TAG, "Could not set initial URI", e);
        }
        
        // Add flags to persist permissions
        intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | 
                       Intent.FLAG_GRANT_WRITE_URI_PERMISSION |
                       Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION |
                       Intent.FLAG_GRANT_PREFIX_URI_PERMISSION);
        
        directoryPickerLauncher.launch(intent);
    }
    
    /**
     * Handle the selected directory URI
     */
    private void handleDirectorySelection(Uri uri) {
        try {
            // Take persistent permissions
            getContentResolver().takePersistableUriPermission(uri,
                Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
            
            // Save the URI for future use
            prefs.edit().putString(PREF_URI_KEY, uri.toString()).apply();
            grantedUri = uri;
            
            // Verify this is the Android/data directory
            DocumentFile docFile = DocumentFile.fromTreeUri(this, uri);
            if (docFile != null && docFile.getName() != null) {
                Log.d(TAG, "Granted access to: " + docFile.getName());
                Toast.makeText(this, "Access granted to: " + docFile.getName(), Toast.LENGTH_SHORT).show();
                
                // Check if target app directory exists
                checkTargetAppDirectory();
            }
            
        } catch (SecurityException e) {
            Log.e(TAG, "Failed to take persistent permission", e);
            Toast.makeText(this, "Failed to grant persistent access", Toast.LENGTH_SHORT).show();
        }
    }
    
    /**
     * Load previously saved URI
     */
    private void loadSavedUri() {
        String uriString = prefs.getString(PREF_URI_KEY, null);
        if (uriString != null) {
            grantedUri = Uri.parse(uriString);
            Log.d(TAG, "Loaded saved URI: " + uriString);
        }
    }
    
    /**
     * Check if target app directory exists
     */
    private void checkTargetAppDirectory() {
        if (grantedUri == null) {
            Toast.makeText(this, "No access granted", Toast.LENGTH_SHORT).show();
            return;
        }
        
        DocumentFile androidDataDir = DocumentFile.fromTreeUri(this, grantedUri);
        if (androidDataDir != null) {
            DocumentFile targetAppDir = androidDataDir.findFile(TARGET_PACKAGE);
            if (targetAppDir != null && targetAppDir.isDirectory()) {
                Log.d(TAG, "Target app directory found: " + TARGET_PACKAGE);
                Toast.makeText(this, "Target app directory found!", Toast.LENGTH_SHORT).show();
            } else {
                Log.w(TAG, "Target app directory not found: " + TARGET_PACKAGE);
                Toast.makeText(this, "Target app directory not found", Toast.LENGTH_SHORT).show();
            }
        }
    }
    
    /**
     * List files in the target app's directory
     */
    private void listTargetAppFiles() {
        if (grantedUri == null) {
            Toast.makeText(this, "Please grant access first", Toast.LENGTH_SHORT).show();
            return;
        }
        
        try {
            DocumentFile androidDataDir = DocumentFile.fromTreeUri(this, grantedUri);
            if (androidDataDir == null) return;
            
            DocumentFile targetAppDir = androidDataDir.findFile(TARGET_PACKAGE);
            if (targetAppDir == null || !targetAppDir.isDirectory()) {
                Toast.makeText(this, "Target app directory not found", Toast.LENGTH_SHORT).show();
                return;
            }
            
            Log.d(TAG, "Listing files in: " + TARGET_PACKAGE);
            listDirectoryContents(targetAppDir, 0);
            
        } catch (Exception e) {
            Log.e(TAG, "Error listing files", e);
            Toast.makeText(this, "Error listing files: " + e.getMessage(), Toast.LENGTH_SHORT).show();
        }
    }
    
    /**
     * Recursively list directory contents
     */
    private void listDirectoryContents(DocumentFile dir, int depth) {
        if (dir == null || !dir.isDirectory()) return;
        
        String indent = "  ".repeat(depth);
        DocumentFile[] files = dir.listFiles();
        
        for (DocumentFile file : files) {
            if (file.isDirectory()) {
                Log.d(TAG, indent + "[DIR] " + file.getName());
                // Recursively list subdirectories (be careful with depth)
                if (depth < 3) {
                    listDirectoryContents(file, depth + 1);
                }
            } else {
                Log.d(TAG, indent + "[FILE] " + file.getName() + " (" + file.length() + " bytes)");
            }
        }
    }
    
    /**
     * Read a sample file from the target app directory
     */
    private void readSampleFile() {
        if (grantedUri == null) {
            Toast.makeText(this, "Please grant access first", Toast.LENGTH_SHORT).show();
            return;
        }
        
        try {
            DocumentFile androidDataDir = DocumentFile.fromTreeUri(this, grantedUri);
            if (androidDataDir == null) return;
            
            DocumentFile targetAppDir = androidDataDir.findFile(TARGET_PACKAGE);
            if (targetAppDir == null) {
                Toast.makeText(this, "Target app directory not found", Toast.LENGTH_SHORT).show();
                return;
            }
            
            // Look for a specific file (e.g., shared_prefs or files subdirectory)
            DocumentFile filesDir = targetAppDir.findFile("files");
            if (filesDir != null && filesDir.isDirectory()) {
                DocumentFile[] files = filesDir.listFiles();
                for (DocumentFile file : files) {
                    if (file.isFile() && file.canRead()) {
                        readFileContent(file);
                        break; // Read first readable file
                    }
                }
            }
            
        } catch (Exception e) {
            Log.e(TAG, "Error reading file", e);
            Toast.makeText(this, "Error reading file: " + e.getMessage(), Toast.LENGTH_SHORT).show();
        }
    }
    
    /**
     * Read content from a DocumentFile
     */
    private void readFileContent(DocumentFile file) {
        try (InputStream inputStream = getContentResolver().openInputStream(file.getUri());
             BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) {
            
            StringBuilder content = new StringBuilder();
            String line;
            int lineCount = 0;
            
            while ((line = reader.readLine()) != null && lineCount < 10) { // Limit to first 10 lines
                content.append(line).append("\n");
                lineCount++;
            }
            
            Log.d(TAG, "File content from " + file.getName() + ":\n" + content.toString());
            Toast.makeText(this, "Read " + lineCount + " lines from " + file.getName(), Toast.LENGTH_SHORT).show();
            
        } catch (IOException e) {
            Log.e(TAG, "Error reading file content", e);
        }
    }
    
    /**
     * Write a sample file to the target app directory (if writable)
     */
    private void writeSampleFile() {
        if (grantedUri == null) {
            Toast.makeText(this, "Please grant access first", Toast.LENGTH_SHORT).show();
            return;
        }
        
        try {
            DocumentFile androidDataDir = DocumentFile.fromTreeUri(this, grantedUri);
            if (androidDataDir == null) return;
            
            DocumentFile targetAppDir = androidDataDir.findFile(TARGET_PACKAGE);
            if (targetAppDir == null) {
                Toast.makeText(this, "Target app directory not found", Toast.LENGTH_SHORT).show();
                return;
            }
            
            // Try to create a file in the files subdirectory
            DocumentFile filesDir = targetAppDir.findFile("files");
            if (filesDir == null) {
                filesDir = targetAppDir.createDirectory("files");
            }
            
            if (filesDir != null && filesDir.canWrite()) {
                DocumentFile newFile = filesDir.createFile("text/plain", "saf_test.txt");
                if (newFile != null) {
                    writeToFile(newFile, "Hello from SAF!\nTimestamp: " + System.currentTimeMillis());
                }
            }
            
        } catch (Exception e) {
            Log.e(TAG, "Error writing file", e);
            Toast.makeText(this, "Error writing file: " + e.getMessage(), Toast.LENGTH_SHORT).show();
        }
    }
    
    /**
     * Write content to a DocumentFile
     */
    private void writeToFile(DocumentFile file, String content) {
        try (OutputStream outputStream = getContentResolver().openOutputStream(file.getUri())) {
            if (outputStream != null) {
                outputStream.write(content.getBytes());
                Log.d(TAG, "Successfully wrote to: " + file.getName());
                Toast.makeText(this, "File written: " + file.getName(), Toast.LENGTH_SHORT).show();
            }
        } catch (IOException e) {
            Log.e(TAG, "Error writing to file", e);
        }
    }
    
    /**
     * Helper method to navigate to a specific subdirectory
     */
    private DocumentFile navigateToSubdirectory(DocumentFile parentDir, String... pathSegments) {
        DocumentFile currentDir = parentDir;
        
        for (String segment : pathSegments) {
            if (currentDir == null) break;
            currentDir = currentDir.findFile(segment);
            if (currentDir == null || !currentDir.isDirectory()) {
                return null;
            }
        }
        
        return currentDir;
    }
    
    /**
     * Get specific app subdirectories (cache, files, shared_prefs, etc.)
     */
    private void accessAppSubdirectories() {
        if (grantedUri == null) return;
        
        DocumentFile androidDataDir = DocumentFile.fromTreeUri(this, grantedUri);
        if (androidDataDir == null) return;
        
        DocumentFile targetAppDir = androidDataDir.findFile(TARGET_PACKAGE);
        if (targetAppDir == null) return;
        
        // Access common subdirectories
        String[] commonDirs = {"cache", "files", "shared_prefs", "databases", "code_cache"};
        
        for (String dirName : commonDirs) {
            DocumentFile subDir = targetAppDir.findFile(dirName);
            if (subDir != null && subDir.isDirectory()) {
                Log.d(TAG, "Found subdirectory: " + dirName);
                // Process files in this subdirectory
                DocumentFile[] files = subDir.listFiles();
                Log.d(TAG, "  Contains " + files.length + " items");
            }
        }
    }
}

Key SAF Implementation Details

1. Permission Flow:

  • Request tree access via ACTION_OPEN_DOCUMENT_TREE
  • User selects /Android/data directory in the system file picker
  • Take persistent permissions to avoid re-requesting

2. Navigation:

// Access target app: /Android/data/my.target.apk/
DocumentFile targetDir = androidDataDir.findFile("my.target.apk");

// Access subdirectories: files, cache, shared_prefs, etc.
DocumentFile filesDir = targetDir.findFile("files");

3. Important Considerations:

  • User Interaction Required: SAF always requires user to manually select the directory
  • Persistent Permissions: Use takePersistableUriPermission() to avoid re-requesting
  • Limited Write Access: Many app directories are read-only
  • Performance: SAF operations are slower than direct file access

4. Common Issues:

  • Directory Not Found: Target app might not exist or have created the directory yet
  • Permission Denied: Some subdirectories may be protected even with SAF access
  • URI Changes: System updates can invalidate saved URIs

5. Best Practices:

  • Always check if directories exist before accessing
  • Handle SecurityException gracefully
  • Cache DocumentFile references when possible
  • Test on different Android versions and devices

This implementation provides a complete solution for accessing /Android/data/my.target.apk using SAF, with proper error handling and user feedback. The user will need to manually grant access through the system file picker, but once granted, your app can persistently access the target directory.

1 Like

Can Anyone Convert the Taifun Code into Extension

You already have the SAF extension together with all the methods. Try to do it with existing blocks

Taifun

Not Working Dear Taifun i tested it on Android 13,14,15 Devices

Can Please All Extension Devloper of Kodular Assemble Here and Give a Solution of File Access of Android/data/ to the Community.

You forgot to provide a screenshot of your relevant blocks

Taifun

It’s 5% possible but Workarounds (Not ideal or officially supported):

  1. Use SAF

You can prompt the user to pick a directory (even /Android/data) manually.

But you still can’t directly access /Android/data/other.app unless the user selects it through ACTION_OPEN_DOCUMENT_TREE.

Even then, some manufacturers block this with extra restrictions (especially Xiaomi, Samsung, etc.)

  1. Rooted Devices

If the device is rooted, you can access anything, but this is not a general solution and not allowed in Play Store apps.

The problem is that you’re not mentioning what type of files are you trying to access in these private paths, also you’re not showing us what you tried so far

I just want to Access Android/data/other.apk.file if you are not able to do this please Say it confidently.

Please don’t manipulate form my target.

Thanks @Ibrahim_Jamar

Here is the Screenshot of a Apk that Access Android/data/ Folder in Android 15 !

Nothing Impossible We Just need a Path to Create that.

@Knox_Gaming I guess my Aix works :joy::joy::joy::joy::joy: yeah it open but files will be limited to be accessed

Anyway all the best,

But be careful this method

What does your extension access? Video, Image, just Media Thigs.

We talk on SAF Framework not Media Sync Extension.

Oky thanks for the feedback

Raise the budget :nerd_face: $25

2 Likes

Disbelief Reaction GIF
:slight_smile:

2 Likes