package paris.tourolle.darkroom; import android.app.Activity; import android.content.ActivityNotFoundException; import android.content.Context; import android.content.Intent; import android.net.Uri; import android.os.Bundle; import android.util.Log; /** * The system's folder picker, for an album's folder on this device (FR-EXP-10). * *
{@code ACTION_OPEN_DOCUMENT_TREE} answers through * {@code onActivityResult}, and the main activity is {@code NativeActivity}, * whose result callback is not ours to override. So this one exists only to * ask: it starts the picker, takes the answer, and finishes — no layout, a * translucent theme, nothing on screen but the system's own chooser, which has * its own "New folder". * *
The answer is left in a static for Rust to poll ({@link #poll}), rather * than called back into native code: a callback would need a registered * native method and a thread to deliver on, and a poll from the Slint timer * that is already running is one static call. * *
A tree URI is usable only while its permission is held, and a plain * result grants it until the process dies. {@code takePersistableUriPermission} * keeps it across restarts — an album's folder is chosen once and exported to * for months. */ public final class FolderPicker extends Activity { private static final String TAG = "DarkRoom"; private static final int REQUEST = 0x5AF; /** The last answer: a tree URI, "" for a cancel, null while none has come. */ private static volatile String answer = null; /** * Start asking. Clears any answer left from before. * *
Takes a {@code Context} rather than an {@code Activity}, because what * native code holds (ndk_context's handle) is the application context, * and starting an activity from one that is not an activity needs * {@code FLAG_ACTIVITY_NEW_TASK} — without it the call throws. The picker * shares the app's task affinity, so it still opens over the app and Back * still returns to it. */ public static void start(Context from) { answer = null; Intent intent = new Intent(from, FolderPicker.class); if (!(from instanceof Activity)) { intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); } from.startActivity(intent); } /** * The answer, once: a tree URI, "" if the user backed out, or null while * the picker is still open. Reading it clears it, so a second poll after a * cancel does not see the cancel again. */ public static String poll() { String a = answer; if (a != null) { answer = null; } return a; } @Override protected void onCreate(Bundle state) { super.onCreate(state); // Recreated after a rotation with the picker already up: asking again // would stack a second chooser over the first. if (state != null) { return; } Intent pick = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE); pick.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION); try { startActivityForResult(pick, REQUEST); } catch (ActivityNotFoundException e) { Log.w(TAG, "no folder picker on this device", e); answer = ""; finish(); } } @Override protected void onActivityResult(int request, int result, Intent data) { if (request != REQUEST) { return; } Uri tree = (result == RESULT_OK && data != null) ? data.getData() : null; if (tree == null) { answer = ""; } else { try { getContentResolver().takePersistableUriPermission(tree, Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); } catch (SecurityException e) { // Still usable this session; said in the log so a folder that // stops working after a restart has an explanation. Log.w(TAG, "the folder grant could not be kept: " + tree, e); } answer = tree.toString(); } finish(); } }