summaryrefslogtreecommitdiffstats
path: root/mobile/android/geckoview_example/src/main/java/org/mozilla/geckoview_example/BasicGeckoViewPrompt.java
diff options
context:
space:
mode:
Diffstat (limited to 'mobile/android/geckoview_example/src/main/java/org/mozilla/geckoview_example/BasicGeckoViewPrompt.java')
-rw-r--r--mobile/android/geckoview_example/src/main/java/org/mozilla/geckoview_example/BasicGeckoViewPrompt.java1127
1 files changed, 1127 insertions, 0 deletions
diff --git a/mobile/android/geckoview_example/src/main/java/org/mozilla/geckoview_example/BasicGeckoViewPrompt.java b/mobile/android/geckoview_example/src/main/java/org/mozilla/geckoview_example/BasicGeckoViewPrompt.java
new file mode 100644
index 0000000000..c0faeb3809
--- /dev/null
+++ b/mobile/android/geckoview_example/src/main/java/org/mozilla/geckoview_example/BasicGeckoViewPrompt.java
@@ -0,0 +1,1127 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.geckoview_example;
+
+import android.annotation.TargetApi;
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.ActivityNotFoundException;
+import android.content.ClipData;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.res.TypedArray;
+import android.graphics.Color;
+import android.graphics.PorterDuff;
+import android.net.Uri;
+import android.os.Build;
+import android.text.InputType;
+import android.text.format.DateFormat;
+import android.util.Log;
+import android.view.InflateException;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.CheckedTextView;
+import android.widget.DatePicker;
+import android.widget.EditText;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.ListView;
+import android.widget.ScrollView;
+import android.widget.Spinner;
+import android.widget.TextView;
+import android.widget.TimePicker;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.Locale;
+import org.mozilla.geckoview.AllowOrDeny;
+import org.mozilla.geckoview.Autocomplete;
+import org.mozilla.geckoview.GeckoResult;
+import org.mozilla.geckoview.GeckoSession;
+import org.mozilla.geckoview.GeckoSession.PermissionDelegate.MediaSource;
+import org.mozilla.geckoview.SlowScriptResponse;
+
+final class BasicGeckoViewPrompt implements GeckoSession.PromptDelegate {
+ protected static final String LOGTAG = "BasicGeckoViewPrompt";
+
+ private final Activity mActivity;
+ public int filePickerRequestCode = 1;
+ private int mFileType;
+ private GeckoResult<PromptResponse> mFileResponse;
+ private FilePrompt mFilePrompt;
+
+ public BasicGeckoViewPrompt(final Activity activity) {
+ mActivity = activity;
+ }
+
+ @Override
+ public GeckoResult<PromptResponse> onAlertPrompt(
+ final GeckoSession session, final AlertPrompt prompt) {
+ final Activity activity = mActivity;
+ if (activity == null) {
+ return GeckoResult.fromValue(prompt.dismiss());
+ }
+ final AlertDialog.Builder builder =
+ new AlertDialog.Builder(activity)
+ .setTitle(prompt.title)
+ .setMessage(prompt.message)
+ .setPositiveButton(android.R.string.ok, /* onClickListener */ null);
+ GeckoResult<PromptResponse> res = new GeckoResult<PromptResponse>();
+ createStandardDialog(builder, prompt, res).show();
+ return res;
+ }
+
+ @Override
+ public GeckoResult<PromptResponse> onButtonPrompt(
+ final GeckoSession session, final ButtonPrompt prompt) {
+ final Activity activity = mActivity;
+ if (activity == null) {
+ return GeckoResult.fromValue(prompt.dismiss());
+ }
+ final AlertDialog.Builder builder =
+ new AlertDialog.Builder(activity).setTitle(prompt.title).setMessage(prompt.message);
+
+ GeckoResult<PromptResponse> res = new GeckoResult<PromptResponse>();
+
+ final DialogInterface.OnClickListener listener =
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(final DialogInterface dialog, final int which) {
+ if (which == DialogInterface.BUTTON_POSITIVE) {
+ res.complete(prompt.confirm(ButtonPrompt.Type.POSITIVE));
+ } else if (which == DialogInterface.BUTTON_NEGATIVE) {
+ res.complete(prompt.confirm(ButtonPrompt.Type.NEGATIVE));
+ } else {
+ res.complete(prompt.dismiss());
+ }
+ }
+ };
+
+ builder.setPositiveButton(android.R.string.ok, listener);
+ builder.setNegativeButton(android.R.string.cancel, listener);
+
+ createStandardDialog(builder, prompt, res).show();
+ return res;
+ }
+
+ @Override
+ public GeckoResult<PromptResponse> onSharePrompt(
+ final GeckoSession session, final SharePrompt prompt) {
+ return GeckoResult.fromValue(prompt.dismiss());
+ }
+
+ @Nullable
+ @Override
+ public GeckoResult<PromptResponse> onRepostConfirmPrompt(
+ final GeckoSession session, final RepostConfirmPrompt prompt) {
+ final Activity activity = mActivity;
+ if (activity == null) {
+ return GeckoResult.fromValue(prompt.dismiss());
+ }
+ final AlertDialog.Builder builder =
+ new AlertDialog.Builder(activity)
+ .setTitle(R.string.repost_confirm_title)
+ .setMessage(R.string.repost_confirm_message);
+
+ GeckoResult<PromptResponse> res = new GeckoResult<>();
+
+ final DialogInterface.OnClickListener listener =
+ (dialog, which) -> {
+ if (which == DialogInterface.BUTTON_POSITIVE) {
+ res.complete(prompt.confirm(AllowOrDeny.ALLOW));
+ } else if (which == DialogInterface.BUTTON_NEGATIVE) {
+ res.complete(prompt.confirm(AllowOrDeny.DENY));
+ } else {
+ res.complete(prompt.dismiss());
+ }
+ };
+
+ builder.setPositiveButton(R.string.repost_confirm_resend, listener);
+ builder.setNegativeButton(R.string.repost_confirm_cancel, listener);
+
+ createStandardDialog(builder, prompt, res).show();
+ return res;
+ }
+
+ @Nullable
+ @Override
+ public GeckoResult<PromptResponse> onCreditCardSave(
+ @NonNull GeckoSession session,
+ @NonNull AutocompleteRequest<Autocomplete.CreditCardSaveOption> request) {
+ Log.i(LOGTAG, "onCreditCardSave " + request.options[0].value);
+ return null;
+ }
+
+ @Nullable
+ @Override
+ public GeckoResult<PromptResponse> onLoginSave(
+ @NonNull GeckoSession session,
+ @NonNull AutocompleteRequest<Autocomplete.LoginSaveOption> request) {
+ Log.i(LOGTAG, "onLoginSave");
+ return GeckoResult.fromValue(request.confirm(request.options[0]));
+ }
+
+ @Nullable
+ @Override
+ public GeckoResult<PromptResponse> onBeforeUnloadPrompt(
+ final GeckoSession session, final BeforeUnloadPrompt prompt) {
+ final Activity activity = mActivity;
+ if (activity == null) {
+ return GeckoResult.fromValue(prompt.dismiss());
+ }
+ final AlertDialog.Builder builder =
+ new AlertDialog.Builder(activity)
+ .setTitle(R.string.before_unload_title)
+ .setMessage(R.string.before_unload_message);
+
+ GeckoResult<PromptResponse> res = new GeckoResult<>();
+
+ final DialogInterface.OnClickListener listener =
+ (dialog, which) -> {
+ if (which == DialogInterface.BUTTON_POSITIVE) {
+ res.complete(prompt.confirm(AllowOrDeny.ALLOW));
+ } else if (which == DialogInterface.BUTTON_NEGATIVE) {
+ res.complete(prompt.confirm(AllowOrDeny.DENY));
+ } else {
+ res.complete(prompt.dismiss());
+ }
+ };
+
+ builder.setPositiveButton(R.string.before_unload_leave_page, listener);
+ builder.setNegativeButton(R.string.before_unload_stay, listener);
+
+ createStandardDialog(builder, prompt, res).show();
+ return res;
+ }
+
+ private int getViewPadding(final AlertDialog.Builder builder) {
+ final TypedArray attr =
+ builder
+ .getContext()
+ .obtainStyledAttributes(new int[] {android.R.attr.listPreferredItemPaddingLeft});
+ final int padding = attr.getDimensionPixelSize(0, 1);
+ attr.recycle();
+ return padding;
+ }
+
+ private LinearLayout addStandardLayout(
+ final AlertDialog.Builder builder, final String title, final String msg) {
+ final ScrollView scrollView = new ScrollView(builder.getContext());
+ final LinearLayout container = new LinearLayout(builder.getContext());
+ final int horizontalPadding = getViewPadding(builder);
+ final int verticalPadding = (msg == null || msg.isEmpty()) ? horizontalPadding : 0;
+ container.setOrientation(LinearLayout.VERTICAL);
+ container.setPadding(
+ /* left */ horizontalPadding, /* top */ verticalPadding,
+ /* right */ horizontalPadding, /* bottom */ verticalPadding);
+ scrollView.addView(container);
+ builder.setTitle(title).setMessage(msg).setView(scrollView);
+ return container;
+ }
+
+ private AlertDialog createStandardDialog(
+ final AlertDialog.Builder builder,
+ final BasePrompt prompt,
+ final GeckoResult<PromptResponse> response) {
+ final AlertDialog dialog = builder.create();
+ dialog.setOnDismissListener(
+ new DialogInterface.OnDismissListener() {
+ @Override
+ public void onDismiss(final DialogInterface dialog) {
+ if (!prompt.isComplete()) {
+ response.complete(prompt.dismiss());
+ }
+ }
+ });
+ return dialog;
+ }
+
+ @Override
+ public GeckoResult<PromptResponse> onTextPrompt(
+ final GeckoSession session, final TextPrompt prompt) {
+ final Activity activity = mActivity;
+ if (activity == null) {
+ return GeckoResult.fromValue(prompt.dismiss());
+ }
+ final AlertDialog.Builder builder = new AlertDialog.Builder(activity);
+ final LinearLayout container = addStandardLayout(builder, prompt.title, prompt.message);
+ final EditText editText = new EditText(builder.getContext());
+ editText.setText(prompt.defaultValue);
+ container.addView(editText);
+
+ GeckoResult<PromptResponse> res = new GeckoResult<PromptResponse>();
+
+ builder
+ .setNegativeButton(android.R.string.cancel, /* listener */ null)
+ .setPositiveButton(
+ android.R.string.ok,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(final DialogInterface dialog, final int which) {
+ res.complete(prompt.confirm(editText.getText().toString()));
+ }
+ });
+
+ createStandardDialog(builder, prompt, res).show();
+ return res;
+ }
+
+ @Override
+ public GeckoResult<PromptResponse> onAuthPrompt(
+ final GeckoSession session, final AuthPrompt prompt) {
+ final Activity activity = mActivity;
+ if (activity == null) {
+ return GeckoResult.fromValue(prompt.dismiss());
+ }
+ final AlertDialog.Builder builder = new AlertDialog.Builder(activity);
+ final LinearLayout container = addStandardLayout(builder, prompt.title, prompt.message);
+
+ final int flags = prompt.authOptions.flags;
+ final int level = prompt.authOptions.level;
+ final EditText username;
+ if ((flags & AuthPrompt.AuthOptions.Flags.ONLY_PASSWORD) == 0) {
+ username = new EditText(builder.getContext());
+ username.setHint(R.string.username);
+ username.setText(prompt.authOptions.username);
+ container.addView(username);
+ } else {
+ username = null;
+ }
+
+ final EditText password = new EditText(builder.getContext());
+ password.setHint(R.string.password);
+ password.setText(prompt.authOptions.password);
+ password.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
+ container.addView(password);
+
+ if (level != AuthPrompt.AuthOptions.Level.NONE) {
+ final ImageView secure = new ImageView(builder.getContext());
+ secure.setImageResource(android.R.drawable.ic_lock_lock);
+ container.addView(secure);
+ }
+
+ GeckoResult<PromptResponse> res = new GeckoResult<PromptResponse>();
+
+ builder
+ .setNegativeButton(android.R.string.cancel, /* listener */ null)
+ .setPositiveButton(
+ android.R.string.ok,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(final DialogInterface dialog, final int which) {
+ if ((flags & AuthPrompt.AuthOptions.Flags.ONLY_PASSWORD) == 0) {
+ res.complete(
+ prompt.confirm(username.getText().toString(), password.getText().toString()));
+
+ } else {
+ res.complete(prompt.confirm(password.getText().toString()));
+ }
+ }
+ });
+ createStandardDialog(builder, prompt, res).show();
+
+ return res;
+ }
+
+ private static class ModifiableChoice {
+ public boolean modifiableSelected;
+ public String modifiableLabel;
+ public final ChoicePrompt.Choice choice;
+
+ public ModifiableChoice(ChoicePrompt.Choice c) {
+ choice = c;
+ modifiableSelected = choice.selected;
+ modifiableLabel = choice.label;
+ }
+ }
+
+ private void addChoiceItems(
+ final int type,
+ final ArrayAdapter<ModifiableChoice> list,
+ final ChoicePrompt.Choice[] items,
+ final String indent) {
+ if (type == ChoicePrompt.Type.MENU) {
+ for (final ChoicePrompt.Choice item : items) {
+ list.add(new ModifiableChoice(item));
+ }
+ return;
+ }
+
+ for (final ChoicePrompt.Choice item : items) {
+ final ModifiableChoice modItem = new ModifiableChoice(item);
+
+ final ChoicePrompt.Choice[] children = item.items;
+
+ if (indent != null && children == null) {
+ modItem.modifiableLabel = indent + modItem.modifiableLabel;
+ }
+ list.add(modItem);
+
+ if (children != null) {
+ final String newIndent;
+ if (type == ChoicePrompt.Type.SINGLE || type == ChoicePrompt.Type.MULTIPLE) {
+ newIndent = (indent != null) ? indent + '\t' : "\t";
+ } else {
+ newIndent = null;
+ }
+ addChoiceItems(type, list, children, newIndent);
+ }
+ }
+ }
+
+ private void onChoicePromptImpl(
+ final GeckoSession session,
+ final String title,
+ final String message,
+ final int type,
+ final ChoicePrompt.Choice[] choices,
+ final ChoicePrompt prompt,
+ final GeckoResult<PromptResponse> res) {
+ final Activity activity = mActivity;
+ if (activity == null) {
+ res.complete(prompt.dismiss());
+ return;
+ }
+ final AlertDialog.Builder builder = new AlertDialog.Builder(activity);
+ addStandardLayout(builder, title, message);
+
+ final ListView list = new ListView(builder.getContext());
+ if (type == ChoicePrompt.Type.MULTIPLE) {
+ list.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
+ }
+
+ final ArrayAdapter<ModifiableChoice> adapter =
+ new ArrayAdapter<ModifiableChoice>(
+ builder.getContext(), android.R.layout.simple_list_item_1) {
+ private static final int TYPE_MENU_ITEM = 0;
+ private static final int TYPE_MENU_CHECK = 1;
+ private static final int TYPE_SEPARATOR = 2;
+ private static final int TYPE_GROUP = 3;
+ private static final int TYPE_SINGLE = 4;
+ private static final int TYPE_MULTIPLE = 5;
+ private static final int TYPE_COUNT = 6;
+
+ private LayoutInflater mInflater;
+ private View mSeparator;
+
+ @Override
+ public int getViewTypeCount() {
+ return TYPE_COUNT;
+ }
+
+ @Override
+ public int getItemViewType(final int position) {
+ final ModifiableChoice item = getItem(position);
+ if (item.choice.separator) {
+ return TYPE_SEPARATOR;
+ } else if (type == ChoicePrompt.Type.MENU) {
+ return item.modifiableSelected ? TYPE_MENU_CHECK : TYPE_MENU_ITEM;
+ } else if (item.choice.items != null) {
+ return TYPE_GROUP;
+ } else if (type == ChoicePrompt.Type.SINGLE) {
+ return TYPE_SINGLE;
+ } else if (type == ChoicePrompt.Type.MULTIPLE) {
+ return TYPE_MULTIPLE;
+ } else {
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ @Override
+ public boolean isEnabled(final int position) {
+ final ModifiableChoice item = getItem(position);
+ return !item.choice.separator
+ && !item.choice.disabled
+ && ((type != ChoicePrompt.Type.SINGLE && type != ChoicePrompt.Type.MULTIPLE)
+ || item.choice.items == null);
+ }
+
+ @Override
+ public View getView(final int position, View view, final ViewGroup parent) {
+ final int itemType = getItemViewType(position);
+ final int layoutId;
+ if (itemType == TYPE_SEPARATOR) {
+ if (mSeparator == null) {
+ mSeparator = new View(getContext());
+ mSeparator.setLayoutParams(
+ new ListView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 2, itemType));
+ final TypedArray attr =
+ getContext().obtainStyledAttributes(new int[] {android.R.attr.listDivider});
+ mSeparator.setBackgroundResource(attr.getResourceId(0, 0));
+ attr.recycle();
+ }
+ return mSeparator;
+ } else if (itemType == TYPE_MENU_ITEM) {
+ layoutId = android.R.layout.simple_list_item_1;
+ } else if (itemType == TYPE_MENU_CHECK) {
+ layoutId = android.R.layout.simple_list_item_checked;
+ } else if (itemType == TYPE_GROUP) {
+ layoutId = android.R.layout.preference_category;
+ } else if (itemType == TYPE_SINGLE) {
+ layoutId = android.R.layout.simple_list_item_single_choice;
+ } else if (itemType == TYPE_MULTIPLE) {
+ layoutId = android.R.layout.simple_list_item_multiple_choice;
+ } else {
+ throw new UnsupportedOperationException();
+ }
+
+ if (view == null) {
+ if (mInflater == null) {
+ mInflater = LayoutInflater.from(builder.getContext());
+ }
+ view = mInflater.inflate(layoutId, parent, false);
+ }
+
+ final ModifiableChoice item = getItem(position);
+ final TextView text = (TextView) view;
+ text.setEnabled(!item.choice.disabled);
+ text.setText(item.modifiableLabel);
+ if (view instanceof CheckedTextView) {
+ final boolean selected = item.modifiableSelected;
+ if (itemType == TYPE_MULTIPLE) {
+ list.setItemChecked(position, selected);
+ } else {
+ ((CheckedTextView) view).setChecked(selected);
+ }
+ }
+ return view;
+ }
+ };
+ addChoiceItems(type, adapter, choices, /* indent */ null);
+
+ list.setAdapter(adapter);
+ builder.setView(list);
+
+ final AlertDialog dialog;
+ if (type == ChoicePrompt.Type.SINGLE || type == ChoicePrompt.Type.MENU) {
+ dialog = createStandardDialog(builder, prompt, res);
+ list.setOnItemClickListener(
+ new AdapterView.OnItemClickListener() {
+ @Override
+ public void onItemClick(
+ final AdapterView<?> parent, final View v, final int position, final long id) {
+ final ModifiableChoice item = adapter.getItem(position);
+ if (type == ChoicePrompt.Type.MENU) {
+ final ChoicePrompt.Choice[] children = item.choice.items;
+ if (children != null) {
+ // Show sub-menu.
+ dialog.setOnDismissListener(null);
+ dialog.dismiss();
+ onChoicePromptImpl(
+ session, item.modifiableLabel, /* msg */ null, type, children, prompt, res);
+ return;
+ }
+ }
+ res.complete(prompt.confirm(item.choice));
+ dialog.dismiss();
+ }
+ });
+ } else if (type == ChoicePrompt.Type.MULTIPLE) {
+ list.setOnItemClickListener(
+ new AdapterView.OnItemClickListener() {
+ @Override
+ public void onItemClick(
+ final AdapterView<?> parent, final View v, final int position, final long id) {
+ final ModifiableChoice item = adapter.getItem(position);
+ item.modifiableSelected = ((CheckedTextView) v).isChecked();
+ }
+ });
+ builder
+ .setNegativeButton(android.R.string.cancel, /* listener */ null)
+ .setPositiveButton(
+ android.R.string.ok,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(final DialogInterface dialog, final int which) {
+ final int len = adapter.getCount();
+ ArrayList<String> items = new ArrayList<>(len);
+ for (int i = 0; i < len; i++) {
+ final ModifiableChoice item = adapter.getItem(i);
+ if (item.modifiableSelected) {
+ items.add(item.choice.id);
+ }
+ }
+ res.complete(prompt.confirm(items.toArray(new String[items.size()])));
+ }
+ });
+ dialog = createStandardDialog(builder, prompt, res);
+ } else {
+ throw new UnsupportedOperationException();
+ }
+ dialog.show();
+
+ prompt.setDelegate(
+ new PromptInstanceDelegate() {
+ @Override
+ public void onPromptDismiss(final BasePrompt prompt) {
+ dialog.dismiss();
+ }
+
+ @Override
+ public void onPromptUpdate(final BasePrompt prompt) {
+ dialog.setOnDismissListener(null);
+ dialog.dismiss();
+ final ChoicePrompt newPrompt = (ChoicePrompt) prompt;
+ onChoicePromptImpl(
+ session,
+ newPrompt.title,
+ newPrompt.message,
+ newPrompt.type,
+ newPrompt.choices,
+ newPrompt,
+ res);
+ }
+ });
+ }
+
+ @Override
+ public GeckoResult<PromptResponse> onChoicePrompt(
+ final GeckoSession session, final ChoicePrompt prompt) {
+ final GeckoResult<PromptResponse> res = new GeckoResult<PromptResponse>();
+ onChoicePromptImpl(
+ session, prompt.title, prompt.message, prompt.type, prompt.choices, prompt, res);
+ return res;
+ }
+
+ private static int parseColor(final String value, final int def) {
+ try {
+ return Color.parseColor(value);
+ } catch (final IllegalArgumentException e) {
+ return def;
+ }
+ }
+
+ @Override
+ public GeckoResult<PromptResponse> onColorPrompt(
+ final GeckoSession session, final ColorPrompt prompt) {
+ final Activity activity = mActivity;
+ if (activity == null) {
+ return GeckoResult.fromValue(prompt.dismiss());
+ }
+ final AlertDialog.Builder builder = new AlertDialog.Builder(activity);
+ addStandardLayout(builder, prompt.title, /* msg */ null);
+
+ final int initial = parseColor(prompt.defaultValue, /* def */ 0);
+ final ArrayAdapter<Integer> adapter =
+ new ArrayAdapter<Integer>(builder.getContext(), android.R.layout.simple_list_item_1) {
+ private LayoutInflater mInflater;
+
+ @Override
+ public int getViewTypeCount() {
+ return 2;
+ }
+
+ @Override
+ public int getItemViewType(final int position) {
+ return (getItem(position) == initial) ? 1 : 0;
+ }
+
+ @Override
+ public View getView(final int position, View view, final ViewGroup parent) {
+ if (mInflater == null) {
+ mInflater = LayoutInflater.from(builder.getContext());
+ }
+ final int color = getItem(position);
+ if (view == null) {
+ view =
+ mInflater.inflate(
+ (color == initial)
+ ? android.R.layout.simple_list_item_checked
+ : android.R.layout.simple_list_item_1,
+ parent,
+ false);
+ }
+ view.setBackgroundResource(android.R.drawable.editbox_background);
+ view.getBackground().setColorFilter(color, PorterDuff.Mode.MULTIPLY);
+ return view;
+ }
+ };
+
+ adapter.addAll(
+ 0xffff4444 /* holo_red_light */,
+ 0xffcc0000 /* holo_red_dark */,
+ 0xffffbb33 /* holo_orange_light */,
+ 0xffff8800 /* holo_orange_dark */,
+ 0xff99cc00 /* holo_green_light */,
+ 0xff669900 /* holo_green_dark */,
+ 0xff33b5e5 /* holo_blue_light */,
+ 0xff0099cc /* holo_blue_dark */,
+ 0xffaa66cc /* holo_purple */,
+ 0xffffffff /* white */,
+ 0xffaaaaaa /* lighter_gray */,
+ 0xff555555 /* darker_gray */,
+ 0xff000000 /* black */);
+
+ final ListView list = new ListView(builder.getContext());
+ list.setAdapter(adapter);
+ builder.setView(list);
+
+ GeckoResult<PromptResponse> res = new GeckoResult<PromptResponse>();
+
+ final AlertDialog dialog = createStandardDialog(builder, prompt, res);
+ list.setOnItemClickListener(
+ new AdapterView.OnItemClickListener() {
+ @Override
+ public void onItemClick(
+ final AdapterView<?> parent, final View v, final int position, final long id) {
+ res.complete(
+ prompt.confirm(String.format("#%06x", 0xffffff & adapter.getItem(position))));
+ dialog.dismiss();
+ }
+ });
+ dialog.show();
+
+ return res;
+ }
+
+ private static Date parseDate(
+ final SimpleDateFormat formatter, final String value, final boolean defaultToNow) {
+ try {
+ if (value != null && !value.isEmpty()) {
+ return formatter.parse(value);
+ }
+ } catch (final ParseException e) {
+ }
+ return defaultToNow ? new Date() : null;
+ }
+
+ @SuppressWarnings("deprecation")
+ private static void setTimePickerTime(final TimePicker picker, final Calendar cal) {
+ if (Build.VERSION.SDK_INT >= 23) {
+ picker.setHour(cal.get(Calendar.HOUR_OF_DAY));
+ picker.setMinute(cal.get(Calendar.MINUTE));
+ } else {
+ picker.setCurrentHour(cal.get(Calendar.HOUR_OF_DAY));
+ picker.setCurrentMinute(cal.get(Calendar.MINUTE));
+ }
+ }
+
+ @SuppressWarnings("deprecation")
+ private static void setCalendarTime(final Calendar cal, final TimePicker picker) {
+ if (Build.VERSION.SDK_INT >= 23) {
+ cal.set(Calendar.HOUR_OF_DAY, picker.getHour());
+ cal.set(Calendar.MINUTE, picker.getMinute());
+ } else {
+ cal.set(Calendar.HOUR_OF_DAY, picker.getCurrentHour());
+ cal.set(Calendar.MINUTE, picker.getCurrentMinute());
+ }
+ }
+
+ @Override
+ public GeckoResult<PromptResponse> onDateTimePrompt(
+ final GeckoSession session, final DateTimePrompt prompt) {
+ final Activity activity = mActivity;
+ if (activity == null) {
+ return GeckoResult.fromValue(prompt.dismiss());
+ }
+ final String format;
+ if (prompt.type == DateTimePrompt.Type.DATE) {
+ format = "yyyy-MM-dd";
+ } else if (prompt.type == DateTimePrompt.Type.MONTH) {
+ format = "yyyy-MM";
+ } else if (prompt.type == DateTimePrompt.Type.WEEK) {
+ format = "yyyy-'W'ww";
+ } else if (prompt.type == DateTimePrompt.Type.TIME) {
+ format = "HH:mm";
+ } else if (prompt.type == DateTimePrompt.Type.DATETIME_LOCAL) {
+ format = "yyyy-MM-dd'T'HH:mm";
+ } else {
+ throw new UnsupportedOperationException();
+ }
+
+ final SimpleDateFormat formatter = new SimpleDateFormat(format, Locale.ROOT);
+ final Date minDate = parseDate(formatter, prompt.minValue, /* defaultToNow */ false);
+ final Date maxDate = parseDate(formatter, prompt.maxValue, /* defaultToNow */ false);
+ final Date date = parseDate(formatter, prompt.defaultValue, /* defaultToNow */ true);
+ final Calendar cal = formatter.getCalendar();
+ cal.setTime(date);
+
+ final AlertDialog.Builder builder = new AlertDialog.Builder(activity);
+ final LayoutInflater inflater = LayoutInflater.from(builder.getContext());
+ final DatePicker datePicker;
+ if (prompt.type == DateTimePrompt.Type.DATE
+ || prompt.type == DateTimePrompt.Type.MONTH
+ || prompt.type == DateTimePrompt.Type.WEEK
+ || prompt.type == DateTimePrompt.Type.DATETIME_LOCAL) {
+ final int resId =
+ builder
+ .getContext()
+ .getResources()
+ .getIdentifier("date_picker_dialog", "layout", "android");
+ DatePicker picker = null;
+ if (resId != 0) {
+ try {
+ picker = (DatePicker) inflater.inflate(resId, /* root */ null);
+ } catch (final ClassCastException | InflateException e) {
+ }
+ }
+ if (picker == null) {
+ picker = new DatePicker(builder.getContext());
+ }
+ picker.init(
+ cal.get(Calendar.YEAR),
+ cal.get(Calendar.MONTH),
+ cal.get(Calendar.DAY_OF_MONTH), /* listener */
+ null);
+ if (minDate != null) {
+ picker.setMinDate(minDate.getTime());
+ }
+ if (maxDate != null) {
+ picker.setMaxDate(maxDate.getTime());
+ }
+ datePicker = picker;
+ } else {
+ datePicker = null;
+ }
+
+ final TimePicker timePicker;
+ if (prompt.type == DateTimePrompt.Type.TIME
+ || prompt.type == DateTimePrompt.Type.DATETIME_LOCAL) {
+ final int resId =
+ builder
+ .getContext()
+ .getResources()
+ .getIdentifier("time_picker_dialog", "layout", "android");
+ TimePicker picker = null;
+ if (resId != 0) {
+ try {
+ picker = (TimePicker) inflater.inflate(resId, /* root */ null);
+ } catch (final ClassCastException | InflateException e) {
+ }
+ }
+ if (picker == null) {
+ picker = new TimePicker(builder.getContext());
+ }
+ setTimePickerTime(picker, cal);
+ picker.setIs24HourView(DateFormat.is24HourFormat(builder.getContext()));
+ timePicker = picker;
+ } else {
+ timePicker = null;
+ }
+
+ final LinearLayout container = addStandardLayout(builder, prompt.title, /* msg */ null);
+ container.setPadding(/* left */ 0, /* top */ 0, /* right */ 0, /* bottom */ 0);
+ if (datePicker != null) {
+ container.addView(datePicker);
+ }
+ if (timePicker != null) {
+ container.addView(timePicker);
+ }
+
+ GeckoResult<PromptResponse> res = new GeckoResult<PromptResponse>();
+
+ final DialogInterface.OnClickListener listener =
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(final DialogInterface dialog, final int which) {
+ if (which == DialogInterface.BUTTON_NEUTRAL) {
+ // Clear
+ res.complete(prompt.confirm(""));
+ return;
+ }
+ if (datePicker != null) {
+ cal.set(datePicker.getYear(), datePicker.getMonth(), datePicker.getDayOfMonth());
+ }
+ if (timePicker != null) {
+ setCalendarTime(cal, timePicker);
+ }
+ res.complete(prompt.confirm(formatter.format(cal.getTime())));
+ }
+ };
+ builder
+ .setNegativeButton(android.R.string.cancel, /* listener */ null)
+ .setNeutralButton(R.string.clear_field, listener)
+ .setPositiveButton(android.R.string.ok, listener);
+
+ final AlertDialog dialog = createStandardDialog(builder, prompt, res);
+ dialog.show();
+
+ prompt.setDelegate(
+ new PromptInstanceDelegate() {
+ @Override
+ public void onPromptDismiss(final BasePrompt prompt) {
+ dialog.dismiss();
+ }
+ });
+ return res;
+ }
+
+ @Override
+ @TargetApi(19)
+ public GeckoResult<PromptResponse> onFilePrompt(GeckoSession session, FilePrompt prompt) {
+ final Activity activity = mActivity;
+ if (activity == null) {
+ return GeckoResult.fromValue(prompt.dismiss());
+ }
+
+ // Merge all given MIME types into one, using wildcard if needed.
+ String mimeType = null;
+ String mimeSubtype = null;
+ if (prompt.mimeTypes != null) {
+ for (final String rawType : prompt.mimeTypes) {
+ final String normalizedType = rawType.trim().toLowerCase(Locale.ROOT);
+ final int len = normalizedType.length();
+ int slash = normalizedType.indexOf('/');
+ if (slash < 0) {
+ slash = len;
+ }
+ final String newType = normalizedType.substring(0, slash);
+ final String newSubtype = normalizedType.substring(Math.min(slash + 1, len));
+ if (mimeType == null) {
+ mimeType = newType;
+ } else if (!mimeType.equals(newType)) {
+ mimeType = "*";
+ }
+ if (mimeSubtype == null) {
+ mimeSubtype = newSubtype;
+ } else if (!mimeSubtype.equals(newSubtype)) {
+ mimeSubtype = "*";
+ }
+ }
+ }
+
+ final Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
+ intent.setType(
+ (mimeType != null ? mimeType : "*") + '/' + (mimeSubtype != null ? mimeSubtype : "*"));
+ intent.addCategory(Intent.CATEGORY_OPENABLE);
+ intent.putExtra(Intent.EXTRA_LOCAL_ONLY, true);
+ if (prompt.type == FilePrompt.Type.MULTIPLE) {
+ intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
+ }
+ if (prompt.mimeTypes.length > 0) {
+ intent.putExtra(Intent.EXTRA_MIME_TYPES, prompt.mimeTypes);
+ }
+
+ GeckoResult<PromptResponse> res = new GeckoResult<PromptResponse>();
+
+ try {
+ mFileResponse = res;
+ mFilePrompt = prompt;
+ activity.startActivityForResult(intent, filePickerRequestCode);
+ } catch (final ActivityNotFoundException e) {
+ Log.e(LOGTAG, "Cannot launch activity", e);
+ return GeckoResult.fromValue(prompt.dismiss());
+ }
+
+ return res;
+ }
+
+ public void onFileCallbackResult(final int resultCode, final Intent data) {
+ if (mFileResponse == null) {
+ return;
+ }
+
+ final GeckoResult<PromptResponse> res = mFileResponse;
+ mFileResponse = null;
+
+ final FilePrompt prompt = mFilePrompt;
+ mFilePrompt = null;
+
+ if (resultCode != Activity.RESULT_OK || data == null) {
+ res.complete(prompt.dismiss());
+ return;
+ }
+
+ final Uri uri = data.getData();
+ final ClipData clip = data.getClipData();
+
+ if (prompt.type == FilePrompt.Type.SINGLE
+ || (prompt.type == FilePrompt.Type.MULTIPLE && clip == null)) {
+ res.complete(prompt.confirm(mActivity, uri));
+ } else if (prompt.type == FilePrompt.Type.MULTIPLE) {
+ if (clip == null) {
+ Log.w(LOGTAG, "No selected file");
+ res.complete(prompt.dismiss());
+ return;
+ }
+ final int count = clip.getItemCount();
+ final ArrayList<Uri> uris = new ArrayList<>(count);
+ for (int i = 0; i < count; i++) {
+ uris.add(clip.getItemAt(i).getUri());
+ }
+ res.complete(prompt.confirm(mActivity, uris.toArray(new Uri[uris.size()])));
+ }
+ }
+
+ public GeckoResult<Integer> onPermissionPrompt(
+ final GeckoSession session,
+ final String title,
+ final GeckoSession.PermissionDelegate.ContentPermission perm) {
+ final Activity activity = mActivity;
+ final GeckoResult<Integer> res = new GeckoResult<>();
+ if (activity == null) {
+ res.complete(GeckoSession.PermissionDelegate.ContentPermission.VALUE_PROMPT);
+ return res;
+ }
+ final AlertDialog.Builder builder = new AlertDialog.Builder(activity);
+ builder
+ .setTitle(title)
+ .setNegativeButton(
+ android.R.string.cancel,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(final DialogInterface dialog, final int which) {
+ res.complete(GeckoSession.PermissionDelegate.ContentPermission.VALUE_DENY);
+ }
+ })
+ .setPositiveButton(
+ android.R.string.ok,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(final DialogInterface dialog, final int which) {
+ res.complete(GeckoSession.PermissionDelegate.ContentPermission.VALUE_ALLOW);
+ }
+ });
+
+ final AlertDialog dialog = builder.create();
+ dialog.show();
+ return res;
+ }
+
+ public void onSlowScriptPrompt(
+ GeckoSession geckoSession, String title, GeckoResult<SlowScriptResponse> reportAction) {
+ final Activity activity = mActivity;
+ if (activity == null) {
+ return;
+ }
+ final AlertDialog.Builder builder = new AlertDialog.Builder(activity);
+ builder
+ .setTitle(title)
+ .setNegativeButton(
+ activity.getString(R.string.wait),
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(final DialogInterface dialog, final int which) {
+ reportAction.complete(SlowScriptResponse.CONTINUE);
+ }
+ })
+ .setPositiveButton(
+ activity.getString(R.string.stop),
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(final DialogInterface dialog, final int which) {
+ reportAction.complete(SlowScriptResponse.STOP);
+ }
+ });
+
+ final AlertDialog dialog = builder.create();
+ dialog.show();
+ }
+
+ private Spinner addMediaSpinner(
+ final Context context,
+ final ViewGroup container,
+ final MediaSource[] sources,
+ final String[] sourceNames) {
+ final ArrayAdapter<MediaSource> adapter =
+ new ArrayAdapter<MediaSource>(context, android.R.layout.simple_spinner_item) {
+ private View convertView(final int position, final View view) {
+ if (view != null) {
+ final MediaSource item = getItem(position);
+ ((TextView) view).setText(sourceNames != null ? sourceNames[position] : item.name);
+ }
+ return view;
+ }
+
+ @Override
+ public View getView(final int position, View view, final ViewGroup parent) {
+ return convertView(position, super.getView(position, view, parent));
+ }
+
+ @Override
+ public View getDropDownView(final int position, final View view, final ViewGroup parent) {
+ return convertView(position, super.getDropDownView(position, view, parent));
+ }
+ };
+ adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+ adapter.addAll(sources);
+
+ final Spinner spinner = new Spinner(context);
+ spinner.setAdapter(adapter);
+ spinner.setSelection(0);
+ container.addView(spinner);
+ return spinner;
+ }
+
+ public void onMediaPrompt(
+ final GeckoSession session,
+ final String title,
+ final MediaSource[] video,
+ final MediaSource[] audio,
+ final String[] videoNames,
+ final String[] audioNames,
+ final GeckoSession.PermissionDelegate.MediaCallback callback) {
+ final Activity activity = mActivity;
+ if (activity == null || (video == null && audio == null)) {
+ callback.reject();
+ return;
+ }
+ final AlertDialog.Builder builder = new AlertDialog.Builder(activity);
+ final LinearLayout container = addStandardLayout(builder, title, /* msg */ null);
+
+ final Spinner videoSpinner;
+ if (video != null) {
+ videoSpinner = addMediaSpinner(builder.getContext(), container, video, videoNames);
+ } else {
+ videoSpinner = null;
+ }
+
+ final Spinner audioSpinner;
+ if (audio != null) {
+ audioSpinner = addMediaSpinner(builder.getContext(), container, audio, audioNames);
+ } else {
+ audioSpinner = null;
+ }
+
+ builder
+ .setNegativeButton(android.R.string.cancel, /* listener */ null)
+ .setPositiveButton(
+ android.R.string.ok,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(final DialogInterface dialog, final int which) {
+ final MediaSource video =
+ (videoSpinner != null) ? (MediaSource) videoSpinner.getSelectedItem() : null;
+ final MediaSource audio =
+ (audioSpinner != null) ? (MediaSource) audioSpinner.getSelectedItem() : null;
+ callback.grant(video, audio);
+ }
+ });
+
+ final AlertDialog dialog = builder.create();
+ dialog.setOnDismissListener(
+ new DialogInterface.OnDismissListener() {
+ @Override
+ public void onDismiss(final DialogInterface dialog) {
+ callback.reject();
+ }
+ });
+ dialog.show();
+ }
+
+ public void onMediaPrompt(
+ final GeckoSession session,
+ final String title,
+ final MediaSource[] video,
+ final MediaSource[] audio,
+ final GeckoSession.PermissionDelegate.MediaCallback callback) {
+ onMediaPrompt(session, title, video, audio, null, null, callback);
+ }
+
+ @Override
+ public GeckoResult<PromptResponse> onPopupPrompt(
+ final GeckoSession session, final PopupPrompt prompt) {
+ return GeckoResult.fromValue(prompt.confirm(AllowOrDeny.ALLOW));
+ }
+}