ActionBarView.java revision e7d468410b3a783560d5158a5798cef1b4b67702
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.ActionMenuView;
22import com.android.internal.view.menu.MenuBuilder;
23
24import android.app.ActionBar;
25import android.app.ActionBar.OnNavigationListener;
26import android.app.Activity;
27import android.content.Context;
28import android.content.pm.ApplicationInfo;
29import android.content.pm.PackageManager;
30import android.content.pm.PackageManager.NameNotFoundException;
31import android.content.res.TypedArray;
32import android.graphics.drawable.Drawable;
33import android.text.TextUtils.TruncateAt;
34import android.util.AttributeSet;
35import android.util.Log;
36import android.view.ActionMode;
37import android.view.Gravity;
38import android.view.LayoutInflater;
39import android.view.Menu;
40import android.view.View;
41import android.view.ViewGroup;
42import android.view.ViewParent;
43import android.view.Window;
44import android.widget.AdapterView;
45import android.widget.HorizontalScrollView;
46import android.widget.ImageView;
47import android.widget.LinearLayout;
48import android.widget.ProgressBar;
49import android.widget.Spinner;
50import android.widget.SpinnerAdapter;
51import android.widget.TextView;
52
53/**
54 * @hide
55 */
56public class ActionBarView extends ViewGroup {
57    private static final String TAG = "ActionBarView";
58
59    /**
60     * Display options applied by default
61     */
62    public static final int DISPLAY_DEFAULT = 0;
63
64    /**
65     * Display options that require re-layout as opposed to a simple invalidate
66     */
67    private static final int DISPLAY_RELAYOUT_MASK =
68            ActionBar.DISPLAY_SHOW_HOME |
69            ActionBar.DISPLAY_USE_LOGO |
70            ActionBar.DISPLAY_HOME_AS_UP |
71            ActionBar.DISPLAY_SHOW_CUSTOM |
72            ActionBar.DISPLAY_SHOW_TITLE;
73
74    private static final int DEFAULT_CUSTOM_GRAVITY = Gravity.LEFT | Gravity.CENTER_VERTICAL;
75
76    private final int mContentHeight;
77
78    private int mNavigationMode;
79    private int mDisplayOptions = ActionBar.DISPLAY_SHOW_HOME | ActionBar.DISPLAY_HOME_AS_UP;
80    private CharSequence mTitle;
81    private CharSequence mSubtitle;
82    private Drawable mIcon;
83    private Drawable mLogo;
84    private Drawable mDivider;
85
86    private View mHomeLayout;
87    private View mHomeAsUpView;
88    private ImageView mIconView;
89    private LinearLayout mTitleLayout;
90    private TextView mTitleView;
91    private TextView mSubtitleView;
92    private Spinner mSpinner;
93    private LinearLayout mListNavLayout;
94    private HorizontalScrollView mTabScrollView;
95    private LinearLayout mTabLayout;
96    private View mCustomNavView;
97    private ProgressBar mProgressView;
98    private ProgressBar mIndeterminateProgressView;
99
100    private int mProgressBarPadding;
101    private int mItemPadding;
102
103    private int mTitleStyleRes;
104    private int mSubtitleStyleRes;
105    private int mProgressStyle;
106    private int mIndeterminateProgressStyle;
107
108    private boolean mShowMenu;
109    private boolean mUserTitle;
110
111    private MenuBuilder mOptionsMenu;
112    private ActionMenuView mMenuView;
113
114    private ActionBarContextView mContextView;
115
116    private ActionMenuItem mLogoNavItem;
117
118    private SpinnerAdapter mSpinnerAdapter;
119    private OnNavigationListener mCallback;
120
121    private final AdapterView.OnItemSelectedListener mNavItemSelectedListener =
122            new AdapterView.OnItemSelectedListener() {
123        public void onItemSelected(AdapterView parent, View view, int position, long id) {
124            if (mCallback != null) {
125                mCallback.onNavigationItemSelected(position, id);
126            }
127        }
128        public void onNothingSelected(AdapterView parent) {
129            // Do nothing
130        }
131    };
132
133    private OnClickListener mTabClickListener = null;
134
135    public ActionBarView(Context context, AttributeSet attrs) {
136        super(context, attrs);
137
138        // Background is always provided by the container.
139        setBackgroundResource(0);
140
141        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ActionBar);
142
143        ApplicationInfo appInfo = context.getApplicationInfo();
144        PackageManager pm = context.getPackageManager();
145        mNavigationMode = a.getInt(R.styleable.ActionBar_navigationMode,
146                ActionBar.NAVIGATION_MODE_STANDARD);
147        mTitle = a.getText(R.styleable.ActionBar_title);
148        mSubtitle = a.getText(R.styleable.ActionBar_subtitle);
149
150        mLogo = a.getDrawable(R.styleable.ActionBar_logo);
151        if (mLogo == null) {
152            if (context instanceof Activity) {
153                try {
154                    mLogo = pm.getActivityLogo(((Activity) context).getComponentName());
155                } catch (NameNotFoundException e) {
156                    Log.e(TAG, "Activity component name not found!", e);
157                }
158            }
159            if (mLogo == null) {
160                mLogo = appInfo.loadLogo(pm);
161            }
162        }
163
164        mIcon = a.getDrawable(R.styleable.ActionBar_icon);
165        if (mIcon == null) {
166            if (context instanceof Activity) {
167                try {
168                    mIcon = pm.getActivityIcon(((Activity) context).getComponentName());
169                } catch (NameNotFoundException e) {
170                    Log.e(TAG, "Activity component name not found!", e);
171                }
172            }
173            if (mIcon == null) {
174                mIcon = appInfo.loadIcon(pm);
175            }
176        }
177
178        final LayoutInflater inflater = LayoutInflater.from(context);
179
180        final int homeResId = a.getResourceId(
181                com.android.internal.R.styleable.ActionBar_homeLayout,
182                com.android.internal.R.layout.action_bar_home);
183
184        mHomeLayout = inflater.inflate(homeResId, this, false);
185
186        mHomeAsUpView = mHomeLayout.findViewById(com.android.internal.R.id.up);
187        mIconView = (ImageView) mHomeLayout.findViewById(com.android.internal.R.id.home);
188
189        mTitleStyleRes = a.getResourceId(R.styleable.ActionBar_titleTextStyle, 0);
190        mSubtitleStyleRes = a.getResourceId(R.styleable.ActionBar_subtitleTextStyle, 0);
191        mProgressStyle = a.getResourceId(R.styleable.ActionBar_progressBarStyle, 0);
192        mIndeterminateProgressStyle = a.getResourceId(
193                R.styleable.ActionBar_indeterminateProgressStyle, 0);
194
195        mProgressBarPadding = a.getDimensionPixelOffset(R.styleable.ActionBar_progressBarPadding, 0);
196        mItemPadding = a.getDimensionPixelOffset(R.styleable.ActionBar_itemPadding, 0);
197
198        setDisplayOptions(a.getInt(R.styleable.ActionBar_displayOptions, DISPLAY_DEFAULT));
199
200        final int customNavId = a.getResourceId(R.styleable.ActionBar_customNavigationLayout, 0);
201        if (customNavId != 0) {
202            mCustomNavView = (View) inflater.inflate(customNavId, this, false);
203            mNavigationMode = ActionBar.NAVIGATION_MODE_STANDARD;
204            setDisplayOptions(mDisplayOptions | ActionBar.DISPLAY_SHOW_CUSTOM);
205        }
206
207        mContentHeight = a.getLayoutDimension(R.styleable.ActionBar_height, 0);
208
209        mDivider = a.getDrawable(R.styleable.ActionBar_divider);
210
211        a.recycle();
212
213        mLogoNavItem = new ActionMenuItem(context, 0, android.R.id.home, 0, 0, mTitle);
214        mHomeLayout.setOnClickListener(new OnClickListener() {
215            public void onClick(View v) {
216                Context context = getContext();
217                if (context instanceof Activity) {
218                    Activity activity = (Activity) context;
219                    activity.onMenuItemSelected(Window.FEATURE_OPTIONS_PANEL, mLogoNavItem);
220                }
221            }
222        });
223        mHomeLayout.setClickable(true);
224        mHomeLayout.setFocusable(true);
225    }
226
227    public void initProgress() {
228        mProgressView = new ProgressBar(mContext, null, 0, mProgressStyle);
229        mProgressView.setId(R.id.progress_horizontal);
230        mProgressView.setMax(10000);
231        addView(mProgressView);
232    }
233
234    public void initIndeterminateProgress() {
235        mIndeterminateProgressView = new ProgressBar(mContext, null, 0,
236                mIndeterminateProgressStyle);
237        mIndeterminateProgressView.setId(R.id.progress_circular);
238        addView(mIndeterminateProgressView);
239    }
240
241    @Override
242    public ActionMode startActionModeForChild(View child, ActionMode.Callback callback) {
243        // No starting an action mode for an action bar child! (Where would it go?)
244        return null;
245    }
246
247    public void setCallback(OnNavigationListener callback) {
248        mCallback = callback;
249    }
250
251    public void setMenu(Menu menu) {
252        MenuBuilder builder = (MenuBuilder) menu;
253        mOptionsMenu = builder;
254        if (mMenuView != null) {
255            removeView(mMenuView);
256        }
257        final ActionMenuView menuView = (ActionMenuView) builder.getMenuView(
258                MenuBuilder.TYPE_ACTION_BUTTON, null);
259        final LayoutParams layoutParams = new LayoutParams(LayoutParams.WRAP_CONTENT,
260                LayoutParams.MATCH_PARENT);
261        menuView.setLayoutParams(layoutParams);
262        addView(menuView);
263        mMenuView = menuView;
264    }
265
266    public boolean showOverflowMenu() {
267        if (mMenuView != null) {
268            return mMenuView.showOverflowMenu();
269        }
270        return false;
271    }
272
273    public void openOverflowMenu() {
274        if (mMenuView != null) {
275            mMenuView.openOverflowMenu();
276        }
277    }
278
279    public void postShowOverflowMenu() {
280        post(new Runnable() {
281            public void run() {
282                showOverflowMenu();
283            }
284        });
285    }
286
287    public boolean hideOverflowMenu() {
288        if (mMenuView != null) {
289            return mMenuView.hideOverflowMenu();
290        }
291        return false;
292    }
293
294    public boolean isOverflowMenuShowing() {
295        if (mMenuView != null) {
296            return mMenuView.isOverflowMenuShowing();
297        }
298        return false;
299    }
300
301    public boolean isOverflowMenuOpen() {
302        if (mMenuView != null) {
303            return mMenuView.isOverflowMenuOpen();
304        }
305        return false;
306    }
307
308    public boolean isOverflowReserved() {
309        return mMenuView != null && mMenuView.isOverflowReserved();
310    }
311
312    public void setCustomNavigationView(View view) {
313        final boolean showCustom = (mDisplayOptions & ActionBar.DISPLAY_SHOW_CUSTOM) != 0;
314        if (mCustomNavView != null && showCustom) {
315            removeView(mCustomNavView);
316        }
317        mCustomNavView = view;
318        if (mCustomNavView != null && showCustom) {
319            addView(mCustomNavView);
320        }
321    }
322
323    public CharSequence getTitle() {
324        return mTitle;
325    }
326
327    /**
328     * Set the action bar title. This will always replace or override window titles.
329     * @param title Title to set
330     *
331     * @see #setWindowTitle(CharSequence)
332     */
333    public void setTitle(CharSequence title) {
334        mUserTitle = true;
335        setTitleImpl(title);
336    }
337
338    /**
339     * Set the window title. A window title will always be replaced or overridden by a user title.
340     * @param title Title to set
341     *
342     * @see #setTitle(CharSequence)
343     */
344    public void setWindowTitle(CharSequence title) {
345        if (!mUserTitle) {
346            setTitleImpl(title);
347        }
348    }
349
350    private void setTitleImpl(CharSequence title) {
351        mTitle = title;
352        if (mTitleView != null) {
353            mTitleView.setText(title);
354        }
355        if (mLogoNavItem != null) {
356            mLogoNavItem.setTitle(title);
357        }
358    }
359
360    public CharSequence getSubtitle() {
361        return mSubtitle;
362    }
363
364    public void setSubtitle(CharSequence subtitle) {
365        mSubtitle = subtitle;
366        if (mSubtitleView != null) {
367            mSubtitleView.setText(subtitle);
368            mSubtitleView.setVisibility(subtitle != null ? VISIBLE : GONE);
369        }
370    }
371
372    public void setDisplayOptions(int options) {
373        // TODO Remove this once DISPLAY_HIDE_HOME is removed
374        if ((options & ActionBar.DISPLAY_HIDE_HOME) != 0) {
375            options &= ~(ActionBar.DISPLAY_HIDE_HOME | ActionBar.DISPLAY_SHOW_HOME);
376        }
377        // End TODO
378
379        final int flagsChanged = options ^ mDisplayOptions;
380        mDisplayOptions = options;
381        if ((flagsChanged & DISPLAY_RELAYOUT_MASK) != 0) {
382            final int vis = (options & ActionBar.DISPLAY_SHOW_HOME) != 0 ? VISIBLE : GONE;
383            mHomeLayout.setVisibility(vis);
384
385            if ((flagsChanged & ActionBar.DISPLAY_HOME_AS_UP) != 0) {
386                mHomeAsUpView.setVisibility((options & ActionBar.DISPLAY_HOME_AS_UP) != 0
387                        ? VISIBLE : INVISIBLE);
388            }
389
390            if ((flagsChanged & ActionBar.DISPLAY_USE_LOGO) != 0) {
391                final boolean logoVis = mLogo != null && (options & ActionBar.DISPLAY_USE_LOGO) != 0;
392                mIconView.setImageDrawable(logoVis ? mLogo : mIcon);
393            }
394
395            if ((flagsChanged & ActionBar.DISPLAY_SHOW_TITLE) != 0) {
396                if ((options & ActionBar.DISPLAY_SHOW_TITLE) != 0) {
397                    initTitle();
398                } else {
399                    removeView(mTitleLayout);
400                }
401            }
402
403            if ((flagsChanged & ActionBar.DISPLAY_SHOW_CUSTOM) != 0 && mCustomNavView != null) {
404                if ((options & ActionBar.DISPLAY_SHOW_CUSTOM) != 0) {
405                    addView(mCustomNavView);
406                } else {
407                    removeView(mCustomNavView);
408                }
409            }
410
411            requestLayout();
412        } else {
413            invalidate();
414        }
415    }
416
417    public void setNavigationMode(int mode) {
418        final int oldMode = mNavigationMode;
419        if (mode != oldMode) {
420            switch (oldMode) {
421            case ActionBar.NAVIGATION_MODE_LIST:
422                if (mSpinner != null) {
423                    removeView(mListNavLayout);
424                }
425                break;
426            case ActionBar.NAVIGATION_MODE_TABS:
427                if (mTabLayout != null) {
428                    removeView(mTabScrollView);
429                }
430            }
431
432            switch (mode) {
433            case ActionBar.NAVIGATION_MODE_LIST:
434                if (mSpinner == null) {
435                    mSpinner = new Spinner(mContext, null,
436                            com.android.internal.R.attr.actionDropDownStyle);
437                    mListNavLayout = new LinearLayout(mContext, null,
438                            com.android.internal.R.attr.actionBarTabBarStyle);
439                    LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
440                            LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);
441                    params.gravity = Gravity.CENTER;
442                    mListNavLayout.addView(mSpinner, params);
443                }
444                if (mSpinner.getAdapter() != mSpinnerAdapter) {
445                    mSpinner.setAdapter(mSpinnerAdapter);
446                }
447                mSpinner.setOnItemSelectedListener(mNavItemSelectedListener);
448                addView(mListNavLayout);
449                break;
450            case ActionBar.NAVIGATION_MODE_TABS:
451                ensureTabsExist();
452                addView(mTabScrollView);
453                break;
454            }
455            mNavigationMode = mode;
456            requestLayout();
457        }
458    }
459
460    private void ensureTabsExist() {
461        if (mTabScrollView == null) {
462            mTabScrollView = new HorizontalScrollView(getContext());
463            mTabLayout = new LinearLayout(getContext(), null,
464                    com.android.internal.R.attr.actionBarTabBarStyle);
465            mTabScrollView.addView(mTabLayout);
466        }
467    }
468
469    public void setDropdownAdapter(SpinnerAdapter adapter) {
470        mSpinnerAdapter = adapter;
471        if (mSpinner != null) {
472            mSpinner.setAdapter(adapter);
473        }
474    }
475
476    public SpinnerAdapter getDropdownAdapter() {
477        return mSpinnerAdapter;
478    }
479
480    public void setDropdownSelectedPosition(int position) {
481        mSpinner.setSelection(position);
482    }
483
484    public int getDropdownSelectedPosition() {
485        return mSpinner.getSelectedItemPosition();
486    }
487
488    public View getCustomNavigationView() {
489        return mCustomNavView;
490    }
491
492    public int getNavigationMode() {
493        return mNavigationMode;
494    }
495
496    public int getDisplayOptions() {
497        return mDisplayOptions;
498    }
499
500    private TabView createTabView(ActionBar.Tab tab) {
501        final TabView tabView = new TabView(getContext(), tab);
502        tabView.setFocusable(true);
503
504        if (mTabClickListener == null) {
505            mTabClickListener = new TabClickListener();
506        }
507        tabView.setOnClickListener(mTabClickListener);
508        return tabView;
509    }
510
511    public void addTab(ActionBar.Tab tab, boolean setSelected) {
512        ensureTabsExist();
513        View tabView = createTabView(tab);
514        mTabLayout.addView(tabView);
515        if (setSelected) {
516            tabView.setSelected(true);
517        }
518    }
519
520    public void addTab(ActionBar.Tab tab, int position, boolean setSelected) {
521        ensureTabsExist();
522        final TabView tabView = createTabView(tab);
523        mTabLayout.addView(tabView, position);
524        if (setSelected) {
525            tabView.setSelected(true);
526        }
527    }
528
529    public void removeTabAt(int position) {
530        if (mTabLayout != null) {
531            mTabLayout.removeViewAt(position);
532        }
533    }
534
535    public void removeAllTabs() {
536        if (mTabLayout != null) {
537            mTabLayout.removeAllViews();
538        }
539    }
540
541    @Override
542    protected LayoutParams generateDefaultLayoutParams() {
543        // Used by custom nav views if they don't supply layout params. Everything else
544        // added to an ActionBarView should have them already.
545        return new ActionBar.LayoutParams(DEFAULT_CUSTOM_GRAVITY);
546    }
547
548    @Override
549    protected void onFinishInflate() {
550        super.onFinishInflate();
551
552        addView(mHomeLayout);
553
554        if (mCustomNavView != null && (mDisplayOptions & ActionBar.DISPLAY_SHOW_CUSTOM) != 0) {
555            final ViewParent parent = mCustomNavView.getParent();
556            if (parent != this) {
557                if (parent instanceof ViewGroup) {
558                    ((ViewGroup) parent).removeView(mCustomNavView);
559                }
560                addView(mCustomNavView);
561            }
562        }
563    }
564
565    private void initTitle() {
566        LayoutInflater inflater = LayoutInflater.from(getContext());
567        mTitleLayout = (LinearLayout) inflater.inflate(R.layout.action_bar_title_item, null);
568        mTitleView = (TextView) mTitleLayout.findViewById(R.id.action_bar_title);
569        mSubtitleView = (TextView) mTitleLayout.findViewById(R.id.action_bar_subtitle);
570
571        if (mTitleStyleRes != 0) {
572            mTitleView.setTextAppearance(mContext, mTitleStyleRes);
573        }
574        if (mTitle != null) {
575            mTitleView.setText(mTitle);
576        }
577
578        if (mSubtitleStyleRes != 0) {
579            mSubtitleView.setTextAppearance(mContext, mSubtitleStyleRes);
580        }
581        if (mSubtitle != null) {
582            mSubtitleView.setText(mSubtitle);
583            mSubtitleView.setVisibility(VISIBLE);
584        }
585
586        addView(mTitleLayout);
587    }
588
589    public void setTabSelected(int position) {
590        ensureTabsExist();
591        final int tabCount = mTabLayout.getChildCount();
592        for (int i = 0; i < tabCount; i++) {
593            final View child = mTabLayout.getChildAt(i);
594            child.setSelected(i == position);
595        }
596    }
597
598    public void setContextView(ActionBarContextView view) {
599        mContextView = view;
600    }
601
602    @Override
603    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
604        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
605        if (widthMode != MeasureSpec.EXACTLY) {
606            throw new IllegalStateException(getClass().getSimpleName() + " can only be used " +
607                    "with android:layout_width=\"match_parent\" (or fill_parent)");
608        }
609
610        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
611        if (heightMode != MeasureSpec.AT_MOST) {
612            throw new IllegalStateException(getClass().getSimpleName() + " can only be used " +
613                    "with android:layout_height=\"wrap_content\"");
614        }
615
616        int contentWidth = MeasureSpec.getSize(widthMeasureSpec);
617
618        int maxHeight = mContentHeight > 0 ?
619                mContentHeight : MeasureSpec.getSize(heightMeasureSpec);
620
621        final int verticalPadding = getPaddingTop() + getPaddingBottom();
622        final int paddingLeft = getPaddingLeft();
623        final int paddingRight = getPaddingRight();
624        final int height = maxHeight - verticalPadding;
625        final int childSpecHeight = MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST);
626
627        int availableWidth = contentWidth - paddingLeft - paddingRight;
628        int leftOfCenter = availableWidth / 2;
629        int rightOfCenter = leftOfCenter;
630
631        if (mHomeLayout.getVisibility() != GONE) {
632            mHomeLayout.measure(MeasureSpec.makeMeasureSpec(availableWidth, MeasureSpec.AT_MOST),
633                    MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
634            final int homeWidth = mHomeLayout.getMeasuredWidth();
635            availableWidth -= homeWidth;
636            leftOfCenter -= homeWidth;
637        }
638
639        if (mMenuView != null) {
640            availableWidth = measureChildView(mMenuView, availableWidth,
641                    childSpecHeight, 0);
642            rightOfCenter -= mMenuView.getMeasuredWidth();
643        }
644
645        boolean showTitle = mTitleLayout != null &&
646                (mDisplayOptions & ActionBar.DISPLAY_SHOW_TITLE) != 0;
647        if (showTitle) {
648            availableWidth = measureChildView(mTitleLayout, availableWidth, childSpecHeight, 0);
649            leftOfCenter -= mTitleLayout.getMeasuredWidth();
650        }
651
652        switch (mNavigationMode) {
653        case ActionBar.NAVIGATION_MODE_LIST:
654            if (mListNavLayout != null) {
655                final int itemPaddingSize = showTitle ? mItemPadding * 2 : mItemPadding;
656                availableWidth -= itemPaddingSize;
657                leftOfCenter -= itemPaddingSize;
658                mListNavLayout.measure(
659                        MeasureSpec.makeMeasureSpec(availableWidth, MeasureSpec.AT_MOST),
660                        MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
661                final int listNavWidth = mListNavLayout.getMeasuredWidth();
662                availableWidth -= listNavWidth;
663                leftOfCenter -= listNavWidth;
664            }
665            break;
666        case ActionBar.NAVIGATION_MODE_TABS:
667            if (mTabScrollView != null) {
668                final int itemPaddingSize = showTitle ? mItemPadding * 2 : mItemPadding;
669                availableWidth -= itemPaddingSize;
670                leftOfCenter -= itemPaddingSize;
671                mTabScrollView.measure(
672                        MeasureSpec.makeMeasureSpec(availableWidth, MeasureSpec.AT_MOST),
673                        MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
674                final int tabWidth = mTabScrollView.getMeasuredWidth();
675                availableWidth -= tabWidth;
676                leftOfCenter -= tabWidth;
677            }
678            break;
679        }
680
681        if (mIndeterminateProgressView != null &&
682                mIndeterminateProgressView.getVisibility() != GONE) {
683            availableWidth = measureChildView(mIndeterminateProgressView, availableWidth,
684                    childSpecHeight, 0);
685            rightOfCenter -= mIndeterminateProgressView.getMeasuredWidth();
686        }
687
688        if ((mDisplayOptions & ActionBar.DISPLAY_SHOW_CUSTOM) != 0 && mCustomNavView != null) {
689            final LayoutParams lp = generateLayoutParams(mCustomNavView.getLayoutParams());
690            final ActionBar.LayoutParams ablp = lp instanceof ActionBar.LayoutParams ?
691                    (ActionBar.LayoutParams) lp : null;
692
693            int horizontalMargin = 0;
694            int verticalMargin = 0;
695            if (ablp != null) {
696                horizontalMargin = ablp.leftMargin + ablp.rightMargin;
697                verticalMargin = ablp.topMargin + ablp.bottomMargin;
698            }
699
700            // If the action bar is wrapping to its content height, don't allow a custom
701            // view to MATCH_PARENT.
702            int customNavHeightMode;
703            if (mContentHeight <= 0) {
704                customNavHeightMode = MeasureSpec.AT_MOST;
705            } else {
706                customNavHeightMode = lp.height != LayoutParams.WRAP_CONTENT ?
707                        MeasureSpec.EXACTLY : MeasureSpec.AT_MOST;
708            }
709            final int customNavHeight = Math.max(0,
710                    (lp.height >= 0 ? Math.min(lp.height, height) : height) - verticalMargin);
711
712            final int customNavWidthMode = lp.width != LayoutParams.WRAP_CONTENT ?
713                    MeasureSpec.EXACTLY : MeasureSpec.AT_MOST;
714            int customNavWidth = Math.max(0,
715                    (lp.width >= 0 ? Math.min(lp.width, availableWidth) : availableWidth)
716                    - horizontalMargin);
717            final int hgrav = (ablp != null ? ablp.gravity : DEFAULT_CUSTOM_GRAVITY) &
718                    Gravity.HORIZONTAL_GRAVITY_MASK;
719
720            // Centering a custom view is treated specially; we try to center within the whole
721            // action bar rather than in the available space.
722            if (hgrav == Gravity.CENTER_HORIZONTAL && lp.width == LayoutParams.MATCH_PARENT) {
723                customNavWidth = Math.min(leftOfCenter, rightOfCenter) * 2;
724            }
725
726            mCustomNavView.measure(
727                    MeasureSpec.makeMeasureSpec(customNavWidth, customNavWidthMode),
728                    MeasureSpec.makeMeasureSpec(customNavHeight, customNavHeightMode));
729        }
730
731        if (mContentHeight <= 0) {
732            int measuredHeight = 0;
733            final int count = getChildCount();
734            for (int i = 0; i < count; i++) {
735                View v = getChildAt(i);
736                int paddedViewHeight = v.getMeasuredHeight() + verticalPadding;
737                if (paddedViewHeight > measuredHeight) {
738                    measuredHeight = paddedViewHeight;
739                }
740            }
741            setMeasuredDimension(contentWidth, measuredHeight);
742        } else {
743            setMeasuredDimension(contentWidth, maxHeight);
744        }
745
746        if (mContextView != null) {
747            mContextView.setHeight(getMeasuredHeight());
748        }
749
750        if (mProgressView != null && mProgressView.getVisibility() != GONE) {
751            mProgressView.measure(MeasureSpec.makeMeasureSpec(
752                    contentWidth - mProgressBarPadding * 2, MeasureSpec.EXACTLY),
753                    MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.AT_MOST));
754        }
755    }
756
757    private int measureChildView(View child, int availableWidth, int childSpecHeight, int spacing) {
758        child.measure(MeasureSpec.makeMeasureSpec(availableWidth, MeasureSpec.AT_MOST),
759                childSpecHeight);
760
761        availableWidth -= child.getMeasuredWidth();
762        availableWidth -= spacing;
763
764        return availableWidth;
765    }
766
767    @Override
768    protected void onLayout(boolean changed, int l, int t, int r, int b) {
769        int x = getPaddingLeft();
770        final int y = getPaddingTop();
771        final int contentHeight = b - t - getPaddingTop() - getPaddingBottom();
772
773        if (mHomeLayout.getVisibility() != GONE) {
774            x += positionChild(mHomeLayout, x, y, contentHeight);
775        }
776
777        final boolean showTitle = mTitleLayout != null &&
778                (mDisplayOptions & ActionBar.DISPLAY_SHOW_TITLE) != 0;
779        if (showTitle) {
780            x += positionChild(mTitleLayout, x, y, contentHeight);
781        }
782
783        switch (mNavigationMode) {
784        case ActionBar.NAVIGATION_MODE_STANDARD:
785            break;
786        case ActionBar.NAVIGATION_MODE_LIST:
787            if (mListNavLayout != null) {
788                if (showTitle) x += mItemPadding;
789                x += positionChild(mListNavLayout, x, y, contentHeight) + mItemPadding;
790            }
791            break;
792        case ActionBar.NAVIGATION_MODE_TABS:
793            if (mTabScrollView != null) {
794                if (showTitle) x += mItemPadding;
795                x += positionChild(mTabScrollView, x, y, contentHeight) + mItemPadding;
796            }
797            break;
798        }
799
800        int menuLeft = r - l - getPaddingRight();
801        if (mMenuView != null) {
802            positionChildInverse(mMenuView, menuLeft, y, contentHeight);
803            menuLeft -= mMenuView.getMeasuredWidth();
804        }
805
806        if (mIndeterminateProgressView != null &&
807                mIndeterminateProgressView.getVisibility() != GONE) {
808            positionChildInverse(mIndeterminateProgressView, menuLeft, y, contentHeight);
809            menuLeft -= mIndeterminateProgressView.getMeasuredWidth();
810        }
811
812        if (mCustomNavView != null && (mDisplayOptions & ActionBar.DISPLAY_SHOW_CUSTOM) != 0) {
813            LayoutParams lp = mCustomNavView.getLayoutParams();
814            final ActionBar.LayoutParams ablp = lp instanceof ActionBar.LayoutParams ?
815                    (ActionBar.LayoutParams) lp : null;
816
817            final int gravity = ablp != null ? ablp.gravity : DEFAULT_CUSTOM_GRAVITY;
818            final int navWidth = mCustomNavView.getMeasuredWidth();
819
820            int topMargin = 0;
821            int bottomMargin = 0;
822            if (ablp != null) {
823                x += ablp.leftMargin;
824                menuLeft -= ablp.rightMargin;
825                topMargin = ablp.topMargin;
826                bottomMargin = ablp.bottomMargin;
827            }
828
829            int hgravity = gravity & Gravity.HORIZONTAL_GRAVITY_MASK;
830            // See if we actually have room to truly center; if not push against left or right.
831            if (hgravity == Gravity.CENTER_HORIZONTAL) {
832                final int centeredLeft = ((mRight - mLeft) - navWidth) / 2;
833                if (centeredLeft < x) {
834                    hgravity = Gravity.LEFT;
835                } else if (centeredLeft + navWidth > menuLeft) {
836                    hgravity = Gravity.RIGHT;
837                }
838            }
839
840            int xpos = 0;
841            switch (hgravity) {
842                case Gravity.CENTER_HORIZONTAL:
843                    xpos = ((mRight - mLeft) - navWidth) / 2;
844                    break;
845                case Gravity.LEFT:
846                    xpos = x;
847                    break;
848                case Gravity.RIGHT:
849                    xpos = menuLeft - navWidth;
850                    break;
851            }
852
853            int ypos = 0;
854            switch (gravity & Gravity.VERTICAL_GRAVITY_MASK) {
855                case Gravity.CENTER_VERTICAL:
856                    final int paddedTop = mTop + getPaddingTop();
857                    final int paddedBottom = mBottom - getPaddingBottom();
858                    ypos = ((paddedBottom - paddedTop) - mCustomNavView.getMeasuredHeight()) / 2;
859                    break;
860                case Gravity.TOP:
861                    ypos = getPaddingTop() + topMargin;
862                    break;
863                case Gravity.BOTTOM:
864                    ypos = getHeight() - getPaddingBottom() - mCustomNavView.getMeasuredHeight()
865                            - bottomMargin;
866                    break;
867            }
868            x += positionChild(mCustomNavView, xpos, ypos, contentHeight);
869        }
870
871        if (mProgressView != null) {
872            mProgressView.bringToFront();
873            final int halfProgressHeight = mProgressView.getMeasuredHeight() / 2;
874            mProgressView.layout(mProgressBarPadding, -halfProgressHeight,
875                    mProgressBarPadding + mProgressView.getMeasuredWidth(), halfProgressHeight);
876        }
877    }
878
879    private int positionChild(View child, int x, int y, int contentHeight) {
880        int childWidth = child.getMeasuredWidth();
881        int childHeight = child.getMeasuredHeight();
882        int childTop = y + (contentHeight - childHeight) / 2;
883
884        child.layout(x, childTop, x + childWidth, childTop + childHeight);
885
886        return childWidth;
887    }
888
889    private int positionChildInverse(View child, int x, int y, int contentHeight) {
890        int childWidth = child.getMeasuredWidth();
891        int childHeight = child.getMeasuredHeight();
892        int childTop = y + (contentHeight - childHeight) / 2;
893
894        child.layout(x - childWidth, childTop, x, childTop + childHeight);
895
896        return childWidth;
897    }
898
899    private static class TabView extends LinearLayout {
900        private ActionBar.Tab mTab;
901
902        public TabView(Context context, ActionBar.Tab tab) {
903            super(context, null, com.android.internal.R.attr.actionBarTabStyle);
904            mTab = tab;
905
906            final View custom = tab.getCustomView();
907            if (custom != null) {
908                addView(custom);
909            } else {
910                // TODO Style tabs based on the theme
911
912                final Drawable icon = tab.getIcon();
913                final CharSequence text = tab.getText();
914
915                if (icon != null) {
916                    ImageView iconView = new ImageView(context);
917                    iconView.setImageDrawable(icon);
918                    LayoutParams lp = new LayoutParams(LayoutParams.WRAP_CONTENT,
919                            LayoutParams.WRAP_CONTENT);
920                    lp.gravity = Gravity.CENTER_VERTICAL;
921                    iconView.setLayoutParams(lp);
922                    addView(iconView);
923                }
924
925                if (text != null) {
926                    TextView textView = new TextView(context, null,
927                            com.android.internal.R.attr.actionBarTabTextStyle);
928                    textView.setText(text);
929                    textView.setSingleLine();
930                    textView.setEllipsize(TruncateAt.END);
931                    LayoutParams lp = new LayoutParams(LayoutParams.WRAP_CONTENT,
932                            LayoutParams.WRAP_CONTENT);
933                    lp.gravity = Gravity.CENTER_VERTICAL;
934                    textView.setLayoutParams(lp);
935                    addView(textView);
936                }
937            }
938
939            setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT,
940                    LayoutParams.MATCH_PARENT, 1));
941        }
942
943        public ActionBar.Tab getTab() {
944            return mTab;
945        }
946    }
947
948    private class TabClickListener implements OnClickListener {
949        public void onClick(View view) {
950            TabView tabView = (TabView) view;
951            tabView.getTab().select();
952            final int tabCount = mTabLayout.getChildCount();
953            for (int i = 0; i < tabCount; i++) {
954                final View child = mTabLayout.getChildAt(i);
955                child.setSelected(child == view);
956            }
957        }
958    }
959}
960