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