Bug 974286 - Remove mSelected from PromptListAdapter. r=bnicholson
authorWes Johnston <wjohnston@mozilla.com>
Tue, 25 Feb 2014 08:55:14 -0800
changeset 170887 44f836fecd71b516ba3221ac8d5627a0e4e17f06
parent 170886 db3625395d32268f8b98d78429193c5680271c1b
child 170888 9e154b6934627348e724f58ae7b7c6eb9b7ae9b4
push id270
push userpvanderbeken@mozilla.com
push dateThu, 06 Mar 2014 09:24:21 +0000
reviewersbnicholson
bugs974286
milestone30.0a1
Bug 974286 - Remove mSelected from PromptListAdapter. r=bnicholson
mobile/android/base/BrowserApp.java
mobile/android/base/prompts/Prompt.java
mobile/android/base/prompts/PromptListAdapter.java
mobile/android/base/prompts/PromptListItem.java
mobile/android/base/prompts/PromptService.java
mobile/android/modules/Prompt.jsm
--- a/mobile/android/base/BrowserApp.java
+++ b/mobile/android/base/BrowserApp.java
@@ -87,16 +87,17 @@ import android.view.MotionEvent;
 import android.view.SubMenu;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewStub;
 import android.view.ViewTreeObserver;
 import android.view.Window;
 import android.view.animation.Interpolator;
 import android.widget.RelativeLayout;
+import android.widget.ListView;
 import android.widget.Toast;
 import android.widget.ViewFlipper;
 
 abstract public class BrowserApp extends GeckoApp
                                  implements TabsPanel.TabsLayoutChangeListener,
                                             PropertyAnimator.PropertyAnimationListener,
                                             View.OnKeyListener,
                                             GeckoLayerClient.OnMetricsChangedListener,
@@ -2419,17 +2420,17 @@ abstract public class BrowserApp extends
         if (type == GuestModeDialog.ENTERING) {
             titleString = R.string.new_guest_session_title;
             msgString = R.string.new_guest_session_text;
         } else {
             titleString = R.string.exit_guest_session_title;
             msgString = R.string.exit_guest_session_text;
         }
 
-        ps.show(res.getString(titleString), res.getString(msgString), null, false);
+        ps.show(res.getString(titleString), res.getString(msgString), null, ListView.CHOICE_MODE_NONE);
     }
 
     public void subscribeToFeeds(Tab tab) {
         if (!tab.hasFeeds()) {
             return;
         }
 
         JSONObject args = new JSONObject();
--- a/mobile/android/base/prompts/Prompt.java
+++ b/mobile/android/base/prompts/Prompt.java
@@ -31,37 +31,39 @@ import android.widget.AdapterView;
 import android.widget.AdapterView.OnItemClickListener;
 import android.widget.ArrayAdapter;
 import android.widget.CheckedTextView;
 import android.widget.LinearLayout;
 import android.widget.ListView;
 import android.widget.ScrollView;
 import android.widget.TextView;
 
+import java.util.ArrayList;
+
 public class Prompt implements OnClickListener, OnCancelListener, OnItemClickListener {
     private static final String LOGTAG = "GeckoPromptService";
 
     private String[] mButtons;
     private PromptInput[] mInputs;
     private AlertDialog mDialog;
 
     private final LayoutInflater mInflater;
     private final Context mContext;
     private PromptCallback mCallback;
     private String mGuid;
+    private PromptListAdapter mAdapter;
 
     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;
@@ -79,47 +81,43 @@ public class Prompt implements OnClickLi
             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) {
         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) {
+    public void show(String title, String text, PromptListItem[] listItems, int choiceMode) {
         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, selected);
+            addListItems(builder, listItems, choiceMode);
         } 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);
@@ -146,33 +144,35 @@ 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 {
-            boolean[] selectedItems = mAdapter.getSelected();
-            if (selectedItems != null) {
-                JSONArray selected = new JSONArray();
-                for (int i = 0; i < selectedItems.length; i++) {
-                    if (selectedItems[i]) {
-                        selected.put(i);
-                    }
+            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("list", selected);
-            } else {
-                // Mirror the selected array from multi choice for consistency.
-                JSONArray selected = new JSONArray();
-                selected.put(which);
-                result.put("list", selected);
-                // Make the button be the index of the select item.
+
                 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 {
@@ -200,116 +200,101 @@ public class Prompt implements OnClickLi
         } catch(JSONException ex) { }
     }
 
     @Override
     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 || mAdapter.getSelected() != null) {
+            if (mAdapter != 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 mAdapter.selected is set in the prompt before addListItems is called, the items will be
-     * shown with "checkmarks" on their left side.
+     * or multiple selection lists.
      *
      * @param builder
      *        The alert builder currently building this dialog.
      * @param listItems
      *        The items to add.
-     * @param multipleSelection
-     *        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.
+     * @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, boolean multipleSelection, boolean[] selected) {
-        if (selected != null && selected.length > 0) {
-            if (multipleSelection) {
-                addMultiSelectList(builder, listItems, selected);
-            } else {
-                addSingleSelectList(builder, listItems, selected);
-            }
-        } else {
-            addMenuList(builder, listItems);
+    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, boolean[] selected) {
-        mAdapter = new PromptListAdapter(mContext, R.layout.select_dialog_multichoice, listItems);
-        mAdapter.setSelected(selected);
-
+    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.setInverseBackgroundForced(true);
         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, boolean[] selected) {
+    private void addSingleSelectList(AlertDialog.Builder builder, PromptListItem[] listItems) {
         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 < selected.length; i++) {
-            if (selected[i]) {
-                selectedIndex = i;
-                break;
-            }
-        }
-
-        builder.setSingleChoiceItems(mAdapter, selectedIndex, this);
+        builder.setSingleChoiceItems(mAdapter, mAdapter.getSelectedIndex(), 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) {
         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);
 
@@ -337,23 +322,21 @@ public class Prompt implements OnClickLi
             boolean scrollable = false; // If any of the innuts 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) {
                 builder.setView(root);
             } else {
                 ScrollView view = new ScrollView(mContext);
                 view.addView(root);
@@ -437,18 +420,26 @@ 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"));
-        boolean multiple = geckoObject.optBoolean("multiple");
-        show(title, text, menuitems, multiple, getBooleanArray(geckoObject, "selected"));
+        String selected = geckoObject.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;
+        }
+
+        show(title, text, menuitems, choiceMode);
     }
 
     private static JSONArray getSafeArray(JSONObject json, String key) {
         try {
             return json.getJSONArray(key);
         } catch (Exception e) {
             return new JSONArray();
         }
--- a/mobile/android/base/prompts/PromptListAdapter.java
+++ b/mobile/android/base/prompts/PromptListAdapter.java
@@ -15,16 +15,18 @@ 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;
 
+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_COUNT = 2;
 
     private static final String LOGTAG = "GeckoPromptListAdapter";
 
     private final int mResourceId;
@@ -33,18 +35,16 @@ public class PromptListAdapter extends A
     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) {
@@ -84,25 +84,18 @@ public class PromptListAdapter extends A
     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;
+        PromptListItem item = getItem(position);
+        item.selected = !item.selected;
     }
 
     private void maybeUpdateIcon(PromptListItem item, TextView t) {
         if (item.icon == null && !item.inGroup && !item.isParent) {
             t.setCompoundDrawablesWithIntrinsicBounds(null, null, null, null);
             return;
         }
 
@@ -128,24 +121,49 @@ public class PromptListAdapter extends A
         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.selected);
+        }
+    }
 
-        if (mSelected == null) {
-            return;
+    boolean isSelected(int position){
+        return getItem(position).selected;
+    }
+
+    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);
+            }
         }
 
-        if (list != null) {
-            list.setItemChecked(position, mSelected[position]);
+        return selected;
+    }
+
+    int getSelectedIndex() {
+        int length = getCount();
+        for (int i = 0; i< length; i++) {
+            if (isSelected(i)) {
+                return i;
+            }
         }
+        return -1;
     }
 
     @Override
     public View getView(int position, View convertView, ViewGroup parent) {
         PromptListItem item = getItem(position);
         int type = getItemViewType(position);
         ViewHolder viewHolder = null;
 
--- a/mobile/android/base/prompts/PromptListItem.java
+++ b/mobile/android/base/prompts/PromptListItem.java
@@ -11,36 +11,37 @@ 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 boolean selected;
+
     public boolean isParent;
-
     public Drawable icon;
 
     PromptListItem(JSONObject aObject) {
         label = aObject.optString("label");
         isGroup = aObject.optBoolean("isGroup");
         inGroup = aObject.optBoolean("inGroup");
         disabled = aObject.optBoolean("disabled");
         id = aObject.optInt("id");
         isParent = aObject.optBoolean("isParent");
+        selected = aObject.optBoolean("selected");
     }
 
     public PromptListItem(String aLabel) {
         label = aLabel;
         isGroup = false;
         inGroup = false;
         disabled = false;
         id = 0;
-        isParent = false;
     }
 
     static PromptListItem[] getArray(JSONArray items) {
         if (items == null) {
             return new PromptListItem[0];
         }
 
         int length = items.length();
--- a/mobile/android/base/prompts/PromptService.java
+++ b/mobile/android/base/prompts/PromptService.java
@@ -27,24 +27,24 @@ public class PromptService implements Ge
     }
 
     public void destroy() {
         GeckoAppShell.getEventDispatcher().unregisterEventListener("Prompt:Show", this);
         GeckoAppShell.getEventDispatcher().unregisterEventListener("Prompt:ShowTop", this);
     }
 
     public void show(final String aTitle, final String aText, final PromptListItem[] aMenuList,
-                     final boolean aMultipleSelection, final Prompt.PromptCallback callback) {
+                     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, aMultipleSelection);
+                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.
--- a/mobile/android/modules/Prompt.jsm
+++ b/mobile/android/modules/Prompt.jsm
@@ -174,22 +174,21 @@ Prompt.prototype = {
     aItems.forEach(function(item) {
       let obj = { id: item.id };
 
       obj.label = item.label;
 
       if (item.disabled)
         obj.disabled = true;
 
-      if (item.selected || hasSelected || this.msg.multiple) {
-        if (!this.msg.selected) {
-          this.msg.selected = new Array(this.msg.listitems.length);
-          hasSelected = true;
+      if (item.selected) {
+        if (!this.msg.choiceMode) {
+          this.msg.choiceMode = "single";
         }
-        this.msg.selected[this.msg.listitems.length] = item.selected;
+        obj.selected = item.selected;
       }
 
       if (item.header)
         obj.isGroup = true;
 
       if (item.menu)
         obj.isParent = true;
 
@@ -202,13 +201,13 @@ Prompt.prototype = {
     return this;
   },
 
   setSingleChoiceItems: function(aItems) {
     return this._setListItems(aItems);
   },
 
   setMultiChoiceItems: function(aItems) {
-    this.msg.multiple = true;
+    this.msg.choiceMode = "multiple";
     return this._setListItems(aItems);
   },
 
 }