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