1/*
2 * Copyright (C) 2014 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 android.widget;
18
19import android.annotation.ColorInt;
20import android.annotation.DrawableRes;
21import android.annotation.MenuRes;
22import android.annotation.NonNull;
23import android.annotation.Nullable;
24import android.annotation.StringRes;
25import android.annotation.StyleRes;
26import android.annotation.TestApi;
27import android.app.ActionBar;
28import android.content.Context;
29import android.content.res.TypedArray;
30import android.graphics.drawable.Drawable;
31import android.os.Parcel;
32import android.os.Parcelable;
33import android.text.Layout;
34import android.text.TextUtils;
35import android.util.AttributeSet;
36import android.view.CollapsibleActionView;
37import android.view.ContextThemeWrapper;
38import android.view.Gravity;
39import android.view.Menu;
40import android.view.MenuInflater;
41import android.view.MenuItem;
42import android.view.MotionEvent;
43import android.view.View;
44import android.view.ViewGroup;
45import android.view.ViewParent;
46
47import com.android.internal.R;
48import com.android.internal.view.menu.MenuBuilder;
49import com.android.internal.view.menu.MenuItemImpl;
50import com.android.internal.view.menu.MenuPresenter;
51import com.android.internal.view.menu.MenuView;
52import com.android.internal.view.menu.SubMenuBuilder;
53import com.android.internal.widget.DecorToolbar;
54import com.android.internal.widget.ToolbarWidgetWrapper;
55
56import java.util.ArrayList;
57import java.util.List;
58
59/**
60 * A standard toolbar for use within application content.
61 *
62 * <p>A Toolbar is a generalization of {@link android.app.ActionBar action bars} for use
63 * within application layouts. While an action bar is traditionally part of an
64 * {@link android.app.Activity Activity's} opaque window decor controlled by the framework,
65 * a Toolbar may be placed at any arbitrary level of nesting within a view hierarchy.
66 * An application may choose to designate a Toolbar as the action bar for an Activity
67 * using the {@link android.app.Activity#setActionBar(Toolbar) setActionBar()} method.</p>
68 *
69 * <p>Toolbar supports a more focused feature set than ActionBar. From start to end, a toolbar
70 * may contain a combination of the following optional elements:
71 *
72 * <ul>
73 *     <li><em>A navigation button.</em> This may be an Up arrow, navigation menu toggle, close,
74 *     collapse, done or another glyph of the app's choosing. This button should always be used
75 *     to access other navigational destinations within the container of the Toolbar and
76 *     its signified content or otherwise leave the current context signified by the Toolbar.
77 *     The navigation button is vertically aligned within the Toolbar's
78 *     {@link android.R.styleable#View_minHeight minimum height}, if set.</li>
79 *     <li><em>A branded logo image.</em> This may extend to the height of the bar and can be
80 *     arbitrarily wide.</li>
81 *     <li><em>A title and subtitle.</em> The title should be a signpost for the Toolbar's current
82 *     position in the navigation hierarchy and the content contained there. The subtitle,
83 *     if present should indicate any extended information about the current content.
84 *     If an app uses a logo image it should strongly consider omitting a title and subtitle.</li>
85 *     <li><em>One or more custom views.</em> The application may add arbitrary child views
86 *     to the Toolbar. They will appear at this position within the layout. If a child view's
87 *     {@link LayoutParams} indicates a {@link Gravity} value of
88 *     {@link Gravity#CENTER_HORIZONTAL CENTER_HORIZONTAL} the view will attempt to center
89 *     within the available space remaining in the Toolbar after all other elements have been
90 *     measured.</li>
91 *     <li><em>An {@link ActionMenuView action menu}.</em> The menu of actions will pin to the
92 *     end of the Toolbar offering a few
93 *     <a href="http://developer.android.com/design/patterns/actionbar.html#ActionButtons">
94 *     frequent, important or typical</a> actions along with an optional overflow menu for
95 *     additional actions. Action buttons are vertically aligned within the Toolbar's
96 *     {@link android.R.styleable#View_minHeight minimum height}, if set.</li>
97 * </ul>
98 * </p>
99 *
100 * <p>In modern Android UIs developers should lean more on a visually distinct color scheme for
101 * toolbars than on their application icon. The use of application icon plus title as a standard
102 * layout is discouraged on API 21 devices and newer.</p>
103 *
104 * @attr ref android.R.styleable#Toolbar_buttonGravity
105 * @attr ref android.R.styleable#Toolbar_collapseContentDescription
106 * @attr ref android.R.styleable#Toolbar_collapseIcon
107 * @attr ref android.R.styleable#Toolbar_contentInsetEnd
108 * @attr ref android.R.styleable#Toolbar_contentInsetLeft
109 * @attr ref android.R.styleable#Toolbar_contentInsetRight
110 * @attr ref android.R.styleable#Toolbar_contentInsetStart
111 * @attr ref android.R.styleable#Toolbar_contentInsetStartWithNavigation
112 * @attr ref android.R.styleable#Toolbar_contentInsetEndWithActions
113 * @attr ref android.R.styleable#Toolbar_gravity
114 * @attr ref android.R.styleable#Toolbar_logo
115 * @attr ref android.R.styleable#Toolbar_logoDescription
116 * @attr ref android.R.styleable#Toolbar_maxButtonHeight
117 * @attr ref android.R.styleable#Toolbar_navigationContentDescription
118 * @attr ref android.R.styleable#Toolbar_navigationIcon
119 * @attr ref android.R.styleable#Toolbar_popupTheme
120 * @attr ref android.R.styleable#Toolbar_subtitle
121 * @attr ref android.R.styleable#Toolbar_subtitleTextAppearance
122 * @attr ref android.R.styleable#Toolbar_subtitleTextColor
123 * @attr ref android.R.styleable#Toolbar_title
124 * @attr ref android.R.styleable#Toolbar_titleMargin
125 * @attr ref android.R.styleable#Toolbar_titleMarginBottom
126 * @attr ref android.R.styleable#Toolbar_titleMarginEnd
127 * @attr ref android.R.styleable#Toolbar_titleMarginStart
128 * @attr ref android.R.styleable#Toolbar_titleMarginTop
129 * @attr ref android.R.styleable#Toolbar_titleTextAppearance
130 * @attr ref android.R.styleable#Toolbar_titleTextColor
131 */
132public class Toolbar extends ViewGroup {
133    private static final String TAG = "Toolbar";
134
135    private ActionMenuView mMenuView;
136    private TextView mTitleTextView;
137    private TextView mSubtitleTextView;
138    private ImageButton mNavButtonView;
139    private ImageView mLogoView;
140
141    private Drawable mCollapseIcon;
142    private CharSequence mCollapseDescription;
143    private ImageButton mCollapseButtonView;
144    View mExpandedActionView;
145
146    /** Context against which to inflate popup menus. */
147    private Context mPopupContext;
148
149    /** Theme resource against which to inflate popup menus. */
150    private int mPopupTheme;
151
152    private int mTitleTextAppearance;
153    private int mSubtitleTextAppearance;
154    private int mNavButtonStyle;
155
156    private int mButtonGravity;
157
158    private int mMaxButtonHeight;
159
160    private int mTitleMarginStart;
161    private int mTitleMarginEnd;
162    private int mTitleMarginTop;
163    private int mTitleMarginBottom;
164
165    private RtlSpacingHelper mContentInsets;
166    private int mContentInsetStartWithNavigation;
167    private int mContentInsetEndWithActions;
168
169    private int mGravity = Gravity.START | Gravity.CENTER_VERTICAL;
170
171    private CharSequence mTitleText;
172    private CharSequence mSubtitleText;
173
174    private int mTitleTextColor;
175    private int mSubtitleTextColor;
176
177    private boolean mEatingTouch;
178
179    // Clear me after use.
180    private final ArrayList<View> mTempViews = new ArrayList<View>();
181
182    // Used to hold views that will be removed while we have an expanded action view.
183    private final ArrayList<View> mHiddenViews = new ArrayList<>();
184
185    private final int[] mTempMargins = new int[2];
186
187    private OnMenuItemClickListener mOnMenuItemClickListener;
188
189    private final ActionMenuView.OnMenuItemClickListener mMenuViewItemClickListener =
190            new ActionMenuView.OnMenuItemClickListener() {
191                @Override
192                public boolean onMenuItemClick(MenuItem item) {
193                    if (mOnMenuItemClickListener != null) {
194                        return mOnMenuItemClickListener.onMenuItemClick(item);
195                    }
196                    return false;
197                }
198            };
199
200    private ToolbarWidgetWrapper mWrapper;
201    private ActionMenuPresenter mOuterActionMenuPresenter;
202    private ExpandedActionViewMenuPresenter mExpandedMenuPresenter;
203    private MenuPresenter.Callback mActionMenuPresenterCallback;
204    private MenuBuilder.Callback mMenuBuilderCallback;
205
206    private boolean mCollapsible;
207
208    private final Runnable mShowOverflowMenuRunnable = new Runnable() {
209        @Override public void run() {
210            showOverflowMenu();
211        }
212    };
213
214    public Toolbar(Context context) {
215        this(context, null);
216    }
217
218    public Toolbar(Context context, AttributeSet attrs) {
219        this(context, attrs, com.android.internal.R.attr.toolbarStyle);
220    }
221
222    public Toolbar(Context context, AttributeSet attrs, int defStyleAttr) {
223        this(context, attrs, defStyleAttr, 0);
224    }
225
226    public Toolbar(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
227        super(context, attrs, defStyleAttr, defStyleRes);
228
229        final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.Toolbar,
230                defStyleAttr, defStyleRes);
231
232        mTitleTextAppearance = a.getResourceId(R.styleable.Toolbar_titleTextAppearance, 0);
233        mSubtitleTextAppearance = a.getResourceId(R.styleable.Toolbar_subtitleTextAppearance, 0);
234        mNavButtonStyle = a.getResourceId(R.styleable.Toolbar_navigationButtonStyle, 0);
235        mGravity = a.getInteger(R.styleable.Toolbar_gravity, mGravity);
236        mButtonGravity = a.getInteger(R.styleable.Toolbar_buttonGravity, Gravity.TOP);
237        mTitleMarginStart = mTitleMarginEnd = mTitleMarginTop = mTitleMarginBottom =
238                a.getDimensionPixelOffset(R.styleable.Toolbar_titleMargin, 0);
239
240        final int marginStart = a.getDimensionPixelOffset(R.styleable.Toolbar_titleMarginStart, -1);
241        if (marginStart >= 0) {
242            mTitleMarginStart = marginStart;
243        }
244
245        final int marginEnd = a.getDimensionPixelOffset(R.styleable.Toolbar_titleMarginEnd, -1);
246        if (marginEnd >= 0) {
247            mTitleMarginEnd = marginEnd;
248        }
249
250        final int marginTop = a.getDimensionPixelOffset(R.styleable.Toolbar_titleMarginTop, -1);
251        if (marginTop >= 0) {
252            mTitleMarginTop = marginTop;
253        }
254
255        final int marginBottom = a.getDimensionPixelOffset(R.styleable.Toolbar_titleMarginBottom,
256                -1);
257        if (marginBottom >= 0) {
258            mTitleMarginBottom = marginBottom;
259        }
260
261        mMaxButtonHeight = a.getDimensionPixelSize(R.styleable.Toolbar_maxButtonHeight, -1);
262
263        final int contentInsetStart =
264                a.getDimensionPixelOffset(R.styleable.Toolbar_contentInsetStart,
265                        RtlSpacingHelper.UNDEFINED);
266        final int contentInsetEnd =
267                a.getDimensionPixelOffset(R.styleable.Toolbar_contentInsetEnd,
268                        RtlSpacingHelper.UNDEFINED);
269        final int contentInsetLeft =
270                a.getDimensionPixelSize(R.styleable.Toolbar_contentInsetLeft, 0);
271        final int contentInsetRight =
272                a.getDimensionPixelSize(R.styleable.Toolbar_contentInsetRight, 0);
273
274        ensureContentInsets();
275        mContentInsets.setAbsolute(contentInsetLeft, contentInsetRight);
276
277        if (contentInsetStart != RtlSpacingHelper.UNDEFINED ||
278                contentInsetEnd != RtlSpacingHelper.UNDEFINED) {
279            mContentInsets.setRelative(contentInsetStart, contentInsetEnd);
280        }
281
282        mContentInsetStartWithNavigation = a.getDimensionPixelOffset(
283                R.styleable.Toolbar_contentInsetStartWithNavigation, RtlSpacingHelper.UNDEFINED);
284        mContentInsetEndWithActions = a.getDimensionPixelOffset(
285                R.styleable.Toolbar_contentInsetEndWithActions, RtlSpacingHelper.UNDEFINED);
286
287        mCollapseIcon = a.getDrawable(R.styleable.Toolbar_collapseIcon);
288        mCollapseDescription = a.getText(R.styleable.Toolbar_collapseContentDescription);
289
290        final CharSequence title = a.getText(R.styleable.Toolbar_title);
291        if (!TextUtils.isEmpty(title)) {
292            setTitle(title);
293        }
294
295        final CharSequence subtitle = a.getText(R.styleable.Toolbar_subtitle);
296        if (!TextUtils.isEmpty(subtitle)) {
297            setSubtitle(subtitle);
298        }
299
300        // Set the default context, since setPopupTheme() may be a no-op.
301        mPopupContext = mContext;
302        setPopupTheme(a.getResourceId(R.styleable.Toolbar_popupTheme, 0));
303
304        final Drawable navIcon = a.getDrawable(R.styleable.Toolbar_navigationIcon);
305        if (navIcon != null) {
306            setNavigationIcon(navIcon);
307        }
308
309        final CharSequence navDesc = a.getText(
310                R.styleable.Toolbar_navigationContentDescription);
311        if (!TextUtils.isEmpty(navDesc)) {
312            setNavigationContentDescription(navDesc);
313        }
314
315        final Drawable logo = a.getDrawable(R.styleable.Toolbar_logo);
316        if (logo != null) {
317            setLogo(logo);
318        }
319
320        final CharSequence logoDesc = a.getText(R.styleable.Toolbar_logoDescription);
321        if (!TextUtils.isEmpty(logoDesc)) {
322            setLogoDescription(logoDesc);
323        }
324
325        if (a.hasValue(R.styleable.Toolbar_titleTextColor)) {
326            setTitleTextColor(a.getColor(R.styleable.Toolbar_titleTextColor, 0xffffffff));
327        }
328
329        if (a.hasValue(R.styleable.Toolbar_subtitleTextColor)) {
330            setSubtitleTextColor(a.getColor(R.styleable.Toolbar_subtitleTextColor, 0xffffffff));
331        }
332        a.recycle();
333    }
334
335    @Override
336    protected void onAttachedToWindow() {
337        super.onAttachedToWindow();
338
339        // If the container is a cluster, unmark itself as a cluster to avoid having nested
340        // clusters.
341        ViewParent parent = getParent();
342        while (parent != null && parent instanceof ViewGroup) {
343            final ViewGroup vgParent = (ViewGroup) parent;
344            if (vgParent.isKeyboardNavigationCluster()) {
345                setKeyboardNavigationCluster(false);
346                if (vgParent.getTouchscreenBlocksFocus()) {
347                    setTouchscreenBlocksFocus(false);
348                }
349                break;
350            }
351            parent = vgParent.getParent();
352        }
353    }
354
355    /**
356     * Specifies the theme to use when inflating popup menus. By default, uses
357     * the same theme as the toolbar itself.
358     *
359     * @param resId theme used to inflate popup menus
360     * @see #getPopupTheme()
361     */
362    public void setPopupTheme(@StyleRes int resId) {
363        if (mPopupTheme != resId) {
364            mPopupTheme = resId;
365            if (resId == 0) {
366                mPopupContext = mContext;
367            } else {
368                mPopupContext = new ContextThemeWrapper(mContext, resId);
369            }
370        }
371    }
372
373    /**
374     * @return resource identifier of the theme used to inflate popup menus, or
375     *         0 if menus are inflated against the toolbar theme
376     * @see #setPopupTheme(int)
377     */
378    public int getPopupTheme() {
379        return mPopupTheme;
380    }
381
382    /**
383     * Sets the title margin.
384     *
385     * @param start the starting title margin in pixels
386     * @param top the top title margin in pixels
387     * @param end the ending title margin in pixels
388     * @param bottom the bottom title margin in pixels
389     * @see #getTitleMarginStart()
390     * @see #getTitleMarginTop()
391     * @see #getTitleMarginEnd()
392     * @see #getTitleMarginBottom()
393     * @attr ref android.R.styleable#Toolbar_titleMargin
394     */
395    public void setTitleMargin(int start, int top, int end, int bottom) {
396        mTitleMarginStart = start;
397        mTitleMarginTop = top;
398        mTitleMarginEnd = end;
399        mTitleMarginBottom = bottom;
400
401        requestLayout();
402    }
403
404    /**
405     * @return the starting title margin in pixels
406     * @see #setTitleMarginStart(int)
407     * @attr ref android.R.styleable#Toolbar_titleMarginStart
408     */
409    public int getTitleMarginStart() {
410        return mTitleMarginStart;
411    }
412
413    /**
414     * Sets the starting title margin in pixels.
415     *
416     * @param margin the starting title margin in pixels
417     * @see #getTitleMarginStart()
418     * @attr ref android.R.styleable#Toolbar_titleMarginStart
419     */
420    public void setTitleMarginStart(int margin) {
421        mTitleMarginStart = margin;
422
423        requestLayout();
424    }
425
426    /**
427     * @return the top title margin in pixels
428     * @see #setTitleMarginTop(int)
429     * @attr ref android.R.styleable#Toolbar_titleMarginTop
430     */
431    public int getTitleMarginTop() {
432        return mTitleMarginTop;
433    }
434
435    /**
436     * Sets the top title margin in pixels.
437     *
438     * @param margin the top title margin in pixels
439     * @see #getTitleMarginTop()
440     * @attr ref android.R.styleable#Toolbar_titleMarginTop
441     */
442    public void setTitleMarginTop(int margin) {
443        mTitleMarginTop = margin;
444
445        requestLayout();
446    }
447
448    /**
449     * @return the ending title margin in pixels
450     * @see #setTitleMarginEnd(int)
451     * @attr ref android.R.styleable#Toolbar_titleMarginEnd
452     */
453    public int getTitleMarginEnd() {
454        return mTitleMarginEnd;
455    }
456
457    /**
458     * Sets the ending title margin in pixels.
459     *
460     * @param margin the ending title margin in pixels
461     * @see #getTitleMarginEnd()
462     * @attr ref android.R.styleable#Toolbar_titleMarginEnd
463     */
464    public void setTitleMarginEnd(int margin) {
465        mTitleMarginEnd = margin;
466
467        requestLayout();
468    }
469
470    /**
471     * @return the bottom title margin in pixels
472     * @see #setTitleMarginBottom(int)
473     * @attr ref android.R.styleable#Toolbar_titleMarginBottom
474     */
475    public int getTitleMarginBottom() {
476        return mTitleMarginBottom;
477    }
478
479    /**
480     * Sets the bottom title margin in pixels.
481     *
482     * @param margin the bottom title margin in pixels
483     * @see #getTitleMarginBottom()
484     * @attr ref android.R.styleable#Toolbar_titleMarginBottom
485     */
486    public void setTitleMarginBottom(int margin) {
487        mTitleMarginBottom = margin;
488        requestLayout();
489    }
490
491    @Override
492    public void onRtlPropertiesChanged(@ResolvedLayoutDir int layoutDirection) {
493        super.onRtlPropertiesChanged(layoutDirection);
494        ensureContentInsets();
495        mContentInsets.setDirection(layoutDirection == LAYOUT_DIRECTION_RTL);
496    }
497
498    /**
499     * Set a logo drawable from a resource id.
500     *
501     * <p>This drawable should generally take the place of title text. The logo cannot be
502     * clicked. Apps using a logo should also supply a description using
503     * {@link #setLogoDescription(int)}.</p>
504     *
505     * @param resId ID of a drawable resource
506     */
507    public void setLogo(@DrawableRes int resId) {
508        setLogo(getContext().getDrawable(resId));
509    }
510
511    /** @hide */
512    public boolean canShowOverflowMenu() {
513        return getVisibility() == VISIBLE && mMenuView != null && mMenuView.isOverflowReserved();
514    }
515
516    /**
517     * Check whether the overflow menu is currently showing. This may not reflect
518     * a pending show operation in progress.
519     *
520     * @return true if the overflow menu is currently showing
521     */
522    public boolean isOverflowMenuShowing() {
523        return mMenuView != null && mMenuView.isOverflowMenuShowing();
524    }
525
526    /** @hide */
527    public boolean isOverflowMenuShowPending() {
528        return mMenuView != null && mMenuView.isOverflowMenuShowPending();
529    }
530
531    /**
532     * Show the overflow items from the associated menu.
533     *
534     * @return true if the menu was able to be shown, false otherwise
535     */
536    public boolean showOverflowMenu() {
537        return mMenuView != null && mMenuView.showOverflowMenu();
538    }
539
540    /**
541     * Hide the overflow items from the associated menu.
542     *
543     * @return true if the menu was able to be hidden, false otherwise
544     */
545    public boolean hideOverflowMenu() {
546        return mMenuView != null && mMenuView.hideOverflowMenu();
547    }
548
549    /** @hide */
550    public void setMenu(MenuBuilder menu, ActionMenuPresenter outerPresenter) {
551        if (menu == null && mMenuView == null) {
552            return;
553        }
554
555        ensureMenuView();
556        final MenuBuilder oldMenu = mMenuView.peekMenu();
557        if (oldMenu == menu) {
558            return;
559        }
560
561        if (oldMenu != null) {
562            oldMenu.removeMenuPresenter(mOuterActionMenuPresenter);
563            oldMenu.removeMenuPresenter(mExpandedMenuPresenter);
564        }
565
566        if (mExpandedMenuPresenter == null) {
567            mExpandedMenuPresenter = new ExpandedActionViewMenuPresenter();
568        }
569
570        outerPresenter.setExpandedActionViewsExclusive(true);
571        if (menu != null) {
572            menu.addMenuPresenter(outerPresenter, mPopupContext);
573            menu.addMenuPresenter(mExpandedMenuPresenter, mPopupContext);
574        } else {
575            outerPresenter.initForMenu(mPopupContext, null);
576            mExpandedMenuPresenter.initForMenu(mPopupContext, null);
577            outerPresenter.updateMenuView(true);
578            mExpandedMenuPresenter.updateMenuView(true);
579        }
580        mMenuView.setPopupTheme(mPopupTheme);
581        mMenuView.setPresenter(outerPresenter);
582        mOuterActionMenuPresenter = outerPresenter;
583    }
584
585    /**
586     * Dismiss all currently showing popup menus, including overflow or submenus.
587     */
588    public void dismissPopupMenus() {
589        if (mMenuView != null) {
590            mMenuView.dismissPopupMenus();
591        }
592    }
593
594    /** @hide */
595    public boolean isTitleTruncated() {
596        if (mTitleTextView == null) {
597            return false;
598        }
599
600        final Layout titleLayout = mTitleTextView.getLayout();
601        if (titleLayout == null) {
602            return false;
603        }
604
605        final int lineCount = titleLayout.getLineCount();
606        for (int i = 0; i < lineCount; i++) {
607            if (titleLayout.getEllipsisCount(i) > 0) {
608                return true;
609            }
610        }
611        return false;
612    }
613
614    /**
615     * Set a logo drawable.
616     *
617     * <p>This drawable should generally take the place of title text. The logo cannot be
618     * clicked. Apps using a logo should also supply a description using
619     * {@link #setLogoDescription(int)}.</p>
620     *
621     * @param drawable Drawable to use as a logo
622     */
623    public void setLogo(Drawable drawable) {
624        if (drawable != null) {
625            ensureLogoView();
626            if (!isChildOrHidden(mLogoView)) {
627                addSystemView(mLogoView, true);
628            }
629        } else if (mLogoView != null && isChildOrHidden(mLogoView)) {
630            removeView(mLogoView);
631            mHiddenViews.remove(mLogoView);
632        }
633        if (mLogoView != null) {
634            mLogoView.setImageDrawable(drawable);
635        }
636    }
637
638    /**
639     * Return the current logo drawable.
640     *
641     * @return The current logo drawable
642     * @see #setLogo(int)
643     * @see #setLogo(android.graphics.drawable.Drawable)
644     */
645    public Drawable getLogo() {
646        return mLogoView != null ? mLogoView.getDrawable() : null;
647    }
648
649    /**
650     * Set a description of the toolbar's logo.
651     *
652     * <p>This description will be used for accessibility or other similar descriptions
653     * of the UI.</p>
654     *
655     * @param resId String resource id
656     */
657    public void setLogoDescription(@StringRes int resId) {
658        setLogoDescription(getContext().getText(resId));
659    }
660
661    /**
662     * Set a description of the toolbar's logo.
663     *
664     * <p>This description will be used for accessibility or other similar descriptions
665     * of the UI.</p>
666     *
667     * @param description Description to set
668     */
669    public void setLogoDescription(CharSequence description) {
670        if (!TextUtils.isEmpty(description)) {
671            ensureLogoView();
672        }
673        if (mLogoView != null) {
674            mLogoView.setContentDescription(description);
675        }
676    }
677
678    /**
679     * Return the description of the toolbar's logo.
680     *
681     * @return A description of the logo
682     */
683    public CharSequence getLogoDescription() {
684        return mLogoView != null ? mLogoView.getContentDescription() : null;
685    }
686
687    private void ensureLogoView() {
688        if (mLogoView == null) {
689            mLogoView = new ImageView(getContext());
690        }
691    }
692
693    /**
694     * Check whether this Toolbar is currently hosting an expanded action view.
695     *
696     * <p>An action view may be expanded either directly from the
697     * {@link android.view.MenuItem MenuItem} it belongs to or by user action. If the Toolbar
698     * has an expanded action view it can be collapsed using the {@link #collapseActionView()}
699     * method.</p>
700     *
701     * @return true if the Toolbar has an expanded action view
702     */
703    public boolean hasExpandedActionView() {
704        return mExpandedMenuPresenter != null &&
705                mExpandedMenuPresenter.mCurrentExpandedItem != null;
706    }
707
708    /**
709     * Collapse a currently expanded action view. If this Toolbar does not have an
710     * expanded action view this method has no effect.
711     *
712     * <p>An action view may be expanded either directly from the
713     * {@link android.view.MenuItem MenuItem} it belongs to or by user action.</p>
714     *
715     * @see #hasExpandedActionView()
716     */
717    public void collapseActionView() {
718        final MenuItemImpl item = mExpandedMenuPresenter == null ? null :
719                mExpandedMenuPresenter.mCurrentExpandedItem;
720        if (item != null) {
721            item.collapseActionView();
722        }
723    }
724
725    /**
726     * Returns the title of this toolbar.
727     *
728     * @return The current title.
729     */
730    public CharSequence getTitle() {
731        return mTitleText;
732    }
733
734    /**
735     * Set the title of this toolbar.
736     *
737     * <p>A title should be used as the anchor for a section of content. It should
738     * describe or name the content being viewed.</p>
739     *
740     * @param resId Resource ID of a string to set as the title
741     */
742    public void setTitle(@StringRes int resId) {
743        setTitle(getContext().getText(resId));
744    }
745
746    /**
747     * Set the title of this toolbar.
748     *
749     * <p>A title should be used as the anchor for a section of content. It should
750     * describe or name the content being viewed.</p>
751     *
752     * @param title Title to set
753     */
754    public void setTitle(CharSequence title) {
755        if (!TextUtils.isEmpty(title)) {
756            if (mTitleTextView == null) {
757                final Context context = getContext();
758                mTitleTextView = new TextView(context);
759                mTitleTextView.setSingleLine();
760                mTitleTextView.setEllipsize(TextUtils.TruncateAt.END);
761                if (mTitleTextAppearance != 0) {
762                    mTitleTextView.setTextAppearance(mTitleTextAppearance);
763                }
764                if (mTitleTextColor != 0) {
765                    mTitleTextView.setTextColor(mTitleTextColor);
766                }
767            }
768            if (!isChildOrHidden(mTitleTextView)) {
769                addSystemView(mTitleTextView, true);
770            }
771        } else if (mTitleTextView != null && isChildOrHidden(mTitleTextView)) {
772            removeView(mTitleTextView);
773            mHiddenViews.remove(mTitleTextView);
774        }
775        if (mTitleTextView != null) {
776            mTitleTextView.setText(title);
777        }
778        mTitleText = title;
779    }
780
781    /**
782     * Return the subtitle of this toolbar.
783     *
784     * @return The current subtitle
785     */
786    public CharSequence getSubtitle() {
787        return mSubtitleText;
788    }
789
790    /**
791     * Set the subtitle of this toolbar.
792     *
793     * <p>Subtitles should express extended information about the current content.</p>
794     *
795     * @param resId String resource ID
796     */
797    public void setSubtitle(@StringRes int resId) {
798        setSubtitle(getContext().getText(resId));
799    }
800
801    /**
802     * Set the subtitle of this toolbar.
803     *
804     * <p>Subtitles should express extended information about the current content.</p>
805     *
806     * @param subtitle Subtitle to set
807     */
808    public void setSubtitle(CharSequence subtitle) {
809        if (!TextUtils.isEmpty(subtitle)) {
810            if (mSubtitleTextView == null) {
811                final Context context = getContext();
812                mSubtitleTextView = new TextView(context);
813                mSubtitleTextView.setSingleLine();
814                mSubtitleTextView.setEllipsize(TextUtils.TruncateAt.END);
815                if (mSubtitleTextAppearance != 0) {
816                    mSubtitleTextView.setTextAppearance(mSubtitleTextAppearance);
817                }
818                if (mSubtitleTextColor != 0) {
819                    mSubtitleTextView.setTextColor(mSubtitleTextColor);
820                }
821            }
822            if (!isChildOrHidden(mSubtitleTextView)) {
823                addSystemView(mSubtitleTextView, true);
824            }
825        } else if (mSubtitleTextView != null && isChildOrHidden(mSubtitleTextView)) {
826            removeView(mSubtitleTextView);
827            mHiddenViews.remove(mSubtitleTextView);
828        }
829        if (mSubtitleTextView != null) {
830            mSubtitleTextView.setText(subtitle);
831        }
832        mSubtitleText = subtitle;
833    }
834
835    /**
836     * Sets the text color, size, style, hint color, and highlight color
837     * from the specified TextAppearance resource.
838     */
839    public void setTitleTextAppearance(Context context, @StyleRes int resId) {
840        mTitleTextAppearance = resId;
841        if (mTitleTextView != null) {
842            mTitleTextView.setTextAppearance(resId);
843        }
844    }
845
846    /**
847     * Sets the text color, size, style, hint color, and highlight color
848     * from the specified TextAppearance resource.
849     */
850    public void setSubtitleTextAppearance(Context context, @StyleRes int resId) {
851        mSubtitleTextAppearance = resId;
852        if (mSubtitleTextView != null) {
853            mSubtitleTextView.setTextAppearance(resId);
854        }
855    }
856
857    /**
858     * Sets the text color of the title, if present.
859     *
860     * @param color The new text color in 0xAARRGGBB format
861     */
862    public void setTitleTextColor(@ColorInt int color) {
863        mTitleTextColor = color;
864        if (mTitleTextView != null) {
865            mTitleTextView.setTextColor(color);
866        }
867    }
868
869    /**
870     * Sets the text color of the subtitle, if present.
871     *
872     * @param color The new text color in 0xAARRGGBB format
873     */
874    public void setSubtitleTextColor(@ColorInt int color) {
875        mSubtitleTextColor = color;
876        if (mSubtitleTextView != null) {
877            mSubtitleTextView.setTextColor(color);
878        }
879    }
880
881    /**
882     * Retrieve the currently configured content description for the navigation button view.
883     * This will be used to describe the navigation action to users through mechanisms such
884     * as screen readers or tooltips.
885     *
886     * @return The navigation button's content description
887     *
888     * @attr ref android.R.styleable#Toolbar_navigationContentDescription
889     */
890    @Nullable
891    public CharSequence getNavigationContentDescription() {
892        return mNavButtonView != null ? mNavButtonView.getContentDescription() : null;
893    }
894
895    /**
896     * Set a content description for the navigation button if one is present. The content
897     * description will be read via screen readers or other accessibility systems to explain
898     * the action of the navigation button.
899     *
900     * @param resId Resource ID of a content description string to set, or 0 to
901     *              clear the description
902     *
903     * @attr ref android.R.styleable#Toolbar_navigationContentDescription
904     */
905    public void setNavigationContentDescription(@StringRes int resId) {
906        setNavigationContentDescription(resId != 0 ? getContext().getText(resId) : null);
907    }
908
909    /**
910     * Set a content description for the navigation button if one is present. The content
911     * description will be read via screen readers or other accessibility systems to explain
912     * the action of the navigation button.
913     *
914     * @param description Content description to set, or <code>null</code> to
915     *                    clear the content description
916     *
917     * @attr ref android.R.styleable#Toolbar_navigationContentDescription
918     */
919    public void setNavigationContentDescription(@Nullable CharSequence description) {
920        if (!TextUtils.isEmpty(description)) {
921            ensureNavButtonView();
922        }
923        if (mNavButtonView != null) {
924            mNavButtonView.setContentDescription(description);
925        }
926    }
927
928    /**
929     * Set the icon to use for the toolbar's navigation button.
930     *
931     * <p>The navigation button appears at the start of the toolbar if present. Setting an icon
932     * will make the navigation button visible.</p>
933     *
934     * <p>If you use a navigation icon you should also set a description for its action using
935     * {@link #setNavigationContentDescription(int)}. This is used for accessibility and
936     * tooltips.</p>
937     *
938     * @param resId Resource ID of a drawable to set
939     *
940     * @attr ref android.R.styleable#Toolbar_navigationIcon
941     */
942    public void setNavigationIcon(@DrawableRes int resId) {
943        setNavigationIcon(getContext().getDrawable(resId));
944    }
945
946    /**
947     * Set the icon to use for the toolbar's navigation button.
948     *
949     * <p>The navigation button appears at the start of the toolbar if present. Setting an icon
950     * will make the navigation button visible.</p>
951     *
952     * <p>If you use a navigation icon you should also set a description for its action using
953     * {@link #setNavigationContentDescription(int)}. This is used for accessibility and
954     * tooltips.</p>
955     *
956     * @param icon Drawable to set, may be null to clear the icon
957     *
958     * @attr ref android.R.styleable#Toolbar_navigationIcon
959     */
960    public void setNavigationIcon(@Nullable Drawable icon) {
961        if (icon != null) {
962            ensureNavButtonView();
963            if (!isChildOrHidden(mNavButtonView)) {
964                addSystemView(mNavButtonView, true);
965            }
966        } else if (mNavButtonView != null && isChildOrHidden(mNavButtonView)) {
967            removeView(mNavButtonView);
968            mHiddenViews.remove(mNavButtonView);
969        }
970        if (mNavButtonView != null) {
971            mNavButtonView.setImageDrawable(icon);
972        }
973    }
974
975    /**
976     * Return the current drawable used as the navigation icon.
977     *
978     * @return The navigation icon drawable
979     *
980     * @attr ref android.R.styleable#Toolbar_navigationIcon
981     */
982    @Nullable
983    public Drawable getNavigationIcon() {
984        return mNavButtonView != null ? mNavButtonView.getDrawable() : null;
985    }
986
987    /**
988     * Set a listener to respond to navigation events.
989     *
990     * <p>This listener will be called whenever the user clicks the navigation button
991     * at the start of the toolbar. An icon must be set for the navigation button to appear.</p>
992     *
993     * @param listener Listener to set
994     * @see #setNavigationIcon(android.graphics.drawable.Drawable)
995     */
996    public void setNavigationOnClickListener(OnClickListener listener) {
997        ensureNavButtonView();
998        mNavButtonView.setOnClickListener(listener);
999    }
1000
1001    /**
1002     * @hide
1003     */
1004    @Nullable
1005    @TestApi
1006    public View getNavigationView() {
1007        return mNavButtonView;
1008    }
1009
1010    /**
1011     * Return the Menu shown in the toolbar.
1012     *
1013     * <p>Applications that wish to populate the toolbar's menu can do so from here. To use
1014     * an XML menu resource, use {@link #inflateMenu(int)}.</p>
1015     *
1016     * @return The toolbar's Menu
1017     */
1018    public Menu getMenu() {
1019        ensureMenu();
1020        return mMenuView.getMenu();
1021    }
1022
1023    /**
1024     * Set the icon to use for the overflow button.
1025     *
1026     * @param icon Drawable to set, may be null to clear the icon
1027     */
1028    public void setOverflowIcon(@Nullable Drawable icon) {
1029        ensureMenu();
1030        mMenuView.setOverflowIcon(icon);
1031    }
1032
1033    /**
1034     * Return the current drawable used as the overflow icon.
1035     *
1036     * @return The overflow icon drawable
1037     */
1038    @Nullable
1039    public Drawable getOverflowIcon() {
1040        ensureMenu();
1041        return mMenuView.getOverflowIcon();
1042    }
1043
1044    private void ensureMenu() {
1045        ensureMenuView();
1046        if (mMenuView.peekMenu() == null) {
1047            // Initialize a new menu for the first time.
1048            final MenuBuilder menu = (MenuBuilder) mMenuView.getMenu();
1049            if (mExpandedMenuPresenter == null) {
1050                mExpandedMenuPresenter = new ExpandedActionViewMenuPresenter();
1051            }
1052            mMenuView.setExpandedActionViewsExclusive(true);
1053            menu.addMenuPresenter(mExpandedMenuPresenter, mPopupContext);
1054        }
1055    }
1056
1057    private void ensureMenuView() {
1058        if (mMenuView == null) {
1059            mMenuView = new ActionMenuView(getContext());
1060            mMenuView.setPopupTheme(mPopupTheme);
1061            mMenuView.setOnMenuItemClickListener(mMenuViewItemClickListener);
1062            mMenuView.setMenuCallbacks(mActionMenuPresenterCallback, mMenuBuilderCallback);
1063            final LayoutParams lp = generateDefaultLayoutParams();
1064            lp.gravity = Gravity.END | (mButtonGravity & Gravity.VERTICAL_GRAVITY_MASK);
1065            mMenuView.setLayoutParams(lp);
1066            addSystemView(mMenuView, false);
1067        }
1068    }
1069
1070    private MenuInflater getMenuInflater() {
1071        return new MenuInflater(getContext());
1072    }
1073
1074    /**
1075     * Inflate a menu resource into this toolbar.
1076     *
1077     * <p>Inflate an XML menu resource into this toolbar. Existing items in the menu will not
1078     * be modified or removed.</p>
1079     *
1080     * @param resId ID of a menu resource to inflate
1081     */
1082    public void inflateMenu(@MenuRes int resId) {
1083        getMenuInflater().inflate(resId, getMenu());
1084    }
1085
1086    /**
1087     * Set a listener to respond to menu item click events.
1088     *
1089     * <p>This listener will be invoked whenever a user selects a menu item from
1090     * the action buttons presented at the end of the toolbar or the associated overflow.</p>
1091     *
1092     * @param listener Listener to set
1093     */
1094    public void setOnMenuItemClickListener(OnMenuItemClickListener listener) {
1095        mOnMenuItemClickListener = listener;
1096    }
1097
1098    /**
1099     * Sets the content insets for this toolbar relative to layout direction.
1100     *
1101     * <p>The content inset affects the valid area for Toolbar content other than
1102     * the navigation button and menu. Insets define the minimum margin for these components
1103     * and can be used to effectively align Toolbar content along well-known gridlines.</p>
1104     *
1105     * @param contentInsetStart Content inset for the toolbar starting edge
1106     * @param contentInsetEnd Content inset for the toolbar ending edge
1107     *
1108     * @see #setContentInsetsAbsolute(int, int)
1109     * @see #getContentInsetStart()
1110     * @see #getContentInsetEnd()
1111     * @see #getContentInsetLeft()
1112     * @see #getContentInsetRight()
1113     * @attr ref android.R.styleable#Toolbar_contentInsetEnd
1114     * @attr ref android.R.styleable#Toolbar_contentInsetStart
1115     */
1116    public void setContentInsetsRelative(int contentInsetStart, int contentInsetEnd) {
1117        ensureContentInsets();
1118        mContentInsets.setRelative(contentInsetStart, contentInsetEnd);
1119    }
1120
1121    /**
1122     * Gets the starting content inset for this toolbar.
1123     *
1124     * <p>The content inset affects the valid area for Toolbar content other than
1125     * the navigation button and menu. Insets define the minimum margin for these components
1126     * and can be used to effectively align Toolbar content along well-known gridlines.</p>
1127     *
1128     * @return The starting content inset for this toolbar
1129     *
1130     * @see #setContentInsetsRelative(int, int)
1131     * @see #setContentInsetsAbsolute(int, int)
1132     * @see #getContentInsetEnd()
1133     * @see #getContentInsetLeft()
1134     * @see #getContentInsetRight()
1135     * @attr ref android.R.styleable#Toolbar_contentInsetStart
1136     */
1137    public int getContentInsetStart() {
1138        return mContentInsets != null ? mContentInsets.getStart() : 0;
1139    }
1140
1141    /**
1142     * Gets the ending content inset for this toolbar.
1143     *
1144     * <p>The content inset affects the valid area for Toolbar content other than
1145     * the navigation button and menu. Insets define the minimum margin for these components
1146     * and can be used to effectively align Toolbar content along well-known gridlines.</p>
1147     *
1148     * @return The ending content inset for this toolbar
1149     *
1150     * @see #setContentInsetsRelative(int, int)
1151     * @see #setContentInsetsAbsolute(int, int)
1152     * @see #getContentInsetStart()
1153     * @see #getContentInsetLeft()
1154     * @see #getContentInsetRight()
1155     * @attr ref android.R.styleable#Toolbar_contentInsetEnd
1156     */
1157    public int getContentInsetEnd() {
1158        return mContentInsets != null ? mContentInsets.getEnd() : 0;
1159    }
1160
1161    /**
1162     * Sets the content insets for this toolbar.
1163     *
1164     * <p>The content inset affects the valid area for Toolbar content other than
1165     * the navigation button and menu. Insets define the minimum margin for these components
1166     * and can be used to effectively align Toolbar content along well-known gridlines.</p>
1167     *
1168     * @param contentInsetLeft Content inset for the toolbar's left edge
1169     * @param contentInsetRight Content inset for the toolbar's right edge
1170     *
1171     * @see #setContentInsetsAbsolute(int, int)
1172     * @see #getContentInsetStart()
1173     * @see #getContentInsetEnd()
1174     * @see #getContentInsetLeft()
1175     * @see #getContentInsetRight()
1176     * @attr ref android.R.styleable#Toolbar_contentInsetLeft
1177     * @attr ref android.R.styleable#Toolbar_contentInsetRight
1178     */
1179    public void setContentInsetsAbsolute(int contentInsetLeft, int contentInsetRight) {
1180        ensureContentInsets();
1181        mContentInsets.setAbsolute(contentInsetLeft, contentInsetRight);
1182    }
1183
1184    /**
1185     * Gets the left content inset for this toolbar.
1186     *
1187     * <p>The content inset affects the valid area for Toolbar content other than
1188     * the navigation button and menu. Insets define the minimum margin for these components
1189     * and can be used to effectively align Toolbar content along well-known gridlines.</p>
1190     *
1191     * @return The left content inset for this toolbar
1192     *
1193     * @see #setContentInsetsRelative(int, int)
1194     * @see #setContentInsetsAbsolute(int, int)
1195     * @see #getContentInsetStart()
1196     * @see #getContentInsetEnd()
1197     * @see #getContentInsetRight()
1198     * @attr ref android.R.styleable#Toolbar_contentInsetLeft
1199     */
1200    public int getContentInsetLeft() {
1201        return mContentInsets != null ? mContentInsets.getLeft() : 0;
1202    }
1203
1204    /**
1205     * Gets the right content inset for this toolbar.
1206     *
1207     * <p>The content inset affects the valid area for Toolbar content other than
1208     * the navigation button and menu. Insets define the minimum margin for these components
1209     * and can be used to effectively align Toolbar content along well-known gridlines.</p>
1210     *
1211     * @return The right content inset for this toolbar
1212     *
1213     * @see #setContentInsetsRelative(int, int)
1214     * @see #setContentInsetsAbsolute(int, int)
1215     * @see #getContentInsetStart()
1216     * @see #getContentInsetEnd()
1217     * @see #getContentInsetLeft()
1218     * @attr ref android.R.styleable#Toolbar_contentInsetRight
1219     */
1220    public int getContentInsetRight() {
1221        return mContentInsets != null ? mContentInsets.getRight() : 0;
1222    }
1223
1224    /**
1225     * Gets the start content inset to use when a navigation button is present.
1226     *
1227     * <p>Different content insets are often called for when additional buttons are present
1228     * in the toolbar, as well as at different toolbar sizes. The larger value of
1229     * {@link #getContentInsetStart()} and this value will be used during layout.</p>
1230     *
1231     * @return the start content inset used when a navigation icon has been set in pixels
1232     *
1233     * @see #setContentInsetStartWithNavigation(int)
1234     * @attr ref android.R.styleable#Toolbar_contentInsetStartWithNavigation
1235     */
1236    public int getContentInsetStartWithNavigation() {
1237        return mContentInsetStartWithNavigation != RtlSpacingHelper.UNDEFINED
1238                ? mContentInsetStartWithNavigation
1239                : getContentInsetStart();
1240    }
1241
1242    /**
1243     * Sets the start content inset to use when a navigation button is present.
1244     *
1245     * <p>Different content insets are often called for when additional buttons are present
1246     * in the toolbar, as well as at different toolbar sizes. The larger value of
1247     * {@link #getContentInsetStart()} and this value will be used during layout.</p>
1248     *
1249     * @param insetStartWithNavigation the inset to use when a navigation icon has been set
1250     *                                 in pixels
1251     *
1252     * @see #getContentInsetStartWithNavigation()
1253     * @attr ref android.R.styleable#Toolbar_contentInsetStartWithNavigation
1254     */
1255    public void setContentInsetStartWithNavigation(int insetStartWithNavigation) {
1256        if (insetStartWithNavigation < 0) {
1257            insetStartWithNavigation = RtlSpacingHelper.UNDEFINED;
1258        }
1259        if (insetStartWithNavigation != mContentInsetStartWithNavigation) {
1260            mContentInsetStartWithNavigation = insetStartWithNavigation;
1261            if (getNavigationIcon() != null) {
1262                requestLayout();
1263            }
1264        }
1265    }
1266
1267    /**
1268     * Gets the end content inset to use when action buttons are present.
1269     *
1270     * <p>Different content insets are often called for when additional buttons are present
1271     * in the toolbar, as well as at different toolbar sizes. The larger value of
1272     * {@link #getContentInsetEnd()} and this value will be used during layout.</p>
1273     *
1274     * @return the end content inset used when a menu has been set in pixels
1275     *
1276     * @see #setContentInsetEndWithActions(int)
1277     * @attr ref android.R.styleable#Toolbar_contentInsetEndWithActions
1278     */
1279    public int getContentInsetEndWithActions() {
1280        return mContentInsetEndWithActions != RtlSpacingHelper.UNDEFINED
1281                ? mContentInsetEndWithActions
1282                : getContentInsetEnd();
1283    }
1284
1285    /**
1286     * Sets the start content inset to use when action buttons are present.
1287     *
1288     * <p>Different content insets are often called for when additional buttons are present
1289     * in the toolbar, as well as at different toolbar sizes. The larger value of
1290     * {@link #getContentInsetEnd()} and this value will be used during layout.</p>
1291     *
1292     * @param insetEndWithActions the inset to use when a menu has been set in pixels
1293     *
1294     * @see #setContentInsetEndWithActions(int)
1295     * @attr ref android.R.styleable#Toolbar_contentInsetEndWithActions
1296     */
1297    public void setContentInsetEndWithActions(int insetEndWithActions) {
1298        if (insetEndWithActions < 0) {
1299            insetEndWithActions = RtlSpacingHelper.UNDEFINED;
1300        }
1301        if (insetEndWithActions != mContentInsetEndWithActions) {
1302            mContentInsetEndWithActions = insetEndWithActions;
1303            if (getNavigationIcon() != null) {
1304                requestLayout();
1305            }
1306        }
1307    }
1308
1309    /**
1310     * Gets the content inset that will be used on the starting side of the bar in the current
1311     * toolbar configuration.
1312     *
1313     * @return the current content inset start in pixels
1314     *
1315     * @see #getContentInsetStartWithNavigation()
1316     */
1317    public int getCurrentContentInsetStart() {
1318        return getNavigationIcon() != null
1319                ? Math.max(getContentInsetStart(), Math.max(mContentInsetStartWithNavigation, 0))
1320                : getContentInsetStart();
1321    }
1322
1323    /**
1324     * Gets the content inset that will be used on the ending side of the bar in the current
1325     * toolbar configuration.
1326     *
1327     * @return the current content inset end in pixels
1328     *
1329     * @see #getContentInsetEndWithActions()
1330     */
1331    public int getCurrentContentInsetEnd() {
1332        boolean hasActions = false;
1333        if (mMenuView != null) {
1334            final MenuBuilder mb = mMenuView.peekMenu();
1335            hasActions = mb != null && mb.hasVisibleItems();
1336        }
1337        return hasActions
1338                ? Math.max(getContentInsetEnd(), Math.max(mContentInsetEndWithActions, 0))
1339                : getContentInsetEnd();
1340    }
1341
1342    /**
1343     * Gets the content inset that will be used on the left side of the bar in the current
1344     * toolbar configuration.
1345     *
1346     * @return the current content inset left in pixels
1347     *
1348     * @see #getContentInsetStartWithNavigation()
1349     * @see #getContentInsetEndWithActions()
1350     */
1351    public int getCurrentContentInsetLeft() {
1352        return isLayoutRtl()
1353                ? getCurrentContentInsetEnd()
1354                : getCurrentContentInsetStart();
1355    }
1356
1357    /**
1358     * Gets the content inset that will be used on the right side of the bar in the current
1359     * toolbar configuration.
1360     *
1361     * @return the current content inset right in pixels
1362     *
1363     * @see #getContentInsetStartWithNavigation()
1364     * @see #getContentInsetEndWithActions()
1365     */
1366    public int getCurrentContentInsetRight() {
1367        return isLayoutRtl()
1368                ? getCurrentContentInsetStart()
1369                : getCurrentContentInsetEnd();
1370    }
1371
1372    private void ensureNavButtonView() {
1373        if (mNavButtonView == null) {
1374            mNavButtonView = new ImageButton(getContext(), null, 0, mNavButtonStyle);
1375            final LayoutParams lp = generateDefaultLayoutParams();
1376            lp.gravity = Gravity.START | (mButtonGravity & Gravity.VERTICAL_GRAVITY_MASK);
1377            mNavButtonView.setLayoutParams(lp);
1378        }
1379    }
1380
1381    private void ensureCollapseButtonView() {
1382        if (mCollapseButtonView == null) {
1383            mCollapseButtonView = new ImageButton(getContext(), null, 0, mNavButtonStyle);
1384            mCollapseButtonView.setImageDrawable(mCollapseIcon);
1385            mCollapseButtonView.setContentDescription(mCollapseDescription);
1386            final LayoutParams lp = generateDefaultLayoutParams();
1387            lp.gravity = Gravity.START | (mButtonGravity & Gravity.VERTICAL_GRAVITY_MASK);
1388            lp.mViewType = LayoutParams.EXPANDED;
1389            mCollapseButtonView.setLayoutParams(lp);
1390            mCollapseButtonView.setOnClickListener(new OnClickListener() {
1391                @Override
1392                public void onClick(View v) {
1393                    collapseActionView();
1394                }
1395            });
1396        }
1397    }
1398
1399    private void addSystemView(View v, boolean allowHide) {
1400        final ViewGroup.LayoutParams vlp = v.getLayoutParams();
1401        final LayoutParams lp;
1402        if (vlp == null) {
1403            lp = generateDefaultLayoutParams();
1404        } else if (!checkLayoutParams(vlp)) {
1405            lp = generateLayoutParams(vlp);
1406        } else {
1407            lp = (LayoutParams) vlp;
1408        }
1409        lp.mViewType = LayoutParams.SYSTEM;
1410
1411        if (allowHide && mExpandedActionView != null) {
1412            v.setLayoutParams(lp);
1413            mHiddenViews.add(v);
1414        } else {
1415            addView(v, lp);
1416        }
1417    }
1418
1419    @Override
1420    protected Parcelable onSaveInstanceState() {
1421        SavedState state = new SavedState(super.onSaveInstanceState());
1422
1423        if (mExpandedMenuPresenter != null && mExpandedMenuPresenter.mCurrentExpandedItem != null) {
1424            state.expandedMenuItemId = mExpandedMenuPresenter.mCurrentExpandedItem.getItemId();
1425        }
1426
1427        state.isOverflowOpen = isOverflowMenuShowing();
1428
1429        return state;
1430    }
1431
1432    @Override
1433    protected void onRestoreInstanceState(Parcelable state) {
1434        final SavedState ss = (SavedState) state;
1435        super.onRestoreInstanceState(ss.getSuperState());
1436
1437        final Menu menu = mMenuView != null ? mMenuView.peekMenu() : null;
1438        if (ss.expandedMenuItemId != 0 && mExpandedMenuPresenter != null && menu != null) {
1439            final MenuItem item = menu.findItem(ss.expandedMenuItemId);
1440            if (item != null) {
1441                item.expandActionView();
1442            }
1443        }
1444
1445        if (ss.isOverflowOpen) {
1446            postShowOverflowMenu();
1447        }
1448    }
1449
1450    private void postShowOverflowMenu() {
1451        removeCallbacks(mShowOverflowMenuRunnable);
1452        post(mShowOverflowMenuRunnable);
1453    }
1454
1455    @Override
1456    protected void onDetachedFromWindow() {
1457        super.onDetachedFromWindow();
1458        removeCallbacks(mShowOverflowMenuRunnable);
1459    }
1460
1461    @Override
1462    public boolean onTouchEvent(MotionEvent ev) {
1463        // Toolbars always eat touch events, but should still respect the touch event dispatch
1464        // contract. If the normal View implementation doesn't want the events, we'll just silently
1465        // eat the rest of the gesture without reporting the events to the default implementation
1466        // since that's what it expects.
1467
1468        final int action = ev.getActionMasked();
1469        if (action == MotionEvent.ACTION_DOWN) {
1470            mEatingTouch = false;
1471        }
1472
1473        if (!mEatingTouch) {
1474            final boolean handled = super.onTouchEvent(ev);
1475            if (action == MotionEvent.ACTION_DOWN && !handled) {
1476                mEatingTouch = true;
1477            }
1478        }
1479
1480        if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
1481            mEatingTouch = false;
1482        }
1483
1484        return true;
1485    }
1486
1487    /**
1488     * @hide
1489     */
1490    @Override
1491    protected void onSetLayoutParams(View child, ViewGroup.LayoutParams lp) {
1492        /*
1493         * Apps may set ActionBar.LayoutParams on their action bar custom views when
1494         * a Toolbar is actually acting in the role of the action bar. Perform a quick
1495         * switch with Toolbar.LayoutParams whenever this happens. This does leave open
1496         * one potential gotcha: if an app retains the ActionBar.LayoutParams reference
1497         * and attempts to keep making changes to it before layout those changes won't
1498         * be reflected in the final results.
1499         */
1500        if (!checkLayoutParams(lp)) {
1501            child.setLayoutParams(generateLayoutParams(lp));
1502        }
1503    }
1504
1505    private void measureChildConstrained(View child, int parentWidthSpec, int widthUsed,
1506            int parentHeightSpec, int heightUsed, int heightConstraint) {
1507        final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
1508
1509        int childWidthSpec = getChildMeasureSpec(parentWidthSpec,
1510                mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
1511                        + widthUsed, lp.width);
1512        int childHeightSpec = getChildMeasureSpec(parentHeightSpec,
1513                mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
1514                        + heightUsed, lp.height);
1515
1516        final int childHeightMode = MeasureSpec.getMode(childHeightSpec);
1517        if (childHeightMode != MeasureSpec.EXACTLY && heightConstraint >= 0) {
1518            final int size = childHeightMode != MeasureSpec.UNSPECIFIED ?
1519                    Math.min(MeasureSpec.getSize(childHeightSpec), heightConstraint) :
1520                    heightConstraint;
1521            childHeightSpec = MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY);
1522        }
1523        child.measure(childWidthSpec, childHeightSpec);
1524    }
1525
1526    /**
1527     * Returns the width + uncollapsed margins
1528     */
1529    private int measureChildCollapseMargins(View child,
1530            int parentWidthMeasureSpec, int widthUsed,
1531            int parentHeightMeasureSpec, int heightUsed, int[] collapsingMargins) {
1532        final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
1533
1534        final int leftDiff = lp.leftMargin - collapsingMargins[0];
1535        final int rightDiff = lp.rightMargin - collapsingMargins[1];
1536        final int leftMargin = Math.max(0, leftDiff);
1537        final int rightMargin = Math.max(0, rightDiff);
1538        final int hMargins = leftMargin + rightMargin;
1539        collapsingMargins[0] = Math.max(0, -leftDiff);
1540        collapsingMargins[1] = Math.max(0, -rightDiff);
1541
1542        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
1543                mPaddingLeft + mPaddingRight + hMargins + widthUsed, lp.width);
1544        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
1545                mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
1546                        + heightUsed, lp.height);
1547
1548        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
1549        return child.getMeasuredWidth() + hMargins;
1550    }
1551
1552    /**
1553     * Returns true if the Toolbar is collapsible and has no child views with a measured size > 0.
1554     */
1555    private boolean shouldCollapse() {
1556        if (!mCollapsible) return false;
1557
1558        final int childCount = getChildCount();
1559        for (int i = 0; i < childCount; i++) {
1560            final View child = getChildAt(i);
1561            if (shouldLayout(child) && child.getMeasuredWidth() > 0 &&
1562                    child.getMeasuredHeight() > 0) {
1563                return false;
1564            }
1565        }
1566        return true;
1567    }
1568
1569    @Override
1570    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
1571        int width = 0;
1572        int height = 0;
1573        int childState = 0;
1574
1575        final int[] collapsingMargins = mTempMargins;
1576        final int marginStartIndex;
1577        final int marginEndIndex;
1578        if (isLayoutRtl()) {
1579            marginStartIndex = 1;
1580            marginEndIndex = 0;
1581        } else {
1582            marginStartIndex = 0;
1583            marginEndIndex = 1;
1584        }
1585
1586        // System views measure first.
1587
1588        int navWidth = 0;
1589        if (shouldLayout(mNavButtonView)) {
1590            measureChildConstrained(mNavButtonView, widthMeasureSpec, width, heightMeasureSpec, 0,
1591                    mMaxButtonHeight);
1592            navWidth = mNavButtonView.getMeasuredWidth() + getHorizontalMargins(mNavButtonView);
1593            height = Math.max(height, mNavButtonView.getMeasuredHeight() +
1594                    getVerticalMargins(mNavButtonView));
1595            childState = combineMeasuredStates(childState, mNavButtonView.getMeasuredState());
1596        }
1597
1598        if (shouldLayout(mCollapseButtonView)) {
1599            measureChildConstrained(mCollapseButtonView, widthMeasureSpec, width,
1600                    heightMeasureSpec, 0, mMaxButtonHeight);
1601            navWidth = mCollapseButtonView.getMeasuredWidth() +
1602                    getHorizontalMargins(mCollapseButtonView);
1603            height = Math.max(height, mCollapseButtonView.getMeasuredHeight() +
1604                    getVerticalMargins(mCollapseButtonView));
1605            childState = combineMeasuredStates(childState, mCollapseButtonView.getMeasuredState());
1606        }
1607
1608        final int contentInsetStart = getCurrentContentInsetStart();
1609        width += Math.max(contentInsetStart, navWidth);
1610        collapsingMargins[marginStartIndex] = Math.max(0, contentInsetStart - navWidth);
1611
1612        int menuWidth = 0;
1613        if (shouldLayout(mMenuView)) {
1614            measureChildConstrained(mMenuView, widthMeasureSpec, width, heightMeasureSpec, 0,
1615                    mMaxButtonHeight);
1616            menuWidth = mMenuView.getMeasuredWidth() + getHorizontalMargins(mMenuView);
1617            height = Math.max(height, mMenuView.getMeasuredHeight() +
1618                    getVerticalMargins(mMenuView));
1619            childState = combineMeasuredStates(childState, mMenuView.getMeasuredState());
1620        }
1621
1622        final int contentInsetEnd = getCurrentContentInsetEnd();
1623        width += Math.max(contentInsetEnd, menuWidth);
1624        collapsingMargins[marginEndIndex] = Math.max(0, contentInsetEnd - menuWidth);
1625
1626        if (shouldLayout(mExpandedActionView)) {
1627            width += measureChildCollapseMargins(mExpandedActionView, widthMeasureSpec, width,
1628                    heightMeasureSpec, 0, collapsingMargins);
1629            height = Math.max(height, mExpandedActionView.getMeasuredHeight() +
1630                    getVerticalMargins(mExpandedActionView));
1631            childState = combineMeasuredStates(childState, mExpandedActionView.getMeasuredState());
1632        }
1633
1634        if (shouldLayout(mLogoView)) {
1635            width += measureChildCollapseMargins(mLogoView, widthMeasureSpec, width,
1636                    heightMeasureSpec, 0, collapsingMargins);
1637            height = Math.max(height, mLogoView.getMeasuredHeight() +
1638                    getVerticalMargins(mLogoView));
1639            childState = combineMeasuredStates(childState, mLogoView.getMeasuredState());
1640        }
1641
1642        final int childCount = getChildCount();
1643        for (int i = 0; i < childCount; i++) {
1644            final View child = getChildAt(i);
1645            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
1646            if (lp.mViewType != LayoutParams.CUSTOM || !shouldLayout(child)) {
1647                // We already got all system views above. Skip them and GONE views.
1648                continue;
1649            }
1650
1651            width += measureChildCollapseMargins(child, widthMeasureSpec, width,
1652                    heightMeasureSpec, 0, collapsingMargins);
1653            height = Math.max(height, child.getMeasuredHeight() + getVerticalMargins(child));
1654            childState = combineMeasuredStates(childState, child.getMeasuredState());
1655        }
1656
1657        int titleWidth = 0;
1658        int titleHeight = 0;
1659        final int titleVertMargins = mTitleMarginTop + mTitleMarginBottom;
1660        final int titleHorizMargins = mTitleMarginStart + mTitleMarginEnd;
1661        if (shouldLayout(mTitleTextView)) {
1662            titleWidth = measureChildCollapseMargins(mTitleTextView, widthMeasureSpec,
1663                    width + titleHorizMargins, heightMeasureSpec, titleVertMargins,
1664                    collapsingMargins);
1665            titleWidth = mTitleTextView.getMeasuredWidth() + getHorizontalMargins(mTitleTextView);
1666            titleHeight = mTitleTextView.getMeasuredHeight() + getVerticalMargins(mTitleTextView);
1667            childState = combineMeasuredStates(childState, mTitleTextView.getMeasuredState());
1668        }
1669        if (shouldLayout(mSubtitleTextView)) {
1670            titleWidth = Math.max(titleWidth, measureChildCollapseMargins(mSubtitleTextView,
1671                    widthMeasureSpec, width + titleHorizMargins,
1672                    heightMeasureSpec, titleHeight + titleVertMargins,
1673                    collapsingMargins));
1674            titleHeight += mSubtitleTextView.getMeasuredHeight() +
1675                    getVerticalMargins(mSubtitleTextView);
1676            childState = combineMeasuredStates(childState, mSubtitleTextView.getMeasuredState());
1677        }
1678
1679        width += titleWidth;
1680        height = Math.max(height, titleHeight);
1681
1682        // Measurement already took padding into account for available space for the children,
1683        // add it in for the final size.
1684        width += getPaddingLeft() + getPaddingRight();
1685        height += getPaddingTop() + getPaddingBottom();
1686
1687        final int measuredWidth = resolveSizeAndState(
1688                Math.max(width, getSuggestedMinimumWidth()),
1689                widthMeasureSpec, childState & MEASURED_STATE_MASK);
1690        final int measuredHeight = resolveSizeAndState(
1691                Math.max(height, getSuggestedMinimumHeight()),
1692                heightMeasureSpec, childState << MEASURED_HEIGHT_STATE_SHIFT);
1693
1694        setMeasuredDimension(measuredWidth, shouldCollapse() ? 0 : measuredHeight);
1695    }
1696
1697    @Override
1698    protected void onLayout(boolean changed, int l, int t, int r, int b) {
1699        final boolean isRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
1700        final int width = getWidth();
1701        final int height = getHeight();
1702        final int paddingLeft = getPaddingLeft();
1703        final int paddingRight = getPaddingRight();
1704        final int paddingTop = getPaddingTop();
1705        final int paddingBottom = getPaddingBottom();
1706        int left = paddingLeft;
1707        int right = width - paddingRight;
1708
1709        final int[] collapsingMargins = mTempMargins;
1710        collapsingMargins[0] = collapsingMargins[1] = 0;
1711
1712        // Align views within the minimum toolbar height, if set.
1713        final int minHeight = getMinimumHeight();
1714        final int alignmentHeight = minHeight >= 0 ? Math.min(minHeight, b - t) : 0;
1715
1716        if (shouldLayout(mNavButtonView)) {
1717            if (isRtl) {
1718                right = layoutChildRight(mNavButtonView, right, collapsingMargins,
1719                        alignmentHeight);
1720            } else {
1721                left = layoutChildLeft(mNavButtonView, left, collapsingMargins,
1722                        alignmentHeight);
1723            }
1724        }
1725
1726        if (shouldLayout(mCollapseButtonView)) {
1727            if (isRtl) {
1728                right = layoutChildRight(mCollapseButtonView, right, collapsingMargins,
1729                        alignmentHeight);
1730            } else {
1731                left = layoutChildLeft(mCollapseButtonView, left, collapsingMargins,
1732                        alignmentHeight);
1733            }
1734        }
1735
1736        if (shouldLayout(mMenuView)) {
1737            if (isRtl) {
1738                left = layoutChildLeft(mMenuView, left, collapsingMargins,
1739                        alignmentHeight);
1740            } else {
1741                right = layoutChildRight(mMenuView, right, collapsingMargins,
1742                        alignmentHeight);
1743            }
1744        }
1745
1746        final int contentInsetLeft = getCurrentContentInsetLeft();
1747        final int contentInsetRight = getCurrentContentInsetRight();
1748        collapsingMargins[0] = Math.max(0, contentInsetLeft - left);
1749        collapsingMargins[1] = Math.max(0, contentInsetRight - (width - paddingRight - right));
1750        left = Math.max(left, contentInsetLeft);
1751        right = Math.min(right, width - paddingRight - contentInsetRight);
1752
1753        if (shouldLayout(mExpandedActionView)) {
1754            if (isRtl) {
1755                right = layoutChildRight(mExpandedActionView, right, collapsingMargins,
1756                        alignmentHeight);
1757            } else {
1758                left = layoutChildLeft(mExpandedActionView, left, collapsingMargins,
1759                        alignmentHeight);
1760            }
1761        }
1762
1763        if (shouldLayout(mLogoView)) {
1764            if (isRtl) {
1765                right = layoutChildRight(mLogoView, right, collapsingMargins,
1766                        alignmentHeight);
1767            } else {
1768                left = layoutChildLeft(mLogoView, left, collapsingMargins,
1769                        alignmentHeight);
1770            }
1771        }
1772
1773        final boolean layoutTitle = shouldLayout(mTitleTextView);
1774        final boolean layoutSubtitle = shouldLayout(mSubtitleTextView);
1775        int titleHeight = 0;
1776        if (layoutTitle) {
1777            final LayoutParams lp = (LayoutParams) mTitleTextView.getLayoutParams();
1778            titleHeight += lp.topMargin + mTitleTextView.getMeasuredHeight() + lp.bottomMargin;
1779        }
1780        if (layoutSubtitle) {
1781            final LayoutParams lp = (LayoutParams) mSubtitleTextView.getLayoutParams();
1782            titleHeight += lp.topMargin + mSubtitleTextView.getMeasuredHeight() + lp.bottomMargin;
1783        }
1784
1785        if (layoutTitle || layoutSubtitle) {
1786            int titleTop;
1787            final View topChild = layoutTitle ? mTitleTextView : mSubtitleTextView;
1788            final View bottomChild = layoutSubtitle ? mSubtitleTextView : mTitleTextView;
1789            final LayoutParams toplp = (LayoutParams) topChild.getLayoutParams();
1790            final LayoutParams bottomlp = (LayoutParams) bottomChild.getLayoutParams();
1791            final boolean titleHasWidth = layoutTitle && mTitleTextView.getMeasuredWidth() > 0
1792                    || layoutSubtitle && mSubtitleTextView.getMeasuredWidth() > 0;
1793
1794            switch (mGravity & Gravity.VERTICAL_GRAVITY_MASK) {
1795                case Gravity.TOP:
1796                    titleTop = getPaddingTop() + toplp.topMargin + mTitleMarginTop;
1797                    break;
1798                default:
1799                case Gravity.CENTER_VERTICAL:
1800                    final int space = height - paddingTop - paddingBottom;
1801                    int spaceAbove = (space - titleHeight) / 2;
1802                    if (spaceAbove < toplp.topMargin + mTitleMarginTop) {
1803                        spaceAbove = toplp.topMargin + mTitleMarginTop;
1804                    } else {
1805                        final int spaceBelow = height - paddingBottom - titleHeight -
1806                                spaceAbove - paddingTop;
1807                        if (spaceBelow < toplp.bottomMargin + mTitleMarginBottom) {
1808                            spaceAbove = Math.max(0, spaceAbove -
1809                                    (bottomlp.bottomMargin + mTitleMarginBottom - spaceBelow));
1810                        }
1811                    }
1812                    titleTop = paddingTop + spaceAbove;
1813                    break;
1814                case Gravity.BOTTOM:
1815                    titleTop = height - paddingBottom - bottomlp.bottomMargin - mTitleMarginBottom -
1816                            titleHeight;
1817                    break;
1818            }
1819            if (isRtl) {
1820                final int rd = (titleHasWidth ? mTitleMarginStart : 0) - collapsingMargins[1];
1821                right -= Math.max(0, rd);
1822                collapsingMargins[1] = Math.max(0, -rd);
1823                int titleRight = right;
1824                int subtitleRight = right;
1825
1826                if (layoutTitle) {
1827                    final LayoutParams lp = (LayoutParams) mTitleTextView.getLayoutParams();
1828                    final int titleLeft = titleRight - mTitleTextView.getMeasuredWidth();
1829                    final int titleBottom = titleTop + mTitleTextView.getMeasuredHeight();
1830                    mTitleTextView.layout(titleLeft, titleTop, titleRight, titleBottom);
1831                    titleRight = titleLeft - mTitleMarginEnd;
1832                    titleTop = titleBottom + lp.bottomMargin;
1833                }
1834                if (layoutSubtitle) {
1835                    final LayoutParams lp = (LayoutParams) mSubtitleTextView.getLayoutParams();
1836                    titleTop += lp.topMargin;
1837                    final int subtitleLeft = subtitleRight - mSubtitleTextView.getMeasuredWidth();
1838                    final int subtitleBottom = titleTop + mSubtitleTextView.getMeasuredHeight();
1839                    mSubtitleTextView.layout(subtitleLeft, titleTop, subtitleRight, subtitleBottom);
1840                    subtitleRight = subtitleRight - mTitleMarginEnd;
1841                    titleTop = subtitleBottom + lp.bottomMargin;
1842                }
1843                if (titleHasWidth) {
1844                    right = Math.min(titleRight, subtitleRight);
1845                }
1846            } else {
1847                final int ld = (titleHasWidth ? mTitleMarginStart : 0) - collapsingMargins[0];
1848                left += Math.max(0, ld);
1849                collapsingMargins[0] = Math.max(0, -ld);
1850                int titleLeft = left;
1851                int subtitleLeft = left;
1852
1853                if (layoutTitle) {
1854                    final LayoutParams lp = (LayoutParams) mTitleTextView.getLayoutParams();
1855                    final int titleRight = titleLeft + mTitleTextView.getMeasuredWidth();
1856                    final int titleBottom = titleTop + mTitleTextView.getMeasuredHeight();
1857                    mTitleTextView.layout(titleLeft, titleTop, titleRight, titleBottom);
1858                    titleLeft = titleRight + mTitleMarginEnd;
1859                    titleTop = titleBottom + lp.bottomMargin;
1860                }
1861                if (layoutSubtitle) {
1862                    final LayoutParams lp = (LayoutParams) mSubtitleTextView.getLayoutParams();
1863                    titleTop += lp.topMargin;
1864                    final int subtitleRight = subtitleLeft + mSubtitleTextView.getMeasuredWidth();
1865                    final int subtitleBottom = titleTop + mSubtitleTextView.getMeasuredHeight();
1866                    mSubtitleTextView.layout(subtitleLeft, titleTop, subtitleRight, subtitleBottom);
1867                    subtitleLeft = subtitleRight + mTitleMarginEnd;
1868                    titleTop = subtitleBottom + lp.bottomMargin;
1869                }
1870                if (titleHasWidth) {
1871                    left = Math.max(titleLeft, subtitleLeft);
1872                }
1873            }
1874        }
1875
1876        // Get all remaining children sorted for layout. This is all prepared
1877        // such that absolute layout direction can be used below.
1878
1879        addCustomViewsWithGravity(mTempViews, Gravity.LEFT);
1880        final int leftViewsCount = mTempViews.size();
1881        for (int i = 0; i < leftViewsCount; i++) {
1882            left = layoutChildLeft(mTempViews.get(i), left, collapsingMargins,
1883                    alignmentHeight);
1884        }
1885
1886        addCustomViewsWithGravity(mTempViews, Gravity.RIGHT);
1887        final int rightViewsCount = mTempViews.size();
1888        for (int i = 0; i < rightViewsCount; i++) {
1889            right = layoutChildRight(mTempViews.get(i), right, collapsingMargins,
1890                    alignmentHeight);
1891        }
1892
1893        // Centered views try to center with respect to the whole bar, but views pinned
1894        // to the left or right can push the mass of centered views to one side or the other.
1895        addCustomViewsWithGravity(mTempViews, Gravity.CENTER_HORIZONTAL);
1896        final int centerViewsWidth = getViewListMeasuredWidth(mTempViews, collapsingMargins);
1897        final int parentCenter = paddingLeft + (width - paddingLeft - paddingRight) / 2;
1898        final int halfCenterViewsWidth = centerViewsWidth / 2;
1899        int centerLeft = parentCenter - halfCenterViewsWidth;
1900        final int centerRight = centerLeft + centerViewsWidth;
1901        if (centerLeft < left) {
1902            centerLeft = left;
1903        } else if (centerRight > right) {
1904            centerLeft -= centerRight - right;
1905        }
1906
1907        final int centerViewsCount = mTempViews.size();
1908        for (int i = 0; i < centerViewsCount; i++) {
1909            centerLeft = layoutChildLeft(mTempViews.get(i), centerLeft, collapsingMargins,
1910                    alignmentHeight);
1911        }
1912
1913        mTempViews.clear();
1914    }
1915
1916    private int getViewListMeasuredWidth(List<View> views, int[] collapsingMargins) {
1917        int collapseLeft = collapsingMargins[0];
1918        int collapseRight = collapsingMargins[1];
1919        int width = 0;
1920        final int count = views.size();
1921        for (int i = 0; i < count; i++) {
1922            final View v = views.get(i);
1923            final LayoutParams lp = (LayoutParams) v.getLayoutParams();
1924            final int l = lp.leftMargin - collapseLeft;
1925            final int r = lp.rightMargin - collapseRight;
1926            final int leftMargin = Math.max(0, l);
1927            final int rightMargin = Math.max(0, r);
1928            collapseLeft = Math.max(0, -l);
1929            collapseRight = Math.max(0, -r);
1930            width += leftMargin + v.getMeasuredWidth() + rightMargin;
1931        }
1932        return width;
1933    }
1934
1935    private int layoutChildLeft(View child, int left, int[] collapsingMargins,
1936            int alignmentHeight) {
1937        final LayoutParams lp = (LayoutParams) child.getLayoutParams();
1938        final int l = lp.leftMargin - collapsingMargins[0];
1939        left += Math.max(0, l);
1940        collapsingMargins[0] = Math.max(0, -l);
1941        final int top = getChildTop(child, alignmentHeight);
1942        final int childWidth = child.getMeasuredWidth();
1943        child.layout(left, top, left + childWidth, top + child.getMeasuredHeight());
1944        left += childWidth + lp.rightMargin;
1945        return left;
1946    }
1947
1948    private int layoutChildRight(View child, int right, int[] collapsingMargins,
1949            int alignmentHeight) {
1950        final LayoutParams lp = (LayoutParams) child.getLayoutParams();
1951        final int r = lp.rightMargin - collapsingMargins[1];
1952        right -= Math.max(0, r);
1953        collapsingMargins[1] = Math.max(0, -r);
1954        final int top = getChildTop(child, alignmentHeight);
1955        final int childWidth = child.getMeasuredWidth();
1956        child.layout(right - childWidth, top, right, top + child.getMeasuredHeight());
1957        right -= childWidth + lp.leftMargin;
1958        return right;
1959    }
1960
1961    private int getChildTop(View child, int alignmentHeight) {
1962        final LayoutParams lp = (LayoutParams) child.getLayoutParams();
1963        final int childHeight = child.getMeasuredHeight();
1964        final int alignmentOffset = alignmentHeight > 0 ? (childHeight - alignmentHeight) / 2 : 0;
1965        switch (getChildVerticalGravity(lp.gravity)) {
1966            case Gravity.TOP:
1967                return getPaddingTop() - alignmentOffset;
1968
1969            case Gravity.BOTTOM:
1970                return getHeight() - getPaddingBottom() - childHeight
1971                        - lp.bottomMargin - alignmentOffset;
1972
1973            default:
1974            case Gravity.CENTER_VERTICAL:
1975                final int paddingTop = getPaddingTop();
1976                final int paddingBottom = getPaddingBottom();
1977                final int height = getHeight();
1978                final int space = height - paddingTop - paddingBottom;
1979                int spaceAbove = (space - childHeight) / 2;
1980                if (spaceAbove < lp.topMargin) {
1981                    spaceAbove = lp.topMargin;
1982                } else {
1983                    final int spaceBelow = height - paddingBottom - childHeight -
1984                            spaceAbove - paddingTop;
1985                    if (spaceBelow < lp.bottomMargin) {
1986                        spaceAbove = Math.max(0, spaceAbove - (lp.bottomMargin - spaceBelow));
1987                    }
1988                }
1989                return paddingTop + spaceAbove;
1990        }
1991    }
1992
1993    private int getChildVerticalGravity(int gravity) {
1994        final int vgrav = gravity & Gravity.VERTICAL_GRAVITY_MASK;
1995        switch (vgrav) {
1996            case Gravity.TOP:
1997            case Gravity.BOTTOM:
1998            case Gravity.CENTER_VERTICAL:
1999                return vgrav;
2000            default:
2001                return mGravity & Gravity.VERTICAL_GRAVITY_MASK;
2002        }
2003    }
2004
2005    /**
2006     * Prepare a list of non-SYSTEM child views. If the layout direction is RTL
2007     * this will be in reverse child order.
2008     *
2009     * @param views List to populate. It will be cleared before use.
2010     * @param gravity Horizontal gravity to match against
2011     */
2012    private void addCustomViewsWithGravity(List<View> views, int gravity) {
2013        final boolean isRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
2014        final int childCount = getChildCount();
2015        final int absGrav = Gravity.getAbsoluteGravity(gravity, getLayoutDirection());
2016
2017        views.clear();
2018
2019        if (isRtl) {
2020            for (int i = childCount - 1; i >= 0; i--) {
2021                final View child = getChildAt(i);
2022                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
2023                if (lp.mViewType == LayoutParams.CUSTOM && shouldLayout(child) &&
2024                        getChildHorizontalGravity(lp.gravity) == absGrav) {
2025                    views.add(child);
2026                }
2027            }
2028        } else {
2029            for (int i = 0; i < childCount; i++) {
2030                final View child = getChildAt(i);
2031                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
2032                if (lp.mViewType == LayoutParams.CUSTOM && shouldLayout(child) &&
2033                        getChildHorizontalGravity(lp.gravity) == absGrav) {
2034                    views.add(child);
2035                }
2036            }
2037        }
2038    }
2039
2040    private int getChildHorizontalGravity(int gravity) {
2041        final int ld = getLayoutDirection();
2042        final int absGrav = Gravity.getAbsoluteGravity(gravity, ld);
2043        final int hGrav = absGrav & Gravity.HORIZONTAL_GRAVITY_MASK;
2044        switch (hGrav) {
2045            case Gravity.LEFT:
2046            case Gravity.RIGHT:
2047            case Gravity.CENTER_HORIZONTAL:
2048                return hGrav;
2049            default:
2050                return ld == LAYOUT_DIRECTION_RTL ? Gravity.RIGHT : Gravity.LEFT;
2051        }
2052    }
2053
2054    private boolean shouldLayout(View view) {
2055        return view != null && view.getParent() == this && view.getVisibility() != GONE;
2056    }
2057
2058    private int getHorizontalMargins(View v) {
2059        final MarginLayoutParams mlp = (MarginLayoutParams) v.getLayoutParams();
2060        return mlp.getMarginStart() + mlp.getMarginEnd();
2061    }
2062
2063    private int getVerticalMargins(View v) {
2064        final MarginLayoutParams mlp = (MarginLayoutParams) v.getLayoutParams();
2065        return mlp.topMargin + mlp.bottomMargin;
2066    }
2067
2068    @Override
2069    public LayoutParams generateLayoutParams(AttributeSet attrs) {
2070        return new LayoutParams(getContext(), attrs);
2071    }
2072
2073    @Override
2074    protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
2075        if (p instanceof LayoutParams) {
2076            return new LayoutParams((LayoutParams) p);
2077        } else if (p instanceof ActionBar.LayoutParams) {
2078            return new LayoutParams((ActionBar.LayoutParams) p);
2079        } else if (p instanceof MarginLayoutParams) {
2080            return new LayoutParams((MarginLayoutParams) p);
2081        } else {
2082            return new LayoutParams(p);
2083        }
2084    }
2085
2086    @Override
2087    protected LayoutParams generateDefaultLayoutParams() {
2088        return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
2089    }
2090
2091    @Override
2092    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
2093        return super.checkLayoutParams(p) && p instanceof LayoutParams;
2094    }
2095
2096    private static boolean isCustomView(View child) {
2097        return ((LayoutParams) child.getLayoutParams()).mViewType == LayoutParams.CUSTOM;
2098    }
2099
2100    /** @hide */
2101    public DecorToolbar getWrapper() {
2102        if (mWrapper == null) {
2103            mWrapper = new ToolbarWidgetWrapper(this, true);
2104        }
2105        return mWrapper;
2106    }
2107
2108    void removeChildrenForExpandedActionView() {
2109        final int childCount = getChildCount();
2110        // Go backwards since we're removing from the list
2111        for (int i = childCount - 1; i >= 0; i--) {
2112            final View child = getChildAt(i);
2113            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
2114            if (lp.mViewType != LayoutParams.EXPANDED && child != mMenuView) {
2115                removeViewAt(i);
2116                mHiddenViews.add(child);
2117            }
2118        }
2119    }
2120
2121    void addChildrenForExpandedActionView() {
2122        final int count = mHiddenViews.size();
2123        // Re-add in reverse order since we removed in reverse order
2124        for (int i = count - 1; i >= 0; i--) {
2125            addView(mHiddenViews.get(i));
2126        }
2127        mHiddenViews.clear();
2128    }
2129
2130    private boolean isChildOrHidden(View child) {
2131        return child.getParent() == this || mHiddenViews.contains(child);
2132    }
2133
2134    /**
2135     * Force the toolbar to collapse to zero-height during measurement if
2136     * it could be considered "empty" (no visible elements with nonzero measured size)
2137     * @hide
2138     */
2139    public void setCollapsible(boolean collapsible) {
2140        mCollapsible = collapsible;
2141        requestLayout();
2142    }
2143
2144    /**
2145     * Must be called before the menu is accessed
2146     * @hide
2147     */
2148    public void setMenuCallbacks(MenuPresenter.Callback pcb, MenuBuilder.Callback mcb) {
2149        mActionMenuPresenterCallback = pcb;
2150        mMenuBuilderCallback = mcb;
2151        if (mMenuView != null) {
2152            mMenuView.setMenuCallbacks(pcb, mcb);
2153        }
2154    }
2155
2156    private void ensureContentInsets() {
2157        if (mContentInsets == null) {
2158            mContentInsets = new RtlSpacingHelper();
2159        }
2160    }
2161
2162    /**
2163     * Accessor to enable LayoutLib to get ActionMenuPresenter directly.
2164     */
2165    ActionMenuPresenter getOuterActionMenuPresenter() {
2166        return mOuterActionMenuPresenter;
2167    }
2168
2169    Context getPopupContext() {
2170        return mPopupContext;
2171    }
2172
2173    /**
2174     * Interface responsible for receiving menu item click events if the items themselves
2175     * do not have individual item click listeners.
2176     */
2177    public interface OnMenuItemClickListener {
2178        /**
2179         * This method will be invoked when a menu item is clicked if the item itself did
2180         * not already handle the event.
2181         *
2182         * @param item {@link MenuItem} that was clicked
2183         * @return <code>true</code> if the event was handled, <code>false</code> otherwise.
2184         */
2185        public boolean onMenuItemClick(MenuItem item);
2186    }
2187
2188    /**
2189     * Layout information for child views of Toolbars.
2190     *
2191     * <p>Toolbar.LayoutParams extends ActionBar.LayoutParams for compatibility with existing
2192     * ActionBar API. See {@link android.app.Activity#setActionBar(Toolbar) Activity.setActionBar}
2193     * for more info on how to use a Toolbar as your Activity's ActionBar.</p>
2194     *
2195     * @attr ref android.R.styleable#Toolbar_LayoutParams_layout_gravity
2196     */
2197    public static class LayoutParams extends ActionBar.LayoutParams {
2198        static final int CUSTOM = 0;
2199        static final int SYSTEM = 1;
2200        static final int EXPANDED = 2;
2201
2202        int mViewType = CUSTOM;
2203
2204        public LayoutParams(@NonNull Context c, AttributeSet attrs) {
2205            super(c, attrs);
2206        }
2207
2208        public LayoutParams(int width, int height) {
2209            super(width, height);
2210            this.gravity = Gravity.CENTER_VERTICAL | Gravity.START;
2211        }
2212
2213        public LayoutParams(int width, int height, int gravity) {
2214            super(width, height);
2215            this.gravity = gravity;
2216        }
2217
2218        public LayoutParams(int gravity) {
2219            this(WRAP_CONTENT, MATCH_PARENT, gravity);
2220        }
2221
2222        public LayoutParams(LayoutParams source) {
2223            super(source);
2224
2225            mViewType = source.mViewType;
2226        }
2227
2228        public LayoutParams(ActionBar.LayoutParams source) {
2229            super(source);
2230        }
2231
2232        public LayoutParams(MarginLayoutParams source) {
2233            super(source);
2234            // ActionBar.LayoutParams doesn't have a MarginLayoutParams constructor.
2235            // Fake it here and copy over the relevant data.
2236            copyMarginsFrom(source);
2237        }
2238
2239        public LayoutParams(ViewGroup.LayoutParams source) {
2240            super(source);
2241        }
2242    }
2243
2244    static class SavedState extends BaseSavedState {
2245        public int expandedMenuItemId;
2246        public boolean isOverflowOpen;
2247
2248        public SavedState(Parcel source) {
2249            super(source);
2250            expandedMenuItemId = source.readInt();
2251            isOverflowOpen = source.readInt() != 0;
2252        }
2253
2254        public SavedState(Parcelable superState) {
2255            super(superState);
2256        }
2257
2258        @Override
2259        public void writeToParcel(Parcel out, int flags) {
2260            super.writeToParcel(out, flags);
2261            out.writeInt(expandedMenuItemId);
2262            out.writeInt(isOverflowOpen ? 1 : 0);
2263        }
2264
2265        public static final Creator<SavedState> CREATOR = new Creator<SavedState>() {
2266
2267            @Override
2268            public SavedState createFromParcel(Parcel source) {
2269                return new SavedState(source);
2270            }
2271
2272            @Override
2273            public SavedState[] newArray(int size) {
2274                return new SavedState[size];
2275            }
2276        };
2277    }
2278
2279    private class ExpandedActionViewMenuPresenter implements MenuPresenter {
2280        MenuBuilder mMenu;
2281        MenuItemImpl mCurrentExpandedItem;
2282
2283        @Override
2284        public void initForMenu(@NonNull Context context, @Nullable MenuBuilder menu) {
2285            // Clear the expanded action view when menus change.
2286            if (mMenu != null && mCurrentExpandedItem != null) {
2287                mMenu.collapseItemActionView(mCurrentExpandedItem);
2288            }
2289            mMenu = menu;
2290        }
2291
2292        @Override
2293        public MenuView getMenuView(ViewGroup root) {
2294            return null;
2295        }
2296
2297        @Override
2298        public void updateMenuView(boolean cleared) {
2299            // Make sure the expanded item we have is still there.
2300            if (mCurrentExpandedItem != null) {
2301                boolean found = false;
2302
2303                if (mMenu != null) {
2304                    final int count = mMenu.size();
2305                    for (int i = 0; i < count; i++) {
2306                        final MenuItem item = mMenu.getItem(i);
2307                        if (item == mCurrentExpandedItem) {
2308                            found = true;
2309                            break;
2310                        }
2311                    }
2312                }
2313
2314                if (!found) {
2315                    // The item we had expanded disappeared. Collapse.
2316                    collapseItemActionView(mMenu, mCurrentExpandedItem);
2317                }
2318            }
2319        }
2320
2321        @Override
2322        public void setCallback(Callback cb) {
2323        }
2324
2325        @Override
2326        public boolean onSubMenuSelected(SubMenuBuilder subMenu) {
2327            return false;
2328        }
2329
2330        @Override
2331        public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
2332        }
2333
2334        @Override
2335        public boolean flagActionItems() {
2336            return false;
2337        }
2338
2339        @Override
2340        public boolean expandItemActionView(MenuBuilder menu, MenuItemImpl item) {
2341            ensureCollapseButtonView();
2342            if (mCollapseButtonView.getParent() != Toolbar.this) {
2343                addView(mCollapseButtonView);
2344            }
2345            mExpandedActionView = item.getActionView();
2346            mCurrentExpandedItem = item;
2347            if (mExpandedActionView.getParent() != Toolbar.this) {
2348                final LayoutParams lp = generateDefaultLayoutParams();
2349                lp.gravity = Gravity.START | (mButtonGravity & Gravity.VERTICAL_GRAVITY_MASK);
2350                lp.mViewType = LayoutParams.EXPANDED;
2351                mExpandedActionView.setLayoutParams(lp);
2352                addView(mExpandedActionView);
2353            }
2354
2355            removeChildrenForExpandedActionView();
2356            requestLayout();
2357            item.setActionViewExpanded(true);
2358
2359            if (mExpandedActionView instanceof CollapsibleActionView) {
2360                ((CollapsibleActionView) mExpandedActionView).onActionViewExpanded();
2361            }
2362
2363            return true;
2364        }
2365
2366        @Override
2367        public boolean collapseItemActionView(MenuBuilder menu, MenuItemImpl item) {
2368            // Do this before detaching the actionview from the hierarchy, in case
2369            // it needs to dismiss the soft keyboard, etc.
2370            if (mExpandedActionView instanceof CollapsibleActionView) {
2371                ((CollapsibleActionView) mExpandedActionView).onActionViewCollapsed();
2372            }
2373
2374            removeView(mExpandedActionView);
2375            removeView(mCollapseButtonView);
2376            mExpandedActionView = null;
2377
2378            addChildrenForExpandedActionView();
2379            mCurrentExpandedItem = null;
2380            requestLayout();
2381            item.setActionViewExpanded(false);
2382
2383            return true;
2384        }
2385
2386        @Override
2387        public int getId() {
2388            return 0;
2389        }
2390
2391        @Override
2392        public Parcelable onSaveInstanceState() {
2393            return null;
2394        }
2395
2396        @Override
2397        public void onRestoreInstanceState(Parcelable state) {
2398        }
2399    }
2400}
2401