From 2128c5d23b9bca67b48ae849977868e44efe09ce Mon Sep 17 00:00:00 2001 From: Tuan Nguyen Date: Tue, 25 Feb 2025 14:14:30 +0700 Subject: [PATCH 1/2] [feat] Support scoped storage when sharing images: https://github.com/elimu-ai/vitabu/issues/105 --- app/build.gradle | 4 +-- app/src/main/AndroidManifest.xml | 7 ++++- .../provider/ImageContentProvider.java | 30 +++++++++++++++++++ .../content_provider/util/FileHelper.java | 19 ++++++++++-- app/src/main/res/xml/image_file_path.xml | 4 +++ 5 files changed, 59 insertions(+), 5 deletions(-) create mode 100644 app/src/main/res/xml/image_file_path.xml diff --git a/app/build.gradle b/app/build.gradle index 8c55bc3..2e24c2d 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -2,13 +2,13 @@ apply plugin: 'com.android.application' apply plugin: 'org.ajoberstar.grgit' android { - compileSdk 34 + compileSdk 35 namespace 'ai.elimu.content_provider' defaultConfig { applicationId "ai.elimu.content_provider" minSdkVersion 24 - targetSdkVersion 34 + targetSdkVersion 35 versionCode 1002029 versionName "1.2.29-SNAPSHOT" setProperty("archivesBaseName", "${applicationId}-${versionCode}") diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 3587b8e..552d641 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -52,7 +52,12 @@ android:name=".provider.ImageContentProvider" android:authorities="${applicationId}.provider.image_provider" android:enabled="true" - android:exported="true" /> + android:exported="true" + android:grantUriPermissions="true"> + + segments = uri.getPathSegments(); + if (segments.size() < 2) { + throw new FileNotFoundException("Invalid URI: " + uri); + } + String fileId = segments.get(1); + + RoomDb roomDb = RoomDb.getDatabase(getContext()); + ImageDao imageDao = roomDb.imageDao(); + Image image = imageDao.load(Long.parseLong(fileId)); + + File imageFile = FileHelper.getImageFile(image, getContext()); + if (imageFile == null) { + throw new FileNotFoundException("File not found!"); + } + if (!imageFile.exists()) { + Log.e(TAG, "imageFile doesn't exist: " + imageFile.getAbsolutePath()); + throw new FileNotFoundException("File not found: " + imageFile.getAbsolutePath()); + } + return ParcelFileDescriptor.open(imageFile, ParcelFileDescriptor.MODE_READ_ONLY); + } + } diff --git a/app/src/main/java/ai/elimu/content_provider/util/FileHelper.java b/app/src/main/java/ai/elimu/content_provider/util/FileHelper.java index 5412267..369b7de 100644 --- a/app/src/main/java/ai/elimu/content_provider/util/FileHelper.java +++ b/app/src/main/java/ai/elimu/content_provider/util/FileHelper.java @@ -3,8 +3,11 @@ import android.content.Context; import android.os.Environment; +import androidx.annotation.RestrictTo; + import java.io.File; +import ai.elimu.content_provider.room.entity.Image; import ai.elimu.model.v2.gson.content.AudioGson; import ai.elimu.model.v2.gson.content.ImageGson; import ai.elimu.model.v2.gson.content.VideoGson; @@ -19,8 +22,20 @@ public static File getImageFile(ImageGson imageGson, Context context) { return null; } File imagesDirectory = context.getExternalFilesDir(Environment.DIRECTORY_PICTURES); - File file = new File(imagesDirectory, imageGson.getId() + "_r" + imageGson.getRevisionNumber() + "." + imageGson.getImageFormat().toString().toLowerCase()); - return file; + return new File(imagesDirectory, imageGson.getId() + + "_r" + imageGson.getRevisionNumber() + "." + + imageGson.getImageFormat().toString().toLowerCase()); + } + + @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX) + public static File getImageFile(Image imageGson, Context context) { + if ((imageGson.getId() == null) || (imageGson.getRevisionNumber() == null)) { + return null; + } + File imagesDirectory = context.getExternalFilesDir(Environment.DIRECTORY_PICTURES); + return new File(imagesDirectory, imageGson.getId() + + "_r" + imageGson.getRevisionNumber() + "." + + imageGson.getImageFormat().toString().toLowerCase()); } public static File getAudioFile(AudioGson audioGson, Context context) { diff --git a/app/src/main/res/xml/image_file_path.xml b/app/src/main/res/xml/image_file_path.xml new file mode 100644 index 0000000..f734c0c --- /dev/null +++ b/app/src/main/res/xml/image_file_path.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file From d19c17bf4780311793da8626e40e556667d63a6e Mon Sep 17 00:00:00 2001 From: Tuan Nguyen Date: Tue, 25 Feb 2025 14:36:45 +0700 Subject: [PATCH 2/2] Add null check for image/imageFile when obtaining from DB/getImageFile function --- .../provider/ImageContentProvider.java | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/ai/elimu/content_provider/provider/ImageContentProvider.java b/app/src/main/java/ai/elimu/content_provider/provider/ImageContentProvider.java index ad44d60..173fa28 100644 --- a/app/src/main/java/ai/elimu/content_provider/provider/ImageContentProvider.java +++ b/app/src/main/java/ai/elimu/content_provider/provider/ImageContentProvider.java @@ -167,11 +167,24 @@ public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundEx RoomDb roomDb = RoomDb.getDatabase(getContext()); ImageDao imageDao = roomDb.imageDao(); - Image image = imageDao.load(Long.parseLong(fileId)); + + long imageId; + try { + imageId = Long.parseLong(fileId); + } catch (NumberFormatException e) { + Log.e(TAG, "Failed to parse image ID: " + fileId, e); + throw new FileNotFoundException("Invalid image ID format: " + fileId); + } + + Image image = imageDao.load(imageId); + + if (image == null) { + throw new FileNotFoundException("File not found with id: " + imageId); + } File imageFile = FileHelper.getImageFile(image, getContext()); if (imageFile == null) { - throw new FileNotFoundException("File not found!"); + throw new FileNotFoundException("imageFile not found with id: " + imageId); } if (!imageFile.exists()) { Log.e(TAG, "imageFile doesn't exist: " + imageFile.getAbsolutePath());