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 164652 1960e499d49ff65ed613b0ed1dbc5f95c98b5502
parent 164651 863087fdc7da7df0ced69f09c4e7d63b560db297
child 164653 9f63bbc005279333095be0000696dcf12629b16d
child 164700 79360e1c33a56ca48ac62548c7f436fa445a04ab
push id3066
push userakeybl@mozilla.com
push dateMon, 09 Dec 2013 19:58:46 +0000
treeherdermozilla-beta@a31a0dce83aa [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmargaret
bugs920170
milestone27.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
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() {