Files
SpotiFLAC-Mobile/android/app/src/androidTest/java/com/zarz/spotiflac/LookupTestDocumentsProvider.java
T

256 lines
11 KiB
Java

package com.zarz.spotiflac;
import android.content.Intent;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.os.Binder;
import android.os.Bundle;
import android.os.CancellationSignal;
import android.os.ParcelFileDescriptor;
import android.provider.DocumentsContract;
import android.provider.DocumentsContract.Document;
import android.provider.DocumentsContract.Root;
import android.provider.DocumentsProvider;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.Map;
/** Uses Java only: a standalone test APK provider cannot use the app's Kotlin runtime. */
public final class LookupTestDocumentsProvider extends DocumentsProvider {
private static final class Entry {
final String id;
final String parent;
final String name;
final String mime;
final File file;
Entry(String id, String parent, String name, String mime, File file) {
this.id = id;
this.parent = parent;
this.name = name;
this.mime = mime;
this.file = file;
}
}
private final Map<String, Entry> entries = new LinkedHashMap<>();
private int sequence;
private int childQueries;
private int documentQueries;
private int closedCursors;
private String projectionMode = "normal";
private boolean failFinalRename;
private File fixtureDir() {
return new File(getContext().getCacheDir(), "lookup-provider");
}
@Override
public boolean onCreate() {
return true;
}
@Override
public Bundle call(String method, String arg, Bundle extras) {
switch (method) {
case "lookup.reset":
File[] previous = fixtureDir().listFiles();
if (previous != null) for (File file : previous) file.delete();
fixtureDir().mkdirs();
entries.clear();
sequence = 0;
projectionMode = extras.getString("mode", "normal");
failFinalRename = extras.getBoolean("failFinalRename");
add("root", null, "Root", projectionMode.equals("root-empty-mime") ? "" : Document.MIME_TYPE_DIR);
for (int i = 0; i < extras.getInt("count"); i++) {
add("child:" + i, "root", "Track " + i + ".flac", "audio/flac");
}
add("nested:音楽/opaque", "root", "音楽 🎵", Document.MIME_TYPE_DIR);
add("song:opaque/%", "nested:音楽/opaque", "歌 🎵.flac", "audio/flac");
if (extras.getBoolean("mimeCases")) {
add("mime:null", "root", "Null.flac", null);
add("mime:empty", "root", "Empty.flac", "");
add("mime:generic", "root", "Generic.flac", "application/octet-stream");
}
Entry original = add("original", "root", "Song.flac", "audio/flac");
try (FileOutputStream output = new FileOutputStream(original.file)) {
output.write(new byte[] {'o', 'r', 'i', 'g', 'i', 'n', 'a', 'l'});
} catch (IOException e) {
throw new IllegalStateException(e);
}
// Simulate the narrow tree grant normally issued by the picker.
long identity = Binder.clearCallingIdentity();
try {
getContext().grantUriPermission(
extras.getString("targetPackage"),
DocumentsContract.buildTreeDocumentUri("com.spotiflac.test.documents.lookup", "root"),
Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION |
Intent.FLAG_GRANT_PREFIX_URI_PERMISSION |
(extras.getBoolean("persistable") ? Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION : 0)
);
} finally {
Binder.restoreCallingIdentity(identity);
}
resetCounters();
return new Bundle();
case "lookup.clearCounters":
resetCounters();
return new Bundle();
case "lookup.stats":
Bundle stats = new Bundle();
stats.putInt("children", childQueries);
stats.putInt("documents", documentQueries);
stats.putInt("closed", closedCursors);
return stats;
default:
return super.call(method, arg, extras);
}
}
private void resetCounters() {
childQueries = 0;
documentQueries = 0;
closedCursors = 0;
}
private Entry add(String id, String parent, String name, String mime) {
Entry entry = new Entry(id, parent, name, mime, new File(fixtureDir(), "data-" + sequence++));
entries.put(id, entry);
return entry;
}
private MatrixCursor cursor(String[] columns) {
return new MatrixCursor(columns) {
@Override
public void close() {
if (!isClosed()) closedCursors++;
super.close();
}
};
}
private void addEntry(MatrixCursor cursor, Entry entry) {
String[] columns = cursor.getColumnNames();
Object[] row = new Object[columns.length];
for (int i = 0; i < columns.length; i++) {
switch (columns[i]) {
case Document.COLUMN_DOCUMENT_ID: row[i] = entry.id; break;
case Document.COLUMN_DISPLAY_NAME: row[i] = entry.name; break;
case Document.COLUMN_MIME_TYPE: row[i] = entry.mime; break;
case Document.COLUMN_SIZE: row[i] = entry.file.length(); break;
case Document.COLUMN_LAST_MODIFIED: row[i] = 1234L; break;
case Document.COLUMN_FLAGS:
row[i] = Document.FLAG_SUPPORTS_WRITE | Document.FLAG_SUPPORTS_DELETE |
Document.FLAG_SUPPORTS_RENAME | Document.FLAG_DIR_SUPPORTS_CREATE;
break;
default: break;
}
}
cursor.addRow(row);
}
@Override
public Cursor queryRoots(String[] projection) {
String[] columns = projection != null ? projection :
new String[] {Root.COLUMN_ROOT_ID, Root.COLUMN_DOCUMENT_ID, Root.COLUMN_TITLE};
MatrixCursor cursor = new MatrixCursor(columns);
Object[] row = new Object[columns.length];
for (int i = 0; i < columns.length; i++) {
if (Root.COLUMN_ROOT_ID.equals(columns[i]) || Root.COLUMN_DOCUMENT_ID.equals(columns[i])) row[i] = "root";
else if (Root.COLUMN_TITLE.equals(columns[i])) row[i] = "Lookup fixture";
}
cursor.addRow(row);
return cursor;
}
@Override
public Cursor queryDocument(String documentId, String[] projection) throws FileNotFoundException {
documentQueries++;
Entry entry = entries.get(documentId);
if (entry == null) throw new FileNotFoundException(documentId);
MatrixCursor cursor = cursor(projection != null ? projection : defaultColumns());
if (documentId.equals("root") &&
(projectionMode.equals("root-metadata-empty") || projectionMode.equals("root-unconfirmed"))) {
return cursor;
}
addEntry(cursor, entry);
return cursor;
}
@Override
public Cursor queryChildDocuments(String parentId, String[] projection, String sortOrder) {
childQueries++;
if (projectionMode.equals("all-null")) return null;
boolean projected = projection != null && Arrays.asList(projection).contains(Document.COLUMN_DISPLAY_NAME);
if (projected && projectionMode.equals("throw")) throw new UnsupportedOperationException("projection");
if (projected && projectionMode.equals("null")) return null;
String[] columns = projected && projectionMode.equals("missing") ?
new String[] {Document.COLUMN_DOCUMENT_ID} : projection != null ? projection : defaultColumns();
MatrixCursor cursor = cursor(columns);
if (projectionMode.equals("always-loading") ||
(projectionMode.equals("loading") && childQueries <= 2)) {
Bundle extras = new Bundle();
extras.putBoolean(DocumentsContract.EXTRA_LOADING, true);
cursor.setExtras(extras);
return cursor;
}
if (parentId.equals("root") &&
(projectionMode.equals("root-unconfirmed") || projectionMode.equals("empty"))) {
return cursor;
}
for (Entry entry : entries.values()) if (parentId.equals(entry.parent)) addEntry(cursor, entry);
return cursor;
}
@Override
public boolean isChildDocument(String parentId, String documentId) {
Entry entry = entries.get(documentId);
while (entry != null && entry.parent != null) {
if (parentId.equals(entry.parent)) return true;
entry = entries.get(entry.parent);
}
return false;
}
@Override
public String createDocument(String parentId, String mimeType, String displayName) {
String id = "created:" + sequence++;
add(id, parentId, displayName, mimeType);
return id;
}
@Override
public String renameDocument(String documentId, String displayName) throws FileNotFoundException {
Entry entry = entries.get(documentId);
if (entry == null) throw new FileNotFoundException(documentId);
if (failFinalRename && entry.name.endsWith(".partial") && displayName.equals("Song.flac")) {
throw new FileNotFoundException("Injected publish rename failure");
}
String newId = "renamed:" + sequence++;
entries.remove(documentId);
entries.put(newId, new Entry(newId, entry.parent, displayName, entry.mime, entry.file));
return newId;
}
@Override
public void deleteDocument(String documentId) {
Entry entry = entries.remove(documentId);
if (entry != null) entry.file.delete();
}
@Override
public ParcelFileDescriptor openDocument(String documentId, String mode, CancellationSignal signal) throws FileNotFoundException {
Entry entry = entries.get(documentId);
if (entry == null) throw new FileNotFoundException(documentId);
return ParcelFileDescriptor.open(entry.file, ParcelFileDescriptor.parseMode(mode));
}
private String[] defaultColumns() {
return new String[] {Document.COLUMN_DOCUMENT_ID, Document.COLUMN_DISPLAY_NAME, Document.COLUMN_MIME_TYPE, Document.COLUMN_SIZE};
}
}