Allow accessing PojavLauncher folder with SAF

This commit is contained in:
artdeell 2021-07-12 13:08:41 +03:00
parent ee39334bf3
commit 18a65cefb7
2 changed files with 188 additions and 0 deletions

View File

@ -70,6 +70,18 @@
android:screenOrientation="sensorLandscape"
android:name=".MainActivity"
android:configChanges="keyboardHidden|orientation|screenSize|keyboard|navigation"/>
<provider
android:name=".scoped.GameFolderProvider"
android:authorities="net.kdt.pojavlaunch.scoped.gamefolder"
android:exported="true"
android:grantUriPermissions="true"
android:permission="android.permission.MANAGE_DOCUMENTS"
android:enabled="true">
<intent-filter>
<action android:name="android.content.action.DOCUMENTS_PROVIDER" />
</intent-filter>
</provider>
</application>
</manifest>

View File

@ -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<File> 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);
}
}