diff --git a/app_pojavlauncher/src/main/AndroidManifest.xml b/app_pojavlauncher/src/main/AndroidManifest.xml
index 157659467..5e49a14a4 100644
--- a/app_pojavlauncher/src/main/AndroidManifest.xml
+++ b/app_pojavlauncher/src/main/AndroidManifest.xml
@@ -70,6 +70,18 @@
android:screenOrientation="sensorLandscape"
android:name=".MainActivity"
android:configChanges="keyboardHidden|orientation|screenSize|keyboard|navigation"/>
+
+
+
+
+
+
diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/scoped/GameFolderProvider.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/scoped/GameFolderProvider.java
new file mode 100644
index 000000000..1f15b1330
--- /dev/null
+++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/scoped/GameFolderProvider.java
@@ -0,0 +1,176 @@
+package net.kdt.pojavlaunch.scoped;
+
+import android.content.res.AssetFileDescriptor;
+import android.database.Cursor;
+import android.database.MatrixCursor;
+import android.graphics.Point;
+import android.os.CancellationSignal;
+import android.os.ParcelFileDescriptor;
+import android.provider.DocumentsContract.Document;
+import android.provider.DocumentsContract.Root;
+import android.provider.DocumentsProvider;
+import android.webkit.MimeTypeMap;
+
+import androidx.annotation.Nullable;
+
+import net.kdt.pojavlaunch.R;
+import net.kdt.pojavlaunch.Tools;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.LinkedList;
+
+public class GameFolderProvider extends DocumentsProvider {
+ static File baseDir = new File(Tools.DIR_GAME_HOME);
+ private static final String[] DEFAULT_ROOT_PROJECTION = new String[]{
+ Root.COLUMN_ROOT_ID,
+ Root.COLUMN_MIME_TYPES,
+ Root.COLUMN_FLAGS,
+ Root.COLUMN_ICON,
+ Root.COLUMN_TITLE,
+ Root.COLUMN_SUMMARY,
+ Root.COLUMN_DOCUMENT_ID,
+ Root.COLUMN_AVAILABLE_BYTES
+ };
+ private static final String[] DEFAULT_DOCUMENT_PROJECTION = new String[]{
+ Document.COLUMN_DOCUMENT_ID,
+ Document.COLUMN_MIME_TYPE,
+ Document.COLUMN_DISPLAY_NAME,
+ Document.COLUMN_LAST_MODIFIED,
+ Document.COLUMN_FLAGS,
+ Document.COLUMN_SIZE
+ };
+
+ @Override
+ public Cursor queryRoots(String[] projection) throws FileNotFoundException {
+ final MatrixCursor result = new MatrixCursor(projection != null ? projection : DEFAULT_ROOT_PROJECTION);
+ final MatrixCursor.RowBuilder row = result.newRow();
+ row.add(Root.COLUMN_ROOT_ID, baseDir.getAbsolutePath());
+ row.add(Root.COLUMN_DOCUMENT_ID, baseDir.getAbsolutePath());
+ row.add(Root.COLUMN_SUMMARY, null);
+ row.add(Root.COLUMN_FLAGS, Root.FLAG_SUPPORTS_CREATE | Root.FLAG_SUPPORTS_RECENTS | Root.FLAG_SUPPORTS_SEARCH);
+ row.add(Root.COLUMN_TITLE, getContext().getString(R.string.app_name));
+ row.add(Root.COLUMN_MIME_TYPES, "*/*");
+ row.add(Root.COLUMN_AVAILABLE_BYTES, baseDir.getFreeSpace());
+ row.add(Root.COLUMN_ICON, R.drawable.ic_launcher);
+ return result;
+ }
+
+ @Override
+ public Cursor queryDocument(String documentId, String[] projection) throws FileNotFoundException {
+ final MatrixCursor result = new MatrixCursor(projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION);
+ includeFile(result,documentId,null);
+ return result;
+ }
+
+ @Override
+ public Cursor queryChildDocuments(String parentDocumentId, String[] projection, String sortOrder) throws FileNotFoundException {
+ final MatrixCursor result = new MatrixCursor(projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION);
+ for(File f : new File(parentDocumentId).listFiles()) {
+ includeFile(result,null,f);
+ }
+ return result;
+ }
+
+ @Override
+ public ParcelFileDescriptor openDocument(String documentId, String mode, @Nullable CancellationSignal signal) throws FileNotFoundException {
+ return ParcelFileDescriptor.open(new File(documentId),ParcelFileDescriptor.parseMode(mode));
+ }
+
+ @Override
+ public AssetFileDescriptor openDocumentThumbnail(String documentId, Point sizeHint, CancellationSignal signal) throws FileNotFoundException {
+ File f = new File(documentId);
+ return new AssetFileDescriptor(ParcelFileDescriptor.open(f, ParcelFileDescriptor.MODE_READ_ONLY), 0, f.length());
+ }
+
+ @Override
+ public boolean onCreate() {
+ return true;
+ }
+
+ @Override
+ public void deleteDocument(String documentId) throws FileNotFoundException {
+ if (!new File(documentId).delete()) throw new FileNotFoundException("Can't remove "+documentId);
+ }
+
+ @Override
+ public String getDocumentType(String documentId) throws FileNotFoundException {
+ return getMimeType(new File(documentId));
+ }
+
+ @Override
+ public Cursor querySearchDocuments(String rootId, String query, String[] projection) throws FileNotFoundException {
+ final MatrixCursor result = new MatrixCursor(projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION);
+ final File parent = new File(rootId);
+ final LinkedList pending = new LinkedList<>();
+ pending.add(parent);
+
+ final int MAX_SEARCH_RESULTS = 50;
+ while (!pending.isEmpty() && result.getCount() < MAX_SEARCH_RESULTS) {
+ final File file = pending.removeFirst();
+ boolean isInsideGameDir;
+ try {
+ isInsideGameDir = file.getCanonicalPath().startsWith(baseDir.getCanonicalPath());
+ } catch (IOException e) {
+ isInsideGameDir = true;
+ }
+ if (isInsideGameDir) {
+ if (file.isDirectory()) {
+ Collections.addAll(pending, file.listFiles());
+ } else {
+ if (file.getName().toLowerCase().contains(query)) {
+ includeFile(result, null, file);
+ }
+ }
+ }
+ }
+
+ return result;
+ }
+ private static String getMimeType(File file) {
+ if (file.isDirectory()) {
+ return Document.MIME_TYPE_DIR;
+ } else {
+ final String name = file.getName();
+ final int lastDot = name.lastIndexOf('.');
+ if (lastDot >= 0) {
+ final String extension = name.substring(lastDot + 1);
+ final String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
+ if (mime != null) return mime;
+ }
+ return "application/octet-stream";
+ }
+ }
+ private void includeFile(MatrixCursor result, String docId, File file)
+ throws FileNotFoundException {
+ if (docId == null) {
+ docId = file.getAbsolutePath();
+ } else {
+ file = new File(docId);
+ }
+
+ int flags = 0;
+ if (file.isDirectory()) {
+ if (file.isDirectory() && file.canWrite()) flags |= Document.FLAG_DIR_SUPPORTS_CREATE;
+ } else if (file.canWrite()) {
+ flags |= Document.FLAG_SUPPORTS_WRITE | Document.FLAG_SUPPORTS_DELETE;
+ }
+
+ final String displayName = file.getName();
+ final String mimeType = getMimeType(file);
+ if (mimeType.startsWith("image/")) flags |= Document.FLAG_SUPPORTS_THUMBNAIL;
+
+ final MatrixCursor.RowBuilder row = result.newRow();
+ row.add(Document.COLUMN_DOCUMENT_ID, docId);
+ row.add(Document.COLUMN_DISPLAY_NAME, displayName);
+ row.add(Document.COLUMN_SIZE, file.length());
+ row.add(Document.COLUMN_MIME_TYPE, mimeType);
+ row.add(Document.COLUMN_LAST_MODIFIED, file.lastModified());
+ row.add(Document.COLUMN_FLAGS, flags);
+ row.add(Document.COLUMN_ICON, R.drawable.ic_launcher);
+ }
+
+
+}