1 /*
2  * Copyright (C) 2006 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.internal.view.menu;
18 
19 import android.compat.annotation.UnsupportedAppUsage;
20 import android.content.Context;
21 import android.content.res.Resources;
22 import android.content.res.TypedArray;
23 import android.graphics.Canvas;
24 import android.graphics.Rect;
25 import android.graphics.drawable.Drawable;
26 import android.os.Parcel;
27 import android.os.Parcelable;
28 import android.util.AttributeSet;
29 import android.view.KeyEvent;
30 import android.view.LayoutInflater;
31 import android.view.View;
32 import android.view.ViewConfiguration;
33 import android.view.ViewGroup;
34 
35 import com.android.internal.view.menu.MenuBuilder.ItemInvoker;
36 
37 import java.util.ArrayList;
38 
39 /**
40  * The icon menu view is an icon-based menu usually with a subset of all the menu items.
41  * It is opened as the default menu, and shows either the first five or all six of the menu items
42  * with text and icon.  In the situation of there being more than six items, the first five items
43  * will be accompanied with a 'More' button that opens an {@link ExpandedMenuView} which lists
44  * all the menu items.
45  *
46  * @attr ref android.R.styleable#IconMenuView_rowHeight
47  * @attr ref android.R.styleable#IconMenuView_maxRows
48  * @attr ref android.R.styleable#IconMenuView_maxItemsPerRow
49  *
50  * @hide
51  */
52 public final class IconMenuView extends ViewGroup implements ItemInvoker, MenuView, Runnable {
53     private static final int ITEM_CAPTION_CYCLE_DELAY = 1000;
54 
55     @UnsupportedAppUsage
56     private MenuBuilder mMenu;
57 
58     /** Height of each row */
59     private int mRowHeight;
60     /** Maximum number of rows to be shown */
61     private int mMaxRows;
62     /** Maximum number of items to show in the icon menu. */
63     @UnsupportedAppUsage
64     private int mMaxItems;
65     /** Maximum number of items per row */
66     private int mMaxItemsPerRow;
67     /** Actual number of items (the 'More' view does not count as an item) shown */
68     private int mNumActualItemsShown;
69 
70     /** Divider that is drawn between all rows */
71     private Drawable mHorizontalDivider;
72     /** Height of the horizontal divider */
73     private int mHorizontalDividerHeight;
74     /** Set of horizontal divider positions where the horizontal divider will be drawn */
75     private ArrayList<Rect> mHorizontalDividerRects;
76 
77     /** Divider that is drawn between all columns */
78     private Drawable mVerticalDivider;
79     /** Width of the vertical divider */
80     private int mVerticalDividerWidth;
81     /** Set of vertical divider positions where the vertical divider will be drawn */
82     private ArrayList<Rect> mVerticalDividerRects;
83 
84     /** Icon for the 'More' button */
85     private Drawable mMoreIcon;
86 
87     /** Background of each item (should contain the selected and focused states) */
88     @UnsupportedAppUsage
89     private Drawable mItemBackground;
90 
91     /** Default animations for this menu */
92     private int mAnimations;
93 
94     /**
95      * Whether this IconMenuView has stale children and needs to update them.
96      * Set true by {@link #markStaleChildren()} and reset to false by
97      * {@link #onMeasure(int, int)}
98      */
99     private boolean mHasStaleChildren;
100 
101     /**
102      * Longpress on MENU (while this is shown) switches to shortcut caption
103      * mode. When the user releases the longpress, we do not want to pass the
104      * key-up event up since that will dismiss the menu.
105      */
106     private boolean mMenuBeingLongpressed = false;
107 
108     /**
109      * While {@link #mMenuBeingLongpressed}, we toggle the children's caption
110      * mode between each's title and its shortcut. This is the last caption mode
111      * we broadcasted to children.
112      */
113     private boolean mLastChildrenCaptionMode;
114 
115     /**
116      * The layout to use for menu items. Each index is the row number (0 is the
117      * top-most). Each value contains the number of items in that row.
118      * <p>
119      * The length of this array should not be used to get the number of rows in
120      * the current layout, instead use {@link #mLayoutNumRows}.
121      */
122     private int[] mLayout;
123 
124     /**
125      * The number of rows in the current layout.
126      */
127     private int mLayoutNumRows;
128 
129     /**
130      * Instantiates the IconMenuView that is linked with the provided MenuBuilder.
131      */
IconMenuView(Context context, AttributeSet attrs)132     public IconMenuView(Context context, AttributeSet attrs) {
133         super(context, attrs);
134 
135         TypedArray a =
136             context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.IconMenuView, 0, 0);
137         mRowHeight = a.getDimensionPixelSize(com.android.internal.R.styleable.IconMenuView_rowHeight, 64);
138         mMaxRows = a.getInt(com.android.internal.R.styleable.IconMenuView_maxRows, 2);
139         mMaxItems = a.getInt(com.android.internal.R.styleable.IconMenuView_maxItems, 6);
140         mMaxItemsPerRow = a.getInt(com.android.internal.R.styleable.IconMenuView_maxItemsPerRow, 3);
141         mMoreIcon = a.getDrawable(com.android.internal.R.styleable.IconMenuView_moreIcon);
142         a.recycle();
143 
144         a = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.MenuView, 0, 0);
145         mItemBackground = a.getDrawable(com.android.internal.R.styleable.MenuView_itemBackground);
146         mHorizontalDivider = a.getDrawable(com.android.internal.R.styleable.MenuView_horizontalDivider);
147         mHorizontalDividerRects = new ArrayList<Rect>();
148         mVerticalDivider =  a.getDrawable(com.android.internal.R.styleable.MenuView_verticalDivider);
149         mVerticalDividerRects = new ArrayList<Rect>();
150         mAnimations = a.getResourceId(com.android.internal.R.styleable.MenuView_windowAnimationStyle, 0);
151         a.recycle();
152 
153         if (mHorizontalDivider != null) {
154             mHorizontalDividerHeight = mHorizontalDivider.getIntrinsicHeight();
155             // Make sure to have some height for the divider
156             if (mHorizontalDividerHeight == -1) mHorizontalDividerHeight = 1;
157         }
158 
159         if (mVerticalDivider != null) {
160             mVerticalDividerWidth = mVerticalDivider.getIntrinsicWidth();
161             // Make sure to have some width for the divider
162             if (mVerticalDividerWidth == -1) mVerticalDividerWidth = 1;
163         }
164 
165         mLayout = new int[mMaxRows];
166 
167         // This view will be drawing the dividers
168         setWillNotDraw(false);
169 
170         // This is so we'll receive the MENU key in touch mode
171         setFocusableInTouchMode(true);
172         // This is so our children can still be arrow-key focused
173         setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
174     }
175 
getMaxItems()176     int getMaxItems() {
177         return mMaxItems;
178     }
179 
180     /**
181      * Figures out the layout for the menu items.
182      *
183      * @param width The available width for the icon menu.
184      */
layoutItems(int width)185     private void layoutItems(int width) {
186         int numItems = getChildCount();
187         if (numItems == 0) {
188             mLayoutNumRows = 0;
189             return;
190         }
191 
192         // Start with the least possible number of rows
193         int curNumRows =
194                 Math.min((int) Math.ceil(numItems / (float) mMaxItemsPerRow), mMaxRows);
195 
196         /*
197          * Increase the number of rows until we find a configuration that fits
198          * all of the items' titles. Worst case, we use mMaxRows.
199          */
200         for (; curNumRows <= mMaxRows; curNumRows++) {
201             layoutItemsUsingGravity(curNumRows, numItems);
202 
203             if (curNumRows >= numItems) {
204                 // Can't have more rows than items
205                 break;
206             }
207 
208             if (doItemsFit()) {
209                 // All the items fit, so this is a good configuration
210                 break;
211             }
212         }
213     }
214 
215     /**
216      * Figures out the layout for the menu items by equally distributing, and
217      * adding any excess items equally to lower rows.
218      *
219      * @param numRows The total number of rows for the menu view
220      * @param numItems The total number of items (across all rows) contained in
221      *            the menu view
222      * @return int[] Where the value of index i contains the number of items for row i
223      */
layoutItemsUsingGravity(int numRows, int numItems)224     private void layoutItemsUsingGravity(int numRows, int numItems) {
225         int numBaseItemsPerRow = numItems / numRows;
226         int numLeftoverItems = numItems % numRows;
227         /**
228          * The bottom rows will each get a leftover item. Rows (indexed at 0)
229          * that are >= this get a leftover item. Note: if there are 0 leftover
230          * items, no rows will get them since this value will be greater than
231          * the last row.
232          */
233         int rowsThatGetALeftoverItem = numRows - numLeftoverItems;
234 
235         int[] layout = mLayout;
236         for (int i = 0; i < numRows; i++) {
237             layout[i] = numBaseItemsPerRow;
238 
239             // Fill the bottom rows with a leftover item each
240             if (i >= rowsThatGetALeftoverItem) {
241                 layout[i]++;
242             }
243         }
244 
245         mLayoutNumRows = numRows;
246     }
247 
248     /**
249      * Checks whether each item's title is fully visible using the current
250      * layout.
251      *
252      * @return True if the items fit (each item's text is fully visible), false
253      *         otherwise.
254      */
doItemsFit()255     private boolean doItemsFit() {
256         int itemPos = 0;
257 
258         int[] layout = mLayout;
259         int numRows = mLayoutNumRows;
260         for (int row = 0; row < numRows; row++) {
261             int numItemsOnRow = layout[row];
262 
263             /*
264              * If there is only one item on this row, increasing the
265              * number of rows won't help.
266              */
267             if (numItemsOnRow == 1) {
268                 itemPos++;
269                 continue;
270             }
271 
272             for (int itemsOnRowCounter = numItemsOnRow; itemsOnRowCounter > 0;
273                     itemsOnRowCounter--) {
274                 View child = getChildAt(itemPos++);
275                 LayoutParams lp = (LayoutParams) child.getLayoutParams();
276                 if (lp.maxNumItemsOnRow < numItemsOnRow) {
277                     return false;
278                 }
279             }
280         }
281 
282         return true;
283     }
284 
getItemBackgroundDrawable()285     Drawable getItemBackgroundDrawable() {
286         return mItemBackground.getConstantState().newDrawable(getContext().getResources());
287     }
288 
289     /**
290      * Creates the item view for the 'More' button which is used to switch to
291      * the expanded menu view. This button is a special case since it does not
292      * have a MenuItemData backing it.
293      * @return The IconMenuItemView for the 'More' button
294      */
295     @UnsupportedAppUsage
createMoreItemView()296     IconMenuItemView createMoreItemView() {
297         Context context = getContext();
298         LayoutInflater inflater = LayoutInflater.from(context);
299 
300         final IconMenuItemView itemView = (IconMenuItemView) inflater.inflate(
301                 com.android.internal.R.layout.icon_menu_item_layout, null);
302 
303         Resources r = context.getResources();
304         itemView.initialize(r.getText(com.android.internal.R.string.more_item_label), mMoreIcon);
305 
306         // Set up a click listener on the view since there will be no invocation sequence
307         // due to the lack of a MenuItemData this view
308         itemView.setOnClickListener(new OnClickListener() {
309             public void onClick(View v) {
310                 // Switches the menu to expanded mode. Requires support from
311                 // the menu's active callback.
312                 mMenu.changeMenuMode();
313             }
314         });
315 
316         return itemView;
317     }
318 
319 
initialize(MenuBuilder menu)320     public void initialize(MenuBuilder menu) {
321         mMenu = menu;
322     }
323 
324     /**
325      * The positioning algorithm that gets called from onMeasure.  It
326      * just computes positions for each child, and then stores them in the child's layout params.
327      * @param menuWidth The width of this menu to assume for positioning
328      * @param menuHeight The height of this menu to assume for positioning
329      */
positionChildren(int menuWidth, int menuHeight)330     private void positionChildren(int menuWidth, int menuHeight) {
331         // Clear the containers for the positions where the dividers should be drawn
332         if (mHorizontalDivider != null) mHorizontalDividerRects.clear();
333         if (mVerticalDivider != null) mVerticalDividerRects.clear();
334 
335         // Get the minimum number of rows needed
336         final int numRows = mLayoutNumRows;
337         final int numRowsMinus1 = numRows - 1;
338         final int numItemsForRow[] = mLayout;
339 
340         // The item position across all rows
341         int itemPos = 0;
342         View child;
343         IconMenuView.LayoutParams childLayoutParams = null;
344 
345         // Use float for this to get precise positions (uniform item widths
346         // instead of last one taking any slack), and then convert to ints at last opportunity
347         float itemLeft;
348         float itemTop = 0;
349         // Since each row can have a different number of items, this will be computed per row
350         float itemWidth;
351         // Subtract the space needed for the horizontal dividers
352         final float itemHeight = (menuHeight - mHorizontalDividerHeight * (numRows - 1))
353                 / (float)numRows;
354 
355         for (int row = 0; row < numRows; row++) {
356             // Start at the left
357             itemLeft = 0;
358 
359             // Subtract the space needed for the vertical dividers, and divide by the number of items
360             itemWidth = (menuWidth - mVerticalDividerWidth * (numItemsForRow[row] - 1))
361                     / (float)numItemsForRow[row];
362 
363             for (int itemPosOnRow = 0; itemPosOnRow < numItemsForRow[row]; itemPosOnRow++) {
364                 // Tell the child to be exactly this size
365                 child = getChildAt(itemPos);
366                 child.measure(MeasureSpec.makeMeasureSpec((int) itemWidth, MeasureSpec.EXACTLY),
367                         MeasureSpec.makeMeasureSpec((int) itemHeight, MeasureSpec.EXACTLY));
368 
369                 // Remember the child's position for layout
370                 childLayoutParams = (IconMenuView.LayoutParams) child.getLayoutParams();
371                 childLayoutParams.left = (int) itemLeft;
372                 childLayoutParams.right = (int) (itemLeft + itemWidth);
373                 childLayoutParams.top = (int) itemTop;
374                 childLayoutParams.bottom = (int) (itemTop + itemHeight);
375 
376                 // Increment by item width
377                 itemLeft += itemWidth;
378                 itemPos++;
379 
380                 // Add a vertical divider to draw
381                 if (mVerticalDivider != null) {
382                     mVerticalDividerRects.add(new Rect((int) itemLeft,
383                             (int) itemTop, (int) (itemLeft + mVerticalDividerWidth),
384                             (int) (itemTop + itemHeight)));
385                 }
386 
387                 // Increment by divider width (even if we're not computing
388                 // dividers, since we need to leave room for them when
389                 // calculating item positions)
390                 itemLeft += mVerticalDividerWidth;
391             }
392 
393             // Last child on each row should extend to very right edge
394             if (childLayoutParams != null) {
395                 childLayoutParams.right = menuWidth;
396             }
397 
398             itemTop += itemHeight;
399 
400             // Add a horizontal divider to draw
401             if ((mHorizontalDivider != null) && (row < numRowsMinus1)) {
402                 mHorizontalDividerRects.add(new Rect(0, (int) itemTop, menuWidth,
403                         (int) (itemTop + mHorizontalDividerHeight)));
404 
405                 itemTop += mHorizontalDividerHeight;
406             }
407         }
408     }
409 
410     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)411     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
412         int measuredWidth = resolveSize(Integer.MAX_VALUE, widthMeasureSpec);
413         calculateItemFittingMetadata(measuredWidth);
414         layoutItems(measuredWidth);
415 
416         // Get the desired height of the icon menu view (last row of items does
417         // not have a divider below)
418         final int layoutNumRows = mLayoutNumRows;
419         final int desiredHeight = (mRowHeight + mHorizontalDividerHeight) *
420                 layoutNumRows - mHorizontalDividerHeight;
421 
422         // Maximum possible width and desired height
423         setMeasuredDimension(measuredWidth,
424                 resolveSize(desiredHeight, heightMeasureSpec));
425 
426         // Position the children
427         if (layoutNumRows > 0) {
428             positionChildren(getMeasuredWidth(), getMeasuredHeight());
429         }
430     }
431 
432 
433     @Override
onLayout(boolean changed, int l, int t, int r, int b)434     protected void onLayout(boolean changed, int l, int t, int r, int b) {
435         View child;
436         IconMenuView.LayoutParams childLayoutParams;
437 
438         for (int i = getChildCount() - 1; i >= 0; i--) {
439             child = getChildAt(i);
440             childLayoutParams = (IconMenuView.LayoutParams)child
441                     .getLayoutParams();
442 
443             // Layout children according to positions set during the measure
444             child.layout(childLayoutParams.left, childLayoutParams.top, childLayoutParams.right,
445                     childLayoutParams.bottom);
446         }
447     }
448 
449     @Override
onDraw(Canvas canvas)450     protected void onDraw(Canvas canvas) {
451         Drawable drawable = mHorizontalDivider;
452         if (drawable != null) {
453             // If we have a horizontal divider to draw, draw it at the remembered positions
454             final ArrayList<Rect> rects = mHorizontalDividerRects;
455             for (int i = rects.size() - 1; i >= 0; i--) {
456                 drawable.setBounds(rects.get(i));
457                 drawable.draw(canvas);
458             }
459         }
460 
461         drawable = mVerticalDivider;
462         if (drawable != null) {
463             // If we have a vertical divider to draw, draw it at the remembered positions
464             final ArrayList<Rect> rects = mVerticalDividerRects;
465             for (int i = rects.size() - 1; i >= 0; i--) {
466                 drawable.setBounds(rects.get(i));
467                 drawable.draw(canvas);
468             }
469         }
470     }
471 
invokeItem(MenuItemImpl item)472     public boolean invokeItem(MenuItemImpl item) {
473         return mMenu.performItemAction(item, 0);
474     }
475 
476     @Override
generateLayoutParams(AttributeSet attrs)477     public LayoutParams generateLayoutParams(AttributeSet attrs) {
478         return new IconMenuView.LayoutParams(getContext(), attrs);
479     }
480 
481     @Override
checkLayoutParams(ViewGroup.LayoutParams p)482     protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
483         // Override to allow type-checking of LayoutParams.
484         return p instanceof IconMenuView.LayoutParams;
485     }
486 
487     /**
488      * Marks as having stale children.
489      */
markStaleChildren()490     void markStaleChildren() {
491         if (!mHasStaleChildren) {
492             mHasStaleChildren = true;
493             requestLayout();
494         }
495     }
496 
497     /**
498      * @return The number of actual items shown (those that are backed by an
499      *         {@link MenuView.ItemView} implementation--eg: excludes More
500      *         item).
501      */
502     @UnsupportedAppUsage
getNumActualItemsShown()503     int getNumActualItemsShown() {
504         return mNumActualItemsShown;
505     }
506 
setNumActualItemsShown(int count)507     void setNumActualItemsShown(int count) {
508         mNumActualItemsShown = count;
509     }
510 
getWindowAnimations()511     public int getWindowAnimations() {
512         return mAnimations;
513     }
514 
515     /**
516      * Returns the number of items per row.
517      * <p>
518      * This should only be used for testing.
519      *
520      * @return The length of the array is the number of rows. A value at a
521      *         position is the number of items in that row.
522      * @hide
523      */
getLayout()524     public int[] getLayout() {
525         return mLayout;
526     }
527 
528     /**
529      * Returns the number of rows in the layout.
530      * <p>
531      * This should only be used for testing.
532      *
533      * @return The length of the array is the number of rows. A value at a
534      *         position is the number of items in that row.
535      * @hide
536      */
getLayoutNumRows()537     public int getLayoutNumRows() {
538         return mLayoutNumRows;
539     }
540 
541     @Override
dispatchKeyEvent(KeyEvent event)542     public boolean dispatchKeyEvent(KeyEvent event) {
543 
544         if (event.getKeyCode() == KeyEvent.KEYCODE_MENU) {
545             if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
546                 removeCallbacks(this);
547                 postDelayed(this, ViewConfiguration.getLongPressTimeout());
548             } else if (event.getAction() == KeyEvent.ACTION_UP) {
549 
550                 if (mMenuBeingLongpressed) {
551                     // It was in cycle mode, so reset it (will also remove us
552                     // from being called back)
553                     setCycleShortcutCaptionMode(false);
554                     return true;
555 
556                 } else {
557                     // Just remove us from being called back
558                     removeCallbacks(this);
559                     // Fall through to normal processing too
560                 }
561             }
562         }
563 
564         return super.dispatchKeyEvent(event);
565     }
566 
567     @Override
onAttachedToWindow()568     protected void onAttachedToWindow() {
569         super.onAttachedToWindow();
570 
571         requestFocus();
572     }
573 
574     @Override
onDetachedFromWindow()575     protected void onDetachedFromWindow() {
576         setCycleShortcutCaptionMode(false);
577         super.onDetachedFromWindow();
578     }
579 
580     @Override
onWindowFocusChanged(boolean hasWindowFocus)581     public void onWindowFocusChanged(boolean hasWindowFocus) {
582 
583         if (!hasWindowFocus) {
584             setCycleShortcutCaptionMode(false);
585         }
586 
587         super.onWindowFocusChanged(hasWindowFocus);
588     }
589 
590     /**
591      * Sets the shortcut caption mode for IconMenuView. This mode will
592      * continuously cycle between a child's shortcut and its title.
593      *
594      * @param cycleShortcutAndNormal Whether to go into cycling shortcut mode,
595      *        or to go back to normal.
596      */
setCycleShortcutCaptionMode(boolean cycleShortcutAndNormal)597     private void setCycleShortcutCaptionMode(boolean cycleShortcutAndNormal) {
598 
599         if (!cycleShortcutAndNormal) {
600             /*
601              * We're setting back to title, so remove any callbacks for setting
602              * to shortcut
603              */
604             removeCallbacks(this);
605             setChildrenCaptionMode(false);
606             mMenuBeingLongpressed = false;
607 
608         } else {
609 
610             // Set it the first time (the cycle will be started in run()).
611             setChildrenCaptionMode(true);
612         }
613 
614     }
615 
616     /**
617      * When this method is invoked if the menu is currently not being
618      * longpressed, it means that the longpress has just been reached (so we set
619      * longpress flag, and start cycling). If it is being longpressed, we cycle
620      * to the next mode.
621      */
run()622     public void run() {
623 
624         if (mMenuBeingLongpressed) {
625 
626             // Cycle to other caption mode on the children
627             setChildrenCaptionMode(!mLastChildrenCaptionMode);
628 
629         } else {
630 
631             // Switch ourselves to continuously cycle the items captions
632             mMenuBeingLongpressed = true;
633             setCycleShortcutCaptionMode(true);
634         }
635 
636         // We should run again soon to cycle to the other caption mode
637         postDelayed(this, ITEM_CAPTION_CYCLE_DELAY);
638     }
639 
640     /**
641      * Iterates children and sets the desired shortcut mode. Only
642      * {@link #setCycleShortcutCaptionMode(boolean)} and {@link #run()} should call
643      * this.
644      *
645      * @param shortcut Whether to show shortcut or the title.
646      */
setChildrenCaptionMode(boolean shortcut)647     private void setChildrenCaptionMode(boolean shortcut) {
648 
649         // Set the last caption mode pushed to children
650         mLastChildrenCaptionMode = shortcut;
651 
652         for (int i = getChildCount() - 1; i >= 0; i--) {
653             ((IconMenuItemView) getChildAt(i)).setCaptionMode(shortcut);
654         }
655     }
656 
657     /**
658      * For each item, calculates the most dense row that fully shows the item's
659      * title.
660      *
661      * @param width The available width of the icon menu.
662      */
calculateItemFittingMetadata(int width)663     private void calculateItemFittingMetadata(int width) {
664         int maxNumItemsPerRow = mMaxItemsPerRow;
665         int numItems = getChildCount();
666         for (int i = 0; i < numItems; i++) {
667             LayoutParams lp = (LayoutParams) getChildAt(i).getLayoutParams();
668             // Start with 1, since that case does not get covered in the loop below
669             lp.maxNumItemsOnRow = 1;
670             for (int curNumItemsPerRow = maxNumItemsPerRow; curNumItemsPerRow > 0;
671                     curNumItemsPerRow--) {
672                 // Check whether this item can fit into a row containing curNumItemsPerRow
673                 if (lp.desiredWidth < width / curNumItemsPerRow) {
674                     // It can, mark this value as the most dense row it can fit into
675                     lp.maxNumItemsOnRow = curNumItemsPerRow;
676                     break;
677                 }
678             }
679         }
680     }
681 
682     @Override
onSaveInstanceState()683     protected Parcelable onSaveInstanceState() {
684         Parcelable superState = super.onSaveInstanceState();
685 
686         View focusedView = getFocusedChild();
687 
688         for (int i = getChildCount() - 1; i >= 0; i--) {
689             if (getChildAt(i) == focusedView) {
690                 return new SavedState(superState, i);
691             }
692         }
693 
694         return new SavedState(superState, -1);
695     }
696 
697     @Override
onRestoreInstanceState(Parcelable state)698     protected void onRestoreInstanceState(Parcelable state) {
699         SavedState ss = (SavedState) state;
700         super.onRestoreInstanceState(ss.getSuperState());
701 
702         if (ss.focusedPosition >= getChildCount()) {
703             return;
704         }
705 
706         View v = getChildAt(ss.focusedPosition);
707         if (v != null) {
708             v.requestFocus();
709         }
710     }
711 
712     private static class SavedState extends BaseSavedState {
713         int focusedPosition;
714 
715         /**
716          * Constructor called from {@link IconMenuView#onSaveInstanceState()}
717          */
SavedState(Parcelable superState, int focusedPosition)718         public SavedState(Parcelable superState, int focusedPosition) {
719             super(superState);
720             this.focusedPosition = focusedPosition;
721         }
722 
723         /**
724          * Constructor called from {@link #CREATOR}
725          */
726         @UnsupportedAppUsage
SavedState(Parcel in)727         private SavedState(Parcel in) {
728             super(in);
729             focusedPosition = in.readInt();
730         }
731 
732         @Override
writeToParcel(Parcel dest, int flags)733         public void writeToParcel(Parcel dest, int flags) {
734             super.writeToParcel(dest, flags);
735             dest.writeInt(focusedPosition);
736         }
737 
738         public static final Parcelable.Creator<SavedState> CREATOR = new Parcelable.Creator<SavedState>() {
739             public SavedState createFromParcel(Parcel in) {
740                 return new SavedState(in);
741             }
742 
743             public SavedState[] newArray(int size) {
744                 return new SavedState[size];
745             }
746         };
747 
748     }
749 
750     /**
751      * Layout parameters specific to IconMenuView (stores the left, top, right, bottom from the
752      * measure pass).
753      */
754     public static class LayoutParams extends ViewGroup.MarginLayoutParams
755     {
756         int left, top, right, bottom;
757         int desiredWidth;
758         int maxNumItemsOnRow;
759 
LayoutParams(Context c, AttributeSet attrs)760         public LayoutParams(Context c, AttributeSet attrs) {
761             super(c, attrs);
762         }
763 
LayoutParams(int width, int height)764         public LayoutParams(int width, int height) {
765             super(width, height);
766         }
767     }
768 }
769