diff options
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.java | 1127 |
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)); + } +} |