From c3f914565fa95a1887572b00c868884fac435502 Mon Sep 17 00:00:00 2001 From: Connor McLaughlin Date: Sat, 17 Apr 2021 20:46:19 +1000 Subject: [PATCH] Android: Use SAF paths for scanning --- .../duckstation/EmulationActivity.java | 12 +- .../stenzek/duckstation/FileHelper.java | 202 +++++++++++- .../github/stenzek/duckstation/FileUtil.java | 293 ------------------ .../duckstation/GameDirectoriesActivity.java | 48 +-- .../stenzek/duckstation/MainActivity.java | 29 +- .../duckstation/MemoryCardEditorActivity.java | 4 +- .../stenzek/duckstation/MemoryCardImage.java | 6 +- 7 files changed, 231 insertions(+), 363 deletions(-) delete mode 100644 android/app/src/main/java/com/github/stenzek/duckstation/FileUtil.java diff --git a/android/app/src/main/java/com/github/stenzek/duckstation/EmulationActivity.java b/android/app/src/main/java/com/github/stenzek/duckstation/EmulationActivity.java index 11e9bfb8e..5737f20d7 100644 --- a/android/app/src/main/java/com/github/stenzek/duckstation/EmulationActivity.java +++ b/android/app/src/main/java/com/github/stenzek/duckstation/EmulationActivity.java @@ -305,19 +305,15 @@ public class EmulationActivity extends AppCompatActivity implements SurfaceHolde applySettings(); } } else if (requestCode == REQUEST_IMPORT_PATCH_CODES) { - if (data == null) + if (data == null || data.getData() == null) return; importPatchesFromFile(data.getData()); } else if (requestCode == REQUEST_CHANGE_DISC_FILE) { - if (data == null) + if (data == null || data.getData() == null) return; - String path = GameDirectoriesActivity.getPathFromUri(this, data.getData()); - if (path == null) - return; - - AndroidHostInterface.getInstance().setMediaFilename(path); + AndroidHostInterface.getInstance().setMediaFilename(data.getDataString()); } } @@ -687,7 +683,7 @@ public class EmulationActivity extends AppCompatActivity implements SurfaceHolde } private void importPatchesFromFile(Uri uri) { - String str = FileUtil.readFileFromUri(this, uri, 512 * 1024); + String str = FileHelper.readStringFromUri(this, uri, 512 * 1024); if (str == null || !AndroidHostInterface.getInstance().importPatchCodesFromString(str)) { reportErrorOnUIThread(getString(R.string.emulation_activity_failed_to_import_patch_codes)); } diff --git a/android/app/src/main/java/com/github/stenzek/duckstation/FileHelper.java b/android/app/src/main/java/com/github/stenzek/duckstation/FileHelper.java index dae26fcb4..edab85e50 100644 --- a/android/app/src/main/java/com/github/stenzek/duckstation/FileHelper.java +++ b/android/app/src/main/java/com/github/stenzek/duckstation/FileHelper.java @@ -3,10 +3,22 @@ package com.github.stenzek.duckstation; import android.content.ContentResolver; import android.content.Context; import android.database.Cursor; +import android.graphics.Bitmap; +import android.graphics.ImageDecoder; import android.net.Uri; import android.os.ParcelFileDescriptor; import android.provider.DocumentsContract; +import android.provider.MediaStore; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; /** @@ -46,6 +58,7 @@ public class FileHelper { /** * File helper class - used to bridge native code to Java storage access framework APIs. + * * @param context Context in which to perform file actions as. */ public FileHelper(Context context) { @@ -53,10 +66,187 @@ public class FileHelper { this.contentResolver = context.getContentResolver(); } + /** + * Reads the specified file as a string, under the specified context. + * + * @param context context to access file under + * @param uri uri to write data to + * @param maxSize maximum file size to read + * @return String containing the file data, otherwise null + */ + public static String readStringFromUri(final Context context, final Uri uri, int maxSize) { + InputStream stream = null; + try { + stream = context.getContentResolver().openInputStream(uri); + } catch (FileNotFoundException e) { + return null; + } + + StringBuilder os = new StringBuilder(); + try { + char[] buffer = new char[1024]; + InputStreamReader reader = new InputStreamReader(stream, Charset.forName(StandardCharsets.UTF_8.name())); + int len; + while ((len = reader.read(buffer)) > 0) { + os.append(buffer, 0, len); + if (os.length() > maxSize) + return null; + } + + stream.close(); + } catch (IOException e) { + return null; + } + + if (os.length() == 0) + return null; + + return os.toString(); + } + + /** + * Reads the specified file as a byte array, under the specified context. + * + * @param context context to access file under + * @param uri uri to write data to + * @param maxSize maximum file size to read + * @return byte array containing the file data, otherwise null + */ + public static byte[] readBytesFromUri(final Context context, final Uri uri, int maxSize) { + InputStream stream = null; + try { + stream = context.getContentResolver().openInputStream(uri); + } catch (FileNotFoundException e) { + return null; + } + + ByteArrayOutputStream os = new ByteArrayOutputStream(); + try { + byte[] buffer = new byte[512 * 1024]; + int len; + while ((len = stream.read(buffer)) > 0) { + os.write(buffer, 0, len); + if (maxSize > 0 && os.size() > maxSize) { + return null; + } + } + + stream.close(); + } catch (IOException e) { + e.printStackTrace(); + return null; + } + + if (os.size() == 0) + return null; + + return os.toByteArray(); + } + + /** + * Writes the specified data to a file referenced by the URI, as the specified context. + * + * @param context context to access file under + * @param uri uri to write data to + * @param bytes data to write file to + * @return true if write was succesful, otherwise false + */ + public static boolean writeBytesToUri(final Context context, final Uri uri, final byte[] bytes) { + OutputStream stream = null; + try { + stream = context.getContentResolver().openOutputStream(uri); + } catch (FileNotFoundException e) { + e.printStackTrace(); + return false; + } + + if (bytes != null && bytes.length > 0) { + try { + stream.write(bytes); + stream.close(); + } catch (IOException e) { + e.printStackTrace(); + return false; + } + } + + return true; + } + + /** + * Deletes the file referenced by the URI, under the specified context. + * + * @param context context to delete file under + * @param uri uri to delete + * @return + */ + public static boolean deleteFileAtUri(final Context context, final Uri uri) { + try { + if (uri.getScheme() == "file") { + final File file = new File(uri.getPath()); + if (!file.isFile()) + return false; + + return file.delete(); + } + return (context.getContentResolver().delete(uri, null, null) > 0); + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + + /** + * Returns the name of the file pointed at by a SAF URI. + * + * @param context context to access file under + * @param uri uri to retrieve file name for + * @return the name of the file, or null + */ + public static String getDocumentNameFromUri(final Context context, final Uri uri) { + Cursor cursor = null; + try { + final String[] proj = {DocumentsContract.Document.COLUMN_DISPLAY_NAME}; + cursor = context.getContentResolver().query(uri, proj, null, null, null); + final int columnIndex = cursor.getColumnIndexOrThrow(DocumentsContract.Document.COLUMN_DISPLAY_NAME); + cursor.moveToFirst(); + return cursor.getString(columnIndex); + } catch (Exception e) { + e.printStackTrace(); + return null; + } finally { + if (cursor != null) + cursor.close(); + } + } + + /** + * Loads a bitmap from the provided SAF URI. + * + * @param context context to access file under + * @param uri uri to retrieve file name for + * @return a decoded bitmap for the file, or null + */ + public static Bitmap loadBitmapFromUri(final Context context, final Uri uri) { + InputStream stream = null; + try { + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.P) { + final ImageDecoder.Source source = ImageDecoder.createSource(context.getContentResolver(), uri); + return ImageDecoder.decodeBitmap(source); + } else { + return MediaStore.Images.Media.getBitmap(context.getContentResolver(), uri); + } + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + /** * Retrieves a file descriptor for a content URI string. Called by native code. + * * @param uriString string of the URI to open - * @param mode Java open mode + * @param mode Java open mode * @return file descriptor for URI, or -1 */ public int openURIAsFileDescriptor(String uriString, String mode) { @@ -73,10 +263,11 @@ public class FileHelper { /** * Recursively iterates documents in the specified tree, searching for files. - * @param treeUri Root tree in which to search for documents. + * + * @param treeUri Root tree in which to search for documents. * @param documentId Document ID representing the directory to start searching. - * @param flags Native search flags. - * @param results Cumulative result array. + * @param flags Native search flags. + * @param results Cumulative result array. */ private void doFindFiles(Uri treeUri, String documentId, int flags, ArrayList results) { try { @@ -116,8 +307,9 @@ public class FileHelper { /** * Recursively iterates documents in the specified URI, searching for files. + * * @param uriString URI containing directory to search. - * @param flags Native filter flags. + * @param flags Native filter flags. * @return Array of find results. */ public FindResult[] findFiles(String uriString, int flags) { diff --git a/android/app/src/main/java/com/github/stenzek/duckstation/FileUtil.java b/android/app/src/main/java/com/github/stenzek/duckstation/FileUtil.java deleted file mode 100644 index 85128dec0..000000000 --- a/android/app/src/main/java/com/github/stenzek/duckstation/FileUtil.java +++ /dev/null @@ -1,293 +0,0 @@ -package com.github.stenzek.duckstation; - -// https://stackoverflow.com/questions/34927748/android-5-0-documentfile-from-tree-uri - -import android.annotation.SuppressLint; -import android.annotation.TargetApi; -import android.content.Context; -import android.database.Cursor; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.graphics.ImageDecoder; -import android.net.Uri; -import android.os.Build; -import android.os.storage.StorageManager; -import android.provider.DocumentsContract; -import android.provider.MediaStore; -import android.widget.Toast; - -import androidx.annotation.Nullable; -import androidx.appcompat.app.AlertDialog; - -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.OutputStream; -import java.lang.reflect.Array; -import java.lang.reflect.Method; -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; - -public final class FileUtil { - static String TAG = "TAG"; - private static final String PRIMARY_VOLUME_NAME = "primary"; - - @Nullable - public static String getFullPathFromTreeUri(@Nullable final Uri treeUri, Context con) { - if (treeUri == null) return null; - String volumePath = getVolumePath(getVolumeIdFromTreeUri(treeUri), con); - 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() > 0) { - if (documentPath.startsWith(File.separator)) - return volumePath + documentPath; - else - return volumePath + File.separator + documentPath; - } else return volumePath; - } - - @SuppressLint("ObsoleteSdkInt") - private static String getVolumePath(final String volumeId, Context context) { - if (volumeId == null) - return null; - - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) return null; - try { - StorageManager mStorageManager = - (StorageManager) context.getSystemService(Context.STORAGE_SERVICE); - Class storageVolumeClazz = Class.forName("android.os.storage.StorageVolume"); - Method getVolumeList = mStorageManager.getClass().getMethod("getVolumeList"); - Method getUuid = storageVolumeClazz.getMethod("getUuid"); - Method getPath = storageVolumeClazz.getMethod("getPath"); - Method isPrimary = storageVolumeClazz.getMethod("isPrimary"); - Object result = getVolumeList.invoke(mStorageManager); - - final int length = Array.getLength(result); - for (int i = 0; i < length; i++) { - Object storageVolumeElement = Array.get(result, i); - String uuid = (String) getUuid.invoke(storageVolumeElement); - Boolean primary = (Boolean) isPrimary.invoke(storageVolumeElement); - - // primary volume? - if (primary && PRIMARY_VOLUME_NAME.equals(volumeId)) - return (String) getPath.invoke(storageVolumeElement); - - // other volumes? - if (uuid != null && uuid.equals(volumeId)) - return (String) getPath.invoke(storageVolumeElement); - } - // not found. - return null; - } catch (Exception ex) { - return null; - } - } - - @TargetApi(Build.VERSION_CODES.LOLLIPOP) - private static String getVolumeIdFromTreeUri(final Uri treeUri) { - final String docId = DocumentsContract.getTreeDocumentId(treeUri); - final String[] split = docId.split(":"); - if (split.length > 0) return split[0]; - else return null; - } - - - @TargetApi(Build.VERSION_CODES.LOLLIPOP) - private static String getDocumentPathFromTreeUri(final Uri treeUri) { - final String docId = DocumentsContract.getTreeDocumentId(treeUri); - final String[] split = docId.split(":"); - if ((split.length >= 2) && (split[1] != null)) return split[1]; - else return File.separator; - } - - @Nullable - public static String getFullPathFromUri(@Nullable final Uri treeUri, Context con) { - if (treeUri == null) return null; - String volumePath = getVolumePath(getVolumeIdFromUri(treeUri), con); - if (volumePath == null) return File.separator; - if (volumePath.endsWith(File.separator)) - volumePath = volumePath.substring(0, volumePath.length() - 1); - - String documentPath = getDocumentPathFromUri(treeUri); - if (documentPath.endsWith(File.separator)) - documentPath = documentPath.substring(0, documentPath.length() - 1); - - if (documentPath.length() > 0) { - if (documentPath.startsWith(File.separator)) - return volumePath + documentPath; - else - return volumePath + File.separator + documentPath; - } else return volumePath; - } - - @TargetApi(Build.VERSION_CODES.LOLLIPOP) - private static String getVolumeIdFromUri(final Uri treeUri) { - try { - final String docId = DocumentsContract.getDocumentId(treeUri); - final String[] split = docId.split(":"); - if (split.length > 0) return split[0]; - else return null; - } catch (Exception e) { - return null; - } - } - - - @TargetApi(Build.VERSION_CODES.LOLLIPOP) - private static String getDocumentPathFromUri(final Uri treeUri) { - try { - final String docId = DocumentsContract.getDocumentId(treeUri); - final String[] split = docId.split(":"); - if ((split.length >= 2) && (split[1] != null)) return split[1]; - else return File.separator; - } catch (Exception e) { - return null; - } - } - - public static String readFileFromUri(final Context context, final Uri uri, int maxSize) { - InputStream stream = null; - try { - stream = context.getContentResolver().openInputStream(uri); - } catch (FileNotFoundException e) { - return null; - } - - StringBuilder os = new StringBuilder(); - try { - char[] buffer = new char[1024]; - InputStreamReader reader = new InputStreamReader(stream, Charset.forName(StandardCharsets.UTF_8.name())); - int len; - while ((len = reader.read(buffer)) > 0) { - os.append(buffer, 0, len); - if (os.length() > maxSize) - return null; - } - - stream.close(); - } catch (IOException e) { - return null; - } - - if (os.length() == 0) - return null; - - return os.toString(); - } - - public static byte[] readBytesFromUri(final Context context, final Uri uri, int maxSize) { - InputStream stream = null; - try { - stream = context.getContentResolver().openInputStream(uri); - } catch (FileNotFoundException e) { - return null; - } - - ByteArrayOutputStream os = new ByteArrayOutputStream(); - try { - byte[] buffer = new byte[512 * 1024]; - int len; - while ((len = stream.read(buffer)) > 0) { - os.write(buffer, 0, len); - if (maxSize > 0 && os.size() > maxSize) { - return null; - } - } - - stream.close(); - } catch (IOException e) { - e.printStackTrace(); - return null; - } - - if (os.size() == 0) - return null; - - return os.toByteArray(); - } - - public static boolean writeBytesToUri(final Context context, final Uri uri, final byte[] bytes) { - OutputStream stream = null; - try { - stream = context.getContentResolver().openOutputStream(uri); - } catch (FileNotFoundException e) { - e.printStackTrace(); - return false; - } - - if (bytes != null && bytes.length > 0) { - try { - stream.write(bytes); - stream.close(); - } catch (IOException e) { - e.printStackTrace(); - return false; - } - } - - return true; - } - - public static boolean deleteFileAtUri(final Context context, final Uri uri) { - try { - if (uri.getScheme() == "file") { - final File file = new File(uri.getPath()); - if (!file.isFile()) - return false; - - return file.delete(); - } - return (context.getContentResolver().delete(uri, null, null) > 0); - } catch (Exception e) { - e.printStackTrace(); - return false; - } - } - - /** - * Returns the name of the file pointed at by a SAF URI. - * @param context context to access file under - * @param uri uri to retrieve file name for - * @return the name of the file, or null - */ - public static String getDocumentNameFromUri(final Context context, final Uri uri) { - Cursor cursor = null; - try { - final String[] proj = {DocumentsContract.Document.COLUMN_DISPLAY_NAME}; - cursor = context.getContentResolver().query(uri, proj, null, null, null); - final int columnIndex = cursor.getColumnIndexOrThrow(DocumentsContract.Document.COLUMN_DISPLAY_NAME); - cursor.moveToFirst(); - return cursor.getString(columnIndex); - } catch (Exception e) { - e.printStackTrace(); - return null; - } finally { - if (cursor != null) - cursor.close(); - } - } - - public static Bitmap loadBitmapFromUri(final Context context, final Uri uri) { - InputStream stream = null; - try { - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.P) { - final ImageDecoder.Source source =ImageDecoder.createSource(context.getContentResolver(), uri); - return ImageDecoder.decodeBitmap(source); - } else { - return MediaStore.Images.Media.getBitmap(context.getContentResolver(), uri); - } - } catch (Exception e) { - e.printStackTrace(); - return null; - } - } -} \ No newline at end of file diff --git a/android/app/src/main/java/com/github/stenzek/duckstation/GameDirectoriesActivity.java b/android/app/src/main/java/com/github/stenzek/duckstation/GameDirectoriesActivity.java index 7b8ad788c..ea893b8c7 100644 --- a/android/app/src/main/java/com/github/stenzek/duckstation/GameDirectoriesActivity.java +++ b/android/app/src/main/java/com/github/stenzek/duckstation/GameDirectoriesActivity.java @@ -239,38 +239,6 @@ public class GameDirectoriesActivity extends AppCompatActivity { return super.onOptionsItemSelected(item); } - public static String getPathFromTreeUri(Context context, Uri treeUri) { - String path = FileUtil.getFullPathFromTreeUri(treeUri, context); - if (path.length() < 5) { - new AlertDialog.Builder(context) - .setTitle(R.string.main_activity_error) - .setMessage(R.string.main_activity_get_path_from_directory_error) - .setPositiveButton(R.string.main_activity_ok, (dialog, button) -> { - }) - .create() - .show(); - return null; - } - - return path; - } - - public static String getPathFromUri(Context context, Uri uri) { - String path = FileUtil.getFullPathFromUri(uri, context); - if (path.length() < 5) { - new AlertDialog.Builder(context) - .setTitle(R.string.main_activity_error) - .setMessage(R.string.main_activity_get_path_from_file_error) - .setPositiveButton(R.string.main_activity_ok, (dialog, button) -> { - }) - .create() - .show(); - return null; - } - - return path; - } - public static void addSearchDirectory(Context context, String path, boolean recursive) { final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); final String key = recursive ? "GameList/RecursivePaths" : "GameList/Paths"; @@ -289,6 +257,8 @@ public class GameDirectoriesActivity extends AppCompatActivity { Intent i = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE); i.addCategory(Intent.CATEGORY_DEFAULT); i.putExtra(Intent.EXTRA_LOCAL_ONLY, true); + i.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + i.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION); startActivityForResult(Intent.createChooser(i, getString(R.string.main_activity_choose_directory)), REQUEST_ADD_DIRECTORY_TO_GAME_LIST); } @@ -318,14 +288,18 @@ public class GameDirectoriesActivity extends AppCompatActivity { switch (requestCode) { case REQUEST_ADD_DIRECTORY_TO_GAME_LIST: { - if (resultCode != RESULT_OK) + if (resultCode != RESULT_OK || data.getData() == null) return; - String path = getPathFromTreeUri(this, data.getData()); - if (path == null) - return; + try { + getContentResolver().takePersistableUriPermission(data.getData(), + Intent.FLAG_GRANT_READ_URI_PERMISSION); + } catch (Exception e) { + Toast.makeText(this, "Failed to take persistable permission.", Toast.LENGTH_LONG); + e.printStackTrace(); + } - addSearchDirectory(this, path, true); + addSearchDirectory(this, data.getDataString(), true); mDirectoryListAdapter.reload(); } break; diff --git a/android/app/src/main/java/com/github/stenzek/duckstation/MainActivity.java b/android/app/src/main/java/com/github/stenzek/duckstation/MainActivity.java index 4bdfb096f..f3139095f 100644 --- a/android/app/src/main/java/com/github/stenzek/duckstation/MainActivity.java +++ b/android/app/src/main/java/com/github/stenzek/duckstation/MainActivity.java @@ -9,7 +9,6 @@ import android.content.pm.PackageManager; import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Bitmap; -import android.graphics.BitmapFactory; import android.net.Uri; import android.os.Build; import android.os.Bundle; @@ -18,12 +17,10 @@ import android.view.Gravity; import android.view.Menu; import android.view.MenuItem; import android.view.View; -import android.widget.Button; import android.widget.ListView; import android.widget.Toast; import androidx.annotation.NonNull; -import androidx.annotation.Nullable; import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatDelegate; @@ -195,6 +192,8 @@ public class MainActivity extends AppCompatActivity { Intent i = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE); i.addCategory(Intent.CATEGORY_DEFAULT); i.putExtra(Intent.EXTRA_LOCAL_ONLY, true); + i.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + i.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION); startActivityForResult(Intent.createChooser(i, getString(R.string.main_activity_choose_directory)), REQUEST_ADD_DIRECTORY_TO_GAME_LIST); } @@ -270,14 +269,18 @@ public class MainActivity extends AppCompatActivity { switch (requestCode) { case REQUEST_ADD_DIRECTORY_TO_GAME_LIST: { - if (resultCode != RESULT_OK) + if (resultCode != RESULT_OK || data.getData() == null) return; - String path = GameDirectoriesActivity.getPathFromTreeUri(this, data.getData()); - if (path == null) - return; + try { + getContentResolver().takePersistableUriPermission(data.getData(), + Intent.FLAG_GRANT_READ_URI_PERMISSION); + } catch (Exception e) { + Toast.makeText(this, "Failed to take persistable permission.", Toast.LENGTH_LONG); + e.printStackTrace(); + } - GameDirectoriesActivity.addSearchDirectory(this, path, true); + GameDirectoriesActivity.addSearchDirectory(this, data.getDataString(), true); mGameList.refresh(false, false, this); } break; @@ -291,14 +294,10 @@ public class MainActivity extends AppCompatActivity { break; case REQUEST_START_FILE: { - if (resultCode != RESULT_OK) + if (resultCode != RESULT_OK || data.getData() == null) return; - String path = GameDirectoriesActivity.getPathFromUri(this, data.getData()); - if (path == null) - return; - - startEmulation(path, shouldResumeStateByDefault()); + startEmulation(data.getDataString(), shouldResumeStateByDefault()); } break; @@ -428,7 +427,7 @@ public class MainActivity extends AppCompatActivity { if (gameListEntry == null) return; - final Bitmap bitmap = FileUtil.loadBitmapFromUri(this, uri); + final Bitmap bitmap = FileHelper.loadBitmapFromUri(this, uri); if (bitmap == null) { Toast.makeText(this, "Failed to open/decode image.", Toast.LENGTH_LONG).show(); return; diff --git a/android/app/src/main/java/com/github/stenzek/duckstation/MemoryCardEditorActivity.java b/android/app/src/main/java/com/github/stenzek/duckstation/MemoryCardEditorActivity.java index 76953c75a..3c8d645d5 100644 --- a/android/app/src/main/java/com/github/stenzek/duckstation/MemoryCardEditorActivity.java +++ b/android/app/src/main/java/com/github/stenzek/duckstation/MemoryCardEditorActivity.java @@ -286,13 +286,13 @@ public class MemoryCardEditorActivity extends AppCompatActivity { if (card == null) return; - final byte[] data = FileUtil.readBytesFromUri(this, uri, 16 * 1024 * 1024); + final byte[] data = FileHelper.readBytesFromUri(this, uri, 16 * 1024 * 1024); if (data == null) { displayError(getString(R.string.memory_card_editor_import_card_read_failed, uri.toString())); return; } - String importFileName = FileUtil.getDocumentNameFromUri(this, uri); + String importFileName = FileHelper.getDocumentNameFromUri(this, uri); if (importFileName == null) { importFileName = uri.getPath(); if (importFileName == null || importFileName.isEmpty()) diff --git a/android/app/src/main/java/com/github/stenzek/duckstation/MemoryCardImage.java b/android/app/src/main/java/com/github/stenzek/duckstation/MemoryCardImage.java index 218889369..5417ecc3d 100644 --- a/android/app/src/main/java/com/github/stenzek/duckstation/MemoryCardImage.java +++ b/android/app/src/main/java/com/github/stenzek/duckstation/MemoryCardImage.java @@ -41,7 +41,7 @@ public class MemoryCardImage { } public static MemoryCardImage open(Context context, Uri uri) { - byte[] data = FileUtil.readBytesFromUri(context, uri, DATA_SIZE); + byte[] data = FileHelper.readBytesFromUri(context, uri, DATA_SIZE); if (data == null) return null; @@ -145,11 +145,11 @@ public class MemoryCardImage { private static native boolean importCard(byte[] data, String filename, byte[] importData); public boolean save() { - return FileUtil.writeBytesToUri(context, uri, data); + return FileHelper.writeBytesToUri(context, uri, data); } public boolean delete() { - return FileUtil.deleteFileAtUri(context, uri); + return FileHelper.deleteFileAtUri(context, uri); } public boolean format() {