From 3b1118229b7ba4ae719adc13893fa9604ba18a36 Mon Sep 17 00:00:00 2001 From: Luke Street Date: Sat, 9 May 2026 20:22:30 -0600 Subject: [PATCH] Add Android DocumentsProvider --- .../android/app/src/main/AndroidManifest.xml | 11 + .../dusk/DuskDocumentsProvider.java | 373 ++++++++++++++++++ .../app/src/main/res/values/strings.xml | 2 + 3 files changed, 386 insertions(+) create mode 100644 platforms/android/app/src/main/java/com/twilitrealm/dusk/DuskDocumentsProvider.java diff --git a/platforms/android/app/src/main/AndroidManifest.xml b/platforms/android/app/src/main/AndroidManifest.xml index f3e11030ca..f5a0365856 100644 --- a/platforms/android/app/src/main/AndroidManifest.xml +++ b/platforms/android/app/src/main/AndroidManifest.xml @@ -24,6 +24,17 @@ + + + + + + 0 ? displayName.substring(0, dot) : displayName; + final String extension = dot > 0 ? displayName.substring(dot) : ""; + for (int i = 1; i < 100; ++i) { + file = new File(parent, baseName + " (" + i + ")" + extension); + if (!file.exists()) { + return file; + } + } + return new File(parent, baseName + " (" + System.currentTimeMillis() + ")" + extension); + } + + private static int modeToParcelMode(String mode) { + if ("r".equals(mode)) { + return ParcelFileDescriptor.MODE_READ_ONLY; + } + if ("w".equals(mode) || "wt".equals(mode)) { + return ParcelFileDescriptor.MODE_WRITE_ONLY | + ParcelFileDescriptor.MODE_CREATE | + ParcelFileDescriptor.MODE_TRUNCATE; + } + if ("wa".equals(mode)) { + return ParcelFileDescriptor.MODE_WRITE_ONLY | + ParcelFileDescriptor.MODE_CREATE | + ParcelFileDescriptor.MODE_APPEND; + } + if ("rw".equals(mode)) { + return ParcelFileDescriptor.MODE_READ_WRITE | + ParcelFileDescriptor.MODE_CREATE; + } + if ("rwt".equals(mode)) { + return ParcelFileDescriptor.MODE_READ_WRITE | + ParcelFileDescriptor.MODE_CREATE | + ParcelFileDescriptor.MODE_TRUNCATE; + } + return ParcelFileDescriptor.MODE_READ_ONLY; + } + + private static String getMimeType(File file) { + final int dot = file.getName().lastIndexOf('.'); + if (dot >= 0) { + final String extension = file.getName().substring(dot + 1).toLowerCase(); + final String mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension); + if (mimeType != null) { + return mimeType; + } + } + return "application/octet-stream"; + } + + private static void deleteRecursively(File file) throws FileNotFoundException { + if (file.isDirectory()) { + final File[] children = file.listFiles(); + if (children != null) { + for (File child : children) { + deleteRecursively(child); + } + } + } + if (!file.delete()) { + throw new FileNotFoundException("Unable to delete document: " + file); + } + } + + private static boolean isInside(File parent, File child) { + try { + final String parentPath = canonicalPath(parent); + final String childPath = canonicalPath(child); + return childPath.equals(parentPath) || childPath.startsWith(parentPath + File.separator); + } catch (FileNotFoundException e) { + return false; + } + } + + private static boolean sameFile(File a, File b) { + try { + return canonicalPath(a).equals(canonicalPath(b)); + } catch (FileNotFoundException e) { + return false; + } + } + + private static String canonicalPath(File file) throws FileNotFoundException { + try { + return file.getCanonicalPath(); + } catch (IOException e) { + throw asFileNotFound("Unable to resolve path", e); + } + } + + private static FileNotFoundException asFileNotFound(String message, IOException cause) { + final FileNotFoundException exception = new FileNotFoundException(message + ": " + cause.getMessage()); + exception.initCause(cause); + return exception; + } +} diff --git a/platforms/android/app/src/main/res/values/strings.xml b/platforms/android/app/src/main/res/values/strings.xml index 79d8812b82..752dae76bf 100644 --- a/platforms/android/app/src/main/res/values/strings.xml +++ b/platforms/android/app/src/main/res/values/strings.xml @@ -1,4 +1,6 @@ Dusk + Dusk Data + Saves, texture packs, settings, and logs