I divide it into 3 methods that requires slightly different access level to use. Method 1 being the easiest way.
All of these methods involves adding custom java code so the PRO version of APK Compiler is required.
The java code is made with the aid of chatgpt.
Method 1
Will give access to create files and folders inside any of the mediastore folders on your device.
Typically existing folders like storage/emulated/0/Downloads (or /Documents, /Pictures, Movies etc.)
Add the file named HollywoodDelegate.java into custom code and the following new commands will be usable in your hollywood script:
Code: Select all
ok = CallJavaMethod("createFolder", {ReturnType=#BOOLEAN}, #STRING, "MyTestFolder"); creates a new folder under /Downloads
ok = CallJavaMethod("createFile", {ReturnType=#BOOLEAN}, #STRING, "MyTestFolder", #STRING, "hello.txt", #STRING, "This is the file content from Hollywood!"); Create a new file in the specified subfolder under /Downloads
ok = CallJavaMethod("appendToFile", {ReturnType=#BOOLEAN}, #STRING, "MyTestFolder", #STRING, "hello.txt", #STRING, "\nAppended line"); add test to the file
ok = CallJavaMethod("copyFileFromExternal", {ReturnType=#BOOLEAN}, #STRING, "MyTestFolder", #STRING, "hello.txt", #STRING, "internalCopy.txt"); copy a file from the /Downloads/MyTestFolder and into the app internal storage
ok = CallJavaMethod("copyFileToExternal", {ReturnType=#BOOLEAN}, #STRING, "internalCopy.txt", #STRING, "MyTestFolder", #STRING, "hello_copy2.txt"); Copy a file from your app internal storage into /Downloads/MyTestFolder
ok = CallJavaMethod("folderExists", {ReturnType=#BOOLEAN}, #STRING, "MyTestFolder") ; True if the folder /Downloads/MyTestFolder exists
ok = CallJavaMethod("fileExists", {ReturnType=#BOOLEAN}, #STRING, "MyTestFolder", #STRING, "hello.txt") ; True if the file /Downloads/MyTestFolder/hello.txt exists
ok = CallJavaMethod("deleteFile", {ReturnType=#BOOLEAN}, #STRING, "MyTestFolder", #STRING, "hello.txt") ; Delete the file in /Downloads/MyTestFolder
ok = CallJavaMethod("deleteFolder", {ReturnType=#BOOLEAN}, #STRING, "MyTestFolder") ; Delete whole folder /Downloads/MyTestFolder
numLines = CallJavaMethod("getNumberOfLines", {ReturnType=#INTEGER}, #STRING, "MyTestFolder", #STRING, "hello_copy2.txt") ; Returns number of lines in a file
list$ = CallJavaMethod("listFilesInFolder", {ReturnType=#STRING}, #STRING, "MyTestFolder") ; lists the files and folders in /Downloads/MyTestFolder
content$ = CallJavaMethod("readFile", {ReturnType=#STRING}, #STRING, "MyTestFolder", #STRING, "hello.txt"); Read whole file into a string
line$ = CallJavaMethod("readLineFromFile", {ReturnType=#STRING}, #STRING, "MyTestFolder", #STRING, "hello_copy2.txt", #INTEGER, i); Reads a single line from a file, i representing the line number (0 is fist line)Environment.DIRECTORY_DOWNLOADS
into
Environment.DIRECTORY_DOCUMENTS
Code: Select all
// make sure this is identical to the package name set in the Hollywood APK Compiler
package com.amy66dev.testdir; -- replace this with your own package name
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.provider.MediaStore;
import android.util.Log;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.IOException;
import java.io.BufferedReader;
import java.io.InputStreamReader;
/**
* HollywoodDelegate - MediaStore-friendly file utilities.
* Must be included as Custom Code in the Hollywood APK Compiler.
*/
public class HollywoodDelegate extends HollywoodActivity {
private static final String TAG = "HollywoodDelegate";
// Helper: find file Uri in MediaStore (Downloads/<folderName>/)
private Uri findFileUri(String folderName, String fileName) {
try {
String relPath = Environment.DIRECTORY_DOWNLOADS + "/" + folderName + "/";
Uri collection = MediaStore.Files.getContentUri("external");
String[] projection = new String[]{ MediaStore.MediaColumns._ID };
String selection = MediaStore.MediaColumns.RELATIVE_PATH + "=? AND " + MediaStore.MediaColumns.DISPLAY_NAME + "=?";
String[] selectionArgs = new String[]{ relPath, fileName };
Cursor c = getContentResolver().query(collection, projection, selection, selectionArgs, null);
if (c != null) {
try {
if (c.moveToFirst()) {
long id = c.getLong(c.getColumnIndexOrThrow(MediaStore.MediaColumns._ID));
return ContentUris.withAppendedId(collection, id);
}
} finally {
c.close();
}
}
} catch (Exception e) {
Log.e(TAG, "findFileUri failed", e);
}
return null;
}
// Check if a file exists in the given folder
public boolean fileExists(String folderName, String fileName) {
try {
File dir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), folderName);
File file = new File(dir, fileName);
return file.exists() && file.isFile();
} catch (Exception e) {
Log.e("HollywoodDelegate", "Error checking file existence: " + e.getMessage());
return false;
}
}
// Check if a folder exists
public boolean folderExists(String folderName) {
try {
File dir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), folderName);
return dir.exists() && dir.isDirectory();
} catch (Exception e) {
Log.e("HollywoodDelegate", "Error checking folder existence: " + e.getMessage());
return false;
}
}
// Create folder by inserting a placeholder into MediaStore (shows folder)
public boolean createFolder(String folderName) {
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
ContentValues values = new ContentValues();
values.put(MediaStore.MediaColumns.DISPLAY_NAME, ".nomedia");
values.put(MediaStore.MediaColumns.MIME_TYPE, "application/octet-stream");
values.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS + "/" + folderName + "/");
values.put("is_pending", 1);
ContentResolver resolver = getContentResolver();
Uri uri = resolver.insert(MediaStore.Files.getContentUri("external"), values);
if (uri == null) {
Log.e(TAG, "createFolder: insert returned null");
return false;
}
// mark not pending
ContentValues cv = new ContentValues();
cv.put("is_pending", 0);
resolver.update(uri, cv, null, null);
Log.i(TAG, "createFolder (MediaStore) created: " + folderName);
return true;
} else {
File docs = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
File folder = new File(docs, folderName);
if (!folder.exists() && !folder.mkdirs()) {
Log.e(TAG, "createFolder: mkdirs failed: " + folder.getAbsolutePath());
return false;
}
// create .nomedia file
File nm = new File(folder, ".nomedia");
try {
if (!nm.exists()) nm.createNewFile();
} catch (IOException e) {
Log.w(TAG, "createFolder: .nomedia create failed", e);
}
Log.i(TAG, "createFolder (legacy) created: " + folder.getAbsolutePath());
return true;
}
} catch (Exception e) {
Log.e(TAG, "createFolder failed", e);
return false;
}
}
// Create or overwrite a file with text content (uses MediaStore on Q+)
public boolean createFile(String folderName, String fileName, String content) {
try {
ContentResolver resolver = getContentResolver();
Uri uri = findFileUri(folderName, fileName);
if (uri != null) {
// overwrite existing
OutputStream out = null;
try {
out = resolver.openOutputStream(uri, "w");
if (out == null) throw new IOException("openOutputStream returned null");
if (content != null) out.write(content.getBytes("UTF-8"));
out.flush();
} finally {
if (out != null) try { out.close(); } catch (IOException ignored) {}
}
Log.i(TAG, "createFile: overwritten " + fileName);
return true;
}
// insert new
ContentValues values = new ContentValues();
values.put(MediaStore.MediaColumns.DISPLAY_NAME, fileName);
values.put(MediaStore.MediaColumns.MIME_TYPE, "text/plain");
values.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS + "/" + folderName + "/");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) values.put("is_pending", 1);
Uri newUri = resolver.insert(MediaStore.Files.getContentUri("external"), values);
if (newUri == null) {
Log.e(TAG, "createFile: insert returned null");
return false;
}
OutputStream out = null;
try {
out = resolver.openOutputStream(newUri);
if (out == null) throw new IOException("openOutputStream returned null");
if (content != null) out.write(content.getBytes("UTF-8"));
out.flush();
} finally {
if (out != null) try { out.close(); } catch (IOException ignored) {}
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
ContentValues cv = new ContentValues();
cv.put("is_pending", 0);
resolver.update(newUri, cv, null, null);
}
Log.i(TAG, "createFile: created " + fileName);
return true;
} catch (Exception e) {
Log.e(TAG, "createFile failed", e);
return false;
}
}
// Append to file (reads existing and writes combined content)
public boolean appendToFile(String folderName, String fileName, String content) {
try {
Uri uri = findFileUri(folderName, fileName);
if (uri == null) {
// create new file
return createFile(folderName, fileName, content);
}
ContentResolver resolver = getContentResolver();
// read existing
ByteArrayOutputStream baos = new ByteArrayOutputStream();
InputStream in = null;
try {
in = resolver.openInputStream(uri);
if (in != null) {
byte[] buf = new byte[4096];
int r;
while ((r = in.read(buf)) > 0) baos.write(buf, 0, r);
}
} finally {
if (in != null) try { in.close(); } catch (IOException ignored) {}
}
byte[] existing = baos.toByteArray();
byte[] add = (content != null) ? content.getBytes("UTF-8") : new byte[0];
byte[] combined = new byte[existing.length + add.length];
System.arraycopy(existing, 0, combined, 0, existing.length);
System.arraycopy(add, 0, combined, existing.length, add.length);
OutputStream out = null;
try {
out = resolver.openOutputStream(uri, "w");
if (out == null) throw new IOException("openOutputStream returned null");
out.write(combined);
out.flush();
} finally {
if (out != null) try { out.close(); } catch (IOException ignored) {}
}
Log.i(TAG, "appendToFile success: " + fileName);
return true;
} catch (Exception e) {
Log.e(TAG, "appendToFile failed", e);
return false;
}
}
// Read file (UTF-8) - returns empty string if not found or on error
public String readFile(String folderName, String fileName) {
try {
Uri uri = findFileUri(folderName, fileName);
if (uri == null) {
Log.w(TAG, "readFile: not found: " + folderName + "/" + fileName);
return "";
}
ContentResolver resolver = getContentResolver();
InputStream in = null;
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
in = resolver.openInputStream(uri);
if (in == null) return "";
byte[] buf = new byte[4096];
int r;
while ((r = in.read(buf)) > 0) baos.write(buf, 0, r);
} finally {
if (in != null) try { in.close(); } catch (IOException ignored) {}
}
return new String(baos.toByteArray(), "UTF-8");
} catch (Exception e) {
Log.e(TAG, "readFile failed", e);
return "";
}
}
public String readLineFromFile(String folder, String filename, int lineNumber) {
try {
Uri fileUri = findFileUri(folder, filename); // your existing helper
if (fileUri == null) return "";
InputStream is = getContentResolver().openInputStream(fileUri);
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
String line = null;
int count = 0;
while ((line = reader.readLine()) != null) {
if (count == lineNumber) {
reader.close();
return line;
}
count++;
}
reader.close();
} catch (Exception e) {
e.printStackTrace();
}
return "";
}
public int getNumberOfLines(String folder, String filename) {
int count = 0;
try {
Uri fileUri = findFileUri(folder, filename); // use your existing helper
if (fileUri == null) return 0;
InputStream is = getContentResolver().openInputStream(fileUri);
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
while (reader.readLine() != null) {
count++;
}
reader.close();
} catch (Exception e) {
e.printStackTrace();
}
return count;
}
// Delete file via MediaStore
public boolean deleteFile(String folderName, String fileName) {
try {
Uri uri = findFileUri(folderName, fileName);
if (uri == null) {
Log.w(TAG, "deleteFile: not found");
return false;
}
int deleted = getContentResolver().delete(uri, null, null);
Log.i(TAG, "deleteFile: deleted rows = " + deleted);
return deleted > 0;
} catch (Exception e) {
Log.e(TAG, "deleteFile failed", e);
return false;
}
}
// Helper: recursive delete
private boolean deleteRecursive(File fileOrDirectory) {
if (fileOrDirectory.isDirectory()) {
File[] children = fileOrDirectory.listFiles();
if (children != null) {
for (File child : children) {
deleteRecursive(child);
}
}
}
return fileOrDirectory.delete();
}
// Public method for Hollywood
public boolean deleteFolder(String folderName) {
try {
File dir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), folderName);
if (dir.exists() && dir.isDirectory()) {
return deleteRecursive(dir);
} else {
return false; // folder does not exist
}
} catch (Exception e) {
Log.e("HollywoodDelegate", "Error deleting folder: " + e.getMessage());
return false;
}
}
// Copy internal (getFilesDir()/internalPath) -> external Downloads/folderName/fileName
public boolean copyFileToExternal(String internalRelativePath, String folderName, String fileName) {
try {
File src = new File(getFilesDir(), internalRelativePath);
if (!src.exists()) {
Log.w(TAG, "copyFileToExternal: source not found: " + src.getAbsolutePath());
return false;
}
ContentResolver resolver = getContentResolver();
Uri dstUri = findFileUri(folderName, fileName);
if (dstUri == null) {
ContentValues values = new ContentValues();
values.put(MediaStore.MediaColumns.DISPLAY_NAME, fileName);
values.put(MediaStore.MediaColumns.MIME_TYPE, "application/octet-stream");
values.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS + "/" + folderName + "/");
values.put("is_pending", 1);
dstUri = resolver.insert(MediaStore.Files.getContentUri("external"), values);
}
if (dstUri == null) {
Log.e(TAG, "copyFileToExternal: insert returned null");
return false;
}
FileInputStream in = null;
OutputStream out = null;
try {
in = new FileInputStream(src);
out = resolver.openOutputStream(dstUri, "w");
if (out == null) throw new IOException("openOutputStream returned null");
byte[] buf = new byte[4096];
int r;
while ((r = in.read(buf)) > 0) out.write(buf, 0, r);
out.flush();
} finally {
if (in != null) try { in.close(); } catch (IOException ignored) {}
if (out != null) try { out.close(); } catch (IOException ignored) {}
}
ContentValues cv = new ContentValues();
cv.put("is_pending", 0);
resolver.update(dstUri, cv, null, null);
Log.i(TAG, "copyFileToExternal success");
return true;
} catch (Exception e) {
Log.e(TAG, "copyFileToExternal failed", e);
return false;
}
}
// Copy external Downloads/folderName/fileName -> internal getFilesDir()/internalRelativePath
public boolean copyFileFromExternal(String folderName, String fileName, String internalRelativePath) {
try {
Uri srcUri = findFileUri(folderName, fileName);
if (srcUri == null) {
Log.w(TAG, "copyFileFromExternal: not found");
return false;
}
InputStream in = null;
FileOutputStream out = null;
try {
in = getContentResolver().openInputStream(srcUri);
if (in == null) throw new IOException("openInputStream returned null");
File dest = new File(getFilesDir(), internalRelativePath);
File parent = dest.getParentFile();
if (parent != null && !parent.exists()) parent.mkdirs();
out = new FileOutputStream(dest);
byte[] buf = new byte[4096];
int r;
while ((r = in.read(buf)) > 0) out.write(buf, 0, r);
out.flush();
} finally {
if (in != null) try { in.close(); } catch (IOException ignored) {}
if (out != null) try { out.close(); } catch (IOException ignored) {}
}
Log.i(TAG, "copyFileFromExternal success -> " + internalRelativePath);
return true;
} catch (Exception e) {
Log.e(TAG, "copyFileFromExternal failed", e);
return false;
}
}
// List files in Downloads/<folderName> via MediaStore; newline-separated string
public String listFilesInFolder(String folderName) {
try {
String relPath = Environment.DIRECTORY_DOWNLOADS + "/" + folderName + "/";
Uri collection = MediaStore.Files.getContentUri("external");
String[] projection = new String[]{ MediaStore.MediaColumns.DISPLAY_NAME, MediaStore.MediaColumns.MIME_TYPE };
String selection = MediaStore.MediaColumns.RELATIVE_PATH + "=?";
String[] selectionArgs = new String[]{ relPath };
Cursor c = getContentResolver().query(collection, projection, selection, selectionArgs, null);
if (c == null) {
Log.w(TAG, "listFilesInFolder: cursor null");
return "";
}
StringBuilder sb = new StringBuilder();
try {
while (c.moveToNext()) {
String name = c.getString(c.getColumnIndexOrThrow(MediaStore.MediaColumns.DISPLAY_NAME));
String mime = c.getString(c.getColumnIndexOrThrow(MediaStore.MediaColumns.MIME_TYPE));
sb.append(name).append(" (").append(mime).append(")").append("\n");
}
} finally {
c.close();
}
return sb.toString().trim();
} catch (Exception e) {
Log.e(TAG, "listFilesInFolder failed", e);
return "";
}
}
}