add a media ContentProvider

Every time the ContentProvider is accessed, a file chooser opens and the
selected file is then provided as media file.
This commit is contained in:
Julian Winkler 2025-04-23 18:13:15 +02:00
parent cff51b230b
commit 49fa854c93
7 changed files with 238 additions and 0 deletions

View file

@ -113,6 +113,7 @@ libtranslationlayer_so = shared_library('translation_layer_main', [
'src/api-impl-jni/AssetInputStream.c', 'src/api-impl-jni/AssetInputStream.c',
'src/api-impl-jni/audio/android_media_AudioTrack.c', 'src/api-impl-jni/audio/android_media_AudioTrack.c',
'src/api-impl-jni/audio/android_media_SoundPool.c', 'src/api-impl-jni/audio/android_media_SoundPool.c',
'src/api-impl-jni/content/android_content_ATLMediaContentProvider.c',
'src/api-impl-jni/content/android_content_ClipboardManager.c', 'src/api-impl-jni/content/android_content_ClipboardManager.c',
'src/api-impl-jni/content/android_content_ContentResolver.c', 'src/api-impl-jni/content/android_content_ContentResolver.c',
'src/api-impl-jni/content/android_content_Context.c', 'src/api-impl-jni/content/android_content_Context.c',

View file

@ -0,0 +1,25 @@
#include <gtk/gtk.h>
#include <stdbool.h>
#include "../generated_headers/android_content_ATLMediaContentProvider.h"
#include "../defines.h"
#include "../util.h"
extern GtkWindow *window;
static void file_dialog_callback(GObject *dialog, GAsyncResult *res, gpointer user_data) {
GFile *file = gtk_file_dialog_open_finish(GTK_FILE_DIALOG(dialog), res, NULL);
JNIEnv *env = get_jni_env();
jobject this = (jobject) user_data;
(*env)->CallVoidMethod(env, this, _METHOD(_CLASS(this), "setSelectedFile", "(Ljava/lang/String;)V"), _JSTRING(g_file_get_path(file)));
g_object_unref(file);
_UNREF(this);
}
JNIEXPORT void JNICALL Java_android_content_ATLMediaContentProvider_native_1open_1media_1folder(JNIEnv *env, jobject this)
{
GtkFileDialog *dialog = gtk_file_dialog_new();
gtk_file_dialog_set_title(GTK_FILE_DIALOG(dialog), "Open Media Folder");
gtk_file_dialog_set_modal(GTK_FILE_DIALOG(dialog), TRUE);
gtk_file_dialog_open(dialog, window, NULL, file_dialog_callback, _REF(this));
}

View file

@ -0,0 +1,21 @@
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class android_content_ATLMediaContentProvider */
#ifndef _Included_android_content_ATLMediaContentProvider
#define _Included_android_content_ATLMediaContentProvider
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: android_content_ATLMediaContentProvider
* Method: native_open_media_folder
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_android_content_ATLMediaContentProvider_native_1open_1media_1folder
(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif

View file

@ -0,0 +1,145 @@
package android.content;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.file.Files;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
import android.os.ParcelFileDescriptor;
public class ATLMediaContentProvider extends ContentProvider {
boolean waitingForFileChooser = false;
File selectedFile = null;
long timestamp = 0;
// called from native
void setSelectedFile(String selectedFile) {
this.selectedFile = new File(selectedFile);
this.waitingForFileChooser = false;
this.timestamp = System.currentTimeMillis();
synchronized(this) {
notifyAll();
}
}
private void openFileChooser() {
if (!waitingForFileChooser) {
waitingForFileChooser = true;
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
native_open_media_folder();
}
});
}
synchronized(this) {
try {
while (waitingForFileChooser) {
wait();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
// if we haven't selected a file, open the file chooser
if (!"0".equals(uri.getLastPathSegment()) && timestamp + 1000 < System.currentTimeMillis()) {
openFileChooser();
}
MatrixCursor cursor = new MatrixCursor(projection);
Object[] row = new Object[projection.length];
if (uri.getQueryParameter("distinct") != null) {
for (int i = 0; i < projection.length; i++) {
switch (projection[i]) {
case "bucket_display_name":
row[i] = "files";
break;
case "bucket_id":
row[i] = 0;
break;
}
}
} else {
for (int i = 0; i < projection.length; i++) {
switch (projection[i]) {
case "_id":
row[i] = 0;
break;
case "_data":
case "title":
row[i] = selectedFile;
break;
case "mime_type":
row[i] = getType(uri);
break;
case "media_type":
if (getType(uri).startsWith("image/"))
row[i] = 1;
else if (getType(uri).startsWith("audio/"))
row[i] = 2;
else if (getType(uri).startsWith("video/"))
row[i] = 3;
else
row[i] = 0;
break;
case "date_modified":
case "datetaken":
row[i] = selectedFile.lastModified();
break;
case "orientation":
row[i] = 0;
break;
case "_size":
row[i] = selectedFile.length();
break;
}
}
}
cursor.addRow(row);
return cursor;
}
@Override
public String getType(Uri uri) {
try {
return Files.probeContentType(selectedFile.toPath());
} catch (IOException e) {
e.printStackTrace();
return "application/octet-stream";
}
}
@Override
public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
return ParcelFileDescriptor.open(selectedFile, ParcelFileDescriptor.parseMode(mode));
}
@Override
public Uri insert(Uri uri, ContentValues values) {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'insert'");
}
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'update'");
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'delete'");
}
private native void native_open_media_folder();
}

View file

@ -34,6 +34,7 @@ public abstract class ContentProvider {
providers.put(provider_parsed.info.authority, provider); providers.put(provider_parsed.info.authority, provider);
} catch(Exception e) { e.printStackTrace(); } } catch(Exception e) { e.printStackTrace(); }
} }
providers.put("media", new ATLMediaContentProvider());
} }
public boolean onCreate() {return false;} public boolean onCreate() {return false;}

View file

@ -1,6 +1,13 @@
package android.provider; package android.provider;
import java.io.FileNotFoundException;
import android.content.ContentResolver;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri; import android.net.Uri;
import android.os.ParcelFileDescriptor;
public class MediaStore { public class MediaStore {
@ -9,6 +16,43 @@ public class MediaStore {
public static class Media { public static class Media {
public static final Uri EXTERNAL_CONTENT_URI = Uri.parse("content://media/external/images/media"); public static final Uri EXTERNAL_CONTENT_URI = Uri.parse("content://media/external/images/media");
public static final Uri INTERNAL_CONTENT_URI = Uri.parse("content://media/internal/images/media");
}
public static class Thumbnails {
public static Cursor queryMiniThumbnail(ContentResolver contentResolver, long id, int kind, String[] projection) {
return null;
}
public static Bitmap getThumbnail(ContentResolver contentResolver, long imageId, long groupId, int kind, BitmapFactory.Options options) throws FileNotFoundException {
ParcelFileDescriptor fd = contentResolver.openFileDescriptor(Media.EXTERNAL_CONTENT_URI.buildUpon().appendPath(String.valueOf(imageId)).build(), "r");
return BitmapFactory.decodeFileDescriptor(fd.getFileDescriptor(), null, options);
}
}
}
public static class Video {
public static class Media {
public static final Uri EXTERNAL_CONTENT_URI = Uri.parse("content://media/external/video/media");
public static final Uri INTERNAL_CONTENT_URI = Uri.parse("content://media/internal/video/media");
}
}
public static class Audio {
public static class Media {
public static final Uri EXTERNAL_CONTENT_URI = Uri.parse("content://media/external/audio/media");
}
}
public static class Files {
public static Uri getContentUri(String type) {
return Uri.parse("content://media/files/" + type);
} }
} }
} }

View file

@ -76,6 +76,7 @@ srcs = [
'android/bluetooth/le/ScanCallback.java', 'android/bluetooth/le/ScanCallback.java',
'android/content/ActivityNotFoundException.java', 'android/content/ActivityNotFoundException.java',
'android/content/AsyncQueryHandler.java', 'android/content/AsyncQueryHandler.java',
'android/content/ATLMediaContentProvider.java',
'android/content/BroadcastReceiver.java', 'android/content/BroadcastReceiver.java',
'android/content/ClipboardManager.java', 'android/content/ClipboardManager.java',
'android/content/ClipData.java', 'android/content/ClipData.java',