Loading mobile/android/geckoview/api.txt +8 −0 Original line number Diff line number Diff line Loading @@ -1464,6 +1464,7 @@ package org.mozilla.geckoview { method @Nullable @UiThread default public GeckoResult<GeckoSession.PromptDelegate.PromptResponse> onCreditCardSelect(@NonNull GeckoSession, @NonNull GeckoSession.PromptDelegate.AutocompleteRequest<Autocomplete.CreditCardSelectOption>); method @Nullable @UiThread default public GeckoResult<GeckoSession.PromptDelegate.PromptResponse> onDateTimePrompt(@NonNull GeckoSession, @NonNull GeckoSession.PromptDelegate.DateTimePrompt); method @Nullable @UiThread default public GeckoResult<GeckoSession.PromptDelegate.PromptResponse> onFilePrompt(@NonNull GeckoSession, @NonNull GeckoSession.PromptDelegate.FilePrompt); method @Nullable @UiThread default public GeckoResult<GeckoSession.PromptDelegate.PromptResponse> onFolderUploadPrompt(@NonNull GeckoSession, @NonNull GeckoSession.PromptDelegate.FolderUploadPrompt); method @Nullable @UiThread default public GeckoResult<GeckoSession.PromptDelegate.PromptResponse> onLoginSave(@NonNull GeckoSession, @NonNull GeckoSession.PromptDelegate.AutocompleteRequest<Autocomplete.LoginSaveOption>); method @Nullable @UiThread default public GeckoResult<GeckoSession.PromptDelegate.PromptResponse> onLoginSelect(@NonNull GeckoSession, @NonNull GeckoSession.PromptDelegate.AutocompleteRequest<Autocomplete.LoginSelectOption>); method @Nullable @UiThread default public GeckoResult<GeckoSession.PromptDelegate.PromptResponse> onPopupPrompt(@NonNull GeckoSession, @NonNull GeckoSession.PromptDelegate.PopupPrompt); Loading Loading @@ -1643,10 +1644,17 @@ package org.mozilla.geckoview { public static class GeckoSession.PromptDelegate.FilePrompt.Type { ctor protected Type(); field public static final int FOLDER = 3; field public static final int MULTIPLE = 2; field public static final int SINGLE = 1; } public static class GeckoSession.PromptDelegate.FolderUploadPrompt extends GeckoSession.PromptDelegate.BasePrompt { ctor protected FolderUploadPrompt(@NonNull String, @Nullable String, @NonNull GeckoSession.PromptDelegate.BasePrompt.Observer); method @NonNull @UiThread public GeckoSession.PromptDelegate.PromptResponse confirm(@Nullable AllowOrDeny); field @Nullable public final String directoryName; } public static final class GeckoSession.PromptDelegate.IdentityCredential { ctor public IdentityCredential(); } Loading mobile/android/geckoview/src/androidTest/assets/www/prompts.html +2 −0 Original line number Diff line number Diff line Loading @@ -30,6 +30,8 @@ accept="image/*,.pdf" /> <input type="file" id="direxample" webkitdirectory /> <datalist id="colorlist"> <option>#000000</option> <option>#808080</option> Loading mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/PromptDelegateTest.kt +41 −0 Original line number Diff line number Diff line Loading @@ -4,9 +4,11 @@ package org.mozilla.geckoview.test import android.net.Uri import android.view.KeyEvent import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.MediumTest import androidx.test.platform.app.InstrumentationRegistry import org.hamcrest.Matchers.* // ktlint-disable no-wildcard-imports import org.junit.Assert import org.junit.Test Loading Loading @@ -1102,6 +1104,7 @@ class PromptDelegateTest : BaseSessionTest( assertThat("First accept attribute should match", "image/*", equalTo(prompt.mimeTypes?.get(0))) assertThat("Second accept attribute should match", ".pdf", equalTo(prompt.mimeTypes?.get(1))) assertThat("Capture attribute should match", PromptDelegate.FilePrompt.Capture.USER, equalTo(prompt.capture)) assertThat("Type should match", prompt.type, equalTo(PromptDelegate.FilePrompt.Type.SINGLE)) return GeckoResult.fromValue(prompt.dismiss()) } }) Loading Loading @@ -1131,6 +1134,44 @@ class PromptDelegateTest : BaseSessionTest( }) } @WithDisplay(width = 100, height = 100) @Test fun directoryTest() { sessionRule.setPrefsUntilTestEnd( mapOf( "dom.disable_open_during_load" to false, "dom.webkitBlink.dirPicker.enabled" to true, ), ) mainSession.loadTestPath(PROMPT_HTML_PATH) mainSession.waitForPageStop() sessionRule.delegateUntilTestEnd(object : PromptDelegate { @AssertCalled(count = 1) override fun onFilePrompt(session: GeckoSession, prompt: PromptDelegate.FilePrompt): GeckoResult<PromptDelegate.PromptResponse> { assertThat("Type should match", prompt.type, equalTo(PromptDelegate.FilePrompt.Type.FOLDER)) return GeckoResult.fromValue( prompt.confirm( InstrumentationRegistry.getInstrumentation().targetContext, Uri.parse("file:///storage/emulated/0/Download"), ), ) } }) mainSession.evaluateJS("document.addEventListener('click', () => document.getElementById('direxample').click(), { once: true });") mainSession.synthesizeTap(1, 1) sessionRule.waitUntilCalled(object : PromptDelegate { @AssertCalled(count = 1) override fun onFolderUploadPrompt(session: GeckoSession, prompt: PromptDelegate.FolderUploadPrompt): GeckoResult<PromptDelegate.PromptResponse>? { assertThat("directoryName should match", prompt.directoryName, equalTo("Download")) return GeckoResult.fromValue(prompt.confirm(AllowOrDeny.ALLOW)) } }) } @Test fun shareTextSucceeds() { sessionRule.setPrefsUntilTestEnd(mapOf("dom.webshare.requireinteraction" to false)) mainSession.loadTestPath(HELLO_HTML_PATH) Loading mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/IntentUtils.java +99 −0 Original line number Diff line number Diff line Loading @@ -6,13 +6,26 @@ package org.mozilla.gecko.util; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.database.Cursor; import android.net.Uri; import android.os.Build; import android.os.Environment; import android.provider.DocumentsContract; import android.util.Log; import java.net.URISyntaxException; import java.util.Locale; /** Utilities for Intents. */ public class IntentUtils { private static final String LOGTAG = "IntentUtils"; private static final boolean DEBUG = false; private static final String EXTERNAL_STORAGE_PROVIDER_AUTHORITY = "com.android.externalstorage.documents"; private IntentUtils() {} /** Loading Loading @@ -117,4 +130,90 @@ public class IntentUtils { private static void nullIntentSelector(final Intent intent) { intent.setSelector(null); } /** * Return a local path from the Uri that is content schema. * * @param context The context. * @param uri The URI. * @return A local path if resolved. If this cannot resolve URI, return null. */ public static String resolveContentUri(final Context context, final Uri uri) { final ContentResolver cr = context.getContentResolver(); try (final Cursor cur = cr.query( uri, new String[] {"_data"}, /* selection */ null, /* args */ null, /* sort */ null)) { final int idx = cur.getColumnIndex("_data"); if (idx < 0 || !cur.moveToFirst()) { return null; } do { try { final String path = cur.getString(idx); if (path != null && !path.isEmpty()) { return path; } } catch (final Exception e) { } } while (cur.moveToNext()); } catch (final UnsupportedOperationException e) { Log.e(LOGTAG, "Failed to query child documents", e); } if (DEBUG) { Log.e(LOGTAG, "Failed to resolve uri. uri=" + uri.toString()); } return null; } /** * Return a local path from tree Uri. * * @param context The context. * @param uri The uri that @{link DoumentContract#isTreeUri} returns true. * @return A local path if resolved. If this cannot resolve URI, return null. */ public static String resolveTreeUri(final Context context, final Uri uri) { final Uri docDirUri = DocumentsContract.buildDocumentUriUsingTree(uri, DocumentsContract.getTreeDocumentId(uri)); return resolveDocumentUri(context, docDirUri); } /** * Return a local path from document Uri. * * @param context The context. * @param uri The uri that @{link DoumentContract#isDocumentUri} returns true. * @return A local path if resolved. If this cannot resolve URI, return null. */ public static String resolveDocumentUri(final Context context, final Uri uri) { if (EXTERNAL_STORAGE_PROVIDER_AUTHORITY.equals(uri.getAuthority())) { final String docId = DocumentsContract.getDocumentId(uri); final String[] split = docId.split(":"); if (split[0].equals("primary")) { // This is the internal storage. final StringBuilder sb = new StringBuilder(Environment.getExternalStorageDirectory().toString()); if (split.length > 1) { sb.append("/").append(split[1]); } return sb.toString(); } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { // This might be sd card. /storage/xxxx-xxxx/... final StringBuilder sb = new StringBuilder(Environment.getStorageDirectory().toString()); sb.append("/").append(split[0]); if (split.length > 1) { sb.append("/").append(split[1]); } return sb.toString(); } } if (DEBUG) { Log.e(LOGTAG, "Failed to resolve uri. uri=" + uri.toString()); } return null; } } mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSession.java +63 −30 Original line number Diff line number Diff line Loading @@ -11,9 +11,7 @@ import static org.mozilla.geckoview.GeckoSession.GeckoPrintException.ERROR_NO_PR import android.Manifest; import android.annotation.SuppressLint; import android.annotation.TargetApi; import android.content.ContentResolver; import android.content.Context; import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.Matrix; import android.graphics.Point; Loading @@ -27,6 +25,7 @@ import android.os.IInterface; import android.os.Parcel; import android.os.Parcelable; import android.os.SystemClock; import android.provider.DocumentsContract; import android.text.TextUtils; import android.util.Base64; import android.util.Log; Loading Loading @@ -5502,6 +5501,42 @@ public class GeckoSession { } } /** * FolderUploadPrompt represents a prompt shown whenever the browser needs to upload folder data */ class FolderUploadPrompt extends BasePrompt { /** The directory name to confirm folder tries to uploading. */ public final @Nullable String directoryName; /** * A constructor for FolderUploadPrompt * * @param id The identification for this prompt. * @param directoryName The directory that is confirmed. * @param observer A callback to notify when the prompt has been completed. */ protected FolderUploadPrompt( @NonNull final String id, @Nullable final String directoryName, @NonNull final Observer observer) { super(id, null, observer); this.directoryName = directoryName; } /** * Confirms the prompt. * * @param allowOrDeny whether the browser should allow resubmitting data. * @return A {@link PromptResponse} which can be used to complete the {@link GeckoResult} * associated with this prompt. */ @UiThread public @NonNull PromptResponse confirm(final @Nullable AllowOrDeny allowOrDeny) { ensureResult().putBoolean("allow", allowOrDeny != AllowOrDeny.DENY); return super.confirm(); } } /** * RepostConfirmPrompt represents a prompt shown whenever the browser needs to resubmit POST * data (e.g. due to page refresh). Loading Loading @@ -6372,7 +6407,7 @@ public class GeckoSession { */ class FilePrompt extends BasePrompt { @Retention(RetentionPolicy.SOURCE) @IntDef({Type.SINGLE, Type.MULTIPLE}) @IntDef({Type.SINGLE, Type.MULTIPLE, Type.FOLDER}) public @interface FileType {} /** Types of file prompts. */ Loading @@ -6383,6 +6418,9 @@ public class GeckoSession { /** Prompt for multiple files. */ public static final int MULTIPLE = 2; /** Prompt for directory. */ public static final int FOLDER = 3; protected Type() {} } Loading Loading @@ -6458,7 +6496,7 @@ public class GeckoSession { @UiThread public @NonNull PromptResponse confirm( @NonNull final Context context, @NonNull final Uri[] uris) { if (Type.SINGLE == type && (uris == null || uris.length != 1)) { if ((Type.SINGLE == type || Type.FOLDER == type) && (uris == null || uris.length != 1)) { throw new IllegalArgumentException(); } Loading @@ -6483,33 +6521,14 @@ public class GeckoSession { if ("file".equals(uri.getScheme())) { return uri.getPath(); } final ContentResolver cr = context.getContentResolver(); final Cursor cur = cr.query( uri, new String[] {"_data"}, /* selection */ null, /* args */ null, /* sort */ null); if (cur == null) { return null; } try { final int idx = cur.getColumnIndex("_data"); if (idx < 0 || !cur.moveToFirst()) { return null; if ("content".equals(uri.getScheme())) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && DocumentsContract.isTreeUri(uri)) { return IntentUtils.resolveTreeUri(context, uri); } do { try { final String path = cur.getString(idx); if (path != null && !path.isEmpty()) { return path; if (DocumentsContract.isDocumentUri(context, uri)) { return IntentUtils.resolveDocumentUri(context, uri); } } catch (final Exception e) { } } while (cur.moveToNext()); } finally { cur.close(); return IntentUtils.resolveContentUri(context, uri); } return null; } Loading Loading @@ -6709,6 +6728,20 @@ public class GeckoSession { return null; } /** * Display a folder upload prompt. * * @param session GeckoSession that triggered the prompt. * @param prompt The {@link FolderUploadPrompt} that describes the prompt. * @return A {@link GeckoResult} resolving to a {@link PromptResponse} which includes all * necessary information to resolve the prompt. */ @UiThread default @Nullable GeckoResult<PromptResponse> onFolderUploadPrompt( @NonNull final GeckoSession session, @NonNull final FolderUploadPrompt prompt) { return null; } /** * Display a text prompt. * Loading Loading
mobile/android/geckoview/api.txt +8 −0 Original line number Diff line number Diff line Loading @@ -1464,6 +1464,7 @@ package org.mozilla.geckoview { method @Nullable @UiThread default public GeckoResult<GeckoSession.PromptDelegate.PromptResponse> onCreditCardSelect(@NonNull GeckoSession, @NonNull GeckoSession.PromptDelegate.AutocompleteRequest<Autocomplete.CreditCardSelectOption>); method @Nullable @UiThread default public GeckoResult<GeckoSession.PromptDelegate.PromptResponse> onDateTimePrompt(@NonNull GeckoSession, @NonNull GeckoSession.PromptDelegate.DateTimePrompt); method @Nullable @UiThread default public GeckoResult<GeckoSession.PromptDelegate.PromptResponse> onFilePrompt(@NonNull GeckoSession, @NonNull GeckoSession.PromptDelegate.FilePrompt); method @Nullable @UiThread default public GeckoResult<GeckoSession.PromptDelegate.PromptResponse> onFolderUploadPrompt(@NonNull GeckoSession, @NonNull GeckoSession.PromptDelegate.FolderUploadPrompt); method @Nullable @UiThread default public GeckoResult<GeckoSession.PromptDelegate.PromptResponse> onLoginSave(@NonNull GeckoSession, @NonNull GeckoSession.PromptDelegate.AutocompleteRequest<Autocomplete.LoginSaveOption>); method @Nullable @UiThread default public GeckoResult<GeckoSession.PromptDelegate.PromptResponse> onLoginSelect(@NonNull GeckoSession, @NonNull GeckoSession.PromptDelegate.AutocompleteRequest<Autocomplete.LoginSelectOption>); method @Nullable @UiThread default public GeckoResult<GeckoSession.PromptDelegate.PromptResponse> onPopupPrompt(@NonNull GeckoSession, @NonNull GeckoSession.PromptDelegate.PopupPrompt); Loading Loading @@ -1643,10 +1644,17 @@ package org.mozilla.geckoview { public static class GeckoSession.PromptDelegate.FilePrompt.Type { ctor protected Type(); field public static final int FOLDER = 3; field public static final int MULTIPLE = 2; field public static final int SINGLE = 1; } public static class GeckoSession.PromptDelegate.FolderUploadPrompt extends GeckoSession.PromptDelegate.BasePrompt { ctor protected FolderUploadPrompt(@NonNull String, @Nullable String, @NonNull GeckoSession.PromptDelegate.BasePrompt.Observer); method @NonNull @UiThread public GeckoSession.PromptDelegate.PromptResponse confirm(@Nullable AllowOrDeny); field @Nullable public final String directoryName; } public static final class GeckoSession.PromptDelegate.IdentityCredential { ctor public IdentityCredential(); } Loading
mobile/android/geckoview/src/androidTest/assets/www/prompts.html +2 −0 Original line number Diff line number Diff line Loading @@ -30,6 +30,8 @@ accept="image/*,.pdf" /> <input type="file" id="direxample" webkitdirectory /> <datalist id="colorlist"> <option>#000000</option> <option>#808080</option> Loading
mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/PromptDelegateTest.kt +41 −0 Original line number Diff line number Diff line Loading @@ -4,9 +4,11 @@ package org.mozilla.geckoview.test import android.net.Uri import android.view.KeyEvent import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.MediumTest import androidx.test.platform.app.InstrumentationRegistry import org.hamcrest.Matchers.* // ktlint-disable no-wildcard-imports import org.junit.Assert import org.junit.Test Loading Loading @@ -1102,6 +1104,7 @@ class PromptDelegateTest : BaseSessionTest( assertThat("First accept attribute should match", "image/*", equalTo(prompt.mimeTypes?.get(0))) assertThat("Second accept attribute should match", ".pdf", equalTo(prompt.mimeTypes?.get(1))) assertThat("Capture attribute should match", PromptDelegate.FilePrompt.Capture.USER, equalTo(prompt.capture)) assertThat("Type should match", prompt.type, equalTo(PromptDelegate.FilePrompt.Type.SINGLE)) return GeckoResult.fromValue(prompt.dismiss()) } }) Loading Loading @@ -1131,6 +1134,44 @@ class PromptDelegateTest : BaseSessionTest( }) } @WithDisplay(width = 100, height = 100) @Test fun directoryTest() { sessionRule.setPrefsUntilTestEnd( mapOf( "dom.disable_open_during_load" to false, "dom.webkitBlink.dirPicker.enabled" to true, ), ) mainSession.loadTestPath(PROMPT_HTML_PATH) mainSession.waitForPageStop() sessionRule.delegateUntilTestEnd(object : PromptDelegate { @AssertCalled(count = 1) override fun onFilePrompt(session: GeckoSession, prompt: PromptDelegate.FilePrompt): GeckoResult<PromptDelegate.PromptResponse> { assertThat("Type should match", prompt.type, equalTo(PromptDelegate.FilePrompt.Type.FOLDER)) return GeckoResult.fromValue( prompt.confirm( InstrumentationRegistry.getInstrumentation().targetContext, Uri.parse("file:///storage/emulated/0/Download"), ), ) } }) mainSession.evaluateJS("document.addEventListener('click', () => document.getElementById('direxample').click(), { once: true });") mainSession.synthesizeTap(1, 1) sessionRule.waitUntilCalled(object : PromptDelegate { @AssertCalled(count = 1) override fun onFolderUploadPrompt(session: GeckoSession, prompt: PromptDelegate.FolderUploadPrompt): GeckoResult<PromptDelegate.PromptResponse>? { assertThat("directoryName should match", prompt.directoryName, equalTo("Download")) return GeckoResult.fromValue(prompt.confirm(AllowOrDeny.ALLOW)) } }) } @Test fun shareTextSucceeds() { sessionRule.setPrefsUntilTestEnd(mapOf("dom.webshare.requireinteraction" to false)) mainSession.loadTestPath(HELLO_HTML_PATH) Loading
mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/IntentUtils.java +99 −0 Original line number Diff line number Diff line Loading @@ -6,13 +6,26 @@ package org.mozilla.gecko.util; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.database.Cursor; import android.net.Uri; import android.os.Build; import android.os.Environment; import android.provider.DocumentsContract; import android.util.Log; import java.net.URISyntaxException; import java.util.Locale; /** Utilities for Intents. */ public class IntentUtils { private static final String LOGTAG = "IntentUtils"; private static final boolean DEBUG = false; private static final String EXTERNAL_STORAGE_PROVIDER_AUTHORITY = "com.android.externalstorage.documents"; private IntentUtils() {} /** Loading Loading @@ -117,4 +130,90 @@ public class IntentUtils { private static void nullIntentSelector(final Intent intent) { intent.setSelector(null); } /** * Return a local path from the Uri that is content schema. * * @param context The context. * @param uri The URI. * @return A local path if resolved. If this cannot resolve URI, return null. */ public static String resolveContentUri(final Context context, final Uri uri) { final ContentResolver cr = context.getContentResolver(); try (final Cursor cur = cr.query( uri, new String[] {"_data"}, /* selection */ null, /* args */ null, /* sort */ null)) { final int idx = cur.getColumnIndex("_data"); if (idx < 0 || !cur.moveToFirst()) { return null; } do { try { final String path = cur.getString(idx); if (path != null && !path.isEmpty()) { return path; } } catch (final Exception e) { } } while (cur.moveToNext()); } catch (final UnsupportedOperationException e) { Log.e(LOGTAG, "Failed to query child documents", e); } if (DEBUG) { Log.e(LOGTAG, "Failed to resolve uri. uri=" + uri.toString()); } return null; } /** * Return a local path from tree Uri. * * @param context The context. * @param uri The uri that @{link DoumentContract#isTreeUri} returns true. * @return A local path if resolved. If this cannot resolve URI, return null. */ public static String resolveTreeUri(final Context context, final Uri uri) { final Uri docDirUri = DocumentsContract.buildDocumentUriUsingTree(uri, DocumentsContract.getTreeDocumentId(uri)); return resolveDocumentUri(context, docDirUri); } /** * Return a local path from document Uri. * * @param context The context. * @param uri The uri that @{link DoumentContract#isDocumentUri} returns true. * @return A local path if resolved. If this cannot resolve URI, return null. */ public static String resolveDocumentUri(final Context context, final Uri uri) { if (EXTERNAL_STORAGE_PROVIDER_AUTHORITY.equals(uri.getAuthority())) { final String docId = DocumentsContract.getDocumentId(uri); final String[] split = docId.split(":"); if (split[0].equals("primary")) { // This is the internal storage. final StringBuilder sb = new StringBuilder(Environment.getExternalStorageDirectory().toString()); if (split.length > 1) { sb.append("/").append(split[1]); } return sb.toString(); } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { // This might be sd card. /storage/xxxx-xxxx/... final StringBuilder sb = new StringBuilder(Environment.getStorageDirectory().toString()); sb.append("/").append(split[0]); if (split.length > 1) { sb.append("/").append(split[1]); } return sb.toString(); } } if (DEBUG) { Log.e(LOGTAG, "Failed to resolve uri. uri=" + uri.toString()); } return null; } }
mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSession.java +63 −30 Original line number Diff line number Diff line Loading @@ -11,9 +11,7 @@ import static org.mozilla.geckoview.GeckoSession.GeckoPrintException.ERROR_NO_PR import android.Manifest; import android.annotation.SuppressLint; import android.annotation.TargetApi; import android.content.ContentResolver; import android.content.Context; import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.Matrix; import android.graphics.Point; Loading @@ -27,6 +25,7 @@ import android.os.IInterface; import android.os.Parcel; import android.os.Parcelable; import android.os.SystemClock; import android.provider.DocumentsContract; import android.text.TextUtils; import android.util.Base64; import android.util.Log; Loading Loading @@ -5502,6 +5501,42 @@ public class GeckoSession { } } /** * FolderUploadPrompt represents a prompt shown whenever the browser needs to upload folder data */ class FolderUploadPrompt extends BasePrompt { /** The directory name to confirm folder tries to uploading. */ public final @Nullable String directoryName; /** * A constructor for FolderUploadPrompt * * @param id The identification for this prompt. * @param directoryName The directory that is confirmed. * @param observer A callback to notify when the prompt has been completed. */ protected FolderUploadPrompt( @NonNull final String id, @Nullable final String directoryName, @NonNull final Observer observer) { super(id, null, observer); this.directoryName = directoryName; } /** * Confirms the prompt. * * @param allowOrDeny whether the browser should allow resubmitting data. * @return A {@link PromptResponse} which can be used to complete the {@link GeckoResult} * associated with this prompt. */ @UiThread public @NonNull PromptResponse confirm(final @Nullable AllowOrDeny allowOrDeny) { ensureResult().putBoolean("allow", allowOrDeny != AllowOrDeny.DENY); return super.confirm(); } } /** * RepostConfirmPrompt represents a prompt shown whenever the browser needs to resubmit POST * data (e.g. due to page refresh). Loading Loading @@ -6372,7 +6407,7 @@ public class GeckoSession { */ class FilePrompt extends BasePrompt { @Retention(RetentionPolicy.SOURCE) @IntDef({Type.SINGLE, Type.MULTIPLE}) @IntDef({Type.SINGLE, Type.MULTIPLE, Type.FOLDER}) public @interface FileType {} /** Types of file prompts. */ Loading @@ -6383,6 +6418,9 @@ public class GeckoSession { /** Prompt for multiple files. */ public static final int MULTIPLE = 2; /** Prompt for directory. */ public static final int FOLDER = 3; protected Type() {} } Loading Loading @@ -6458,7 +6496,7 @@ public class GeckoSession { @UiThread public @NonNull PromptResponse confirm( @NonNull final Context context, @NonNull final Uri[] uris) { if (Type.SINGLE == type && (uris == null || uris.length != 1)) { if ((Type.SINGLE == type || Type.FOLDER == type) && (uris == null || uris.length != 1)) { throw new IllegalArgumentException(); } Loading @@ -6483,33 +6521,14 @@ public class GeckoSession { if ("file".equals(uri.getScheme())) { return uri.getPath(); } final ContentResolver cr = context.getContentResolver(); final Cursor cur = cr.query( uri, new String[] {"_data"}, /* selection */ null, /* args */ null, /* sort */ null); if (cur == null) { return null; } try { final int idx = cur.getColumnIndex("_data"); if (idx < 0 || !cur.moveToFirst()) { return null; if ("content".equals(uri.getScheme())) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && DocumentsContract.isTreeUri(uri)) { return IntentUtils.resolveTreeUri(context, uri); } do { try { final String path = cur.getString(idx); if (path != null && !path.isEmpty()) { return path; if (DocumentsContract.isDocumentUri(context, uri)) { return IntentUtils.resolveDocumentUri(context, uri); } } catch (final Exception e) { } } while (cur.moveToNext()); } finally { cur.close(); return IntentUtils.resolveContentUri(context, uri); } return null; } Loading Loading @@ -6709,6 +6728,20 @@ public class GeckoSession { return null; } /** * Display a folder upload prompt. * * @param session GeckoSession that triggered the prompt. * @param prompt The {@link FolderUploadPrompt} that describes the prompt. * @return A {@link GeckoResult} resolving to a {@link PromptResponse} which includes all * necessary information to resolve the prompt. */ @UiThread default @Nullable GeckoResult<PromptResponse> onFolderUploadPrompt( @NonNull final GeckoSession session, @NonNull final FolderUploadPrompt prompt) { return null; } /** * Display a text prompt. * Loading