Bug 973013 - Pull PromptListAdapter into its own class. r=bnicholson
authorWesley Johnston <wjohnston>
Thu, 06 Feb 2014 16:16:00 -0800
changeset 170861 6c1982ae44001e40561649062a3c70e1f6940f9e
parent 170860 b92e36eba34932089a1e205070838104ef7037e4
child 170862 72553da558454175c0c63b661a8faf1aeb3b925e
push id270
push userpvanderbeken@mozilla.com
push dateThu, 06 Mar 2014 09:24:21 +0000
reviewersbnicholson
bugs973013
milestone30.0a1
Bug 973013 - Pull PromptListAdapter into its own class. r=bnicholson
mobile/android/base/moz.build
mobile/android/base/prompts/Prompt.java
mobile/android/base/prompts/PromptListAdapter.java
--- a/mobile/android/base/moz.build
+++ b/mobile/android/base/moz.build
@@ -293,16 +293,17 @@ gbjar.sources += [
     'preferences/SearchPreferenceCategory.java',
     'preferences/SyncPreference.java',
     'PrefsHelper.java',
     'PrivateTab.java',
     'prompts/ColorPickerInput.java',
     'prompts/IconGridInput.java',
     'prompts/Prompt.java',
     'prompts/PromptInput.java',
+    'prompts/PromptListAdapter.java',
     'prompts/PromptListItem.java',
     'prompts/PromptService.java',
     'ReaderModeUtils.java',
     'ReferrerReceiver.java',
     'RemoteTabs.java',
     'Restarter.java',
     'ScrollAnimator.java',
     'ServiceNotificationClient.java',
--- a/mobile/android/base/prompts/Prompt.java
+++ b/mobile/android/base/prompts/Prompt.java
@@ -36,32 +36,32 @@ import android.widget.ListView;
 import android.widget.ScrollView;
 import android.widget.TextView;
 
 public class Prompt implements OnClickListener, OnCancelListener, OnItemClickListener {
     private static final String LOGTAG = "GeckoPromptService";
 
     private String[] mButtons;
     private PromptInput[] mInputs;
-    private boolean[] mSelected;
     private AlertDialog mDialog;
 
     private final LayoutInflater mInflater;
     private final Context mContext;
     private PromptCallback mCallback;
     private String mGuid;
 
     private static boolean mInitialized = false;
     private static int mGroupPaddingSize;
     private static int mLeftRightTextWithIconPadding;
     private static int mTopBottomTextWithIconPadding;
     private static int mIconTextPadding;
     private static int mIconSize;
     private static int mInputPaddingSize;
     private static int mMinRowSize;
+    private PromptListAdapter mAdapter;
 
     public Prompt(Context context, PromptCallback callback) {
         this(context);
         mCallback = callback;
     }
 
     private Prompt(Context context) {
         mContext = context;
@@ -87,34 +87,39 @@ public class Prompt implements OnClickLi
         }
         return view;
     }
 
     public void show(JSONObject message) {
         processMessage(message);
     }
 
+
     public void show(String title, String text, PromptListItem[] listItems, boolean multipleSelection) {
+        show(title, text, listItems, multipleSelection, null);
+    }
+
+    public void show(String title, String text, PromptListItem[] listItems, boolean multipleSelection, boolean[] selected) {
         ThreadUtils.assertOnUiThread();
 
         GeckoAppShell.getLayerView().abortPanning();
 
         AlertDialog.Builder builder = new AlertDialog.Builder(mContext);
         if (!TextUtils.isEmpty(title)) {
             builder.setTitle(title);
         }
 
         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, multipleSelection);
+            addListItems(builder, listItems, multipleSelection, selected);
         } else if (!addInputs(builder)) {
             // If we failed to add any requested input elements, don't show the dialog
             return;
         }
 
         int length = mButtons == null ? 0 : mButtons.length;
         if (length > 0) {
             builder.setPositiveButton(mButtons[0], this);
@@ -141,20 +146,21 @@ public class Prompt implements OnClickLi
 
     /* 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) {
         try {
-            if (mSelected != null) {
+            boolean[] selectedItems = mAdapter.getSelected();
+            if (selectedItems != null) {
                 JSONArray selected = new JSONArray();
-                for (int i = 0; i < mSelected.length; i++) {
-                    if (mSelected[i]) {
+                for (int i = 0; i < selectedItems.length; i++) {
+                    if (selectedItems[i]) {
                         selected.put(i);
                     }
                 }
                 result.put("list", selected);
             } else {
                 // Mirror the selected array from multi choice for consistency.
                 JSONArray selected = new JSONArray();
                 selected.put(which);
@@ -198,106 +204,109 @@ public class Prompt implements OnClickLi
     public void onClick(DialogInterface dialog, int which) {
         ThreadUtils.assertOnUiThread();
         JSONObject ret = new JSONObject();
         try {
             ListView list = mDialog.getListView();
             addButtonResult(ret, which);
             addInputValues(ret);
 
-            if (list != null || mSelected != null) {
+            if (list != null || mAdapter.getSelected() != null) {
                 addListResult(ret, which);
             }
         } catch(Exception ex) {
             Log.i(LOGTAG, "Error building return: " + ex);
         }
 
         if (dialog != null) {
             dialog.dismiss();
         }
 
         finishDialog(ret);
     }
 
     /* 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. If mSelected is set in the prompt before addlistItems is called, the items will be
+     * or multiple selection lists. If mAdapter.selected is set in the prompt before addListItems is called, the items will be
      * shown with "checkmarks" on their left side.
      *
      * @param builder
      *        The alert builder currently building this dialog.
      * @param listItems
      *        The items to add.
      * @param multipleSelection
-     *        If true, and mSelected is defined to be a non-zero-length list, the list will show checkmarks on the
+     *        If true, and mAdapter.getSelected is defined to be a non-zero-length list, the list will show checkmarks on the
      *        left and allow multiple selection. 
+     * @param selected
+     *        A boolean array indicating whether a rows is selected or not. If this is a single select list, only the first true element will be used.
     */
-    private void addlistItems(AlertDialog.Builder builder, PromptListItem[] listItems, boolean multipleSelection) {
-        if (mSelected != null && mSelected.length > 0) {
+    private void addListItems(AlertDialog.Builder builder, PromptListItem[] listItems, boolean multipleSelection, boolean[] selected) {
+        if (selected != null && selected.length > 0) {
             if (multipleSelection) {
-                addMultiSelectList(builder, listItems);
+                addMultiSelectList(builder, listItems, selected);
             } else {
-                addSingleSelectList(builder, listItems);
+                addSingleSelectList(builder, listItems, selected);
             }
         } else {
             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) {
-        PromptListAdapter adapter = new PromptListAdapter(mContext, R.layout.select_dialog_multichoice, listItems);
-        adapter.listView = (ListView) mInflater.inflate(R.layout.select_dialog_list, null);
-        adapter.listView.setOnItemClickListener(this);
+    private void addMultiSelectList(AlertDialog.Builder builder, PromptListItem[] listItems, boolean[] selected) {
+        mAdapter = new PromptListAdapter(mContext, R.layout.select_dialog_multichoice, listItems);
+        mAdapter.setSelected(selected);
+
+        ListView listView = (ListView) mInflater.inflate(R.layout.select_dialog_list, null);
+        listView.setOnItemClickListener(this);
+        listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
+        listView.setAdapter(mAdapter);
+
         builder.setInverseBackgroundForced(true);
-        adapter.listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
-        adapter.listView.setAdapter(adapter);
-        builder.setView(adapter.listView);
+        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) {
-        PromptListAdapter adapter = new PromptListAdapter(mContext, R.layout.select_dialog_singlechoice, listItems);
+    private void addSingleSelectList(AlertDialog.Builder builder, PromptListItem[] listItems, boolean[] selected) {
+        mAdapter = new PromptListAdapter(mContext, R.layout.select_dialog_singlechoice, listItems);
         // For single select, we only maintain a single index of the selected row
         int selectedIndex = -1;
-        for (int i = 0; i < mSelected.length; i++) {
-            if (mSelected[i]) {
+        for (int i = 0; i < selected.length; i++) {
+            if (selected[i]) {
                 selectedIndex = i;
                 break;
             }
         }
-        mSelected = null;
 
-        builder.setSingleChoiceItems(adapter, selectedIndex, this);
+        builder.setSingleChoiceItems(mAdapter, selectedIndex, this);
     }
 
     /* 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) {
-        PromptListAdapter adapter = new PromptListAdapter(mContext, android.R.layout.simple_list_item_1, listItems);
-        builder.setAdapter(adapter, this);
-        mSelected = null;
+        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);
@@ -362,17 +371,17 @@ public class Prompt implements OnClickLi
     }
 
     /* AdapterView.OnItemClickListener
      * Called when a list item is clicked
      */
     @Override
     public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
         ThreadUtils.assertOnUiThread();
-        mSelected[position] = !mSelected[position];
+        mAdapter.toggleSelected(position);
     }
 
     /* @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.
@@ -397,17 +406,16 @@ public class Prompt implements OnClickLi
 
     /* Called any time we're closing the dialog to cleanup and notify listeners that the dialog
      * is closing.
      */
     public void finishDialog(JSONObject aReturn) {
         mInputs = null;
         mButtons = null;
         mDialog = null;
-        mSelected = null;
         try {
             aReturn.put("guid", mGuid);
         } catch(JSONException ex) { }
 
         // poke the Gecko thread in case it's waiting for new events
         GeckoAppShell.sendEventToGecko(GeckoEvent.createNoOpEvent());
 
         if (mCallback != null) {
@@ -429,19 +437,18 @@ public class Prompt implements OnClickLi
         mInputs = new PromptInput[inputs.length()];
         for (int i = 0; i < mInputs.length; i++) {
             try {
                 mInputs[i] = PromptInput.getInput(inputs.getJSONObject(i));
             } catch(Exception ex) { }
         }
 
         PromptListItem[] menuitems = PromptListItem.getArray(geckoObject.optJSONArray("listitems"));
-        mSelected = getBooleanArray(geckoObject, "selected");
         boolean multiple = geckoObject.optBoolean("multiple");
-        show(title, text, menuitems, multiple);
+        show(title, text, menuitems, multiple, getBooleanArray(geckoObject, "selected"));
     }
 
     private static JSONArray getSafeArray(JSONObject json, String key) {
         try {
             return json.getJSONArray(key);
         } catch (Exception e) {
             return new JSONArray();
         }
@@ -472,149 +479,9 @@ public class Prompt implements OnClickLi
             } catch(Exception ex) { }
         }
         return list;
     }
 
     public interface PromptCallback {
         public void onPromptFinished(String jsonResult);
     }
-
-    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_COUNT = 2;
-
-        public ListView listView;
-        private int mResourceId = -1;
-        private Drawable mBlankDrawable = null;
-        private Drawable mMoreDrawable = null;
-
-        PromptListAdapter(Context context, int textViewResourceId, PromptListItem[] objects) {
-            super(context, textViewResourceId, objects);
-            mResourceId = textViewResourceId;
-        }
-
-        @Override
-        public int getItemViewType(int position) {
-            PromptListItem item = getItem(position);
-            return (item.isGroup ? VIEW_TYPE_GROUP : VIEW_TYPE_ITEM);
-        }
-
-        @Override
-        public int getViewTypeCount() {
-            return VIEW_TYPE_COUNT;
-        }
-
-        private Drawable getMoreDrawable(Resources res) {
-            if (mMoreDrawable == null) {
-                mMoreDrawable = res.getDrawable(android.R.drawable.ic_menu_more);
-            }
-            return mMoreDrawable;
-        }
-
-        private Drawable getBlankDrawable(Resources res) {
-            if (mBlankDrawable == null) {
-                mBlankDrawable = res.getDrawable(R.drawable.blank);
-            }
-            return mBlankDrawable;
-        }
-
-        private void maybeUpdateIcon(PromptListItem item, TextView t) {
-            if (item.icon == null && !item.inGroup && !item.isParent) {
-                t.setCompoundDrawablesWithIntrinsicBounds(null, null, null, null);
-                return;
-            }
-
-            Drawable d = null;
-            Resources res = mContext.getResources();
-            // Set the padding between the icon and the text.
-            t.setCompoundDrawablePadding(mIconTextPadding);
-            if (item.icon != 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.icon).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(int position, PromptListItem item, ViewHolder viewHolder) {
-            viewHolder.textView.setEnabled(!item.disabled && !item.isGroup);
-            viewHolder.textView.setClickable(item.isGroup || item.disabled);
-
-            if (mSelected == null) {
-                return;
-            }
-
-            CheckedTextView ct;
-            try {
-                ct = (CheckedTextView) viewHolder.textView;
-                // 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
-                if (listView != null) {
-                    listView.setItemChecked(position, mSelected[position]);
-                }
-            } catch (Exception e) {
-                return;
-            }
-
-        }
-
-        @Override
-        public View getView(int position, View convertView, ViewGroup parent) {
-            PromptListItem item = getItem(position);
-            ViewHolder viewHolder = null;
-
-            if (convertView == null) {
-                int resourceId = mResourceId;
-                if (item.isGroup) {
-                    resourceId = R.layout.list_item_header;
-                }
-
-                convertView = mInflater.inflate(resourceId, null);
-                convertView.setMinimumHeight(mMinRowSize);
-
-                TextView tv = (TextView) convertView.findViewById(android.R.id.text1);
-                viewHolder = new ViewHolder(tv, tv.getPaddingLeft(), tv.getPaddingRight(),
-                                            tv.getPaddingTop(), tv.getPaddingBottom());
-
-                convertView.setTag(viewHolder);
-            } else {
-                viewHolder = (ViewHolder) convertView.getTag();
-            }
-
-            viewHolder.textView.setText(item.label);
-            maybeUpdateCheckedState(position, item, viewHolder);
-            maybeUpdateIcon(item, viewHolder.textView);
-
-            return convertView;
-        }
-
-        private 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;
-            }
-        }
-    }
 }
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/prompts/PromptListAdapter.java
@@ -0,0 +1,192 @@
+package org.mozilla.gecko.prompts;
+
+import org.mozilla.gecko.R;
+
+import org.json.JSONArray;
+import org.json.JSONObject;
+import org.json.JSONException;
+
+import android.content.Context;
+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.ArrayAdapter;
+import android.widget.AdapterView;
+import android.widget.CheckedTextView;
+import android.widget.TextView;
+import android.widget.ListView;
+
+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_COUNT = 2;
+
+    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 boolean mInitialized = false;
+
+    private boolean[] mSelected;
+
+    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.prompt_service_min_list_item_height));
+            mInitialized = true;
+        }
+    }
+
+    @Override
+    public int getItemViewType(int position) {
+        PromptListItem item = getItem(position);
+        if (item.isGroup) {
+            return VIEW_TYPE_GROUP;
+        } 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) {
+        mSelected[position] = !mSelected[position];
+    }
+
+    public void setSelected(boolean[] selected) {
+        mSelected = selected;
+    }
+
+    public boolean[] getSelected() {
+        return mSelected;
+    }
+
+    private void maybeUpdateIcon(PromptListItem item, TextView t) {
+        if (item.icon == 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.icon != 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.icon).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 (mSelected == null) {
+            return;
+        }
+
+        if (list != null) {
+            list.setItemChecked(position, mSelected[position]);
+        }
+    }
+
+    @Override
+    public View getView(int position, View convertView, ViewGroup parent) {
+        PromptListItem item = getItem(position);
+        int type = getItemViewType(position);
+        ViewHolder viewHolder = null;
+
+        if (convertView == null) {
+            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);
+            viewHolder = new ViewHolder(tv, tv.getPaddingLeft(), tv.getPaddingRight(),
+                                        tv.getPaddingTop(), tv.getPaddingBottom());
+
+            convertView.setTag(viewHolder);
+        } else {
+            viewHolder = (ViewHolder) convertView.getTag();
+        }
+
+        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;
+        }
+    }
+}