ActionBarView.java revision c3076425bba5c129408338be4e138f8be16855a6
1/*
2 * Copyright (C) 2010 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
17package com.android.internal.widget;
18
19import com.android.internal.R;
20import com.android.internal.view.menu.ActionMenuItem;
21import com.android.internal.view.menu.ActionMenuPresenter;
22import com.android.internal.view.menu.ActionMenuView;
23import com.android.internal.view.menu.MenuBuilder;
24import com.android.internal.view.menu.MenuItemImpl;
25import com.android.internal.view.menu.MenuPresenter;
26import com.android.internal.view.menu.MenuView;
27import com.android.internal.view.menu.SubMenuBuilder;
28
29import android.app.ActionBar;
30import android.app.ActionBar.OnNavigationListener;
31import android.app.Activity;
32import android.content.Context;
33import android.content.pm.ApplicationInfo;
34import android.content.pm.PackageManager;
35import android.content.pm.PackageManager.NameNotFoundException;
36import android.content.res.Resources;
37import android.content.res.TypedArray;
38import android.graphics.drawable.Drawable;
39import android.os.Parcel;
40import android.os.Parcelable;
41import android.text.TextUtils;
42import android.util.AttributeSet;
43import android.util.DisplayMetrics;
44import android.util.Log;
45import android.view.Gravity;
46import android.view.LayoutInflater;
47import android.view.Menu;
48import android.view.MenuItem;
49import android.view.View;
50import android.view.ViewGroup;
51import android.view.ViewParent;
52import android.view.Window;
53import android.widget.AdapterView;
54import android.widget.FrameLayout;
55import android.widget.ImageView;
56import android.widget.LinearLayout;
57import android.widget.ProgressBar;
58import android.widget.Spinner;
59import android.widget.SpinnerAdapter;
60import android.widget.TextView;
61
62/**
63 * @hide
64 */
65public class ActionBarView extends AbsActionBarView {
66    private static final String TAG = "ActionBarView";
67
68    /**
69     * Display options applied by default
70     */
71    public static final int DISPLAY_DEFAULT = 0;
72
73    /**
74     * Display options that require re-layout as opposed to a simple invalidate
75     */
76    private static final int DISPLAY_RELAYOUT_MASK =
77            ActionBar.DISPLAY_SHOW_HOME |
78            ActionBar.DISPLAY_USE_LOGO |
79            ActionBar.DISPLAY_HOME_AS_UP |
80            ActionBar.DISPLAY_SHOW_CUSTOM |
81            ActionBar.DISPLAY_SHOW_TITLE;
82
83    private static final int DEFAULT_CUSTOM_GRAVITY = Gravity.LEFT | Gravity.CENTER_VERTICAL;
84
85    private int mContentHeight;
86
87    private int mNavigationMode;
88    private int mDisplayOptions = ActionBar.DISPLAY_SHOW_HOME | ActionBar.DISPLAY_HOME_AS_UP;
89    private CharSequence mTitle;
90    private CharSequence mSubtitle;
91    private Drawable mIcon;
92    private Drawable mLogo;
93
94    private HomeView mHomeLayout;
95    private HomeView mExpandedHomeLayout;
96    private LinearLayout mTitleLayout;
97    private TextView mTitleView;
98    private TextView mSubtitleView;
99    private Spinner mSpinner;
100    private LinearLayout mListNavLayout;
101    private ScrollingTabContainerView mTabScrollView;
102    private View mCustomNavView;
103    private ProgressBar mProgressView;
104    private ProgressBar mIndeterminateProgressView;
105
106    private int mProgressBarPadding;
107    private int mItemPadding;
108
109    private int mTitleStyleRes;
110    private int mSubtitleStyleRes;
111    private int mProgressStyle;
112    private int mIndeterminateProgressStyle;
113
114    private boolean mSplitActionBar;
115    private boolean mUserTitle;
116    private boolean mIncludeTabs;
117    private boolean mIsCollapsable;
118
119    private MenuBuilder mOptionsMenu;
120
121    private ActionBarContextView mContextView;
122
123    private ActionMenuItem mLogoNavItem;
124
125    private SpinnerAdapter mSpinnerAdapter;
126    private OnNavigationListener mCallback;
127
128    private Runnable mTabSelector;
129
130    private ExpandedActionViewMenuPresenter mExpandedMenuPresenter;
131    View mExpandedActionView;
132
133    private final AdapterView.OnItemSelectedListener mNavItemSelectedListener =
134            new AdapterView.OnItemSelectedListener() {
135        public void onItemSelected(AdapterView parent, View view, int position, long id) {
136            if (mCallback != null) {
137                mCallback.onNavigationItemSelected(position, id);
138            }
139        }
140        public void onNothingSelected(AdapterView parent) {
141            // Do nothing
142        }
143    };
144
145    private final OnClickListener mExpandedActionViewUpListener = new OnClickListener() {
146        @Override
147        public void onClick(View v) {
148            final MenuItemImpl item = mExpandedMenuPresenter.mCurrentExpandedItem;
149            if (item != null) {
150                item.collapseActionView();
151            }
152        }
153    };
154
155    public ActionBarView(Context context, AttributeSet attrs) {
156        super(context, attrs);
157
158        // Background is always provided by the container.
159        setBackgroundResource(0);
160
161        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ActionBar);
162
163        ApplicationInfo appInfo = context.getApplicationInfo();
164        PackageManager pm = context.getPackageManager();
165        mNavigationMode = a.getInt(R.styleable.ActionBar_navigationMode,
166                ActionBar.NAVIGATION_MODE_STANDARD);
167        mTitle = a.getText(R.styleable.ActionBar_title);
168        mSubtitle = a.getText(R.styleable.ActionBar_subtitle);
169
170        mLogo = a.getDrawable(R.styleable.ActionBar_logo);
171        if (mLogo == null) {
172            if (context instanceof Activity) {
173                try {
174                    mLogo = pm.getActivityLogo(((Activity) context).getComponentName());
175                } catch (NameNotFoundException e) {
176                    Log.e(TAG, "Activity component name not found!", e);
177                }
178            }
179            if (mLogo == null) {
180                mLogo = appInfo.loadLogo(pm);
181            }
182        }
183
184        mIcon = a.getDrawable(R.styleable.ActionBar_icon);
185        if (mIcon == null) {
186            if (context instanceof Activity) {
187                try {
188                    mIcon = pm.getActivityIcon(((Activity) context).getComponentName());
189                } catch (NameNotFoundException e) {
190                    Log.e(TAG, "Activity component name not found!", e);
191                }
192            }
193            if (mIcon == null) {
194                mIcon = appInfo.loadIcon(pm);
195            }
196        }
197
198        final LayoutInflater inflater = LayoutInflater.from(context);
199
200        final int homeResId = a.getResourceId(
201                com.android.internal.R.styleable.ActionBar_homeLayout,
202                com.android.internal.R.layout.action_bar_home);
203
204        mHomeLayout = (HomeView) inflater.inflate(homeResId, this, false);
205
206        mExpandedHomeLayout = (HomeView) inflater.inflate(homeResId, this, false);
207        mExpandedHomeLayout.setUp(true);
208        mExpandedHomeLayout.setOnClickListener(mExpandedActionViewUpListener);
209
210        mTitleStyleRes = a.getResourceId(R.styleable.ActionBar_titleTextStyle, 0);
211        mSubtitleStyleRes = a.getResourceId(R.styleable.ActionBar_subtitleTextStyle, 0);
212        mProgressStyle = a.getResourceId(R.styleable.ActionBar_progressBarStyle, 0);
213        mIndeterminateProgressStyle = a.getResourceId(
214                R.styleable.ActionBar_indeterminateProgressStyle, 0);
215
216        mProgressBarPadding = a.getDimensionPixelOffset(R.styleable.ActionBar_progressBarPadding, 0);
217        mItemPadding = a.getDimensionPixelOffset(R.styleable.ActionBar_itemPadding, 0);
218
219        setDisplayOptions(a.getInt(R.styleable.ActionBar_displayOptions, DISPLAY_DEFAULT));
220
221        final int customNavId = a.getResourceId(R.styleable.ActionBar_customNavigationLayout, 0);
222        if (customNavId != 0) {
223            mCustomNavView = (View) inflater.inflate(customNavId, this, false);
224            mNavigationMode = ActionBar.NAVIGATION_MODE_STANDARD;
225            setDisplayOptions(mDisplayOptions | ActionBar.DISPLAY_SHOW_CUSTOM);
226        }
227
228        mContentHeight = a.getLayoutDimension(R.styleable.ActionBar_height, 0);
229
230        a.recycle();
231
232        mLogoNavItem = new ActionMenuItem(context, 0, android.R.id.home, 0, 0, mTitle);
233        mHomeLayout.setOnClickListener(new OnClickListener() {
234            public void onClick(View v) {
235                Context context = getContext();
236                if (context instanceof Activity) {
237                    Activity activity = (Activity) context;
238                    activity.onMenuItemSelected(Window.FEATURE_OPTIONS_PANEL, mLogoNavItem);
239                }
240            }
241        });
242        mHomeLayout.setClickable(true);
243        mHomeLayout.setFocusable(true);
244    }
245
246    @Override
247    public void onDetachedFromWindow() {
248        super.onDetachedFromWindow();
249        removeCallbacks(mTabSelector);
250    }
251
252    @Override
253    public boolean shouldDelayChildPressedState() {
254        return false;
255    }
256
257    public void initProgress() {
258        mProgressView = new ProgressBar(mContext, null, 0, mProgressStyle);
259        mProgressView.setId(R.id.progress_horizontal);
260        mProgressView.setMax(10000);
261        addView(mProgressView);
262    }
263
264    public void initIndeterminateProgress() {
265        mIndeterminateProgressView = new ProgressBar(mContext, null, 0,
266                mIndeterminateProgressStyle);
267        mIndeterminateProgressView.setId(R.id.progress_circular);
268        addView(mIndeterminateProgressView);
269    }
270
271    public void setContentHeight(int height) {
272        mContentHeight = height;
273        requestLayout();
274    }
275
276    public void setSplitActionBar(boolean splitActionBar) {
277        if (mSplitActionBar != splitActionBar) {
278            if (mMenuView != null) {
279                if (splitActionBar) {
280                    removeView(mMenuView);
281                    if (mSplitView != null) {
282                        mSplitView.addView(mMenuView);
283                    }
284                } else {
285                    addView(mMenuView);
286                }
287            }
288            mSplitActionBar = splitActionBar;
289        }
290    }
291
292    public boolean isSplitActionBar() {
293        return mSplitActionBar;
294    }
295
296    public boolean hasEmbeddedTabs() {
297        return mIncludeTabs;
298    }
299
300    public void setEmbeddedTabView(ScrollingTabContainerView tabs) {
301        mTabScrollView = tabs;
302        mIncludeTabs = tabs != null;
303        if (mIncludeTabs && mNavigationMode == ActionBar.NAVIGATION_MODE_TABS) {
304            addView(mTabScrollView);
305        }
306    }
307
308    public void setCallback(OnNavigationListener callback) {
309        mCallback = callback;
310    }
311
312    public void setMenu(Menu menu, MenuPresenter.Callback cb) {
313        if (menu == mOptionsMenu) return;
314
315        if (mOptionsMenu != null) {
316            mOptionsMenu.removeMenuPresenter(mActionMenuPresenter);
317            mOptionsMenu.removeMenuPresenter(mExpandedMenuPresenter);
318        }
319
320        MenuBuilder builder = (MenuBuilder) menu;
321        mOptionsMenu = builder;
322        if (mMenuView != null) {
323            removeView(mMenuView);
324        }
325        if (mActionMenuPresenter == null) {
326            mActionMenuPresenter = new ActionMenuPresenter();
327            mActionMenuPresenter.setCallback(cb);
328            mExpandedMenuPresenter = new ExpandedActionViewMenuPresenter();
329        }
330        builder.addMenuPresenter(mActionMenuPresenter);
331        builder.addMenuPresenter(mExpandedMenuPresenter);
332
333        final ActionMenuView menuView = (ActionMenuView) mActionMenuPresenter.getMenuView(this);
334        final LayoutParams layoutParams = new LayoutParams(LayoutParams.WRAP_CONTENT,
335                LayoutParams.MATCH_PARENT);
336        menuView.setLayoutParams(layoutParams);
337        if (!mSplitActionBar) {
338            addView(menuView);
339        } else {
340            // Allow full screen width in split mode.
341            mActionMenuPresenter.setWidthLimit(
342                    getContext().getResources().getDisplayMetrics().widthPixels, true);
343            // No limit to the item count; use whatever will fit.
344            mActionMenuPresenter.setItemLimit(Integer.MAX_VALUE);
345            // Span the whole width
346            layoutParams.width = LayoutParams.MATCH_PARENT;
347            if (mSplitView != null) {
348                mSplitView.addView(menuView);
349            } // We'll add this later if we missed it this time.
350        }
351        mMenuView = menuView;
352    }
353
354    public void setCustomNavigationView(View view) {
355        final boolean showCustom = (mDisplayOptions & ActionBar.DISPLAY_SHOW_CUSTOM) != 0;
356        if (mCustomNavView != null && showCustom) {
357            removeView(mCustomNavView);
358        }
359        mCustomNavView = view;
360        if (mCustomNavView != null && showCustom) {
361            addView(mCustomNavView);
362        }
363    }
364
365    public CharSequence getTitle() {
366        return mTitle;
367    }
368
369    /**
370     * Set the action bar title. This will always replace or override window titles.
371     * @param title Title to set
372     *
373     * @see #setWindowTitle(CharSequence)
374     */
375    public void setTitle(CharSequence title) {
376        mUserTitle = true;
377        setTitleImpl(title);
378    }
379
380    /**
381     * Set the window title. A window title will always be replaced or overridden by a user title.
382     * @param title Title to set
383     *
384     * @see #setTitle(CharSequence)
385     */
386    public void setWindowTitle(CharSequence title) {
387        if (!mUserTitle) {
388            setTitleImpl(title);
389        }
390    }
391
392    private void setTitleImpl(CharSequence title) {
393        mTitle = title;
394        if (mTitleView != null) {
395            mTitleView.setText(title);
396            mTitleLayout.setVisibility(TextUtils.isEmpty(mTitle) && TextUtils.isEmpty(mSubtitle) ?
397                    GONE : VISIBLE);
398        }
399        if (mLogoNavItem != null) {
400            mLogoNavItem.setTitle(title);
401        }
402    }
403
404    public CharSequence getSubtitle() {
405        return mSubtitle;
406    }
407
408    public void setSubtitle(CharSequence subtitle) {
409        mSubtitle = subtitle;
410        if (mSubtitleView != null) {
411            mSubtitleView.setText(subtitle);
412            mSubtitleView.setVisibility(subtitle != null ? VISIBLE : GONE);
413            mTitleLayout.setVisibility(TextUtils.isEmpty(mTitle) && TextUtils.isEmpty(mSubtitle) ?
414                    GONE : VISIBLE);
415        }
416    }
417
418    public void setDisplayOptions(int options) {
419        final int flagsChanged = options ^ mDisplayOptions;
420        mDisplayOptions = options;
421
422        if ((flagsChanged & ActionBar.DISPLAY_DISABLE_HOME) != 0) {
423            final boolean disableHome = (options & ActionBar.DISPLAY_DISABLE_HOME) != 0;
424            mHomeLayout.setEnabled(!disableHome);
425        }
426
427        if ((flagsChanged & DISPLAY_RELAYOUT_MASK) != 0) {
428            final int vis = (options & ActionBar.DISPLAY_SHOW_HOME) != 0 ? VISIBLE : GONE;
429            mHomeLayout.setVisibility(vis);
430
431            if ((flagsChanged & ActionBar.DISPLAY_HOME_AS_UP) != 0) {
432                mHomeLayout.setUp((options & ActionBar.DISPLAY_HOME_AS_UP) != 0);
433            }
434
435            if ((flagsChanged & ActionBar.DISPLAY_USE_LOGO) != 0) {
436                final boolean logoVis = mLogo != null && (options & ActionBar.DISPLAY_USE_LOGO) != 0;
437                mHomeLayout.setIcon(logoVis ? mLogo : mIcon);
438            }
439
440            if ((flagsChanged & ActionBar.DISPLAY_SHOW_TITLE) != 0) {
441                if ((options & ActionBar.DISPLAY_SHOW_TITLE) != 0) {
442                    initTitle();
443                } else {
444                    removeView(mTitleLayout);
445                }
446            }
447
448            if ((flagsChanged & ActionBar.DISPLAY_SHOW_CUSTOM) != 0 && mCustomNavView != null) {
449                if ((options & ActionBar.DISPLAY_SHOW_CUSTOM) != 0) {
450                    addView(mCustomNavView);
451                } else {
452                    removeView(mCustomNavView);
453                }
454            }
455
456            requestLayout();
457        } else {
458            invalidate();
459        }
460
461        // Make sure the home button has an accurate content description for accessibility.
462        if ((options & ActionBar.DISPLAY_DISABLE_HOME) != 0) {
463            mHomeLayout.setContentDescription(null);
464        } else if ((options & ActionBar.DISPLAY_HOME_AS_UP) != 0) {
465            mHomeLayout.setContentDescription(mContext.getResources().getText(
466                    R.string.action_bar_up_description));
467        } else {
468            mHomeLayout.setContentDescription(mContext.getResources().getText(
469                    R.string.action_bar_home_description));
470        }
471    }
472
473    public void setIcon(Drawable icon) {
474        mIcon = icon;
475        if (icon != null &&
476                ((mDisplayOptions & ActionBar.DISPLAY_USE_LOGO) == 0 || mLogo == null)) {
477            mHomeLayout.setIcon(icon);
478        }
479    }
480
481    public void setIcon(int resId) {
482        setIcon(mContext.getResources().getDrawableForDensity(resId, getPreferredIconDensity()));
483    }
484
485    public void setLogo(Drawable logo) {
486        mLogo = logo;
487        if (logo != null && (mDisplayOptions & ActionBar.DISPLAY_USE_LOGO) != 0) {
488            mHomeLayout.setIcon(logo);
489        }
490    }
491
492    public void setLogo(int resId) {
493        mContext.getResources().getDrawable(resId);
494    }
495
496    /**
497     * @return Drawable density to load that will best fit the available height.
498     */
499    private int getPreferredIconDensity() {
500        final Resources res = mContext.getResources();
501        final int availableHeight = getLayoutParams().height -
502                mHomeLayout.getVerticalIconPadding();
503        int iconSize = res.getDimensionPixelSize(android.R.dimen.app_icon_size);
504
505        if (iconSize * DisplayMetrics.DENSITY_LOW >= availableHeight) {
506            return DisplayMetrics.DENSITY_LOW;
507        } else if (iconSize * DisplayMetrics.DENSITY_MEDIUM >= availableHeight) {
508            return DisplayMetrics.DENSITY_MEDIUM;
509        } else if (iconSize * DisplayMetrics.DENSITY_HIGH >= availableHeight) {
510            return DisplayMetrics.DENSITY_HIGH;
511        }
512        return DisplayMetrics.DENSITY_XHIGH;
513    }
514
515    public void setNavigationMode(int mode) {
516        final int oldMode = mNavigationMode;
517        if (mode != oldMode) {
518            switch (oldMode) {
519            case ActionBar.NAVIGATION_MODE_LIST:
520                if (mListNavLayout != null) {
521                    removeView(mListNavLayout);
522                }
523                break;
524            case ActionBar.NAVIGATION_MODE_TABS:
525                if (mTabScrollView != null && mIncludeTabs) {
526                    removeView(mTabScrollView);
527                }
528            }
529
530            switch (mode) {
531            case ActionBar.NAVIGATION_MODE_LIST:
532                if (mSpinner == null) {
533                    mSpinner = new Spinner(mContext, null,
534                            com.android.internal.R.attr.actionDropDownStyle);
535                    mListNavLayout = new LinearLayout(mContext, null,
536                            com.android.internal.R.attr.actionBarTabBarStyle);
537                    LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
538                            LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);
539                    params.gravity = Gravity.CENTER;
540                    mListNavLayout.addView(mSpinner, params);
541                }
542                if (mSpinner.getAdapter() != mSpinnerAdapter) {
543                    mSpinner.setAdapter(mSpinnerAdapter);
544                }
545                mSpinner.setOnItemSelectedListener(mNavItemSelectedListener);
546                addView(mListNavLayout);
547                break;
548            case ActionBar.NAVIGATION_MODE_TABS:
549                if (mTabScrollView != null && mIncludeTabs) {
550                    addView(mTabScrollView);
551                }
552                break;
553            }
554            mNavigationMode = mode;
555            requestLayout();
556        }
557    }
558
559    public ScrollingTabContainerView createTabContainer() {
560        final LinearLayout tabLayout = new LinearLayout(getContext(), null,
561                com.android.internal.R.attr.actionBarTabBarStyle);
562        tabLayout.setMeasureWithLargestChildEnabled(true);
563        tabLayout.setLayoutParams(new LinearLayout.LayoutParams(
564                LinearLayout.LayoutParams.WRAP_CONTENT, mContentHeight));
565
566        final ScrollingTabContainerView scroller = new ScrollingTabContainerView(mContext);
567        scroller.setTabLayout(tabLayout);
568        return scroller;
569    }
570
571    public void setDropdownAdapter(SpinnerAdapter adapter) {
572        mSpinnerAdapter = adapter;
573        if (mSpinner != null) {
574            mSpinner.setAdapter(adapter);
575        }
576    }
577
578    public SpinnerAdapter getDropdownAdapter() {
579        return mSpinnerAdapter;
580    }
581
582    public void setDropdownSelectedPosition(int position) {
583        mSpinner.setSelection(position);
584    }
585
586    public int getDropdownSelectedPosition() {
587        return mSpinner.getSelectedItemPosition();
588    }
589
590    public View getCustomNavigationView() {
591        return mCustomNavView;
592    }
593
594    public int getNavigationMode() {
595        return mNavigationMode;
596    }
597
598    public int getDisplayOptions() {
599        return mDisplayOptions;
600    }
601
602    @Override
603    protected LayoutParams generateDefaultLayoutParams() {
604        // Used by custom nav views if they don't supply layout params. Everything else
605        // added to an ActionBarView should have them already.
606        return new ActionBar.LayoutParams(DEFAULT_CUSTOM_GRAVITY);
607    }
608
609    @Override
610    protected void onFinishInflate() {
611        super.onFinishInflate();
612
613        addView(mHomeLayout);
614
615        if (mCustomNavView != null && (mDisplayOptions & ActionBar.DISPLAY_SHOW_CUSTOM) != 0) {
616            final ViewParent parent = mCustomNavView.getParent();
617            if (parent != this) {
618                if (parent instanceof ViewGroup) {
619                    ((ViewGroup) parent).removeView(mCustomNavView);
620                }
621                addView(mCustomNavView);
622            }
623        }
624    }
625
626    private void initTitle() {
627        LayoutInflater inflater = LayoutInflater.from(getContext());
628        mTitleLayout = (LinearLayout) inflater.inflate(R.layout.action_bar_title_item, null);
629        mTitleView = (TextView) mTitleLayout.findViewById(R.id.action_bar_title);
630        mSubtitleView = (TextView) mTitleLayout.findViewById(R.id.action_bar_subtitle);
631
632        if (mTitleStyleRes != 0) {
633            mTitleView.setTextAppearance(mContext, mTitleStyleRes);
634        }
635        if (mTitle != null) {
636            mTitleView.setText(mTitle);
637        }
638
639        if (mSubtitleStyleRes != 0) {
640            mSubtitleView.setTextAppearance(mContext, mSubtitleStyleRes);
641        }
642        if (mSubtitle != null) {
643            mSubtitleView.setText(mSubtitle);
644            mSubtitleView.setVisibility(VISIBLE);
645        }
646
647        addView(mTitleLayout);
648    }
649
650    public void setContextView(ActionBarContextView view) {
651        mContextView = view;
652    }
653
654    public void setCollapsable(boolean collapsable) {
655        mIsCollapsable = collapsable;
656    }
657
658    @Override
659    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
660        final int childCount = getChildCount();
661        if (mIsCollapsable) {
662            int visibleChildren = 0;
663            for (int i = 0; i < childCount; i++) {
664                final View child = getChildAt(i);
665                if (child.getVisibility() != GONE &&
666                        !(child == mMenuView && mMenuView.getChildCount() == 0)) {
667                    visibleChildren++;
668                }
669            }
670
671            if (visibleChildren == 0) {
672                // No size for an empty action bar when collapsable.
673                setMeasuredDimension(0, 0);
674                return;
675            }
676        }
677
678        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
679        if (widthMode != MeasureSpec.EXACTLY) {
680            throw new IllegalStateException(getClass().getSimpleName() + " can only be used " +
681                    "with android:layout_width=\"match_parent\" (or fill_parent)");
682        }
683
684        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
685        if (heightMode != MeasureSpec.AT_MOST) {
686            throw new IllegalStateException(getClass().getSimpleName() + " can only be used " +
687                    "with android:layout_height=\"wrap_content\"");
688        }
689
690        int contentWidth = MeasureSpec.getSize(widthMeasureSpec);
691
692        int maxHeight = mContentHeight > 0 ?
693                mContentHeight : MeasureSpec.getSize(heightMeasureSpec);
694
695        final int verticalPadding = getPaddingTop() + getPaddingBottom();
696        final int paddingLeft = getPaddingLeft();
697        final int paddingRight = getPaddingRight();
698        final int height = maxHeight - verticalPadding;
699        final int childSpecHeight = MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST);
700
701        int availableWidth = contentWidth - paddingLeft - paddingRight;
702        int leftOfCenter = availableWidth / 2;
703        int rightOfCenter = leftOfCenter;
704
705        View homeLayout = mExpandedActionView != null ? mExpandedHomeLayout : mHomeLayout;
706
707        if (homeLayout.getVisibility() != GONE) {
708            homeLayout.measure(
709                    MeasureSpec.makeMeasureSpec(availableWidth, MeasureSpec.AT_MOST),
710                    MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
711            final int homeWidth = homeLayout.getMeasuredWidth();
712            availableWidth = Math.max(0, availableWidth - homeWidth);
713            leftOfCenter = Math.max(0, availableWidth - homeWidth);
714        }
715
716        if (mMenuView != null && mMenuView.getParent() == this) {
717            availableWidth = measureChildView(mMenuView, availableWidth,
718                    childSpecHeight, 0);
719            rightOfCenter = Math.max(0, rightOfCenter - mMenuView.getMeasuredWidth());
720        }
721
722        if (mExpandedActionView == null) {
723            boolean showTitle = mTitleLayout != null && mTitleLayout.getVisibility() != GONE &&
724            (mDisplayOptions & ActionBar.DISPLAY_SHOW_TITLE) != 0;
725            if (showTitle) {
726                availableWidth = measureChildView(mTitleLayout, availableWidth, childSpecHeight, 0);
727                leftOfCenter = Math.max(0, leftOfCenter - mTitleLayout.getMeasuredWidth());
728            }
729
730            switch (mNavigationMode) {
731                case ActionBar.NAVIGATION_MODE_LIST:
732                    if (mListNavLayout != null) {
733                        final int itemPaddingSize = showTitle ? mItemPadding * 2 : mItemPadding;
734                        availableWidth = Math.max(0, availableWidth - itemPaddingSize);
735                        leftOfCenter = Math.max(0, leftOfCenter - itemPaddingSize);
736                        mListNavLayout.measure(
737                                MeasureSpec.makeMeasureSpec(availableWidth, MeasureSpec.AT_MOST),
738                                MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
739                        final int listNavWidth = mListNavLayout.getMeasuredWidth();
740                        availableWidth = Math.max(0, availableWidth - listNavWidth);
741                        leftOfCenter = Math.max(0, leftOfCenter - listNavWidth);
742                    }
743                    break;
744                case ActionBar.NAVIGATION_MODE_TABS:
745                    if (mTabScrollView != null) {
746                        final int itemPaddingSize = showTitle ? mItemPadding * 2 : mItemPadding;
747                        availableWidth = Math.max(0, availableWidth - itemPaddingSize);
748                        leftOfCenter = Math.max(0, leftOfCenter - itemPaddingSize);
749                        mTabScrollView.measure(
750                                MeasureSpec.makeMeasureSpec(availableWidth, MeasureSpec.AT_MOST),
751                                MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
752                        final int tabWidth = mTabScrollView.getMeasuredWidth();
753                        availableWidth = Math.max(0, availableWidth - tabWidth);
754                        leftOfCenter = Math.max(0, leftOfCenter - tabWidth);
755                    }
756                    break;
757            }
758        }
759
760        if (mIndeterminateProgressView != null &&
761                mIndeterminateProgressView.getVisibility() != GONE) {
762            availableWidth = measureChildView(mIndeterminateProgressView, availableWidth,
763                    childSpecHeight, 0);
764            rightOfCenter = Math.max(0,
765                    rightOfCenter - mIndeterminateProgressView.getMeasuredWidth());
766        }
767
768        View customView = null;
769        if (mExpandedActionView != null) {
770            customView = mExpandedActionView;
771        } else if ((mDisplayOptions & ActionBar.DISPLAY_SHOW_CUSTOM) != 0 &&
772                mCustomNavView != null) {
773            customView = mCustomNavView;
774        }
775
776        if (customView != null) {
777            final LayoutParams lp = generateLayoutParams(customView.getLayoutParams());
778            final ActionBar.LayoutParams ablp = lp instanceof ActionBar.LayoutParams ?
779                    (ActionBar.LayoutParams) lp : null;
780
781            int horizontalMargin = 0;
782            int verticalMargin = 0;
783            if (ablp != null) {
784                horizontalMargin = ablp.leftMargin + ablp.rightMargin;
785                verticalMargin = ablp.topMargin + ablp.bottomMargin;
786            }
787
788            // If the action bar is wrapping to its content height, don't allow a custom
789            // view to MATCH_PARENT.
790            int customNavHeightMode;
791            if (mContentHeight <= 0) {
792                customNavHeightMode = MeasureSpec.AT_MOST;
793            } else {
794                customNavHeightMode = lp.height != LayoutParams.WRAP_CONTENT ?
795                        MeasureSpec.EXACTLY : MeasureSpec.AT_MOST;
796            }
797            final int customNavHeight = Math.max(0,
798                    (lp.height >= 0 ? Math.min(lp.height, height) : height) - verticalMargin);
799
800            final int customNavWidthMode = lp.width != LayoutParams.WRAP_CONTENT ?
801                    MeasureSpec.EXACTLY : MeasureSpec.AT_MOST;
802            int customNavWidth = Math.max(0,
803                    (lp.width >= 0 ? Math.min(lp.width, availableWidth) : availableWidth)
804                    - horizontalMargin);
805            final int hgrav = (ablp != null ? ablp.gravity : DEFAULT_CUSTOM_GRAVITY) &
806                    Gravity.HORIZONTAL_GRAVITY_MASK;
807
808            // Centering a custom view is treated specially; we try to center within the whole
809            // action bar rather than in the available space.
810            if (hgrav == Gravity.CENTER_HORIZONTAL && lp.width == LayoutParams.MATCH_PARENT) {
811                customNavWidth = Math.min(leftOfCenter, rightOfCenter) * 2;
812            }
813
814            customView.measure(
815                    MeasureSpec.makeMeasureSpec(customNavWidth, customNavWidthMode),
816                    MeasureSpec.makeMeasureSpec(customNavHeight, customNavHeightMode));
817        }
818
819        if (mContentHeight <= 0) {
820            int measuredHeight = 0;
821            for (int i = 0; i < childCount; i++) {
822                View v = getChildAt(i);
823                int paddedViewHeight = v.getMeasuredHeight() + verticalPadding;
824                if (paddedViewHeight > measuredHeight) {
825                    measuredHeight = paddedViewHeight;
826                }
827            }
828            setMeasuredDimension(contentWidth, measuredHeight);
829        } else {
830            setMeasuredDimension(contentWidth, maxHeight);
831        }
832
833        if (mContextView != null) {
834            mContextView.setHeight(getMeasuredHeight());
835        }
836
837        if (mProgressView != null && mProgressView.getVisibility() != GONE) {
838            mProgressView.measure(MeasureSpec.makeMeasureSpec(
839                    contentWidth - mProgressBarPadding * 2, MeasureSpec.EXACTLY),
840                    MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.AT_MOST));
841        }
842    }
843
844    @Override
845    protected void onLayout(boolean changed, int l, int t, int r, int b) {
846        int x = getPaddingLeft();
847        final int y = getPaddingTop();
848        final int contentHeight = b - t - getPaddingTop() - getPaddingBottom();
849
850        if (contentHeight <= 0) {
851            // Nothing to do if we can't see anything.
852            return;
853        }
854
855        View homeLayout = mExpandedActionView != null ? mExpandedHomeLayout : mHomeLayout;
856        if (homeLayout.getVisibility() != GONE) {
857            x += positionChild(homeLayout, x, y, contentHeight);
858        }
859
860        if (mExpandedActionView == null) {
861            final boolean showTitle = mTitleLayout != null && mTitleLayout.getVisibility() != GONE &&
862            (mDisplayOptions & ActionBar.DISPLAY_SHOW_TITLE) != 0;
863            if (showTitle) {
864                x += positionChild(mTitleLayout, x, y, contentHeight);
865            }
866
867            switch (mNavigationMode) {
868                case ActionBar.NAVIGATION_MODE_STANDARD:
869                    break;
870                case ActionBar.NAVIGATION_MODE_LIST:
871                    if (mListNavLayout != null) {
872                        if (showTitle) x += mItemPadding;
873                        x += positionChild(mListNavLayout, x, y, contentHeight) + mItemPadding;
874                    }
875                    break;
876                case ActionBar.NAVIGATION_MODE_TABS:
877                    if (mTabScrollView != null) {
878                        if (showTitle) x += mItemPadding;
879                        x += positionChild(mTabScrollView, x, y, contentHeight) + mItemPadding;
880                    }
881                    break;
882            }
883        }
884
885        int menuLeft = r - l - getPaddingRight();
886        if (mMenuView != null && mMenuView.getParent() == this) {
887            positionChildInverse(mMenuView, menuLeft, y, contentHeight);
888            menuLeft -= mMenuView.getMeasuredWidth();
889        }
890
891        if (mIndeterminateProgressView != null &&
892                mIndeterminateProgressView.getVisibility() != GONE) {
893            positionChildInverse(mIndeterminateProgressView, menuLeft, y, contentHeight);
894            menuLeft -= mIndeterminateProgressView.getMeasuredWidth();
895        }
896
897        View customView = null;
898        if (mExpandedActionView != null) {
899            customView = mExpandedActionView;
900        } else if ((mDisplayOptions & ActionBar.DISPLAY_SHOW_CUSTOM) != 0 &&
901                mCustomNavView != null) {
902            customView = mCustomNavView;
903        }
904        if (customView != null) {
905            LayoutParams lp = customView.getLayoutParams();
906            final ActionBar.LayoutParams ablp = lp instanceof ActionBar.LayoutParams ?
907                    (ActionBar.LayoutParams) lp : null;
908
909            final int gravity = ablp != null ? ablp.gravity : DEFAULT_CUSTOM_GRAVITY;
910            final int navWidth = customView.getMeasuredWidth();
911
912            int topMargin = 0;
913            int bottomMargin = 0;
914            if (ablp != null) {
915                x += ablp.leftMargin;
916                menuLeft -= ablp.rightMargin;
917                topMargin = ablp.topMargin;
918                bottomMargin = ablp.bottomMargin;
919            }
920
921            int hgravity = gravity & Gravity.HORIZONTAL_GRAVITY_MASK;
922            // See if we actually have room to truly center; if not push against left or right.
923            if (hgravity == Gravity.CENTER_HORIZONTAL) {
924                final int centeredLeft = ((mRight - mLeft) - navWidth) / 2;
925                if (centeredLeft < x) {
926                    hgravity = Gravity.LEFT;
927                } else if (centeredLeft + navWidth > menuLeft) {
928                    hgravity = Gravity.RIGHT;
929                }
930            }
931
932            int xpos = 0;
933            switch (hgravity) {
934                case Gravity.CENTER_HORIZONTAL:
935                    xpos = ((mRight - mLeft) - navWidth) / 2;
936                    break;
937                case Gravity.LEFT:
938                    xpos = x;
939                    break;
940                case Gravity.RIGHT:
941                    xpos = menuLeft - navWidth;
942                    break;
943            }
944
945            int ypos = 0;
946            switch (gravity & Gravity.VERTICAL_GRAVITY_MASK) {
947                case Gravity.CENTER_VERTICAL:
948                    final int paddedTop = mTop + getPaddingTop();
949                    final int paddedBottom = mBottom - getPaddingBottom();
950                    ypos = ((paddedBottom - paddedTop) - customView.getMeasuredHeight()) / 2;
951                    break;
952                case Gravity.TOP:
953                    ypos = getPaddingTop() + topMargin;
954                    break;
955                case Gravity.BOTTOM:
956                    ypos = getHeight() - getPaddingBottom() - customView.getMeasuredHeight()
957                            - bottomMargin;
958                    break;
959            }
960            x += positionChild(customView, xpos, ypos, contentHeight);
961        }
962
963        if (mProgressView != null) {
964            mProgressView.bringToFront();
965            final int halfProgressHeight = mProgressView.getMeasuredHeight() / 2;
966            mProgressView.layout(mProgressBarPadding, -halfProgressHeight,
967                    mProgressBarPadding + mProgressView.getMeasuredWidth(), halfProgressHeight);
968        }
969    }
970
971    @Override
972    public LayoutParams generateLayoutParams(LayoutParams lp) {
973        if (lp == null) {
974            lp = generateDefaultLayoutParams();
975        }
976        return lp;
977    }
978
979    @Override
980    public Parcelable onSaveInstanceState() {
981        Parcelable superState = super.onSaveInstanceState();
982        SavedState state = new SavedState(superState);
983
984        if (mExpandedMenuPresenter != null && mExpandedMenuPresenter.mCurrentExpandedItem != null) {
985            state.expandedMenuItemId = mExpandedMenuPresenter.mCurrentExpandedItem.getItemId();
986        }
987
988        state.isOverflowOpen = isOverflowMenuShowing();
989
990        return state;
991    }
992
993    @Override
994    public void onRestoreInstanceState(Parcelable p) {
995        SavedState state = (SavedState) p;
996
997        super.onRestoreInstanceState(state.getSuperState());
998
999        if (state.expandedMenuItemId != 0 &&
1000                mExpandedMenuPresenter != null && mOptionsMenu != null) {
1001            final MenuItem item = mOptionsMenu.findItem(state.expandedMenuItemId);
1002            if (item != null) {
1003                item.expandActionView();
1004            }
1005        }
1006
1007        if (state.isOverflowOpen) {
1008            postShowOverflowMenu();
1009        }
1010    }
1011
1012    static class SavedState extends BaseSavedState {
1013        int expandedMenuItemId;
1014        boolean isOverflowOpen;
1015
1016        SavedState(Parcelable superState) {
1017            super(superState);
1018        }
1019
1020        private SavedState(Parcel in) {
1021            super(in);
1022            expandedMenuItemId = in.readInt();
1023            isOverflowOpen = in.readInt() != 0;
1024        }
1025
1026        @Override
1027        public void writeToParcel(Parcel out, int flags) {
1028            super.writeToParcel(out, flags);
1029            out.writeInt(expandedMenuItemId);
1030            out.writeInt(isOverflowOpen ? 1 : 0);
1031        }
1032
1033        public static final Parcelable.Creator<SavedState> CREATOR =
1034                new Parcelable.Creator<SavedState>() {
1035            public SavedState createFromParcel(Parcel in) {
1036                return new SavedState(in);
1037            }
1038
1039            public SavedState[] newArray(int size) {
1040                return new SavedState[size];
1041            }
1042        };
1043    }
1044
1045    private static class HomeView extends FrameLayout {
1046        private View mUpView;
1047        private ImageView mIconView;
1048
1049        public HomeView(Context context) {
1050            this(context, null);
1051        }
1052
1053        public HomeView(Context context, AttributeSet attrs) {
1054            super(context, attrs);
1055        }
1056
1057        public void setUp(boolean isUp) {
1058            mUpView.setVisibility(isUp ? VISIBLE : GONE);
1059        }
1060
1061        public void setIcon(Drawable icon) {
1062            mIconView.setImageDrawable(icon);
1063        }
1064
1065        @Override
1066        protected void onFinishInflate() {
1067            mUpView = findViewById(com.android.internal.R.id.up);
1068            mIconView = (ImageView) findViewById(com.android.internal.R.id.home);
1069        }
1070
1071        public int getVerticalIconPadding() {
1072            return mIconView.getPaddingTop() + mIconView.getPaddingBottom();
1073        }
1074
1075        @Override
1076        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
1077            measureChildWithMargins(mUpView, widthMeasureSpec, 0, heightMeasureSpec, 0);
1078            final LayoutParams upLp = (LayoutParams) mUpView.getLayoutParams();
1079            int width = upLp.leftMargin + mUpView.getMeasuredWidth() + upLp.rightMargin;
1080            int height = upLp.topMargin + mUpView.getMeasuredHeight() + upLp.bottomMargin;
1081            measureChildWithMargins(mIconView, widthMeasureSpec, width, heightMeasureSpec, 0);
1082            final LayoutParams iconLp = (LayoutParams) mIconView.getLayoutParams();
1083            width += iconLp.leftMargin + mIconView.getMeasuredWidth() + iconLp.rightMargin;
1084            height = Math.max(height,
1085                    iconLp.topMargin + mIconView.getMeasuredHeight() + iconLp.bottomMargin);
1086            setMeasuredDimension(width, height);
1087        }
1088
1089        @Override
1090        protected void onLayout(boolean changed, int l, int t, int r, int b) {
1091            final int vCenter = (b - t) / 2;
1092            int width = r - l;
1093            if (mUpView.getVisibility() != GONE) {
1094                final LayoutParams upLp = (LayoutParams) mUpView.getLayoutParams();
1095                final int upHeight = mUpView.getMeasuredHeight();
1096                final int upWidth = mUpView.getMeasuredWidth();
1097                final int upTop = t + vCenter - upHeight / 2;
1098                mUpView.layout(l, upTop, l + upWidth, upTop + upHeight);
1099                final int upOffset = upLp.leftMargin + upWidth + upLp.rightMargin;
1100                width -= upOffset;
1101                l += upOffset;
1102            }
1103            final LayoutParams iconLp = (LayoutParams) mIconView.getLayoutParams();
1104            final int iconHeight = mIconView.getMeasuredHeight();
1105            final int iconWidth = mIconView.getMeasuredWidth();
1106            final int hCenter = (r - l) / 2;
1107            final int iconLeft = l + iconLp.leftMargin + hCenter - iconWidth / 2;
1108            final int iconTop = t + iconLp.topMargin + vCenter - iconHeight / 2;
1109            mIconView.layout(iconLeft, iconTop, iconLeft + iconWidth, iconTop + iconHeight);
1110        }
1111    }
1112
1113    private class ExpandedActionViewMenuPresenter implements MenuPresenter {
1114        MenuBuilder mMenu;
1115        MenuItemImpl mCurrentExpandedItem;
1116
1117        @Override
1118        public void initForMenu(Context context, MenuBuilder menu) {
1119            // Clear the expanded action view when menus change.
1120            mExpandedActionView = null;
1121            if (mCurrentExpandedItem != null) {
1122                mCurrentExpandedItem.collapseActionView();
1123            }
1124            mMenu = menu;
1125        }
1126
1127        @Override
1128        public MenuView getMenuView(ViewGroup root) {
1129            return null;
1130        }
1131
1132        @Override
1133        public void updateMenuView(boolean cleared) {
1134            // Make sure the expanded item we have is still there.
1135            if (mCurrentExpandedItem != null) {
1136                boolean found = false;
1137                final int count = mMenu.size();
1138                for (int i = 0; i < count; i++) {
1139                    final MenuItem item = mMenu.getItem(i);
1140                    if (item == mCurrentExpandedItem) {
1141                        found = true;
1142                        break;
1143                    }
1144                }
1145
1146                if (!found) {
1147                    // The item we had expanded disappeared. Collapse.
1148                    collapseItemActionView(mMenu, mCurrentExpandedItem);
1149                }
1150            }
1151        }
1152
1153        @Override
1154        public void setCallback(Callback cb) {
1155        }
1156
1157        @Override
1158        public boolean onSubMenuSelected(SubMenuBuilder subMenu) {
1159            return false;
1160        }
1161
1162        @Override
1163        public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
1164        }
1165
1166        @Override
1167        public boolean flagActionItems() {
1168            return false;
1169        }
1170
1171        @Override
1172        public boolean expandItemActionView(MenuBuilder menu, MenuItemImpl item) {
1173            mExpandedActionView = item.getActionView();
1174            mExpandedHomeLayout.setIcon(item.getIcon());
1175            mCurrentExpandedItem = item;
1176            if (mExpandedActionView.getParent() != ActionBarView.this) {
1177                addView(mExpandedActionView);
1178            }
1179            if (mExpandedHomeLayout.getParent() != ActionBarView.this) {
1180                addView(mExpandedHomeLayout);
1181            }
1182            mHomeLayout.setVisibility(GONE);
1183            mTitleLayout.setVisibility(GONE);
1184            if (mTabScrollView != null) mTabScrollView.setVisibility(GONE);
1185            if (mSpinner != null) mSpinner.setVisibility(GONE);
1186            if (mCustomNavView != null) mCustomNavView.setVisibility(GONE);
1187            requestLayout();
1188            item.setActionViewExpanded(true);
1189            return true;
1190        }
1191
1192        @Override
1193        public boolean collapseItemActionView(MenuBuilder menu, MenuItemImpl item) {
1194            removeView(mExpandedActionView);
1195            removeView(mExpandedHomeLayout);
1196            if ((mDisplayOptions & ActionBar.DISPLAY_SHOW_HOME) != 0) {
1197                mHomeLayout.setVisibility(VISIBLE);
1198            }
1199            if ((mDisplayOptions & ActionBar.DISPLAY_SHOW_TITLE) != 0) {
1200                mTitleLayout.setVisibility(VISIBLE);
1201            }
1202            if (mTabScrollView != null && mNavigationMode == ActionBar.NAVIGATION_MODE_TABS) {
1203                mTabScrollView.setVisibility(VISIBLE);
1204            }
1205            if (mSpinner != null && mNavigationMode == ActionBar.NAVIGATION_MODE_LIST) {
1206                mSpinner.setVisibility(VISIBLE);
1207            }
1208            if (mCustomNavView != null && (mDisplayOptions & ActionBar.DISPLAY_SHOW_CUSTOM) != 0) {
1209                mCustomNavView.setVisibility(VISIBLE);
1210            }
1211            mExpandedActionView = null;
1212            mExpandedHomeLayout.setIcon(null);
1213            mCurrentExpandedItem = null;
1214            requestLayout();
1215            item.setActionViewExpanded(false);
1216            return true;
1217        }
1218    }
1219}
1220