import FIREFOX_52_6_0esr_RELEASE from mozilla-esr52 hg repo

This commit is contained in:
Roy Tam 2018-01-19 03:59:58 +08:00
commit dcd9973243
150858 changed files with 23884658 additions and 0 deletions

View file

@ -0,0 +1,59 @@
/* -*- 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.gecko.prompts;
import org.json.JSONObject;
import org.mozilla.gecko.R;
import org.mozilla.gecko.widget.BasicColorPicker;
import android.content.Context;
import android.graphics.Color;
import android.view.LayoutInflater;
import android.view.View;
public class ColorPickerInput extends PromptInput {
public static final String INPUT_TYPE = "color";
public static final String LOGTAG = "GeckoColorPickerInput";
private final boolean mShowAdvancedButton = true;
private final int mInitialColor;
public ColorPickerInput(JSONObject obj) {
super(obj);
String init = obj.optString("value");
mInitialColor = Color.rgb(Integer.parseInt(init.substring(1, 3), 16),
Integer.parseInt(init.substring(3, 5), 16),
Integer.parseInt(init.substring(5, 7), 16));
}
@Override
public View getView(Context context) throws UnsupportedOperationException {
LayoutInflater inflater = LayoutInflater.from(context);
mView = inflater.inflate(R.layout.basic_color_picker_dialog, null);
BasicColorPicker cp = (BasicColorPicker) mView.findViewById(R.id.colorpicker);
cp.setColor(mInitialColor);
return mView;
}
@Override
public Object getValue() {
BasicColorPicker cp = (BasicColorPicker) mView.findViewById(R.id.colorpicker);
int color = cp.getColor();
return "#" + Integer.toHexString(color).substring(2);
}
@Override
public boolean getScrollable() {
return true;
}
@Override
public boolean canApplyInputStyle() {
return false;
}
}

View file

@ -0,0 +1,171 @@
/* -*- 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.gecko.prompts;
import java.util.ArrayList;
import java.util.List;
import org.json.JSONArray;
import org.json.JSONObject;
import org.mozilla.gecko.GeckoAppShell;
import org.mozilla.gecko.R;
import org.mozilla.gecko.util.ResourceDrawableUtils;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.text.TextUtils;
import android.view.Display;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ArrayAdapter;
import android.widget.GridView;
import android.widget.ImageView;
import android.widget.TextView;
public class IconGridInput extends PromptInput implements OnItemClickListener {
public static final String INPUT_TYPE = "icongrid";
public static final String LOGTAG = "GeckoIconGridInput";
private ArrayAdapter<IconGridItem> mAdapter; // An adapter holding a list of items to show in the grid
private static int mColumnWidth = -1; // The maximum width of columns
private static int mMaxColumns = -1; // The maximum number of columns to show
private static int mIconSize = -1; // Size of icons in the grid
private int mSelected; // Current selection
private final JSONArray mArray;
public IconGridInput(JSONObject obj) {
super(obj);
mArray = obj.optJSONArray("items");
}
@Override
public View getView(Context context) throws UnsupportedOperationException {
if (mColumnWidth < 0) {
// getColumnWidth isn't available on pre-ICS, so we pull it out and assign it here
mColumnWidth = context.getResources().getDimensionPixelSize(R.dimen.icongrid_columnwidth);
}
if (mIconSize < 0) {
mIconSize = GeckoAppShell.getPreferredIconSize();
}
if (mMaxColumns < 0) {
mMaxColumns = context.getResources().getInteger(R.integer.max_icon_grid_columns);
}
// TODO: Dynamically handle size changes
final WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
final Display display = wm.getDefaultDisplay();
final int screenWidth = display.getWidth();
int maxColumns = Math.min(mMaxColumns, screenWidth / mColumnWidth);
final GridView view = (GridView) LayoutInflater.from(context).inflate(R.layout.icon_grid, null, false);
view.setColumnWidth(mColumnWidth);
final ArrayList<IconGridItem> items = new ArrayList<IconGridItem>(mArray.length());
for (int i = 0; i < mArray.length(); i++) {
IconGridItem item = new IconGridItem(context, mArray.optJSONObject(i));
items.add(item);
if (item.selected) {
mSelected = i;
}
}
view.setNumColumns(Math.min(items.size(), maxColumns));
view.setOnItemClickListener(this);
// Despite what the docs say, setItemChecked was not moved into the AbsListView class until sometime between
// Android 2.3.7 and Android 4.0.3. For other versions the item won't be visually highlighted, BUT we really only
// mSelected will still be set so that we default to its behavior.
if (mSelected > -1) {
view.setItemChecked(mSelected, true);
}
mAdapter = new IconGridAdapter(context, -1, items);
view.setAdapter(mAdapter);
mView = view;
return mView;
}
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
mSelected = position;
notifyListeners(Integer.toString(position));
}
@Override
public Object getValue() {
return mSelected;
}
@Override
public boolean getScrollable() {
return true;
}
private class IconGridAdapter extends ArrayAdapter<IconGridItem> {
public IconGridAdapter(Context context, int resource, List<IconGridItem> items) {
super(context, resource, items);
}
@Override
public View getView(int position, View convert, ViewGroup parent) {
final Context context = parent.getContext();
if (convert == null) {
convert = LayoutInflater.from(context).inflate(R.layout.icon_grid_item, parent, false);
}
bindView(convert, context, position);
return convert;
}
private void bindView(View v, Context c, int position) {
final IconGridItem item = getItem(position);
final TextView text1 = (TextView) v.findViewById(android.R.id.text1);
text1.setText(item.label);
final TextView text2 = (TextView) v.findViewById(android.R.id.text2);
if (TextUtils.isEmpty(item.description)) {
text2.setVisibility(View.GONE);
} else {
text2.setVisibility(View.VISIBLE);
text2.setText(item.description);
}
final ImageView icon = (ImageView) v.findViewById(R.id.icon);
icon.setImageDrawable(item.icon);
ViewGroup.LayoutParams lp = icon.getLayoutParams();
lp.width = lp.height = mIconSize;
}
}
private class IconGridItem {
final String label;
final String description;
final boolean selected;
Drawable icon;
public IconGridItem(final Context context, final JSONObject obj) {
label = obj.optString("name");
final String iconUrl = obj.optString("iconUri");
description = obj.optString("description");
selected = obj.optBoolean("selected");
ResourceDrawableUtils.getDrawable(context, iconUrl, new ResourceDrawableUtils.BitmapLoader() {
@Override
public void onBitmapFound(Drawable d) {
icon = d;
if (mAdapter != null) {
mAdapter.notifyDataSetChanged();
}
}
});
}
}
}

View file

@ -0,0 +1,158 @@
/* 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.gecko.prompts;
import org.mozilla.gecko.GeckoAppShell;
import org.mozilla.gecko.util.ThreadUtils;
import org.mozilla.gecko.widget.GeckoActionProvider;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.widget.ListView;
import android.util.Log;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.List;
/**
* Shows a prompt letting the user pick from a list of intent handlers for a set of Intents or
* for a GeckoActionProvider. Basic usage:
* IntentChooserPrompt prompt = new IntentChooserPrompt(context, new Intent[] {
* ... // some intents
* });
* prompt.show("Title", context, new IntentHandler() {
* public void onIntentSelected(Intent intent, int position) { }
* public void onCancelled() { }
* });
**/
public class IntentChooserPrompt {
private static final String LOGTAG = "GeckoIntentChooser";
private final ArrayList<PromptListItem> mItems;
public IntentChooserPrompt(Context context, Intent[] intents) {
mItems = getItems(context, intents);
}
public IntentChooserPrompt(Context context, GeckoActionProvider provider) {
mItems = getItems(context, provider);
}
/* If an IntentHandler is passed in, will asynchronously call the handler when the dialog is closed
* Otherwise, will return the Intent that was chosen by the user. Must be called on the UI thread.
*/
public void show(final String title, final Context context, final IntentHandler handler) {
ThreadUtils.assertOnUiThread();
if (mItems.isEmpty()) {
Log.i(LOGTAG, "No activities for the intent chooser!");
handler.onCancelled();
return;
}
// If there's only one item in the intent list, just return it
if (mItems.size() == 1) {
handler.onIntentSelected(mItems.get(0).getIntent(), 0);
return;
}
final Prompt prompt = new Prompt(context, new Prompt.PromptCallback() {
@Override
public void onPromptFinished(String promptServiceResult) {
if (handler == null) {
return;
}
int itemId = -1;
try {
itemId = new JSONObject(promptServiceResult).getInt("button");
} catch (JSONException e) {
Log.e(LOGTAG, "result from promptservice was invalid: ", e);
}
if (itemId == -1) {
handler.onCancelled();
} else {
handler.onIntentSelected(mItems.get(itemId).getIntent(), itemId);
}
}
});
PromptListItem[] arrays = new PromptListItem[mItems.size()];
mItems.toArray(arrays);
prompt.show(title, "", arrays, ListView.CHOICE_MODE_NONE);
return;
}
// Whether or not any activities were found. Useful for checking if you should try a different Intent set
public boolean hasActivities(Context context) {
return mItems.isEmpty();
}
// Gets a list of PromptListItems for an Intent array
private ArrayList<PromptListItem> getItems(final Context context, Intent[] intents) {
final ArrayList<PromptListItem> items = new ArrayList<PromptListItem>();
// If we have intents, use them to build the initial list
for (final Intent intent : intents) {
items.addAll(getItemsForIntent(context, intent));
}
return items;
}
// Gets a list of PromptListItems for a GeckoActionProvider
private ArrayList<PromptListItem> getItems(final Context context, final GeckoActionProvider provider) {
final ArrayList<PromptListItem> items = new ArrayList<PromptListItem>();
// Add any intents from the provider.
final PackageManager packageManager = context.getPackageManager();
final ArrayList<ResolveInfo> infos = provider.getSortedActivities();
for (final ResolveInfo info : infos) {
items.add(getItemForResolveInfo(info, packageManager, provider.getIntent()));
}
return items;
}
private PromptListItem getItemForResolveInfo(ResolveInfo info, PackageManager pm, Intent intent) {
PromptListItem item = new PromptListItem(info.loadLabel(pm).toString());
item.setIcon(info.loadIcon(pm));
Intent i = new Intent(intent);
// These intents should be implicit.
i.setComponent(new ComponentName(info.activityInfo.applicationInfo.packageName,
info.activityInfo.name));
item.setIntent(new Intent(i));
return item;
}
private ArrayList<PromptListItem> getItemsForIntent(Context context, Intent intent) {
ArrayList<PromptListItem> items = new ArrayList<PromptListItem>();
PackageManager pm = context.getPackageManager();
List<ResolveInfo> lri = pm.queryIntentActivityOptions(GeckoAppShell.getGeckoInterface().getActivity().getComponentName(), null, intent, 0);
// If we didn't find any activities, just return the empty list
if (lri == null) {
return items;
}
// Otherwise, convert the ResolveInfo. Note we don't currently check for duplicates here.
for (ResolveInfo ri : lri) {
items.add(getItemForResolveInfo(ri, pm, intent));
}
return items;
}
}

View file

@ -0,0 +1,12 @@
/* 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.gecko.prompts;
import android.content.Intent;
public interface IntentHandler {
public void onIntentSelected(Intent intent, int position);
public void onCancelled();
}

View file

@ -0,0 +1,586 @@
/* -*- 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.gecko.prompts;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.mozilla.gecko.GeckoAppShell;
import org.mozilla.gecko.R;
import org.mozilla.gecko.gfx.LayerView;
import org.mozilla.gecko.util.ThreadUtils;
import org.mozilla.gecko.Tab;
import org.mozilla.gecko.Tabs;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnCancelListener;
import android.content.DialogInterface.OnClickListener;
import android.content.res.Resources;
import android.text.TextUtils;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.ScrollView;
import java.util.ArrayList;
public class Prompt implements OnClickListener, OnCancelListener, OnItemClickListener,
PromptInput.OnChangeListener, Tabs.OnTabsChangedListener {
private static final String LOGTAG = "GeckoPromptService";
private String[] mButtons;
private PromptInput[] mInputs;
private AlertDialog mDialog;
private int mDoubleTapButtonType;
private final LayoutInflater mInflater;
private final Context mContext;
private PromptCallback mCallback;
private String mGuid;
private PromptListAdapter mAdapter;
private static boolean mInitialized;
private static int mInputPaddingSize;
private int mTabId = Tabs.INVALID_TAB_ID;
private Object mPreviousInputValue = null;
public Prompt(Context context, PromptCallback callback) {
this(context);
mCallback = callback;
}
private Prompt(Context context) {
mContext = context;
mInflater = LayoutInflater.from(mContext);
if (!mInitialized) {
Resources res = mContext.getResources();
mInputPaddingSize = (int) (res.getDimension(R.dimen.prompt_service_inputs_padding));
mInitialized = true;
}
}
private View applyInputStyle(View view, PromptInput input) {
// Don't add padding to color picker views
if (input.canApplyInputStyle()) {
view.setPadding(mInputPaddingSize, 0, mInputPaddingSize, 0);
}
return view;
}
public void show(JSONObject message) {
String title = message.optString("title");
String text = message.optString("text");
mGuid = message.optString("guid");
mButtons = getStringArray(message, "buttons");
final int buttonCount = mButtons == null ? 0 : mButtons.length;
mDoubleTapButtonType = convertIndexToButtonType(message.optInt("doubleTapButton", -1), buttonCount);
mPreviousInputValue = null;
JSONArray inputs = getSafeArray(message, "inputs");
mInputs = new PromptInput[inputs.length()];
for (int i = 0; i < mInputs.length; i++) {
try {
mInputs[i] = PromptInput.getInput(inputs.getJSONObject(i));
mInputs[i].setListener(this);
} catch (Exception ex) { }
}
PromptListItem[] menuitems = PromptListItem.getArray(message.optJSONArray("listitems"));
String selected = message.optString("choiceMode");
int choiceMode = ListView.CHOICE_MODE_NONE;
if ("single".equals(selected)) {
choiceMode = ListView.CHOICE_MODE_SINGLE;
} else if ("multiple".equals(selected)) {
choiceMode = ListView.CHOICE_MODE_MULTIPLE;
}
if (message.has("tabId")) {
mTabId = message.optInt("tabId", Tabs.INVALID_TAB_ID);
}
show(title, text, menuitems, choiceMode);
}
private int convertIndexToButtonType(final int buttonIndex, final int buttonCount) {
if (buttonIndex < 0 || buttonIndex >= buttonCount) {
// All valid DialogInterface button values are < 0,
// so we return 0 as an invalid value.
return 0;
}
switch (buttonIndex) {
case 0:
return DialogInterface.BUTTON_POSITIVE;
case 1:
return DialogInterface.BUTTON_NEUTRAL;
case 2:
return DialogInterface.BUTTON_NEGATIVE;
default:
return 0;
}
}
public void show(String title, String text, PromptListItem[] listItems, int choiceMode) {
ThreadUtils.assertOnUiThread();
try {
create(title, text, listItems, choiceMode);
} catch (IllegalStateException ex) {
Log.i(LOGTAG, "Error building dialog", ex);
return;
}
if (mTabId != Tabs.INVALID_TAB_ID) {
Tabs.registerOnTabsChangedListener(this);
final Tab tab = Tabs.getInstance().getTab(mTabId);
if (Tabs.getInstance().getSelectedTab() == tab) {
mDialog.show();
}
} else {
mDialog.show();
}
}
@Override
public void onTabChanged(final Tab tab, final Tabs.TabEvents msg, final String data) {
if (tab != Tabs.getInstance().getTab(mTabId)) {
return;
}
switch (msg) {
case SELECTED:
Log.i(LOGTAG, "Selected");
mDialog.show();
break;
case UNSELECTED:
Log.i(LOGTAG, "Unselected");
mDialog.hide();
break;
case LOCATION_CHANGE:
Log.i(LOGTAG, "Location change");
mDialog.cancel();
break;
}
}
private void create(String title, String text, PromptListItem[] listItems, int choiceMode)
throws IllegalStateException {
AlertDialog.Builder builder = new AlertDialog.Builder(mContext);
if (!TextUtils.isEmpty(title)) {
// Long strings can delay showing the dialog, so we cap the number of characters shown to 256.
builder.setTitle(title.substring(0, Math.min(title.length(), 256)));
}
if (!TextUtils.isEmpty(text)) {
builder.setMessage(text);
}
// Because lists are currently added through the normal Android AlertBuilder interface, they're
// incompatible with also adding additional input elements to a dialog.
if (listItems != null && listItems.length > 0) {
addListItems(builder, listItems, choiceMode);
} else if (!addInputs(builder)) {
throw new IllegalStateException("Could not add inputs to dialog");
}
int length = mButtons == null ? 0 : mButtons.length;
if (length > 0) {
builder.setPositiveButton(mButtons[0], this);
if (length > 1) {
builder.setNeutralButton(mButtons[1], this);
if (length > 2) {
builder.setNegativeButton(mButtons[2], this);
}
}
}
mDialog = builder.create();
mDialog.setOnCancelListener(Prompt.this);
}
public void setButtons(String[] buttons) {
mButtons = buttons;
}
public void setInputs(PromptInput[] inputs) {
mInputs = inputs;
}
/* Adds to a result value from the lists that can be shown in dialogs.
* Will set the selected value(s) to the button attribute of the
* object that's passed in. If this is a multi-select dialog, sets a
* selected attribute to an array of booleans.
*/
private void addListResult(final JSONObject result, int which) {
if (mAdapter == null) {
return;
}
try {
JSONArray selected = new JSONArray();
// If the button has already been filled in
ArrayList<Integer> selectedItems = mAdapter.getSelected();
for (Integer item : selectedItems) {
selected.put(item);
}
// If we haven't assigned a button yet, or we assigned it to -1, assign the which
// parameter to both selected and the button.
if (!result.has("button") || result.optInt("button") == -1) {
if (!selectedItems.contains(which)) {
selected.put(which);
}
result.put("button", which);
}
result.put("list", selected);
} catch (JSONException ex) { }
}
/* Adds to a result value from the inputs that can be shown in dialogs.
* Each input will set its own value in the result.
*/
private void addInputValues(final JSONObject result) {
try {
if (mInputs != null) {
for (int i = 0; i < mInputs.length; i++) {
if (mInputs[i] != null) {
result.put(mInputs[i].getId(), mInputs[i].getValue());
}
}
}
} catch (JSONException ex) { }
}
/* Adds the selected button to a result. This should only be called if there
* are no lists shown on the dialog, since they also write their results to the button
* attribute.
*/
private void addButtonResult(final JSONObject result, int which) {
int button = -1;
switch (which) {
case DialogInterface.BUTTON_POSITIVE : button = 0; break;
case DialogInterface.BUTTON_NEUTRAL : button = 1; break;
case DialogInterface.BUTTON_NEGATIVE : button = 2; break;
}
try {
result.put("button", button);
} catch (JSONException ex) { }
}
@Override
public void onClick(DialogInterface dialog, int which) {
ThreadUtils.assertOnUiThread();
closeDialog(which);
}
/* Adds a set of list items to the prompt. This can be used for either context menu type dialogs, checked lists,
* or multiple selection lists.
*
* @param builder
* The alert builder currently building this dialog.
* @param listItems
* The items to add.
* @param choiceMode
* One of the ListView.CHOICE_MODE constants to designate whether this list shows checkmarks, radios buttons, or nothing.
*/
private void addListItems(AlertDialog.Builder builder, PromptListItem[] listItems, int choiceMode) {
switch (choiceMode) {
case ListView.CHOICE_MODE_MULTIPLE_MODAL:
case ListView.CHOICE_MODE_MULTIPLE:
addMultiSelectList(builder, listItems);
break;
case ListView.CHOICE_MODE_SINGLE:
addSingleSelectList(builder, listItems);
break;
case ListView.CHOICE_MODE_NONE:
default:
addMenuList(builder, listItems);
}
}
/* Shows a multi-select list with checkmarks on the side. Android doesn't support using an adapter for
* multi-choice lists by default so instead we insert our own custom list so that we can do fancy things
* to the rows like disabling/indenting them.
*
* @param builder
* The alert builder currently building this dialog.
* @param listItems
* The items to add.
*/
private void addMultiSelectList(AlertDialog.Builder builder, PromptListItem[] listItems) {
ListView listView = (ListView) mInflater.inflate(R.layout.select_dialog_list, null);
listView.setOnItemClickListener(this);
listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
mAdapter = new PromptListAdapter(mContext, R.layout.select_dialog_multichoice, listItems);
listView.setAdapter(mAdapter);
builder.setView(listView);
}
/* Shows a single-select list with radio boxes on the side.
*
* @param builder
* the alert builder currently building this dialog.
* @param listItems
* The items to add.
*/
private void addSingleSelectList(AlertDialog.Builder builder, PromptListItem[] listItems) {
mAdapter = new PromptListAdapter(mContext, R.layout.select_dialog_singlechoice, listItems);
builder.setSingleChoiceItems(mAdapter, mAdapter.getSelectedIndex(), new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
// The adapter isn't aware of single vs. multi choice lists, so manually
// clear any other selected items first.
ArrayList<Integer> selected = mAdapter.getSelected();
for (Integer sel : selected) {
mAdapter.toggleSelected(sel);
}
// Now select this item.
mAdapter.toggleSelected(which);
closeIfNoButtons(which);
}
});
}
/* Shows a single-select list.
*
* @param builder
* the alert builder currently building this dialog.
* @param listItems
* The items to add.
*/
private void addMenuList(AlertDialog.Builder builder, PromptListItem[] listItems) {
mAdapter = new PromptListAdapter(mContext, android.R.layout.simple_list_item_1, listItems);
builder.setAdapter(mAdapter, this);
}
/* Wraps an input in a linearlayout. We do this so that we can set padding that appears outside the background
* drawable for the view.
*/
private View wrapInput(final PromptInput input) {
final LinearLayout linearLayout = new LinearLayout(mContext);
linearLayout.setOrientation(LinearLayout.VERTICAL);
applyInputStyle(linearLayout, input);
linearLayout.addView(input.getView(mContext));
return linearLayout;
}
/* Add the requested input elements to the dialog.
*
* @param builder
* the alert builder currently building this dialog.
* @return
* return true if the inputs were added successfully. This may fail
* if the requested input is compatible with this Android version.
*/
private boolean addInputs(AlertDialog.Builder builder) {
int length = mInputs == null ? 0 : mInputs.length;
if (length == 0) {
return true;
}
try {
View root = null;
boolean scrollable = false; // If any of the inputs are scrollable, we won't wrap this in a ScrollView
if (length == 1) {
root = wrapInput(mInputs[0]);
scrollable |= mInputs[0].getScrollable();
} else if (length > 1) {
LinearLayout linearLayout = new LinearLayout(mContext);
linearLayout.setOrientation(LinearLayout.VERTICAL);
for (int i = 0; i < length; i++) {
View content = wrapInput(mInputs[i]);
linearLayout.addView(content);
scrollable |= mInputs[i].getScrollable();
}
root = linearLayout;
}
if (scrollable) {
// If we're showing some sort of scrollable list, force an inverse background.
builder.setInverseBackgroundForced(true);
builder.setView(root);
} else {
ScrollView view = new ScrollView(mContext);
view.addView(root);
builder.setView(view);
}
} catch (Exception ex) {
Log.e(LOGTAG, "Error showing prompt inputs", ex);
// We cannot display these input widgets with this sdk version,
// do not display any dialog and finish the prompt now.
cancelDialog();
return false;
}
return true;
}
/* AdapterView.OnItemClickListener
* Called when a list item is clicked
*/
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
ThreadUtils.assertOnUiThread();
mAdapter.toggleSelected(position);
// If there are no buttons on this dialog, then we take selecting an item as a sign to close
// the dialog. Note that means it will be hard to select multiple things in this list, but
// given there is no way to confirm+close the dialog, it seems reasonable.
closeIfNoButtons(position);
}
private boolean closeIfNoButtons(int selected) {
ThreadUtils.assertOnUiThread();
if (mButtons == null || mButtons.length == 0) {
closeDialog(selected);
return true;
}
return false;
}
/* @DialogInterface.OnCancelListener
* Called when the user hits back to cancel a dialog. The dialog will close itself when this
* ends. Setup the correct return values here.
*
* @param aDialog
* A dialog interface for the dialog that's being closed.
*/
@Override
public void onCancel(DialogInterface aDialog) {
ThreadUtils.assertOnUiThread();
cancelDialog();
}
/* Called in situations where we want to cancel the dialog . This can happen if the user hits back,
* or if the dialog can't be created because of invalid JSON.
*/
private void cancelDialog() {
JSONObject ret = new JSONObject();
try {
ret.put("button", -1);
} catch (Exception ex) { }
addInputValues(ret);
notifyClosing(ret);
}
/* Called any time we're closing the dialog to cleanup and notify listeners that the dialog
* is closing.
*/
private void closeDialog(int which) {
JSONObject ret = new JSONObject();
mDialog.dismiss();
addButtonResult(ret, which);
addListResult(ret, which);
addInputValues(ret);
notifyClosing(ret);
}
/* Called any time we're closing the dialog to cleanup and notify listeners that the dialog
* is closing.
*/
private void notifyClosing(JSONObject aReturn) {
try {
aReturn.put("guid", mGuid);
} catch (JSONException ex) { }
if (mTabId != Tabs.INVALID_TAB_ID) {
Tabs.unregisterOnTabsChangedListener(this);
}
if (mCallback != null) {
mCallback.onPromptFinished(aReturn.toString());
}
}
// Called when the prompt inputs on the dialog change
@Override
public void onChange(PromptInput input) {
// If there are no buttons on this dialog, assuming that "changing" an input
// means something was selected and we can close. This provides a way to tap
// on a list item and close the dialog automatically.
if (!closeIfNoButtons(-1)) {
// Alternatively, if a default button has been specified for double tapping,
// we want to close the dialog if the same input value has been transmitted
// twice in a row.
closeIfDoubleTapEnabled(input.getValue());
}
}
private boolean closeIfDoubleTapEnabled(Object inputValue) {
if (mDoubleTapButtonType != 0 && inputValue == mPreviousInputValue) {
closeDialog(mDoubleTapButtonType);
return true;
}
mPreviousInputValue = inputValue;
return false;
}
private static JSONArray getSafeArray(JSONObject json, String key) {
try {
return json.getJSONArray(key);
} catch (Exception e) {
return new JSONArray();
}
}
public static String[] getStringArray(JSONObject aObject, String aName) {
JSONArray items = getSafeArray(aObject, aName);
int length = items.length();
String[] list = new String[length];
for (int i = 0; i < length; i++) {
try {
list[i] = items.getString(i);
} catch (Exception ex) { }
}
return list;
}
private static boolean[] getBooleanArray(JSONObject aObject, String aName) {
JSONArray items = new JSONArray();
try {
items = aObject.getJSONArray(aName);
} catch (Exception ex) { return null; }
int length = items.length();
boolean[] list = new boolean[length];
for (int i = 0; i < length; i++) {
try {
list[i] = items.getBoolean(i);
} catch (Exception ex) { }
}
return list;
}
public interface PromptCallback {
/**
* Called when the Prompt has been completed (i.e. when the user has selected an item or action in the Prompt).
* This callback is run on the UI thread.
*/
public void onPromptFinished(String jsonResult);
}
}

View file

@ -0,0 +1,398 @@
/* -*- 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.gecko.prompts;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.GregorianCalendar;
import org.json.JSONObject;
import org.mozilla.gecko.AppConstants.Versions;
import org.mozilla.gecko.widget.AllCapsTextView;
import org.mozilla.gecko.widget.DateTimePicker;
import android.content.Context;
import android.content.res.Configuration;
import android.support.design.widget.TextInputLayout;
import android.support.v7.widget.AppCompatCheckBox;
import android.text.Html;
import android.text.InputType;
import android.text.TextUtils;
import android.text.format.DateFormat;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup.LayoutParams;
import android.view.inputmethod.InputMethodManager;
import android.widget.ArrayAdapter;
import android.widget.CheckBox;
import android.widget.DatePicker;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.TimePicker;
public abstract class PromptInput {
protected final String mLabel;
protected final String mType;
protected final String mId;
protected final String mValue;
protected final String mMinValue;
protected final String mMaxValue;
protected OnChangeListener mListener;
protected View mView;
public static final String LOGTAG = "GeckoPromptInput";
public interface OnChangeListener {
void onChange(PromptInput input);
}
public void setListener(OnChangeListener listener) {
mListener = listener;
}
public static class EditInput extends PromptInput {
protected final String mHint;
protected final boolean mAutofocus;
public static final String INPUT_TYPE = "textbox";
public EditInput(JSONObject object) {
super(object);
mHint = object.optString("hint");
mAutofocus = object.optBoolean("autofocus");
}
@Override
public View getView(final Context context) throws UnsupportedOperationException {
EditText input = new EditText(context);
input.setInputType(InputType.TYPE_CLASS_TEXT);
input.setText(mValue);
if (!TextUtils.isEmpty(mHint)) {
input.setHint(mHint);
}
if (mAutofocus) {
input.setOnFocusChangeListener(new View.OnFocusChangeListener() {
@Override
public void onFocusChange(View v, boolean hasFocus) {
if (hasFocus) {
((InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE)).showSoftInput(v, 0);
}
}
});
input.requestFocus();
}
TextInputLayout inputLayout = new TextInputLayout(context);
inputLayout.addView(input);
mView = (View) inputLayout;
return mView;
}
@Override
public Object getValue() {
final TextInputLayout inputLayout = (TextInputLayout) mView;
return inputLayout.getEditText().getText();
}
}
public static class NumberInput extends EditInput {
public static final String INPUT_TYPE = "number";
public NumberInput(JSONObject obj) {
super(obj);
}
@Override
public View getView(final Context context) throws UnsupportedOperationException {
final TextInputLayout inputLayout = (TextInputLayout) super.getView(context);
final EditText input = inputLayout.getEditText();
input.setRawInputType(Configuration.KEYBOARD_12KEY);
input.setInputType(InputType.TYPE_CLASS_NUMBER |
InputType.TYPE_NUMBER_FLAG_SIGNED);
return input;
}
}
public static class PasswordInput extends EditInput {
public static final String INPUT_TYPE = "password";
public PasswordInput(JSONObject obj) {
super(obj);
}
@Override
public View getView(Context context) throws UnsupportedOperationException {
final TextInputLayout inputLayout = (TextInputLayout) super.getView(context);
inputLayout.getEditText().setInputType(InputType.TYPE_CLASS_TEXT |
InputType.TYPE_TEXT_VARIATION_PASSWORD |
InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
return inputLayout;
}
}
public static class CheckboxInput extends PromptInput {
public static final String INPUT_TYPE = "checkbox";
private final boolean mChecked;
public CheckboxInput(JSONObject obj) {
super(obj);
mChecked = obj.optBoolean("checked");
}
@Override
public View getView(Context context) throws UnsupportedOperationException {
final CheckBox checkbox = new AppCompatCheckBox(context);
checkbox.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
checkbox.setText(mLabel);
checkbox.setChecked(mChecked);
mView = (View)checkbox;
return mView;
}
@Override
public Object getValue() {
CheckBox checkbox = (CheckBox)mView;
return checkbox.isChecked() ? Boolean.TRUE : Boolean.FALSE;
}
}
public static class DateTimeInput extends PromptInput {
public static final String[] INPUT_TYPES = new String[] {
"date",
"week",
"time",
"datetime-local",
"datetime",
"month"
};
public DateTimeInput(JSONObject obj) {
super(obj);
}
@Override
public View getView(Context context) throws UnsupportedOperationException {
if (mType.equals("date")) {
try {
DateTimePicker input = new DateTimePicker(context, "yyyy-MM-dd", mValue,
DateTimePicker.PickersState.DATE, mMinValue, mMaxValue);
input.toggleCalendar(true);
mView = (View)input;
} catch (UnsupportedOperationException ex) {
// We can't use our custom version of the DatePicker widget because the sdk is too old.
// But we can fallback on the native one.
DatePicker input = new DatePicker(context);
try {
if (!TextUtils.isEmpty(mValue)) {
GregorianCalendar calendar = new GregorianCalendar();
calendar.setTime(new SimpleDateFormat("yyyy-MM-dd").parse(mValue));
input.updateDate(calendar.get(Calendar.YEAR),
calendar.get(Calendar.MONTH),
calendar.get(Calendar.DAY_OF_MONTH));
}
} catch (Exception e) {
Log.e(LOGTAG, "error parsing format string: " + e);
}
mView = (View)input;
}
} else if (mType.equals("week")) {
DateTimePicker input = new DateTimePicker(context, "yyyy-'W'ww", mValue,
DateTimePicker.PickersState.WEEK, mMinValue, mMaxValue);
mView = (View)input;
} else if (mType.equals("time")) {
TimePicker input = new TimePicker(context);
input.setIs24HourView(DateFormat.is24HourFormat(context));
GregorianCalendar calendar = new GregorianCalendar();
if (!TextUtils.isEmpty(mValue)) {
try {
calendar.setTime(new SimpleDateFormat("HH:mm").parse(mValue));
} catch (Exception e) { }
}
input.setCurrentHour(calendar.get(GregorianCalendar.HOUR_OF_DAY));
input.setCurrentMinute(calendar.get(GregorianCalendar.MINUTE));
mView = (View)input;
} else if (mType.equals("datetime-local") || mType.equals("datetime")) {
DateTimePicker input = new DateTimePicker(context, "yyyy-MM-dd HH:mm", mValue.replace("T", " ").replace("Z", ""),
DateTimePicker.PickersState.DATETIME,
mMinValue.replace("T", " ").replace("Z", ""), mMaxValue.replace("T", " ").replace("Z", ""));
input.toggleCalendar(true);
mView = (View)input;
} else if (mType.equals("month")) {
DateTimePicker input = new DateTimePicker(context, "yyyy-MM", mValue,
DateTimePicker.PickersState.MONTH, mMinValue, mMaxValue);
mView = (View)input;
}
return mView;
}
private static String formatDateString(String dateFormat, Calendar calendar) {
return new SimpleDateFormat(dateFormat).format(calendar.getTime());
}
@Override
public Object getValue() {
if (mType.equals("time")) {
TimePicker tp = (TimePicker)mView;
GregorianCalendar calendar =
new GregorianCalendar(0, 0, 0, tp.getCurrentHour(), tp.getCurrentMinute());
return formatDateString("HH:mm", calendar);
} else {
DateTimePicker dp = (DateTimePicker)mView;
GregorianCalendar calendar = new GregorianCalendar();
calendar.setTimeInMillis(dp.getTimeInMillis());
if (mType.equals("date")) {
return formatDateString("yyyy-MM-dd", calendar);
} else if (mType.equals("week")) {
return formatDateString("yyyy-'W'ww", calendar);
} else if (mType.equals("datetime-local")) {
return formatDateString("yyyy-MM-dd'T'HH:mm", calendar);
} else if (mType.equals("datetime")) {
calendar.set(GregorianCalendar.ZONE_OFFSET, 0);
calendar.setTimeInMillis(dp.getTimeInMillis());
return formatDateString("yyyy-MM-dd'T'HH:mm'Z'", calendar);
} else if (mType.equals("month")) {
return formatDateString("yyyy-MM", calendar);
}
}
return super.getValue();
}
}
public static class MenulistInput extends PromptInput {
public static final String INPUT_TYPE = "menulist";
private static String[] mListitems;
private static int mSelected;
public Spinner spinner;
public AllCapsTextView textView;
public MenulistInput(JSONObject obj) {
super(obj);
mListitems = Prompt.getStringArray(obj, "values");
mSelected = obj.optInt("selected");
}
@Override
public View getView(final Context context) throws UnsupportedOperationException {
spinner = new Spinner(context, Spinner.MODE_DIALOG);
try {
if (mListitems.length > 0) {
ArrayAdapter<String> adapter = new ArrayAdapter<String>(context, android.R.layout.simple_spinner_item, mListitems);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
spinner.setAdapter(adapter);
spinner.setSelection(mSelected);
}
} catch (Exception ex) {
}
if (!TextUtils.isEmpty(mLabel)) {
LinearLayout container = new LinearLayout(context);
container.setOrientation(LinearLayout.VERTICAL);
textView = new AllCapsTextView(context, null);
textView.setText(mLabel);
container.addView(textView);
container.addView(spinner);
return container;
}
return spinner;
}
@Override
public Object getValue() {
return spinner.getSelectedItemPosition();
}
}
public static class LabelInput extends PromptInput {
public static final String INPUT_TYPE = "label";
public LabelInput(JSONObject obj) {
super(obj);
}
@Override
public View getView(Context context) throws UnsupportedOperationException {
// not really an input, but a way to add labels and such to the dialog
TextView view = new TextView(context);
view.setText(Html.fromHtml(mLabel));
mView = view;
return mView;
}
}
public PromptInput(JSONObject obj) {
mLabel = obj.optString("label");
mType = obj.optString("type");
String id = obj.optString("id");
mId = TextUtils.isEmpty(id) ? mType : id;
mValue = obj.optString("value");
mMaxValue = obj.optString("max");
mMinValue = obj.optString("min");
}
public static PromptInput getInput(JSONObject obj) {
String type = obj.optString("type");
switch (type) {
case EditInput.INPUT_TYPE:
return new EditInput(obj);
case NumberInput.INPUT_TYPE:
return new NumberInput(obj);
case PasswordInput.INPUT_TYPE:
return new PasswordInput(obj);
case CheckboxInput.INPUT_TYPE:
return new CheckboxInput(obj);
case MenulistInput.INPUT_TYPE:
return new MenulistInput(obj);
case LabelInput.INPUT_TYPE:
return new LabelInput(obj);
case IconGridInput.INPUT_TYPE:
return new IconGridInput(obj);
case ColorPickerInput.INPUT_TYPE:
return new ColorPickerInput(obj);
case TabInput.INPUT_TYPE:
return new TabInput(obj);
default:
for (String dtType : DateTimeInput.INPUT_TYPES) {
if (dtType.equals(type)) {
return new DateTimeInput(obj);
}
}
break;
}
return null;
}
public abstract View getView(Context context) throws UnsupportedOperationException;
public String getId() {
return mId;
}
public Object getValue() {
return null;
}
public boolean getScrollable() {
return false;
}
public boolean canApplyInputStyle() {
return true;
}
protected void notifyListeners(String val) {
if (mListener != null) {
mListener.onChange(this);
}
}
}

View file

@ -0,0 +1,281 @@
package org.mozilla.gecko.prompts;
import org.mozilla.gecko.R;
import org.mozilla.gecko.Telemetry;
import org.mozilla.gecko.TelemetryContract;
import org.mozilla.gecko.menu.MenuItemSwitcherLayout;
import org.mozilla.gecko.widget.GeckoActionProvider;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CheckedTextView;
import android.widget.TextView;
import android.widget.ListView;
import android.widget.ArrayAdapter;
import android.util.TypedValue;
import java.util.ArrayList;
public class PromptListAdapter extends ArrayAdapter<PromptListItem> {
private static final int VIEW_TYPE_ITEM = 0;
private static final int VIEW_TYPE_GROUP = 1;
private static final int VIEW_TYPE_ACTIONS = 2;
private static final int VIEW_TYPE_COUNT = 3;
private static final String LOGTAG = "GeckoPromptListAdapter";
private final int mResourceId;
private Drawable mBlankDrawable;
private Drawable mMoreDrawable;
private static int mGroupPaddingSize;
private static int mLeftRightTextWithIconPadding;
private static int mTopBottomTextWithIconPadding;
private static int mIconSize;
private static int mMinRowSize;
private static int mIconTextPadding;
private static float mTextSize;
private static boolean mInitialized;
PromptListAdapter(Context context, int textViewResourceId, PromptListItem[] objects) {
super(context, textViewResourceId, objects);
mResourceId = textViewResourceId;
init();
}
private void init() {
if (!mInitialized) {
Resources res = getContext().getResources();
mGroupPaddingSize = (int) (res.getDimension(R.dimen.prompt_service_group_padding_size));
mLeftRightTextWithIconPadding = (int) (res.getDimension(R.dimen.prompt_service_left_right_text_with_icon_padding));
mTopBottomTextWithIconPadding = (int) (res.getDimension(R.dimen.prompt_service_top_bottom_text_with_icon_padding));
mIconTextPadding = (int) (res.getDimension(R.dimen.prompt_service_icon_text_padding));
mIconSize = (int) (res.getDimension(R.dimen.prompt_service_icon_size));
mMinRowSize = (int) (res.getDimension(R.dimen.menu_item_row_height));
mTextSize = res.getDimension(R.dimen.menu_item_textsize);
mInitialized = true;
}
}
@Override
public int getItemViewType(int position) {
PromptListItem item = getItem(position);
if (item.isGroup) {
return VIEW_TYPE_GROUP;
} else if (item.showAsActions) {
return VIEW_TYPE_ACTIONS;
} else {
return VIEW_TYPE_ITEM;
}
}
@Override
public int getViewTypeCount() {
return VIEW_TYPE_COUNT;
}
private Drawable getMoreDrawable(Resources res) {
if (mMoreDrawable == null) {
mMoreDrawable = res.getDrawable(R.drawable.menu_item_more);
}
return mMoreDrawable;
}
private Drawable getBlankDrawable(Resources res) {
if (mBlankDrawable == null) {
mBlankDrawable = res.getDrawable(R.drawable.blank);
}
return mBlankDrawable;
}
public void toggleSelected(int position) {
PromptListItem item = getItem(position);
item.setSelected(!item.getSelected());
}
private void maybeUpdateIcon(PromptListItem item, TextView t) {
if (item.getIcon() == null && !item.inGroup && !item.isParent) {
t.setCompoundDrawablesWithIntrinsicBounds(null, null, null, null);
return;
}
Drawable d = null;
Resources res = getContext().getResources();
// Set the padding between the icon and the text.
t.setCompoundDrawablePadding(mIconTextPadding);
if (item.getIcon() != null) {
// We want the icon to be of a specific size. Some do not
// follow this rule so we have to resize them.
Bitmap bitmap = ((BitmapDrawable) item.getIcon()).getBitmap();
d = new BitmapDrawable(res, Bitmap.createScaledBitmap(bitmap, mIconSize, mIconSize, true));
} else if (item.inGroup) {
// We don't currently support "indenting" items with icons
d = getBlankDrawable(res);
}
Drawable moreDrawable = null;
if (item.isParent) {
moreDrawable = getMoreDrawable(res);
}
if (d != null || moreDrawable != null) {
t.setCompoundDrawablesWithIntrinsicBounds(d, null, moreDrawable, null);
}
}
private void maybeUpdateCheckedState(ListView list, int position, PromptListItem item, ViewHolder viewHolder) {
viewHolder.textView.setEnabled(!item.disabled && !item.isGroup);
viewHolder.textView.setClickable(item.isGroup || item.disabled);
if (viewHolder.textView instanceof CheckedTextView) {
// Apparently just using ct.setChecked(true) doesn't work, so this
// is stolen from the android source code as a way to set the checked
// state of these items
list.setItemChecked(position, item.getSelected());
}
}
boolean isSelected(int position) {
return getItem(position).getSelected();
}
ArrayList<Integer> getSelected() {
int length = getCount();
ArrayList<Integer> selected = new ArrayList<Integer>();
for (int i = 0; i < length; i++) {
if (isSelected(i)) {
selected.add(i);
}
}
return selected;
}
int getSelectedIndex() {
int length = getCount();
for (int i = 0; i < length; i++) {
if (isSelected(i)) {
return i;
}
}
return -1;
}
private View getActionView(PromptListItem item, final ListView list, final int position) {
final GeckoActionProvider provider = GeckoActionProvider.getForType(item.getIntent().getType(), getContext());
provider.setIntent(item.getIntent());
final MenuItemSwitcherLayout view = (MenuItemSwitcherLayout) provider.onCreateActionView(
GeckoActionProvider.ActionViewType.CONTEXT_MENU);
// If a quickshare button is clicked, we need to close the dialog.
view.addActionButtonClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ListView.OnItemClickListener listener = list.getOnItemClickListener();
if (listener != null) {
listener.onItemClick(list, view, position, position);
}
}
});
return view;
}
private void updateActionView(final PromptListItem item, final MenuItemSwitcherLayout view, final ListView list, final int position) {
view.setTitle(item.label);
view.setIcon(item.getIcon());
view.setSubMenuIndicator(item.isParent);
// If the share button is clicked, we need to close the dialog and then show an intent chooser
view.setMenuItemClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ListView.OnItemClickListener listener = list.getOnItemClickListener();
if (listener != null) {
listener.onItemClick(list, view, position, position);
}
final GeckoActionProvider provider = GeckoActionProvider.getForType(item.getIntent().getType(), getContext());
IntentChooserPrompt prompt = new IntentChooserPrompt(getContext(), provider);
prompt.show(item.label, getContext(), new IntentHandler() {
@Override
public void onIntentSelected(final Intent intent, final int p) {
provider.chooseActivity(p);
// Context: Sharing via content contextmenu list (no explicit session is active)
Telemetry.sendUIEvent(TelemetryContract.Event.SHARE, TelemetryContract.Method.LIST, "promptlist");
}
@Override
public void onCancelled() {
// do nothing
}
});
}
});
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
PromptListItem item = getItem(position);
int type = getItemViewType(position);
ViewHolder viewHolder = null;
if (convertView == null) {
if (type == VIEW_TYPE_ACTIONS) {
convertView = getActionView(item, (ListView) parent, position);
} else {
int resourceId = mResourceId;
if (item.isGroup) {
resourceId = R.layout.list_item_header;
}
LayoutInflater mInflater = LayoutInflater.from(getContext());
convertView = mInflater.inflate(resourceId, null);
convertView.setMinimumHeight(mMinRowSize);
TextView tv = (TextView) convertView.findViewById(android.R.id.text1);
tv.setTextSize(TypedValue.COMPLEX_UNIT_PX, mTextSize);
viewHolder = new ViewHolder(tv, tv.getPaddingLeft(), tv.getPaddingRight(),
tv.getPaddingTop(), tv.getPaddingBottom());
convertView.setTag(viewHolder);
}
} else {
viewHolder = (ViewHolder) convertView.getTag();
}
if (type == VIEW_TYPE_ACTIONS) {
updateActionView(item, (MenuItemSwitcherLayout) convertView, (ListView) parent, position);
} else {
viewHolder.textView.setText(item.label);
maybeUpdateCheckedState((ListView) parent, position, item, viewHolder);
maybeUpdateIcon(item, viewHolder.textView);
}
return convertView;
}
private static class ViewHolder {
public final TextView textView;
public final int paddingLeft;
public final int paddingRight;
public final int paddingTop;
public final int paddingBottom;
ViewHolder(TextView aTextView, int aLeft, int aRight, int aTop, int aBottom) {
textView = aTextView;
paddingLeft = aLeft;
paddingRight = aRight;
paddingTop = aTop;
paddingBottom = aBottom;
}
}
}

View file

@ -0,0 +1,128 @@
package org.mozilla.gecko.prompts;
import org.json.JSONException;
import org.mozilla.gecko.IntentHelper;
import org.mozilla.gecko.GeckoAppShell;
import org.mozilla.gecko.ThumbnailHelper;
import org.mozilla.gecko.util.ResourceDrawableUtils;
import org.mozilla.gecko.widget.GeckoActionProvider;
import org.json.JSONArray;
import org.json.JSONObject;
import android.content.Context;
import android.content.Intent;
import android.graphics.drawable.Drawable;
import java.util.List;
import java.util.ArrayList;
// This class should die and be replaced with normal menu items
public class PromptListItem {
private static final String LOGTAG = "GeckoPromptListItem";
public final String label;
public final boolean isGroup;
public final boolean inGroup;
public final boolean disabled;
public final int id;
public final boolean showAsActions;
public final boolean isParent;
public Intent mIntent;
public boolean mSelected;
public Drawable mIcon;
PromptListItem(JSONObject aObject) {
Context context = GeckoAppShell.getContext();
label = aObject.isNull("label") ? "" : aObject.optString("label");
isGroup = aObject.optBoolean("isGroup");
inGroup = aObject.optBoolean("inGroup");
disabled = aObject.optBoolean("disabled");
id = aObject.optInt("id");
mSelected = aObject.optBoolean("selected");
JSONObject obj = aObject.optJSONObject("showAsActions");
if (obj != null) {
showAsActions = true;
String uri = obj.isNull("uri") ? "" : obj.optString("uri");
String type = obj.isNull("type") ? GeckoActionProvider.DEFAULT_MIME_TYPE :
obj.optString("type", GeckoActionProvider.DEFAULT_MIME_TYPE);
mIntent = IntentHelper.getShareIntent(context, uri, type, "");
isParent = true;
} else {
mIntent = null;
showAsActions = false;
// Support both "isParent" (backwards compat for older consumers), and "menu" for the new Tabbed prompt ui.
isParent = aObject.optBoolean("isParent") || aObject.optBoolean("menu");
}
final String iconStr = aObject.optString("icon");
if (iconStr != null) {
final ResourceDrawableUtils.BitmapLoader loader = new ResourceDrawableUtils.BitmapLoader() {
@Override
public void onBitmapFound(Drawable d) {
mIcon = d;
}
};
if (iconStr.startsWith("thumbnail:")) {
final int id = Integer.parseInt(iconStr.substring(10), 10);
ThumbnailHelper.getInstance().getAndProcessThumbnailFor(id, loader);
} else {
ResourceDrawableUtils.getDrawable(context, iconStr, loader);
}
}
}
public void setIntent(Intent i) {
mIntent = i;
}
public Intent getIntent() {
return mIntent;
}
public void setIcon(Drawable icon) {
mIcon = icon;
}
public Drawable getIcon() {
return mIcon;
}
public void setSelected(boolean selected) {
mSelected = selected;
}
public boolean getSelected() {
return mSelected;
}
public PromptListItem(String aLabel) {
label = aLabel;
isGroup = false;
inGroup = false;
isParent = false;
disabled = false;
id = 0;
showAsActions = false;
}
static PromptListItem[] getArray(JSONArray items) {
if (items == null) {
return new PromptListItem[0];
}
int length = items.length();
List<PromptListItem> list = new ArrayList<>(length);
for (int i = 0; i < length; i++) {
try {
PromptListItem item = new PromptListItem(items.getJSONObject(i));
list.add(item);
} catch (JSONException ex) { }
}
return list.toArray(new PromptListItem[length]);
}
}

View file

@ -0,0 +1,72 @@
/* -*- 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.gecko.prompts;
import org.json.JSONException;
import org.json.JSONObject;
import org.mozilla.gecko.EventDispatcher;
import org.mozilla.gecko.GeckoApp;
import org.mozilla.gecko.GeckoAppShell;
import org.mozilla.gecko.util.GeckoEventListener;
import org.mozilla.gecko.util.ThreadUtils;
import android.content.Context;
import android.util.Log;
public class PromptService implements GeckoEventListener {
private static final String LOGTAG = "GeckoPromptService";
private final Context mContext;
public PromptService(Context context) {
GeckoApp.getEventDispatcher().registerGeckoThreadListener(this,
"Prompt:Show",
"Prompt:ShowTop");
mContext = context;
}
public void destroy() {
GeckoApp.getEventDispatcher().unregisterGeckoThreadListener(this,
"Prompt:Show",
"Prompt:ShowTop");
}
public void show(final String aTitle, final String aText, final PromptListItem[] aMenuList,
final int aChoiceMode, final Prompt.PromptCallback callback) {
// The dialog must be created on the UI thread.
ThreadUtils.postToUiThread(new Runnable() {
@Override
public void run() {
Prompt p;
p = new Prompt(mContext, callback);
p.show(aTitle, aText, aMenuList, aChoiceMode);
}
});
}
// GeckoEventListener implementation
@Override
public void handleMessage(String event, final JSONObject message) {
// The dialog must be created on the UI thread.
ThreadUtils.postToUiThread(new Runnable() {
@Override
public void run() {
Prompt p;
p = new Prompt(mContext, new Prompt.PromptCallback() {
@Override
public void onPromptFinished(String jsonResult) {
try {
EventDispatcher.sendResponse(message, new JSONObject(jsonResult));
} catch (JSONException ex) {
Log.i(LOGTAG, "Error building json response", ex);
}
}
});
p.show(message);
}
});
}
}

View file

@ -0,0 +1,107 @@
/* -*- 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.gecko.prompts;
import java.util.LinkedHashMap;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.mozilla.gecko.AppConstants.Versions;
import org.mozilla.gecko.R;
import org.mozilla.gecko.util.ThreadUtils;
import android.content.Context;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ListView;
import android.widget.TabHost;
import android.widget.TextView;
public class TabInput extends PromptInput implements AdapterView.OnItemClickListener {
public static final String INPUT_TYPE = "tabs";
public static final String LOGTAG = "GeckoTabInput";
/* Keeping the order of this in sync with the JSON is important. */
final private LinkedHashMap<String, PromptListItem[]> mTabs;
private TabHost mHost;
private int mPosition;
public TabInput(JSONObject obj) {
super(obj);
mTabs = new LinkedHashMap<String, PromptListItem[]>();
try {
JSONArray tabs = obj.getJSONArray("items");
for (int i = 0; i < tabs.length(); i++) {
JSONObject tab = tabs.getJSONObject(i);
String title = tab.getString("label");
JSONArray items = tab.getJSONArray("items");
mTabs.put(title, PromptListItem.getArray(items));
}
} catch (JSONException ex) {
Log.e(LOGTAG, "Exception", ex);
}
}
@Override
public View getView(final Context context) throws UnsupportedOperationException {
final LayoutInflater inflater = LayoutInflater.from(context);
mHost = (TabHost) inflater.inflate(R.layout.tab_prompt_input, null);
mHost.setup();
for (String title : mTabs.keySet()) {
final TabHost.TabSpec spec = mHost.newTabSpec(title);
spec.setContent(new TabHost.TabContentFactory() {
@Override
public View createTabContent(final String tag) {
PromptListAdapter adapter = new PromptListAdapter(context, android.R.layout.simple_list_item_1, mTabs.get(tag));
ListView listView = new ListView(context);
listView.setCacheColorHint(0);
listView.setOnItemClickListener(TabInput.this);
listView.setAdapter(adapter);
return listView;
}
});
spec.setIndicator(title);
mHost.addTab(spec);
}
mView = mHost;
return mHost;
}
@Override
public Object getValue() {
JSONObject obj = new JSONObject();
try {
obj.put("tab", mHost.getCurrentTab());
obj.put("item", mPosition);
} catch (JSONException ex) { }
return obj;
}
@Override
public boolean getScrollable() {
return true;
}
@Override
public boolean canApplyInputStyle() {
return false;
}
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
ThreadUtils.assertOnUiThread();
mPosition = position;
notifyListeners(Integer.toString(position));
}
}