FileDocCategorySizeDatePackage
InCallMenuView.javaAPI DocAndroid 1.5 API20104Wed May 06 22:42:46 BST 2009com.android.phone

InCallMenuView

public class InCallMenuView extends android.view.ViewGroup
Custom View used as the "options panel" for the InCallScreen (i.e. the standard menu triggered by the MENU button.) This class purely handles the layout and display of the in-call menu items, *not* the actual contents of the menu or the states of the items. (See InCallMenu for the corresponding "model" class.)

Fields Summary
private static final String
LOG_TAG
private static final boolean
DBG
private int
mRowHeight
private android.graphics.drawable.Drawable
mHorizontalDivider
Divider that is drawn between all rows
private int
mHorizontalDividerHeight
Height of the horizontal divider
private ArrayList
mHorizontalDividerRects
Set of horizontal divider positions where the horizontal divider will be drawn
private android.graphics.drawable.Drawable
mVerticalDivider
Divider that is drawn between all columns
private int
mVerticalDividerWidth
Width of the vertical divider
private ArrayList
mVerticalDividerRects
Set of vertical divider positions where the vertical divider will be drawn
private android.graphics.drawable.Drawable
mItemBackground
Background of each item (should contain the selected and focused states)
private static final int
NUM_ROWS
The actual layout of items in the menu, organized into 3 rows. Row 0 is the topmost row onscreen, item 0 is the leftmost item in a row. Individual items may be disabled or hidden, but never move between rows or change their order within a row.
private static final int
MAX_ITEMS_PER_ROW
private InCallMenuItemView[]
mItems
private int[]
mNumItemsForRow
private int[]
mNumVisibleItemsForRow
Number of visible items per row, given the current state of all the menu items. A row with zero visible items isn't drawn at all.
private int
mNumVisibleRows
private InCallScreen
mInCallScreen
Reference to the InCallScreen activity that owns us. This will be null if we haven't been initialized yet *or* after the InCallScreen activity has been destroyed.
Constructors Summary
InCallMenuView(android.content.Context context, InCallScreen inCallScreen)



        
        super(context);
        if (DBG) log("InCallMenuView constructor...");

        mInCallScreen = inCallScreen;

        // Look up a few styled attrs from IconMenuView and/or MenuView
        // (to keep our look and feel at least *somewhat* consistent with
        // menus in other apps.)

        TypedArray a =
                mContext.obtainStyledAttributes(com.android.internal.R.styleable.IconMenuView);
        if (DBG) log("- IconMenuView styled attrs: " + a);
        mRowHeight = a.getDimensionPixelSize(
                com.android.internal.R.styleable.IconMenuView_rowHeight, 64);
        if (DBG) log("  - mRowHeight: " + mRowHeight);
        a.recycle();

        a = mContext.obtainStyledAttributes(com.android.internal.R.styleable.MenuView);
        if (DBG) log("- MenuView styled attrs: " + a);
        mItemBackground = a.getDrawable(com.android.internal.R.styleable.MenuView_itemBackground);
        if (DBG) log("  - mItemBackground: " + mItemBackground);
        mHorizontalDivider = a.getDrawable(com.android.internal.R.styleable.MenuView_horizontalDivider);
        if (DBG) log("  - mHorizontalDivider: " + mHorizontalDivider);
        mHorizontalDividerRects = new ArrayList<Rect>();
        mVerticalDivider =  a.getDrawable(com.android.internal.R.styleable.MenuView_verticalDivider);
        if (DBG) log("  - mVerticalDivider: " + mVerticalDivider);
        mVerticalDividerRects = new ArrayList<Rect>();
        a.recycle();

        if (mHorizontalDivider != null) {
            mHorizontalDividerHeight = mHorizontalDivider.getIntrinsicHeight();
            // Make sure to have some height for the divider
            if (mHorizontalDividerHeight == -1) mHorizontalDividerHeight = 1;
        }

        if (mVerticalDivider != null) {
            mVerticalDividerWidth = mVerticalDivider.getIntrinsicWidth();
            // Make sure to have some width for the divider
            if (mVerticalDividerWidth == -1) mVerticalDividerWidth = 1;
        }

        // This view will be drawing the dividers.
        setWillNotDraw(false);

        // Arrange to get key events even when there's no focused item in
        // the in-call menu (i.e. when in touch mode).
        // (We *always* want key events whenever we're visible, so that we
        // can forward them to the InCallScreen activity; see dispatchKeyEvent().)
        setFocusableInTouchMode(true);
        setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);

        // The default ViewGroup.LayoutParams width and height are
        // WRAP_CONTENT.  (This applies to us right now since we
        // initially have no LayoutParams at all.)
        // But in the Menu framework, when returning a view from
        // onCreatePanelView(), a layout width of WRAP_CONTENT indicates
        // that you want the smaller-sized "More" menu frame.  We want the
        // full-screen-width menu frame instead, though, so we need to
        // give ourselves a LayoutParams with width==FILL_PARENT.
        ViewGroup.LayoutParams lp =
                new ViewGroup.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,
                                           ViewGroup.LayoutParams.WRAP_CONTENT);
        setLayoutParams(lp);
    
Methods Summary
voidaddItemView(InCallMenuItemView itemView, int row)
Adds an InCallMenuItemView to the specified row.

        if (DBG) log("addItemView(" + itemView + ", row " + row + ")...");

        if (row >= NUM_ROWS) {
            throw new IllegalStateException("Row index " + row + " > NUM_ROWS");
        }

        int indexInRow = mNumItemsForRow[row];
        if (indexInRow >= MAX_ITEMS_PER_ROW) {
            throw new IllegalStateException("Too many items (" + indexInRow + ") in row " + row);
        }
        mNumItemsForRow[row]++;
        mItems[row][indexInRow] = itemView;

        //
        // Finally, add this item as a child.
        //

        ViewGroup.LayoutParams lp = itemView.getLayoutParams();

        if (lp == null) {
            // Default layout parameters
            lp = new LayoutParams(android.view.ViewGroup.LayoutParams.FILL_PARENT, android.view.ViewGroup.LayoutParams.FILL_PARENT);
        }

        // Apply the background to the item view
        itemView.setBackgroundDrawable(mItemBackground.getConstantState().newDrawable());

        addView(itemView, lp);
    
protected booleancheckLayoutParams(ViewGroup.LayoutParams p)

        // Override to allow type-checking of LayoutParams.
        return p instanceof InCallMenuView.LayoutParams;
    
voidclearInCallScreenReference()
Null out our reference to the InCallScreen activity. This indicates that the InCallScreen activity has been destroyed.

        mInCallScreen = null;
    
public booleandispatchKeyEvent(android.view.KeyEvent event)

        if (DBG) log("dispatchKeyEvent(" + event + ")...");

        // In most other apps, when a menu is up, the menu itself handles
        // keypresses.  And keys that aren't handled by the menu do NOT
        // get dispatched to the current Activity.
        //
        // But in the in-call UI, we don't have any menu shortcuts, *and*
        // it's important for buttons like CALL to work normally even
        // while the menu is up.  So we handle ALL key events (with some
        // exceptions -- see below) by simply forwarding them to the
        // InCallScreen.

        int keyCode = event.getKeyCode();
        if (event.isDown()) {
            switch (keyCode) {
                // The BACK key dismisses the menu.
                case KeyEvent.KEYCODE_BACK:
                    if (DBG) log("==> BACK key!  handling it ourselves...");
                    // We don't need to do anything here (since BACK
                    // is magically handled by the framework); we just
                    // need to *not* forward it to the InCallScreen.
                    break;

                // Don't send KEYCODE_DPAD_CENTER/KEYCODE_ENTER to the
                // InCallScreen either, since the framework needs those to
                // activate the focused item when using the trackball.
                case KeyEvent.KEYCODE_DPAD_CENTER:
                case KeyEvent.KEYCODE_ENTER:
                    break;

                // Anything else gets forwarded to the InCallScreen.
                default:
                    if (DBG) log("==> dispatchKeyEvent: forwarding event to the InCallScreen");
                    if (mInCallScreen != null) {
                        return mInCallScreen.onKeyDown(keyCode, event);
                    }
                    break;
            }
        } else if (mInCallScreen != null &&
                (keyCode == KeyEvent.KEYCODE_CALL ||
                        mInCallScreen.isKeyEventAcceptableDTMF(event))) {

            // Forward the key-up for the call and dialer buttons to the
            // InCallScreen.  All other key-up events are NOT handled here,
            // but instead fall through to dispatchKeyEvent from the superclass.
            if (DBG) log("==> dispatchKeyEvent: forwarding key up event to the InCallScreen");
            return mInCallScreen.onKeyUp(keyCode, event);
        }
        return super.dispatchKeyEvent(event);
    
voiddumpState()

        if (DBG) log("============ dumpState() ============");
        if (DBG) log("- mItems LENGTH: " + mItems.length);
        for (int row = 0; row < NUM_ROWS; row++) {
            if (DBG) log("-      Row " + row + ": length " + mItems[row].length
                         + ", num items " + mNumItemsForRow[row]
                         + ", num visible " + mNumVisibleItemsForRow[row]);
        }
    
public com.android.phone.InCallMenuView$LayoutParamsgenerateLayoutParams(android.util.AttributeSet attrs)

        return new InCallMenuView.LayoutParams(getContext(), attrs);
    
private voidlog(java.lang.String msg)

        Log.d(LOG_TAG, msg);
    
protected voidonDraw(android.graphics.Canvas canvas)

        if (DBG) log("onDraw()...");

        if (mHorizontalDivider != null) {
            // If we have a horizontal divider to draw, draw it at the remembered positions
            for (int i = mHorizontalDividerRects.size() - 1; i >= 0; i--) {
                mHorizontalDivider.setBounds(mHorizontalDividerRects.get(i));
                mHorizontalDivider.draw(canvas);
            }
        }

        if (mVerticalDivider != null) {
            // If we have a vertical divider to draw, draw it at the remembered positions
            for (int i = mVerticalDividerRects.size() - 1; i >= 0; i--) {
                mVerticalDivider.setBounds(mVerticalDividerRects.get(i));
                mVerticalDivider.draw(canvas);
            }
        }
    
protected voidonLayout(boolean changed, int l, int t, int r, int b)

        if (DBG) log("onLayout(changed " + changed
                     + ", l " + l + " t " + t + " r " + r + " b " + b + ")...");

        View child;
        InCallMenuView.LayoutParams childLayoutParams;

        for (int i = getChildCount() - 1; i >= 0; i--) {
            child = getChildAt(i);
            childLayoutParams = (InCallMenuView.LayoutParams) child.getLayoutParams();

            // Layout children according to positions set during the measure
            child.layout(childLayoutParams.left, childLayoutParams.top,
                         childLayoutParams.right, childLayoutParams.bottom);
        }
    
protected voidonMeasure(int widthMeasureSpec, int heightMeasureSpec)

        if (DBG) log("onMeasure(" + widthMeasureSpec + " x " + heightMeasureSpec + ")...");

        // Get the desired height of the icon menu view (last row of items does
        // not have a divider below)
        final int desiredHeight = (mRowHeight + mHorizontalDividerHeight) * mNumVisibleRows
                - mHorizontalDividerHeight;

        // Maximum possible width and desired height
        setMeasuredDimension(resolveSize(Integer.MAX_VALUE, widthMeasureSpec),
                             resolveSize(desiredHeight, heightMeasureSpec));

        // Position the children
        positionChildren(mMeasuredWidth, mMeasuredHeight);
    
private voidpositionChildren(int menuWidth, int menuHeight)
The positioning algorithm that gets called from onMeasure. It just computes positions for each child, and then stores them in the child's layout params. At this point the visibility of each item in mItems[][] is correct, and mNumVisibleRows and mNumVisibleItemsForRow[] have already been precomputed.

param
menuWidth The width of this menu to assume for positioning
param
menuHeight The height of this menu to assume for positioning TODO: This is a near-exact duplicate of IconMenuView.positionChildren(). Consider abstracting this out into a more general-purpose "grid layout with dividers" container that both classes could use...

        if (DBG) log("positionChildren(" + menuWidth + " x " + menuHeight + ")...");

        // Clear the containers for the positions where the dividers should be drawn
        if (mHorizontalDivider != null) mHorizontalDividerRects.clear();
        if (mVerticalDivider != null) mVerticalDividerRects.clear();

        InCallMenuItemView child;
        InCallMenuView.LayoutParams childLayoutParams = null;

        // Use float for this to get precise positions (uniform item widths
        // instead of last one taking any slack), and then convert to ints at last opportunity
        float itemLeft;
        float itemTop = 0;
        // Since each row can have a different number of items, this will be computed per row
        float itemWidth;
        // Subtract the space needed for the horizontal dividers
        final float itemHeight = (menuHeight - mHorizontalDividerHeight * (mNumVisibleRows - 1))
                / (float) mNumVisibleRows;

        // We add horizontal dividers between each visible row, so there should
        // be a total of mNumVisibleRows-1 of them.
        int numHorizDividersRemainingToDraw = mNumVisibleRows - 1;

        for (int row = 0; row < NUM_ROWS; row++) {
            int numItemsThisRow = mNumItemsForRow[row];
            int numVisibleThisRow = mNumVisibleItemsForRow[row];
            if (DBG) log("  - num visible for row " + row + ": " + numVisibleThisRow);
            if (numVisibleThisRow == 0) {
                continue;
            }

            InCallMenuItemView[] thisRow = mItems[row];

            // Start at the left
            itemLeft = 0;

            // Subtract the space needed for the vertical dividers, and
            // divide by the number of items.
            itemWidth = (menuWidth - mVerticalDividerWidth * (numVisibleThisRow - 1))
                    / (float) numVisibleThisRow;

            for (int itemIndex = 0; itemIndex < numItemsThisRow; itemIndex++) {
                child = mItems[row][itemIndex];

                if (!child.isVisible()) continue;

                if (DBG) log("==> child [" + row + "][" + itemIndex + "]: " + child);

                // Tell the child to be exactly this size
                child.measure(MeasureSpec.makeMeasureSpec((int) itemWidth, MeasureSpec.EXACTLY),
                              MeasureSpec.makeMeasureSpec((int) itemHeight, MeasureSpec.EXACTLY));

                // Remember the child's position for layout
                childLayoutParams = (InCallMenuView.LayoutParams) child.getLayoutParams();
                childLayoutParams.left = (int) itemLeft;
                childLayoutParams.right = (int) (itemLeft + itemWidth);
                childLayoutParams.top = (int) itemTop;
                childLayoutParams.bottom = (int) (itemTop + itemHeight);

                // Increment by item width
                itemLeft += itemWidth;

                // Add a vertical divider to draw
                if (mVerticalDivider != null) {
                    mVerticalDividerRects.add(new Rect((int) itemLeft,
                            (int) itemTop, (int) (itemLeft + mVerticalDividerWidth),
                            (int) (itemTop + itemHeight)));
                }

                // Increment by divider width (even if we're not computing
                // dividers, since we need to leave room for them when
                // calculating item positions)
                itemLeft += mVerticalDividerWidth;
            }

            // Last child on each row should extend to very right edge
            if (childLayoutParams != null) {
                childLayoutParams.right = menuWidth;
            }

            itemTop += itemHeight;

            // Add a horizontal divider (if we need one under this row)
            if ((mHorizontalDivider != null) && (numHorizDividersRemainingToDraw-- > 0)) {
                mHorizontalDividerRects.add(new Rect(0, (int) itemTop, menuWidth,
                                                     (int) (itemTop + mHorizontalDividerHeight)));
                itemTop += mHorizontalDividerHeight;
            }
        }
    
voidupdateVisibility()
Precomputes the number of visible items per row, and the total number of visible rows. (A row with zero visible items isn't drawn at all.)

        if (DBG) log("updateVisibility()...");

        mNumVisibleRows = 0;

        for (int row = 0; row < NUM_ROWS; row++) {
            InCallMenuItemView[] thisRow = mItems[row];
            int numItemsThisRow = mNumItemsForRow[row];
            int numVisibleThisRow = 0;
            for (int itemIndex = 0; itemIndex < numItemsThisRow; itemIndex++) {
                // if (DBG) log("  - Checking item: " + mItems[row][itemIndex]);
                if  (mItems[row][itemIndex].isVisible()) numVisibleThisRow++;
            }
            if (DBG) log("==> Num visible for row " + row + ": " + numVisibleThisRow);
            mNumVisibleItemsForRow[row] = numVisibleThisRow;
            if (numVisibleThisRow > 0) mNumVisibleRows++;
        }
        if (DBG) log("==> Num visible rows: " + mNumVisibleRows);