Bug 920170 - Allow adding a grid of icons to prompts. r=margaret
authorWes Johnston <wjohnston@mozilla.com>
Tue, 15 Oct 2013 08:53:16 -0700
changeset 150768 1960e499d49ff65ed613b0ed1dbc5f95c98b5502
parent 150767 863087fdc7da7df0ced69f09c4e7d63b560db297
child 150769 79360e1c33a56ca48ac62548c7f436fa445a04ab
child 150881 9f63bbc005279333095be0000696dcf12629b16d
push id3038
push userwjohnston@mozilla.com
push dateTue, 15 Oct 2013 15:57:39 +0000
treeherderfx-team@1960e499d49f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmargaret
bugs920170
milestone27.0a1
Bug 920170 - Allow adding a grid of icons to prompts. r=margaret
mobile/android/base/Makefile.in
mobile/android/base/prompts/IconGridInput.java
mobile/android/base/prompts/Prompt.java
mobile/android/base/prompts/PromptInput.java
mobile/android/base/resources/drawable-hdpi/grid_icon_bg_activated.9.png
mobile/android/base/resources/drawable-hdpi/grid_icon_bg_activated.png
mobile/android/base/resources/drawable-hdpi/grid_icon_bg_focused.9.png
mobile/android/base/resources/drawable-mdpi/grid_icon_bg_activated.9.png
mobile/android/base/resources/drawable-mdpi/grid_icon_bg_focused.9.png
mobile/android/base/resources/drawable-xhdpi/grid_icon_bg_activated.9.png
mobile/android/base/resources/drawable-xhdpi/grid_icon_bg_focused.9.png
mobile/android/base/resources/drawable/icon_grid_item_bg.xml
mobile/android/base/resources/layout/icon_grid.xml
mobile/android/base/resources/layout/icon_grid_item.xml
mobile/android/base/resources/values/dimens.xml
mobile/android/base/resources/values/integers.xml
mobile/android/base/tests/robocop.ini
mobile/android/base/tests/roboextender/robocop_prompt_gridinput.html
mobile/android/base/tests/testPromptGridInput.java.in
mobile/android/modules/Prompt.jsm
--- a/mobile/android/base/Makefile.in
+++ b/mobile/android/base/Makefile.in
@@ -127,16 +127,17 @@ FENNEC_JAVA_FILES = \
   OrderedBroadcastHelper.java \
   PageActionLayout.java \
   PrefsHelper.java \
   PrivateDataPreference.java \
   PrivateTab.java \
   prompts/Prompt.java \
   prompts/PromptInput.java \
   prompts/PromptService.java \
+  prompts/IconGridInput.java \
   Restarter.java \
   sqlite/ByteBufferInputStream.java \
   sqlite/MatrixBlobCursor.java \
   sqlite/SQLiteBridge.java \
   sqlite/SQLiteBridgeException.java \
   ReaderModeUtils.java \
   RemoteTabs.java \
   RobocopAPI.java \
@@ -442,16 +443,18 @@ RES_LAYOUT = \
   res/layout/home_history_list.xml \
   res/layout/home_most_recent_page.xml \
   res/layout/home_pager.xml \
   res/layout/home_reading_list_page.xml \
   res/layout/home_search_item_row.xml \
   res/layout/home_banner.xml \
   res/layout/home_suggestion_prompt.xml \
   res/layout/home_top_sites_page.xml \
+  res/layout/icon_grid.xml \
+  res/layout/icon_grid_item.xml \
   res/layout/web_app.xml \
   res/layout/launch_app_list.xml \
   res/layout/launch_app_listitem.xml \
   res/layout/menu_action_bar.xml \
   res/layout/menu_item_action_view.xml \
   res/layout/menu_popup.xml \
   res/layout/notification_icon_text.xml \
   res/layout/notification_progress.xml \
@@ -608,16 +611,18 @@ RES_DRAWABLE_MDPI = \
   res/drawable-mdpi/alert_camera.png \
   res/drawable-mdpi/alert_mic.png \
   res/drawable-mdpi/alert_mic_camera.png \
   res/drawable-mdpi/arrow_popup_bg.9.png \
   res/drawable-mdpi/autocomplete_list_bg.9.png \
   res/drawable-mdpi/bookmark_folder_closed.png \
   res/drawable-mdpi/bookmark_folder_opened.png \
   res/drawable-mdpi/desktop_notification.png \
+  res/drawable-mdpi/grid_icon_bg_activated.9.png \
+  res/drawable-mdpi/grid_icon_bg_focused.9.png \
   res/drawable-mdpi/home_tab_menu_strip.9.png \
   res/drawable-mdpi/ic_menu_addons_filler.png \
   res/drawable-mdpi/ic_menu_bookmark_add.png \
   res/drawable-mdpi/ic_menu_bookmark_remove.png \
   res/drawable-mdpi/ic_menu_character_encoding.png \
   res/drawable-mdpi/close.png \
   res/drawable-mdpi/ic_menu_forward.png \
   res/drawable-mdpi/ic_menu_guest.png \
@@ -713,16 +718,18 @@ RES_DRAWABLE_LDPI = \
 
 RES_DRAWABLE_HDPI = \
   $(SYNC_RES_DRAWABLE_HDPI) \
   res/drawable-hdpi/blank.png \
   res/drawable-hdpi/favicon.png \
   res/drawable-hdpi/folder.png \
   res/drawable-hdpi/home_bg.png \
   res/drawable-hdpi/home_star.png \
+  res/drawable-hdpi/grid_icon_bg_activated.9.png \
+  res/drawable-hdpi/grid_icon_bg_focused.9.png \
   res/drawable-hdpi/abouthome_thumbnail.png \
   res/drawable-hdpi/alert_addon.png \
   res/drawable-hdpi/alert_app.png \
   res/drawable-hdpi/alert_download.png \
   res/drawable-hdpi/bookmark_folder_closed.png \
   res/drawable-hdpi/bookmark_folder_opened.png \
   res/drawable-hdpi/alert_camera.png \
   res/drawable-hdpi/alert_mic.png \
@@ -825,16 +832,18 @@ RES_DRAWABLE_XHDPI = \
   res/drawable-xhdpi/alert_download.png \
   res/drawable-xhdpi/bookmark_folder_closed.png \
   res/drawable-xhdpi/bookmark_folder_opened.png \
   res/drawable-xhdpi/alert_camera.png \
   res/drawable-xhdpi/alert_mic.png \
   res/drawable-xhdpi/alert_mic_camera.png \
   res/drawable-xhdpi/arrow_popup_bg.9.png \
   res/drawable-xhdpi/home_tab_menu_strip.9.png \
+  res/drawable-xhdpi/grid_icon_bg_activated.9.png \
+  res/drawable-xhdpi/grid_icon_bg_focused.9.png \
   res/drawable-xhdpi/ic_menu_addons_filler.png \
   res/drawable-xhdpi/ic_menu_bookmark_add.png \
   res/drawable-xhdpi/ic_menu_bookmark_remove.png \
   res/drawable-xhdpi/close.png \
   res/drawable-xhdpi/ic_menu_character_encoding.png \
   res/drawable-xhdpi/ic_menu_forward.png \
   res/drawable-xhdpi/ic_menu_guest.png \
   res/drawable-xhdpi/ic_menu_new_private_tab.png \
@@ -1074,16 +1083,17 @@ endif
 RES_DRAWABLE += \
   $(SYNC_RES_DRAWABLE)                                \
   res/drawable/action_bar_button.xml                  \
   res/drawable/action_bar_button_inverse.xml          \
   res/drawable/top_sites_thumbnail_bg.xml             \
   res/drawable/url_bar_bg.xml                         \
   res/drawable/url_bar_entry.xml                      \
   res/drawable/url_bar_nav_button.xml                 \
+  res/drawable/icon_grid_item_bg.xml                  \
   res/drawable/url_bar_right_edge.xml                 \
   res/drawable/bookmark_folder.xml                    \
   res/drawable/divider_horizontal.xml                 \
   res/drawable/divider_vertical.xml                   \
   res/drawable/favicon_bg.xml                         \
   res/drawable/handle_end_level.xml                   \
   res/drawable/handle_start_level.xml                 \
   res/drawable/home_history_tabs_indicator.xml        \
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/prompts/IconGridInput.java
@@ -0,0 +1,172 @@
+/* -*- 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.JSONObject;
+
+import org.mozilla.gecko.R;
+import org.mozilla.gecko.GeckoAppShell;
+import org.mozilla.gecko.gfx.BitmapUtils;
+import org.mozilla.gecko.util.ThreadUtils;
+
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.text.TextUtils;
+import android.view.Display;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewGroup.LayoutParams;
+import android.view.LayoutInflater;
+import android.view.WindowManager;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.ArrayAdapter;
+import android.widget.GridView;
+import android.widget.TextView;
+import android.widget.ImageView;
+import android.widget.ListView;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+
+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 = -1;           // Current selection
+    private 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.setSelection(i);
+            }
+        }
+
+        view.setNumColumns(Math.min(items.size(), maxColumns));
+        view.setOnItemClickListener(this);
+
+        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;
+    }
+
+    @Override
+    public String getValue() {
+        return Integer.toString(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");
+
+            BitmapUtils.getDrawable(context, iconUrl, new BitmapUtils.BitmapLoader() {
+                public void onBitmapFound(Drawable d) {
+                    icon = d;
+                    if (mAdapter != null) {
+                        mAdapter.notifyDataSetChanged();
+                    }
+                }
+            });
+        }
+    }
+}
--- a/mobile/android/base/prompts/Prompt.java
+++ b/mobile/android/base/prompts/Prompt.java
@@ -312,40 +312,49 @@ public class Prompt implements OnClickLi
         builder.setAdapter(adapter, this);
         mSelected = null;
     }
 
     /* Add the requested input elements to the dialog.
      *
      * @param builder
      *        the alert builder currently building this dialog.
-     * @return
-     *        returns true if the inputs were added successfully. They may fail
-     *        if the requested input is incompatible with this Android version
+     * @return 
+     *         return true if the inputs were added successfully. This may fail
+     *         if the requested input is compatible with this Android verison
      */
     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 innuts are scrollable, we won't wrap this in a ScrollView
+
             if (length == 1) {
-                ScrollView view = new ScrollView(mContext);
-                view.addView(mInputs[0].getView(mContext));
-                builder.setView(applyInputStyle(view));
+                root = mInputs[0].getView(mContext);
+                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 = mInputs[i].getView(mContext);
                     linearLayout.addView(content);
+                    scrollable |= mInputs[i].getScrollable();
                 }
+                root = linearLayout;
+            }
+
+            if (scrollable) {
+                builder.setView(applyInputStyle(root));
+            } else {
                 ScrollView view = new ScrollView(mContext);
-                view.addView(linearLayout);
+                view.addView(root);
                 builder.setView(applyInputStyle(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;
--- a/mobile/android/base/prompts/PromptInput.java
+++ b/mobile/android/base/prompts/PromptInput.java
@@ -37,18 +37,16 @@ import android.widget.TimePicker;
 
 import java.text.SimpleDateFormat;
 import java.util.Calendar;
 import java.util.GregorianCalendar;
 import java.util.concurrent.ConcurrentLinkedQueue;
 import java.util.concurrent.TimeUnit;
 
 public class PromptInput {
-    private final JSONObject mJSONInput;
-
     protected final String mLabel;
     protected final String mType;
     protected final String mId;
     protected final String mValue;
     protected View mView;
     public static final String LOGTAG = "GeckoPromptInput";
 
     public static class EditInput extends PromptInput {
@@ -324,17 +322,16 @@ public class PromptInput {
         }
 
         public String getValue() {
             return "";
         }
     }
 
     public PromptInput(JSONObject obj) {
-        mJSONInput = obj;
         mLabel = obj.optString("label");
         mType = obj.optString("type");
         String id = obj.optString("id");
         mId = TextUtils.isEmpty(id) ? mType : id;
         mValue = obj.optString("value");
     }
 
     public static PromptInput getInput(JSONObject obj) {
@@ -346,16 +343,18 @@ public class PromptInput {
         } else if (PasswordInput.INPUT_TYPE.equals(type)) {
             return new PasswordInput(obj);
         } else if (CheckboxInput.INPUT_TYPE.equals(type)) {
             return new CheckboxInput(obj);
         } else if (MenulistInput.INPUT_TYPE.equals(type)) {
             return new MenulistInput(obj);
         } else if (LabelInput.INPUT_TYPE.equals(type)) {
             return new LabelInput(obj);
+        } else if (IconGridInput.INPUT_TYPE.equals(type)) {
+            return new IconGridInput(obj);
         } else {
             for (String dtType : DateTimeInput.INPUT_TYPES) {
                 if (dtType.equals(type)) {
                     return new DateTimeInput(obj);
                 }
             }
         }
         return null;
@@ -367,9 +366,13 @@ public class PromptInput {
 
     public String getId() {
         return mId;
     }
 
     public String getValue() {
         return "";
     }
+
+    public boolean getScrollable() {
+        return false;
+    }
 }
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..e591a7b009ad931342bf25593aa270489ce00436
GIT binary patch
literal 326
zc%17D@N?(olHy`uVBq!ia0vp^;vmey1|%P7U0DF6Sc;uILpXq-h9ji|$mcBZh%9Dc
z;O+!rM)Q-W*8&A!c)B=-cyw-^YRK2DAkt><`#kQzRpuEtPA+6$alJ5qSGBCI1G~8}
z|ASYoRgKqKH8K@dx#|xw@Y}^T%x~`y(AaqE|4F5k`^{fB#5S_6XfSHn%*1VQHuvKe
zX0|x~+mbIDqCL{&dL8VyALUlq&g6b@#n-v-o+R`=(zubcphE4lnoz{*jSoXpDp(7G
zWbd!8U}<>Qw^3-q9l?%ePd+4cziU(vF8Xo)zmV`eAJ2!kT(t9)ck&1=65a9Q%=Ebe
z^TmG1oEDL^liaaNAz9_a3hha?3u{|=-HtNK<lAKNiVAKz^Lp`w`r|tzTq>BA^Q^xs
TcBLK$`kBGg)z4*}Q$iB}9#MTD
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..e591a7b009ad931342bf25593aa270489ce00436
GIT binary patch
literal 326
zc%17D@N?(olHy`uVBq!ia0vp^;vmey1|%P7U0DF6Sc;uILpXq-h9ji|$mcBZh%9Dc
z;O+!rM)Q-W*8&A!c)B=-cyw-^YRK2DAkt><`#kQzRpuEtPA+6$alJ5qSGBCI1G~8}
z|ASYoRgKqKH8K@dx#|xw@Y}^T%x~`y(AaqE|4F5k`^{fB#5S_6XfSHn%*1VQHuvKe
zX0|x~+mbIDqCL{&dL8VyALUlq&g6b@#n-v-o+R`=(zubcphE4lnoz{*jSoXpDp(7G
zWbd!8U}<>Qw^3-q9l?%ePd+4cziU(vF8Xo)zmV`eAJ2!kT(t9)ck&1=65a9Q%=Ebe
z^TmG1oEDL^liaaNAz9_a3hha?3u{|=-HtNK<lAKNiVAKz^Lp`w`r|tzTq>BA^Q^xs
TcBLK$`kBGg)z4*}Q$iB}9#MTD
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..ea27290d76dd858d01935506622f40dcb2c64e56
GIT binary patch
literal 263
zc%17D@N?(olHy`uVBq!ia0vp^;vmey1|%P7U0DF6Sc;uILpXq-h9ji|$mcBZh%9Dc
z;O+!rM)Q-W*8&Asdb&7<cyzv<a*_9t0guc14&7g;*;2d9SlSXq+7epIxOX|$Ss(u>
zyim)kBl@a5hu<l8rTg8fFL@>vbae;oe=Dx{T_$(Ca38}9#yK)QED1AqJ&S8i|4^Db
znV~@2-lAhs9dpF5zC|xgyH;kOGflES*MDic>(!rmT<z_yFLxhTYThDl<Fl|pbWw{;
zhpU5)*U`Hn&ku7waF2fx_Q(5_$Dud5eKytZvpgSh@=w>j^!WN~1`zOc^>bP0l+XkK
D9ZzIW
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..7dfea4c00546a18eeb6eeccd12d751247caa65c5
GIT binary patch
literal 284
zc%17D@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`EX7WqAsj$Z!;#Vf<Z~8yL>4nJ
zaCd?*qxs3xYk`9MJY5_^EPA(2J}cN{Akezs;NI6Kt=zT~^j;j`+Q4;8YYUV00`3=$
z+~R&w2R|nmHA%44-}6b^bo0B}>`G}BL&lw7tuzyBCQN%Pb%R;VgJ~AqiiYI5N5db)
zDd^tjEnwRg{PxQQ#@$xk`V*~Bh<6m;kvSl~f%}M@Si;k$Cz_QU9bNbA7)4k+HVYai
zUcSQ=U!HJp)4!XI<qH#ExELn&U1Ydc9#C6Q$=a_zXGj0@m=eFw&F57(lil1}(vlsf
a9O2zlruQsf{=Y5ICk&phelF{r5}E*wSZS{S
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..99b0279469264df100cdea866a3302c376e8c30a
GIT binary patch
literal 242
zc%17D@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`EX7WqAsj$Z!;#Vf<Z~8yL>4nJ
zaCd?*qxs3xYk`7OJY5_^EPD4&I>>uiL4f7?nv#e8)+Y|XJY@RQ)%TrJ-415Eb;+L<
zg?2I&iX7EeJb3rr#w14}UX8Uyo7dWMPLn^Dx}V9|AmoQ2$LlNk=bfH&=o+pxf8Ofd
zc);rG_DNMS-*)eIxOedSi;N>BB?r#)y-|KWpRa~XP~uYdb%D)$E7=bUdOa#Qb34+&
kbH-lQz~9pPE}k|Fdqk9v&oxZF0(2jPr>mdKI;Vst01kFqRR910
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..f01a79e3bf44ced72d89e813da41d336d5d0a2ce
GIT binary patch
literal 413
zc%17D@N?(olHy`uVBq!ia0vp^av;pX1|+Qw)-3{3EX7WqAsj$Z!;#Vf<Z~8yL>4nJ
zaCd?*qxs3xYZ(|A?LA!_Lp(a4PB+XuWFX=Yue78~+T~Qkf*u}^l6R~{?eA<^PigM@
z&KI+wT7~TxLz2TO-T<+>_CGsRTs?Uuw4QoODp~(Oy7BYh-i`vspxtle#12|tH;8@{
z-zaFn%9M~JY0YYsz?m~i<Ik${6&>l_d--`9B_tSXy%Xm$Z(x3Qz^wDoWBrX@_6?jL
zN|tW$<6G14J?Pzh6Fynbi!+2v6OLFd->Cd@rA^p$$&VdLezC>@(*^x^)+zpN-M6d9
zVn_296^0iteQ(!GMYGK}-_pzYQQFF4>Fh_{Wu4I-@=oSQ)4xBd1~R@MNnPicdG5OJ
z&g;`eDm2~a+%+hWmi$xmO4T*-*{tSWJr&M@yt~qtuk)Jvlc(m<x_74<x2ten4~V@Q
z-6dpFt`fVF`3FC1?OWD6@`)}i=d*pUZ>ug#b`X3h9m^J}uJl3G85q6{p00i_>zopr
E0KAQ$-2eap
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..7bea197d0c510cf6cf5cb7f37fdc624e88ff9b7c
GIT binary patch
literal 324
zc%17D@N?(olHy`uVBq!ia0vp^av;pX1|+Qw)-3{3EX7WqAsj$Z!;#Vf<Z~8yL>4nJ
zaCd?*qxs3xYk`8#JY5_^JUZV_v*l}Y5OBLM#h<uj>xSc(q-PXx@h2Qrv|wJY@Q`g<
z5pz`Ub_wq70e2Xu%@;U2{lbPb-;IKKx;ri~uC%E;dUsC!KAy$0Ek8?nj~w8-QSofr
z0a=eO=Yrze4J#(P9oW|_61|ySY4+EHL7F|eiSd00#4JMj=g5ARc^_c0c}w5@W6?5A
zbNe;VtQXfktrT^9v9bS073u1Ei|6^QdG~tb(a8q(8@H|Yjw{Pa{r)S+>Yn7<$JP9S
zt95+mFuY{AbR})8@a@-LY;y|xS9M;xmUipv^YibMU09N(u5W#BzuPRprGmk()a<7F
T%n2+&A2WEm`njxgN@xNAyaj?H
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/resources/drawable/icon_grid_item_bg.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2012 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <item android:state_focused="true"
+          android:state_pressed="true"
+          android:drawable="@drawable/grid_icon_bg_focused" />
+    <item android:state_activated="true"
+          android:drawable="@drawable/grid_icon_bg_activated" />
+    <item android:drawable="@android:color/transparent" />
+
+</selector>
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/resources/layout/icon_grid.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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/. -->
+
+<GridView xmlns:android="http://schemas.android.com/apk/res/android"
+          android:layout_height="wrap_content"
+          android:layout_width="match_parent"
+          android:choiceMode="singleChoice"
+          android:padding="@dimen/icongrid_padding"/>
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/resources/layout/icon_grid_item.xml
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  //device/apps/common/res/any/layout/resolve_list_item.xml
+  Copyright 2006, The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:gravity="center"
+              android:orientation="vertical"
+              android:layout_height="wrap_content"
+              android:layout_width="match_parent"
+              android:background="@drawable/icon_grid_item_bg"
+              android:padding="16dp">
+
+    <!-- Extended activity info to distinguish between duplicate activity names -->
+    <TextView android:id="@android:id/text2"
+              android:textAppearance="?android:attr/textAppearance"
+              android:layout_width="wrap_content"
+              android:layout_height="wrap_content"
+              android:gravity="center"
+              android:minLines="2"
+              android:maxLines="2"
+              android:paddingTop="4dip"
+              android:paddingBottom="4dip" />
+
+    <!-- Activity icon when presenting dialog
+         Size will be filled in by ResolverActivity -->
+    <ImageView android:id="@+id/icon"
+               android:layout_width="0dp"
+               android:layout_height="0dp"
+               android:scaleType="fitCenter" />
+
+    <!-- Activity name -->
+    <TextView android:id="@android:id/text1"
+              android:textAppearance="?android:attr/textAppearanceSmall"
+              android:layout_width="wrap_content"
+              android:layout_height="wrap_content"
+              android:gravity="center"
+              android:minLines="2"
+              android:maxLines="2"
+              android:paddingTop="4dip"
+              android:paddingBottom="4dip" />
+</LinearLayout>
+
--- a/mobile/android/base/resources/values/dimens.xml
+++ b/mobile/android/base/resources/values/dimens.xml
@@ -91,9 +91,13 @@
          50dp * 2 Views + 30dp padding + 4dp dividers-->
     <dimen name="history_tab_widget_height">134dp</dimen>
 
     <!-- PageActionButtons dimensions -->
     <dimen name="page_action_button_width">32dp</dimen>
 
     <!-- Banner -->
     <dimen name="home_banner_height">72dp</dimen>
+
+    <!-- Icon Grid -->
+    <dimen name="icongrid_columnwidth">128dp</dimen>
+    <dimen name="icongrid_padding">16dp</dimen>
 </resources>
--- a/mobile/android/base/resources/values/integers.xml
+++ b/mobile/android/base/resources/values/integers.xml
@@ -2,10 +2,11 @@
 <!-- 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/. -->
 
 <resources>
 
     <integer name="number_of_top_sites">6</integer>
     <integer name="number_of_top_sites_cols">2</integer>
+    <integer name="max_icon_grid_columns">4</integer>
 
 </resources>
--- a/mobile/android/base/tests/robocop.ini
+++ b/mobile/android/base/tests/robocop.ini
@@ -15,16 +15,17 @@
 [testFlingCorrectness]
 [testOverscroll]
 [testAxisLocking]
 [testAboutPage]
 [testLinkContextMenu]
 [testMailToContextMenu]
 [testPictureLinkContextMenu]
 [testPasswordProvider]
+[testPromptGridInput]
 # [testPasswordEncrypt] # see bug 824067
 [testFormHistory]
 [testBrowserProvider]
 [testSearchSuggestions]
 [testSharedPreferences]
 # [testThumbnails] # see bug 813107
 [testAddonManager]
 # [testHistory] # see bug 915350
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/tests/roboextender/robocop_prompt_gridinput.html
@@ -0,0 +1,51 @@
+<html>
+  <head>
+    <title>IconGrid test page</title>
+    <meta name="viewport" content="initial-scale=1.0"/>
+    <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+    <script type="application/javascript">
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+Components.utils.import("resource://gre/modules/Services.jsm");
+Components.utils.import("resource://gre/modules/Prompt.jsm");
+
+function start() {
+  var test = location.hash.substring(1);
+  window[test]();
+}
+
+function test1() {
+  var p = new Prompt({
+    title: "Prompt 1",
+    buttons: [
+      "OK"
+    ],
+  }).addIconGrid({
+    items: [
+      { iconUri: "drawable://alert_app", name: "Icon 1", selected: true },
+      { iconUri: "drawable://alert_download", name: "Icon 2" },
+      { iconUri: "drawable://alert_addon", name: "Icon 3" },
+      { iconUri: "drawable://alert_addon", name: "Icon 4" },
+      { iconUri: "drawable://alert_addon", name: "Icon 5" },
+      { iconUri: "drawable://alert_addon", name: "Icon 6" },
+      { iconUri: "drawable://alert_addon", name: "Icon 7" },
+      { iconUri: "drawable://alert_addon", name: "Icon 8" },
+      { iconUri: "drawable://alert_addon", name: "Icon 9" },
+      { iconUri: "drawable://alert_addon", name: "Icon 10" },
+      { iconUri: "drawable://alert_addon", name: "Icon 11" },
+    ]
+  });
+  p.show(function(data) {
+    sendResult(data.icongrid0 == 10, "Got result " + data.icongrid0);
+  });
+}
+
+function sendResult(pass, message) {
+  setTimeout(function() {
+    alert((pass ? "PASS " : "FAIL ") + message);
+  }, 1000);
+}
+    </script>
+  </head>
+  <body onload="start();">
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/tests/testPromptGridInput.java.in
@@ -0,0 +1,61 @@
+#filter substitution
+package @ANDROID_PACKAGE_NAME@.tests;
+
+import @ANDROID_PACKAGE_NAME@.*;
+
+import android.graphics.drawable.Drawable;
+import android.widget.EditText;
+import android.widget.CheckedTextView;
+import android.widget.TextView;
+import android.text.InputType;
+import android.util.DisplayMetrics;
+import android.view.View;
+import android.util.Log;
+
+import org.json.JSONObject;
+
+public class testPromptGridInput extends BaseTest {
+    @Override
+    protected int getTestType() {
+        return TEST_MOCHITEST;
+    }
+
+    protected int index = 1;
+    public void testPromptGridInput() {
+        blockForGeckoReady();
+
+        test(1);
+
+        testGridItem("Icon 1");
+        testGridItem("Icon 2");
+        testGridItem("Icon 3");
+        testGridItem("Icon 4");
+        testGridItem("Icon 5");
+        testGridItem("Icon 6");
+        testGridItem("Icon 7");
+        testGridItem("Icon 8");
+        testGridItem("Icon 9");
+        testGridItem("Icon 10");
+        testGridItem("Icon 11");
+
+        mSolo.clickOnText("Icon 11");
+        mSolo.clickOnText("OK");
+
+        mAsserter.ok(waitForText("PASS"), "test passed", "PASS");
+        mActions.sendSpecialKey(Actions.SpecialKey.BACK);
+    }
+
+    public void testGridItem(String title) {
+        // Force the list to scroll if necessary
+        mSolo.waitForText(title, 1, 500, true);
+        mAsserter.ok(waitForText(title), "Found grid item", title);
+    }
+
+    public void test(final int num) {
+        // Load about:blank between each test to ensure we reset state
+        loadUrl("about:blank");
+        mAsserter.ok(waitForText("about:blank"), "Loaded blank page", "page title match");
+
+        loadUrl("chrome://roboextender/content/robocop_prompt_gridinput.html#test" + num);
+    }
+}
--- a/mobile/android/modules/Prompt.jsm
+++ b/mobile/android/modules/Prompt.jsm
@@ -117,16 +117,24 @@ Prompt.prototype = {
   addMenulist: function(aOptions) {
     return this._addInput({
       type: "menulist",
       values: aOptions.values,
       id: aOptions.id
     });
   },
 
+  addIconGrid: function(aOptions) {
+    return this._addInput({
+      type: "icongrid",
+      items: aOptions.items,
+      id: aOptions.id
+    });
+  },
+
   show: function(callback) {
     this.callback = callback;
     log("Sending message");
     Services.obs.addObserver(this, "Prompt:Reply", false);
     this._innerShow();
   },
 
   _innerShow: function() {