1 /*
2  * Copyright (C) 2010 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.internal.widget;
18 
19 import android.animation.LayoutTransition;
20 import android.app.ActionBar;
21 import android.content.Context;
22 import android.content.res.Configuration;
23 import android.content.res.TypedArray;
24 import android.graphics.drawable.Drawable;
25 import android.os.Parcel;
26 import android.os.Parcelable;
27 import android.text.Layout;
28 import android.text.TextUtils;
29 import android.util.AttributeSet;
30 import android.view.CollapsibleActionView;
31 import android.view.Gravity;
32 import android.view.LayoutInflater;
33 import android.view.Menu;
34 import android.view.MenuItem;
35 import android.view.MotionEvent;
36 import android.view.View;
37 import android.view.ViewGroup;
38 import android.view.ViewParent;
39 import android.view.Window;
40 import android.view.accessibility.AccessibilityEvent;
41 import android.widget.ActionMenuPresenter;
42 import android.widget.ActionMenuView;
43 import android.widget.AdapterView;
44 import android.widget.FrameLayout;
45 import android.widget.ImageView;
46 import android.widget.LinearLayout;
47 import android.widget.ProgressBar;
48 import android.widget.Spinner;
49 import android.widget.SpinnerAdapter;
50 import android.widget.TextView;
51 import com.android.internal.R;
52 import com.android.internal.view.menu.ActionMenuItem;
53 import com.android.internal.view.menu.MenuBuilder;
54 import com.android.internal.view.menu.MenuItemImpl;
55 import com.android.internal.view.menu.MenuPresenter;
56 import com.android.internal.view.menu.MenuView;
57 import com.android.internal.view.menu.SubMenuBuilder;
58 
59 /**
60  * @hide
61  */
62 public class ActionBarView extends AbsActionBarView implements DecorToolbar {
63     private static final String TAG = "ActionBarView";
64 
65     /**
66      * Display options applied by default
67      */
68     public static final int DISPLAY_DEFAULT = 0;
69 
70     /**
71      * Display options that require re-layout as opposed to a simple invalidate
72      */
73     private static final int DISPLAY_RELAYOUT_MASK =
74             ActionBar.DISPLAY_SHOW_HOME |
75             ActionBar.DISPLAY_USE_LOGO |
76             ActionBar.DISPLAY_HOME_AS_UP |
77             ActionBar.DISPLAY_SHOW_CUSTOM |
78             ActionBar.DISPLAY_SHOW_TITLE |
79             ActionBar.DISPLAY_TITLE_MULTIPLE_LINES;
80 
81     private static final int DEFAULT_CUSTOM_GRAVITY = Gravity.START | Gravity.CENTER_VERTICAL;
82 
83     private int mNavigationMode;
84     private int mDisplayOptions = -1;
85     private CharSequence mTitle;
86     private CharSequence mSubtitle;
87     private Drawable mIcon;
88     private Drawable mLogo;
89     private CharSequence mHomeDescription;
90     private int mHomeDescriptionRes;
91 
92     private HomeView mHomeLayout;
93     private HomeView mExpandedHomeLayout;
94     private LinearLayout mTitleLayout;
95     private TextView mTitleView;
96     private TextView mSubtitleView;
97     private ViewGroup mUpGoerFive;
98 
99     private Spinner mSpinner;
100     private LinearLayout mListNavLayout;
101     private ScrollingTabContainerView mTabScrollView;
102     private View mCustomNavView;
103     private ProgressBar mProgressView;
104     private ProgressBar mIndeterminateProgressView;
105 
106     private int mProgressBarPadding;
107     private int mItemPadding;
108 
109     private final int mTitleStyleRes;
110     private final int mSubtitleStyleRes;
111     private final int mProgressStyle;
112     private final int mIndeterminateProgressStyle;
113 
114     private boolean mUserTitle;
115     private boolean mIncludeTabs;
116     private boolean mIsCollapsible;
117     private boolean mWasHomeEnabled; // Was it enabled before action view expansion?
118 
119     private MenuBuilder mOptionsMenu;
120     private boolean mMenuPrepared;
121 
122     private ActionBarContextView mContextView;
123 
124     private ActionMenuItem mLogoNavItem;
125 
126     private SpinnerAdapter mSpinnerAdapter;
127     private AdapterView.OnItemSelectedListener mNavItemSelectedListener;
128 
129     private Runnable mTabSelector;
130 
131     private ExpandedActionViewMenuPresenter mExpandedMenuPresenter;
132     View mExpandedActionView;
133     private int mDefaultUpDescription = R.string.action_bar_up_description;
134 
135     Window.Callback mWindowCallback;
136 
137     private final OnClickListener mExpandedActionViewUpListener = new OnClickListener() {
138         @Override
139         public void onClick(View v) {
140             final MenuItemImpl item = mExpandedMenuPresenter.mCurrentExpandedItem;
141             if (item != null) {
142                 item.collapseActionView();
143             }
144         }
145     };
146 
147     private final OnClickListener mUpClickListener = new OnClickListener() {
148         public void onClick(View v) {
149             if (mMenuPrepared) {
150                 // Only invoke the window callback if the options menu has been initialized.
151                 mWindowCallback.onMenuItemSelected(Window.FEATURE_OPTIONS_PANEL, mLogoNavItem);
152             }
153         }
154     };
155 
ActionBarView(Context context, AttributeSet attrs)156     public ActionBarView(Context context, AttributeSet attrs) {
157         super(context, attrs);
158 
159         // Background is always provided by the container.
160         setBackgroundResource(0);
161 
162         TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ActionBar,
163                 com.android.internal.R.attr.actionBarStyle, 0);
164 
165         mNavigationMode = a.getInt(R.styleable.ActionBar_navigationMode,
166                 ActionBar.NAVIGATION_MODE_STANDARD);
167         mTitle = a.getText(R.styleable.ActionBar_title);
168         mSubtitle = a.getText(R.styleable.ActionBar_subtitle);
169         mLogo = a.getDrawable(R.styleable.ActionBar_logo);
170         mIcon = a.getDrawable(R.styleable.ActionBar_icon);
171 
172         final LayoutInflater inflater = LayoutInflater.from(context);
173 
174         final int homeResId = a.getResourceId(
175                 com.android.internal.R.styleable.ActionBar_homeLayout,
176                 com.android.internal.R.layout.action_bar_home);
177 
178         mUpGoerFive = (ViewGroup) inflater.inflate(
179                 com.android.internal.R.layout.action_bar_up_container, this, false);
180         mHomeLayout = (HomeView) inflater.inflate(homeResId, mUpGoerFive, false);
181 
182         mExpandedHomeLayout = (HomeView) inflater.inflate(homeResId, mUpGoerFive, false);
183         mExpandedHomeLayout.setShowUp(true);
184         mExpandedHomeLayout.setOnClickListener(mExpandedActionViewUpListener);
185         mExpandedHomeLayout.setContentDescription(getResources().getText(
186                 mDefaultUpDescription));
187 
188         // This needs to highlight/be focusable on its own.
189         // TODO: Clean up the handoff between expanded/normal.
190         final Drawable upBackground = mUpGoerFive.getBackground();
191         if (upBackground != null) {
192             mExpandedHomeLayout.setBackground(upBackground.getConstantState().newDrawable());
193         }
194         mExpandedHomeLayout.setEnabled(true);
195         mExpandedHomeLayout.setFocusable(true);
196 
197         mTitleStyleRes = a.getResourceId(R.styleable.ActionBar_titleTextStyle, 0);
198         mSubtitleStyleRes = a.getResourceId(R.styleable.ActionBar_subtitleTextStyle, 0);
199         mProgressStyle = a.getResourceId(R.styleable.ActionBar_progressBarStyle, 0);
200         mIndeterminateProgressStyle = a.getResourceId(
201                 R.styleable.ActionBar_indeterminateProgressStyle, 0);
202 
203         mProgressBarPadding = a.getDimensionPixelOffset(R.styleable.ActionBar_progressBarPadding, 0);
204         mItemPadding = a.getDimensionPixelOffset(R.styleable.ActionBar_itemPadding, 0);
205 
206         setDisplayOptions(a.getInt(R.styleable.ActionBar_displayOptions, DISPLAY_DEFAULT));
207 
208         final int customNavId = a.getResourceId(R.styleable.ActionBar_customNavigationLayout, 0);
209         if (customNavId != 0) {
210             mCustomNavView = (View) inflater.inflate(customNavId, this, false);
211             mNavigationMode = ActionBar.NAVIGATION_MODE_STANDARD;
212             setDisplayOptions(mDisplayOptions | ActionBar.DISPLAY_SHOW_CUSTOM);
213         }
214 
215         mContentHeight = a.getLayoutDimension(R.styleable.ActionBar_height, 0);
216 
217         a.recycle();
218 
219         mLogoNavItem = new ActionMenuItem(context, 0, android.R.id.home, 0, 0, mTitle);
220 
221         mUpGoerFive.setOnClickListener(mUpClickListener);
222         mUpGoerFive.setClickable(true);
223         mUpGoerFive.setFocusable(true);
224 
225         if (getImportantForAccessibility() == View.IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
226             setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
227         }
228     }
229 
230     @Override
onConfigurationChanged(Configuration newConfig)231     protected void onConfigurationChanged(Configuration newConfig) {
232         super.onConfigurationChanged(newConfig);
233 
234         mTitleView = null;
235         mSubtitleView = null;
236         if (mTitleLayout != null && mTitleLayout.getParent() == mUpGoerFive) {
237             mUpGoerFive.removeView(mTitleLayout);
238         }
239         mTitleLayout = null;
240         if ((mDisplayOptions & ActionBar.DISPLAY_SHOW_TITLE) != 0) {
241             initTitle();
242         }
243 
244         if (mHomeDescriptionRes != 0) {
245             setNavigationContentDescription(mHomeDescriptionRes);
246         }
247 
248         if (mTabScrollView != null && mIncludeTabs) {
249             ViewGroup.LayoutParams lp = mTabScrollView.getLayoutParams();
250             if (lp != null) {
251                 lp.width = LayoutParams.WRAP_CONTENT;
252                 lp.height = LayoutParams.MATCH_PARENT;
253             }
254             mTabScrollView.setAllowCollapse(true);
255         }
256     }
257 
258     /**
259      * Set the window callback used to invoke menu items; used for dispatching home button presses.
260      * @param cb Window callback to dispatch to
261      */
setWindowCallback(Window.Callback cb)262     public void setWindowCallback(Window.Callback cb) {
263         mWindowCallback = cb;
264     }
265 
266     @Override
onDetachedFromWindow()267     public void onDetachedFromWindow() {
268         super.onDetachedFromWindow();
269         removeCallbacks(mTabSelector);
270         if (mActionMenuPresenter != null) {
271             mActionMenuPresenter.hideOverflowMenu();
272             mActionMenuPresenter.hideSubMenus();
273         }
274     }
275 
276     @Override
shouldDelayChildPressedState()277     public boolean shouldDelayChildPressedState() {
278         return false;
279     }
280 
initProgress()281     public void initProgress() {
282         mProgressView = new ProgressBar(mContext, null, 0, mProgressStyle);
283         mProgressView.setId(R.id.progress_horizontal);
284         mProgressView.setMax(10000);
285         mProgressView.setVisibility(GONE);
286         addView(mProgressView);
287     }
288 
initIndeterminateProgress()289     public void initIndeterminateProgress() {
290         mIndeterminateProgressView = new ProgressBar(mContext, null, 0,
291                 mIndeterminateProgressStyle);
292         mIndeterminateProgressView.setId(R.id.progress_circular);
293         mIndeterminateProgressView.setVisibility(GONE);
294         addView(mIndeterminateProgressView);
295     }
296 
297     @Override
setSplitToolbar(boolean splitActionBar)298     public void setSplitToolbar(boolean splitActionBar) {
299         if (mSplitActionBar != splitActionBar) {
300             if (mMenuView != null) {
301                 final ViewGroup oldParent = (ViewGroup) mMenuView.getParent();
302                 if (oldParent != null) {
303                     oldParent.removeView(mMenuView);
304                 }
305                 if (splitActionBar) {
306                     if (mSplitView != null) {
307                         mSplitView.addView(mMenuView);
308                     }
309                     mMenuView.getLayoutParams().width = LayoutParams.MATCH_PARENT;
310                 } else {
311                     addView(mMenuView);
312                     mMenuView.getLayoutParams().width = LayoutParams.WRAP_CONTENT;
313                 }
314                 mMenuView.requestLayout();
315             }
316             if (mSplitView != null) {
317                 mSplitView.setVisibility(splitActionBar ? VISIBLE : GONE);
318             }
319 
320             if (mActionMenuPresenter != null) {
321                 if (!splitActionBar) {
322                     mActionMenuPresenter.setExpandedActionViewsExclusive(
323                             getResources().getBoolean(
324                                     com.android.internal.R.bool.action_bar_expanded_action_views_exclusive));
325                 } else {
326                     mActionMenuPresenter.setExpandedActionViewsExclusive(false);
327                     // Allow full screen width in split mode.
328                     mActionMenuPresenter.setWidthLimit(
329                             getContext().getResources().getDisplayMetrics().widthPixels, true);
330                     // No limit to the item count; use whatever will fit.
331                     mActionMenuPresenter.setItemLimit(Integer.MAX_VALUE);
332                 }
333             }
334             super.setSplitToolbar(splitActionBar);
335         }
336     }
337 
isSplit()338     public boolean isSplit() {
339         return mSplitActionBar;
340     }
341 
canSplit()342     public boolean canSplit() {
343         return true;
344     }
345 
hasEmbeddedTabs()346     public boolean hasEmbeddedTabs() {
347         return mIncludeTabs;
348     }
349 
350     @Override
setEmbeddedTabView(ScrollingTabContainerView tabs)351     public void setEmbeddedTabView(ScrollingTabContainerView tabs) {
352         if (mTabScrollView != null) {
353             removeView(mTabScrollView);
354         }
355         mTabScrollView = tabs;
356         mIncludeTabs = tabs != null;
357         if (mIncludeTabs && mNavigationMode == ActionBar.NAVIGATION_MODE_TABS) {
358             addView(mTabScrollView);
359             ViewGroup.LayoutParams lp = mTabScrollView.getLayoutParams();
360             lp.width = LayoutParams.WRAP_CONTENT;
361             lp.height = LayoutParams.MATCH_PARENT;
362             tabs.setAllowCollapse(true);
363         }
364     }
365 
setMenuPrepared()366     public void setMenuPrepared() {
367         mMenuPrepared = true;
368     }
369 
setMenu(Menu menu, MenuPresenter.Callback cb)370     public void setMenu(Menu menu, MenuPresenter.Callback cb) {
371         if (menu == mOptionsMenu) return;
372 
373         if (mOptionsMenu != null) {
374             mOptionsMenu.removeMenuPresenter(mActionMenuPresenter);
375             mOptionsMenu.removeMenuPresenter(mExpandedMenuPresenter);
376         }
377 
378         MenuBuilder builder = (MenuBuilder) menu;
379         mOptionsMenu = builder;
380         if (mMenuView != null) {
381             final ViewGroup oldParent = (ViewGroup) mMenuView.getParent();
382             if (oldParent != null) {
383                 oldParent.removeView(mMenuView);
384             }
385         }
386         if (mActionMenuPresenter == null) {
387             mActionMenuPresenter = new ActionMenuPresenter(mContext);
388             mActionMenuPresenter.setCallback(cb);
389             mActionMenuPresenter.setId(com.android.internal.R.id.action_menu_presenter);
390             mExpandedMenuPresenter = new ExpandedActionViewMenuPresenter();
391         }
392 
393         ActionMenuView menuView;
394         final LayoutParams layoutParams = new LayoutParams(LayoutParams.WRAP_CONTENT,
395                 LayoutParams.MATCH_PARENT);
396         if (!mSplitActionBar) {
397             mActionMenuPresenter.setExpandedActionViewsExclusive(
398                     getResources().getBoolean(
399                     com.android.internal.R.bool.action_bar_expanded_action_views_exclusive));
400             configPresenters(builder);
401             menuView = (ActionMenuView) mActionMenuPresenter.getMenuView(this);
402             final ViewGroup oldParent = (ViewGroup) menuView.getParent();
403             if (oldParent != null && oldParent != this) {
404                 oldParent.removeView(menuView);
405             }
406             addView(menuView, layoutParams);
407         } else {
408             mActionMenuPresenter.setExpandedActionViewsExclusive(false);
409             // Allow full screen width in split mode.
410             mActionMenuPresenter.setWidthLimit(
411                     getContext().getResources().getDisplayMetrics().widthPixels, true);
412             // No limit to the item count; use whatever will fit.
413             mActionMenuPresenter.setItemLimit(Integer.MAX_VALUE);
414             // Span the whole width
415             layoutParams.width = LayoutParams.MATCH_PARENT;
416             layoutParams.height = LayoutParams.WRAP_CONTENT;
417             configPresenters(builder);
418             menuView = (ActionMenuView) mActionMenuPresenter.getMenuView(this);
419             if (mSplitView != null) {
420                 final ViewGroup oldParent = (ViewGroup) menuView.getParent();
421                 if (oldParent != null && oldParent != mSplitView) {
422                     oldParent.removeView(menuView);
423                 }
424                 menuView.setVisibility(getAnimatedVisibility());
425                 mSplitView.addView(menuView, layoutParams);
426             } else {
427                 // We'll add this later if we missed it this time.
428                 menuView.setLayoutParams(layoutParams);
429             }
430         }
431         mMenuView = menuView;
432     }
433 
configPresenters(MenuBuilder builder)434     private void configPresenters(MenuBuilder builder) {
435         if (builder != null) {
436             builder.addMenuPresenter(mActionMenuPresenter, mPopupContext);
437             builder.addMenuPresenter(mExpandedMenuPresenter, mPopupContext);
438         } else {
439             mActionMenuPresenter.initForMenu(mPopupContext, null);
440             mExpandedMenuPresenter.initForMenu(mPopupContext, null);
441             mActionMenuPresenter.updateMenuView(true);
442             mExpandedMenuPresenter.updateMenuView(true);
443         }
444     }
445 
hasExpandedActionView()446     public boolean hasExpandedActionView() {
447         return mExpandedMenuPresenter != null &&
448                 mExpandedMenuPresenter.mCurrentExpandedItem != null;
449     }
450 
collapseActionView()451     public void collapseActionView() {
452         final MenuItemImpl item = mExpandedMenuPresenter == null ? null :
453                 mExpandedMenuPresenter.mCurrentExpandedItem;
454         if (item != null) {
455             item.collapseActionView();
456         }
457     }
458 
setCustomView(View view)459     public void setCustomView(View view) {
460         final boolean showCustom = (mDisplayOptions & ActionBar.DISPLAY_SHOW_CUSTOM) != 0;
461         if (mCustomNavView != null && showCustom) {
462             removeView(mCustomNavView);
463         }
464         mCustomNavView = view;
465         if (mCustomNavView != null && showCustom) {
466             addView(mCustomNavView);
467         }
468     }
469 
getTitle()470     public CharSequence getTitle() {
471         return mTitle;
472     }
473 
474     /**
475      * Set the action bar title. This will always replace or override window titles.
476      * @param title Title to set
477      *
478      * @see #setWindowTitle(CharSequence)
479      */
setTitle(CharSequence title)480     public void setTitle(CharSequence title) {
481         mUserTitle = true;
482         setTitleImpl(title);
483     }
484 
485     /**
486      * Set the window title. A window title will always be replaced or overridden by a user title.
487      * @param title Title to set
488      *
489      * @see #setTitle(CharSequence)
490      */
setWindowTitle(CharSequence title)491     public void setWindowTitle(CharSequence title) {
492         if (!mUserTitle) {
493             setTitleImpl(title);
494         }
495     }
496 
setTitleImpl(CharSequence title)497     private void setTitleImpl(CharSequence title) {
498         mTitle = title;
499         if (mTitleView != null) {
500             mTitleView.setText(title);
501             final boolean visible = mExpandedActionView == null &&
502                     (mDisplayOptions & ActionBar.DISPLAY_SHOW_TITLE) != 0 &&
503                     (!TextUtils.isEmpty(mTitle) || !TextUtils.isEmpty(mSubtitle));
504             mTitleLayout.setVisibility(visible ? VISIBLE : GONE);
505         }
506         if (mLogoNavItem != null) {
507             mLogoNavItem.setTitle(title);
508         }
509         updateHomeAccessibility(mUpGoerFive.isEnabled());
510     }
511 
getSubtitle()512     public CharSequence getSubtitle() {
513         return mSubtitle;
514     }
515 
setSubtitle(CharSequence subtitle)516     public void setSubtitle(CharSequence subtitle) {
517         mSubtitle = subtitle;
518         if (mSubtitleView != null) {
519             mSubtitleView.setText(subtitle);
520             mSubtitleView.setVisibility(subtitle != null ? VISIBLE : GONE);
521             final boolean visible = mExpandedActionView == null &&
522                     (mDisplayOptions & ActionBar.DISPLAY_SHOW_TITLE) != 0 &&
523                     (!TextUtils.isEmpty(mTitle) || !TextUtils.isEmpty(mSubtitle));
524             mTitleLayout.setVisibility(visible ? VISIBLE : GONE);
525         }
526         updateHomeAccessibility(mUpGoerFive.isEnabled());
527     }
528 
setHomeButtonEnabled(boolean enable)529     public void setHomeButtonEnabled(boolean enable) {
530         setHomeButtonEnabled(enable, true);
531     }
532 
setHomeButtonEnabled(boolean enable, boolean recordState)533     private void setHomeButtonEnabled(boolean enable, boolean recordState) {
534         if (recordState) {
535             mWasHomeEnabled = enable;
536         }
537 
538         if (mExpandedActionView != null) {
539             // There's an action view currently showing and we want to keep the state
540             // configured for the action view at the moment. If we needed to record the
541             // new state for later we will have done so above.
542             return;
543         }
544 
545         mUpGoerFive.setEnabled(enable);
546         mUpGoerFive.setFocusable(enable);
547         // Make sure the home button has an accurate content description for accessibility.
548         updateHomeAccessibility(enable);
549     }
550 
updateHomeAccessibility(boolean homeEnabled)551     private void updateHomeAccessibility(boolean homeEnabled) {
552         if (!homeEnabled) {
553             mUpGoerFive.setContentDescription(null);
554             mUpGoerFive.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
555         } else {
556             mUpGoerFive.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_AUTO);
557             mUpGoerFive.setContentDescription(buildHomeContentDescription());
558         }
559     }
560 
561     /**
562      * Compose a content description for the Home/Up affordance.
563      *
564      * <p>As this encompasses the icon/logo, title and subtitle all in one, we need
565      * a description for the whole wad of stuff that can be localized properly.</p>
566      */
buildHomeContentDescription()567     private CharSequence buildHomeContentDescription() {
568         final CharSequence homeDesc;
569         if (mHomeDescription != null) {
570             homeDesc = mHomeDescription;
571         } else {
572             if ((mDisplayOptions & ActionBar.DISPLAY_HOME_AS_UP) != 0) {
573                 homeDesc = mContext.getResources().getText(mDefaultUpDescription);
574             } else {
575                 homeDesc = mContext.getResources().getText(R.string.action_bar_home_description);
576             }
577         }
578 
579         final CharSequence title = getTitle();
580         final CharSequence subtitle = getSubtitle();
581         if (!TextUtils.isEmpty(title)) {
582             final String result;
583             if (!TextUtils.isEmpty(subtitle)) {
584                 result = getResources().getString(
585                         R.string.action_bar_home_subtitle_description_format,
586                         title, subtitle, homeDesc);
587             } else {
588                 result = getResources().getString(R.string.action_bar_home_description_format,
589                         title, homeDesc);
590             }
591             return result;
592         }
593         return homeDesc;
594     }
595 
setDisplayOptions(int options)596     public void setDisplayOptions(int options) {
597         final int flagsChanged = mDisplayOptions == -1 ? -1 : options ^ mDisplayOptions;
598         mDisplayOptions = options;
599 
600         if ((flagsChanged & DISPLAY_RELAYOUT_MASK) != 0) {
601 
602             if ((flagsChanged & ActionBar.DISPLAY_HOME_AS_UP) != 0) {
603                 final boolean setUp = (options & ActionBar.DISPLAY_HOME_AS_UP) != 0;
604                 mHomeLayout.setShowUp(setUp);
605 
606                 // Showing home as up implicitly enables interaction with it.
607                 // In honeycomb it was always enabled, so make this transition
608                 // a bit easier for developers in the common case.
609                 // (It would be silly to show it as up without responding to it.)
610                 if (setUp) {
611                     setHomeButtonEnabled(true);
612                 }
613             }
614 
615             if ((flagsChanged & ActionBar.DISPLAY_USE_LOGO) != 0) {
616                 final boolean logoVis = mLogo != null && (options & ActionBar.DISPLAY_USE_LOGO) != 0;
617                 mHomeLayout.setIcon(logoVis ? mLogo : mIcon);
618             }
619 
620             if ((flagsChanged & ActionBar.DISPLAY_SHOW_TITLE) != 0) {
621                 if ((options & ActionBar.DISPLAY_SHOW_TITLE) != 0) {
622                     initTitle();
623                 } else {
624                     mUpGoerFive.removeView(mTitleLayout);
625                 }
626             }
627 
628             final boolean showHome = (options & ActionBar.DISPLAY_SHOW_HOME) != 0;
629             final boolean homeAsUp = (mDisplayOptions & ActionBar.DISPLAY_HOME_AS_UP) != 0;
630             final boolean titleUp = !showHome && homeAsUp;
631             mHomeLayout.setShowIcon(showHome);
632 
633             final int homeVis = (showHome || titleUp) && mExpandedActionView == null ?
634                     VISIBLE : GONE;
635             mHomeLayout.setVisibility(homeVis);
636 
637             if ((flagsChanged & ActionBar.DISPLAY_SHOW_CUSTOM) != 0 && mCustomNavView != null) {
638                 if ((options & ActionBar.DISPLAY_SHOW_CUSTOM) != 0) {
639                     addView(mCustomNavView);
640                 } else {
641                     removeView(mCustomNavView);
642                 }
643             }
644 
645             if (mTitleLayout != null &&
646                     (flagsChanged & ActionBar.DISPLAY_TITLE_MULTIPLE_LINES) != 0) {
647                 if ((options & ActionBar.DISPLAY_TITLE_MULTIPLE_LINES) != 0) {
648                     mTitleView.setSingleLine(false);
649                     mTitleView.setMaxLines(2);
650                 } else {
651                     mTitleView.setMaxLines(1);
652                     mTitleView.setSingleLine(true);
653                 }
654             }
655 
656             requestLayout();
657         } else {
658             invalidate();
659         }
660 
661         // Make sure the home button has an accurate content description for accessibility.
662         updateHomeAccessibility(mUpGoerFive.isEnabled());
663     }
664 
setIcon(Drawable icon)665     public void setIcon(Drawable icon) {
666         mIcon = icon;
667         if (icon != null &&
668                 ((mDisplayOptions & ActionBar.DISPLAY_USE_LOGO) == 0 || mLogo == null)) {
669             mHomeLayout.setIcon(icon);
670         }
671         if (mExpandedActionView != null) {
672             mExpandedHomeLayout.setIcon(mIcon.getConstantState().newDrawable(getResources()));
673         }
674     }
675 
setIcon(int resId)676     public void setIcon(int resId) {
677         setIcon(resId != 0 ? mContext.getDrawable(resId) : null);
678     }
679 
hasIcon()680     public boolean hasIcon() {
681         return mIcon != null;
682     }
683 
setLogo(Drawable logo)684     public void setLogo(Drawable logo) {
685         mLogo = logo;
686         if (logo != null && (mDisplayOptions & ActionBar.DISPLAY_USE_LOGO) != 0) {
687             mHomeLayout.setIcon(logo);
688         }
689     }
690 
setLogo(int resId)691     public void setLogo(int resId) {
692         setLogo(resId != 0 ? mContext.getDrawable(resId) : null);
693     }
694 
hasLogo()695     public boolean hasLogo() {
696         return mLogo != null;
697     }
698 
setNavigationMode(int mode)699     public void setNavigationMode(int mode) {
700         final int oldMode = mNavigationMode;
701         if (mode != oldMode) {
702             switch (oldMode) {
703             case ActionBar.NAVIGATION_MODE_LIST:
704                 if (mListNavLayout != null) {
705                     removeView(mListNavLayout);
706                 }
707                 break;
708             case ActionBar.NAVIGATION_MODE_TABS:
709                 if (mTabScrollView != null && mIncludeTabs) {
710                     removeView(mTabScrollView);
711                 }
712             }
713 
714             switch (mode) {
715             case ActionBar.NAVIGATION_MODE_LIST:
716                 if (mSpinner == null) {
717                     mSpinner = new Spinner(mContext, null,
718                             com.android.internal.R.attr.actionDropDownStyle);
719                     mSpinner.setId(com.android.internal.R.id.action_bar_spinner);
720                     mListNavLayout = new LinearLayout(mContext, null,
721                             com.android.internal.R.attr.actionBarTabBarStyle);
722                     LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
723                             LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);
724                     params.gravity = Gravity.CENTER;
725                     mListNavLayout.addView(mSpinner, params);
726                 }
727                 if (mSpinner.getAdapter() != mSpinnerAdapter) {
728                     mSpinner.setAdapter(mSpinnerAdapter);
729                 }
730                 mSpinner.setOnItemSelectedListener(mNavItemSelectedListener);
731                 addView(mListNavLayout);
732                 break;
733             case ActionBar.NAVIGATION_MODE_TABS:
734                 if (mTabScrollView != null && mIncludeTabs) {
735                     addView(mTabScrollView);
736                 }
737                 break;
738             }
739             mNavigationMode = mode;
740             requestLayout();
741         }
742     }
743 
setDropdownParams(SpinnerAdapter adapter, AdapterView.OnItemSelectedListener l)744     public void setDropdownParams(SpinnerAdapter adapter, AdapterView.OnItemSelectedListener l) {
745         mSpinnerAdapter = adapter;
746         mNavItemSelectedListener = l;
747         if (mSpinner != null) {
748             mSpinner.setAdapter(adapter);
749             mSpinner.setOnItemSelectedListener(l);
750         }
751     }
752 
getDropdownItemCount()753     public int getDropdownItemCount() {
754         return mSpinnerAdapter != null ? mSpinnerAdapter.getCount() : 0;
755     }
756 
setDropdownSelectedPosition(int position)757     public void setDropdownSelectedPosition(int position) {
758         mSpinner.setSelection(position);
759     }
760 
getDropdownSelectedPosition()761     public int getDropdownSelectedPosition() {
762         return mSpinner.getSelectedItemPosition();
763     }
764 
getCustomView()765     public View getCustomView() {
766         return mCustomNavView;
767     }
768 
getNavigationMode()769     public int getNavigationMode() {
770         return mNavigationMode;
771     }
772 
getDisplayOptions()773     public int getDisplayOptions() {
774         return mDisplayOptions;
775     }
776 
777     @Override
getViewGroup()778     public ViewGroup getViewGroup() {
779         return this;
780     }
781 
782     @Override
generateDefaultLayoutParams()783     protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
784         // Used by custom nav views if they don't supply layout params. Everything else
785         // added to an ActionBarView should have them already.
786         return new ActionBar.LayoutParams(DEFAULT_CUSTOM_GRAVITY);
787     }
788 
789     @Override
onFinishInflate()790     protected void onFinishInflate() {
791         super.onFinishInflate();
792 
793         mUpGoerFive.addView(mHomeLayout, 0);
794         addView(mUpGoerFive);
795 
796         if (mCustomNavView != null && (mDisplayOptions & ActionBar.DISPLAY_SHOW_CUSTOM) != 0) {
797             final ViewParent parent = mCustomNavView.getParent();
798             if (parent != this) {
799                 if (parent instanceof ViewGroup) {
800                     ((ViewGroup) parent).removeView(mCustomNavView);
801                 }
802                 addView(mCustomNavView);
803             }
804         }
805     }
806 
initTitle()807     private void initTitle() {
808         if (mTitleLayout == null) {
809             LayoutInflater inflater = LayoutInflater.from(getContext());
810             mTitleLayout = (LinearLayout) inflater.inflate(R.layout.action_bar_title_item,
811                     this, false);
812             mTitleView = (TextView) mTitleLayout.findViewById(R.id.action_bar_title);
813             mSubtitleView = (TextView) mTitleLayout.findViewById(R.id.action_bar_subtitle);
814 
815             if (mTitleStyleRes != 0) {
816                 mTitleView.setTextAppearance(mTitleStyleRes);
817             }
818             if (mTitle != null) {
819                 mTitleView.setText(mTitle);
820             }
821 
822             if (mSubtitleStyleRes != 0) {
823                 mSubtitleView.setTextAppearance(mSubtitleStyleRes);
824             }
825             if (mSubtitle != null) {
826                 mSubtitleView.setText(mSubtitle);
827                 mSubtitleView.setVisibility(VISIBLE);
828             }
829         }
830 
831         mUpGoerFive.addView(mTitleLayout);
832         if (mExpandedActionView != null ||
833                 (TextUtils.isEmpty(mTitle) && TextUtils.isEmpty(mSubtitle))) {
834             // Don't show while in expanded mode or with empty text
835             mTitleLayout.setVisibility(GONE);
836         } else {
837             mTitleLayout.setVisibility(VISIBLE);
838         }
839     }
840 
setContextView(ActionBarContextView view)841     public void setContextView(ActionBarContextView view) {
842         mContextView = view;
843     }
844 
setCollapsible(boolean collapsible)845     public void setCollapsible(boolean collapsible) {
846         mIsCollapsible = collapsible;
847     }
848 
849     /**
850      * @return True if any characters in the title were truncated
851      */
isTitleTruncated()852     public boolean isTitleTruncated() {
853         if (mTitleView == null) {
854             return false;
855         }
856 
857         final Layout titleLayout = mTitleView.getLayout();
858         if (titleLayout == null) {
859             return false;
860         }
861 
862         final int lineCount = titleLayout.getLineCount();
863         for (int i = 0; i < lineCount; i++) {
864             if (titleLayout.getEllipsisCount(i) > 0) {
865                 return true;
866             }
867         }
868         return false;
869     }
870 
871     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)872     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
873         final int childCount = getChildCount();
874         if (mIsCollapsible) {
875             int visibleChildren = 0;
876             for (int i = 0; i < childCount; i++) {
877                 final View child = getChildAt(i);
878                 if (child.getVisibility() != GONE &&
879                         !(child == mMenuView && mMenuView.getChildCount() == 0) &&
880                         child != mUpGoerFive) {
881                     visibleChildren++;
882                 }
883             }
884 
885             final int upChildCount = mUpGoerFive.getChildCount();
886             for (int i = 0; i < upChildCount; i++) {
887                 final View child = mUpGoerFive.getChildAt(i);
888                 if (child.getVisibility() != GONE) {
889                     visibleChildren++;
890                 }
891             }
892 
893             if (visibleChildren == 0) {
894                 // No size for an empty action bar when collapsable.
895                 setMeasuredDimension(0, 0);
896                 return;
897             }
898         }
899 
900         int widthMode = MeasureSpec.getMode(widthMeasureSpec);
901         if (widthMode != MeasureSpec.EXACTLY) {
902             throw new IllegalStateException(getClass().getSimpleName() + " can only be used " +
903                     "with android:layout_width=\"match_parent\" (or fill_parent)");
904         }
905 
906         int heightMode = MeasureSpec.getMode(heightMeasureSpec);
907         if (heightMode != MeasureSpec.AT_MOST) {
908             throw new IllegalStateException(getClass().getSimpleName() + " can only be used " +
909                     "with android:layout_height=\"wrap_content\"");
910         }
911 
912         int contentWidth = MeasureSpec.getSize(widthMeasureSpec);
913 
914         int maxHeight = mContentHeight >= 0 ?
915                 mContentHeight : MeasureSpec.getSize(heightMeasureSpec);
916 
917         final int verticalPadding = getPaddingTop() + getPaddingBottom();
918         final int paddingLeft = getPaddingLeft();
919         final int paddingRight = getPaddingRight();
920         final int height = maxHeight - verticalPadding;
921         final int childSpecHeight = MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST);
922         final int exactHeightSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
923 
924         int availableWidth = contentWidth - paddingLeft - paddingRight;
925         int leftOfCenter = availableWidth / 2;
926         int rightOfCenter = leftOfCenter;
927 
928         final boolean showTitle = mTitleLayout != null && mTitleLayout.getVisibility() != GONE &&
929                 (mDisplayOptions & ActionBar.DISPLAY_SHOW_TITLE) != 0;
930 
931         HomeView homeLayout = mExpandedActionView != null ? mExpandedHomeLayout : mHomeLayout;
932 
933         final ViewGroup.LayoutParams homeLp = homeLayout.getLayoutParams();
934         int homeWidthSpec;
935         if (homeLp.width < 0) {
936             homeWidthSpec = MeasureSpec.makeMeasureSpec(availableWidth, MeasureSpec.AT_MOST);
937         } else {
938             homeWidthSpec = MeasureSpec.makeMeasureSpec(homeLp.width, MeasureSpec.EXACTLY);
939         }
940 
941         /*
942          * This is a little weird.
943          * We're only measuring the *home* affordance within the Up container here
944          * on purpose, because we want to give the available space to all other views before
945          * the title text. We'll remeasure the whole up container again later.
946          * We need to measure this container so we know the right offset for the up affordance
947          * no matter what.
948          */
949         homeLayout.measure(homeWidthSpec, exactHeightSpec);
950 
951         int homeWidth = 0;
952         if ((homeLayout.getVisibility() != GONE && homeLayout.getParent() == mUpGoerFive)
953                 || showTitle) {
954             homeWidth = homeLayout.getMeasuredWidth();
955             final int homeOffsetWidth = homeWidth + homeLayout.getStartOffset();
956             availableWidth = Math.max(0, availableWidth - homeOffsetWidth);
957             leftOfCenter = Math.max(0, availableWidth - homeOffsetWidth);
958         }
959 
960         if (mMenuView != null && mMenuView.getParent() == this) {
961             availableWidth = measureChildView(mMenuView, availableWidth, exactHeightSpec, 0);
962             rightOfCenter = Math.max(0, rightOfCenter - mMenuView.getMeasuredWidth());
963         }
964 
965         if (mIndeterminateProgressView != null &&
966                 mIndeterminateProgressView.getVisibility() != GONE) {
967             availableWidth = measureChildView(mIndeterminateProgressView, availableWidth,
968                     childSpecHeight, 0);
969             rightOfCenter = Math.max(0,
970                     rightOfCenter - mIndeterminateProgressView.getMeasuredWidth());
971         }
972 
973         if (mExpandedActionView == null) {
974             switch (mNavigationMode) {
975                 case ActionBar.NAVIGATION_MODE_LIST:
976                     if (mListNavLayout != null) {
977                         final int itemPaddingSize = showTitle ? mItemPadding * 2 : mItemPadding;
978                         availableWidth = Math.max(0, availableWidth - itemPaddingSize);
979                         leftOfCenter = Math.max(0, leftOfCenter - itemPaddingSize);
980                         mListNavLayout.measure(
981                                 MeasureSpec.makeMeasureSpec(availableWidth, MeasureSpec.AT_MOST),
982                                 MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
983                         final int listNavWidth = mListNavLayout.getMeasuredWidth();
984                         availableWidth = Math.max(0, availableWidth - listNavWidth);
985                         leftOfCenter = Math.max(0, leftOfCenter - listNavWidth);
986                     }
987                     break;
988                 case ActionBar.NAVIGATION_MODE_TABS:
989                     if (mTabScrollView != null) {
990                         final int itemPaddingSize = showTitle ? mItemPadding * 2 : mItemPadding;
991                         availableWidth = Math.max(0, availableWidth - itemPaddingSize);
992                         leftOfCenter = Math.max(0, leftOfCenter - itemPaddingSize);
993                         mTabScrollView.measure(
994                                 MeasureSpec.makeMeasureSpec(availableWidth, MeasureSpec.AT_MOST),
995                                 MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
996                         final int tabWidth = mTabScrollView.getMeasuredWidth();
997                         availableWidth = Math.max(0, availableWidth - tabWidth);
998                         leftOfCenter = Math.max(0, leftOfCenter - tabWidth);
999                     }
1000                     break;
1001             }
1002         }
1003 
1004         View customView = null;
1005         if (mExpandedActionView != null) {
1006             customView = mExpandedActionView;
1007         } else if ((mDisplayOptions & ActionBar.DISPLAY_SHOW_CUSTOM) != 0 &&
1008                 mCustomNavView != null) {
1009             customView = mCustomNavView;
1010         }
1011 
1012         if (customView != null) {
1013             final ViewGroup.LayoutParams lp = generateLayoutParams(customView.getLayoutParams());
1014             final ActionBar.LayoutParams ablp = lp instanceof ActionBar.LayoutParams ?
1015                     (ActionBar.LayoutParams) lp : null;
1016 
1017             int horizontalMargin = 0;
1018             int verticalMargin = 0;
1019             if (ablp != null) {
1020                 horizontalMargin = ablp.leftMargin + ablp.rightMargin;
1021                 verticalMargin = ablp.topMargin + ablp.bottomMargin;
1022             }
1023 
1024             // If the action bar is wrapping to its content height, don't allow a custom
1025             // view to MATCH_PARENT.
1026             int customNavHeightMode;
1027             if (mContentHeight <= 0) {
1028                 customNavHeightMode = MeasureSpec.AT_MOST;
1029             } else {
1030                 customNavHeightMode = lp.height != LayoutParams.WRAP_CONTENT ?
1031                         MeasureSpec.EXACTLY : MeasureSpec.AT_MOST;
1032             }
1033             final int customNavHeight = Math.max(0,
1034                     (lp.height >= 0 ? Math.min(lp.height, height) : height) - verticalMargin);
1035 
1036             final int customNavWidthMode = lp.width != LayoutParams.WRAP_CONTENT ?
1037                     MeasureSpec.EXACTLY : MeasureSpec.AT_MOST;
1038             int customNavWidth = Math.max(0,
1039                     (lp.width >= 0 ? Math.min(lp.width, availableWidth) : availableWidth)
1040                     - horizontalMargin);
1041             final int hgrav = (ablp != null ? ablp.gravity : DEFAULT_CUSTOM_GRAVITY) &
1042                     Gravity.HORIZONTAL_GRAVITY_MASK;
1043 
1044             // Centering a custom view is treated specially; we try to center within the whole
1045             // action bar rather than in the available space.
1046             if (hgrav == Gravity.CENTER_HORIZONTAL && lp.width == LayoutParams.MATCH_PARENT) {
1047                 customNavWidth = Math.min(leftOfCenter, rightOfCenter) * 2;
1048             }
1049 
1050             customView.measure(
1051                     MeasureSpec.makeMeasureSpec(customNavWidth, customNavWidthMode),
1052                     MeasureSpec.makeMeasureSpec(customNavHeight, customNavHeightMode));
1053             availableWidth -= horizontalMargin + customView.getMeasuredWidth();
1054         }
1055 
1056         /*
1057          * Measure the whole up container now, allowing for the full home+title sections.
1058          * (This will re-measure the home view.)
1059          */
1060         availableWidth = measureChildView(mUpGoerFive, availableWidth + homeWidth,
1061                 MeasureSpec.makeMeasureSpec(mContentHeight, MeasureSpec.EXACTLY), 0);
1062         if (mTitleLayout != null) {
1063             leftOfCenter = Math.max(0, leftOfCenter - mTitleLayout.getMeasuredWidth());
1064         }
1065 
1066         if (mContentHeight <= 0) {
1067             int measuredHeight = 0;
1068             for (int i = 0; i < childCount; i++) {
1069                 View v = getChildAt(i);
1070                 int paddedViewHeight = v.getMeasuredHeight() + verticalPadding;
1071                 if (paddedViewHeight > measuredHeight) {
1072                     measuredHeight = paddedViewHeight;
1073                 }
1074             }
1075             setMeasuredDimension(contentWidth, measuredHeight);
1076         } else {
1077             setMeasuredDimension(contentWidth, maxHeight);
1078         }
1079 
1080         if (mContextView != null) {
1081             mContextView.setContentHeight(getMeasuredHeight());
1082         }
1083 
1084         if (mProgressView != null && mProgressView.getVisibility() != GONE) {
1085             mProgressView.measure(MeasureSpec.makeMeasureSpec(
1086                     contentWidth - mProgressBarPadding * 2, MeasureSpec.EXACTLY),
1087                     MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.AT_MOST));
1088         }
1089     }
1090 
1091     @Override
onLayout(boolean changed, int l, int t, int r, int b)1092     protected void onLayout(boolean changed, int l, int t, int r, int b) {
1093         final int contentHeight = b - t - getPaddingTop() - getPaddingBottom();
1094 
1095         if (contentHeight <= 0) {
1096             // Nothing to do if we can't see anything.
1097             return;
1098         }
1099 
1100         final boolean isLayoutRtl = isLayoutRtl();
1101         final int direction = isLayoutRtl ? 1 : -1;
1102         int menuStart = isLayoutRtl ? getPaddingLeft() : r - l - getPaddingRight();
1103         // In LTR mode, we start from left padding and go to the right; in RTL mode, we start
1104         // from the padding right and go to the left (in reverse way)
1105         int x = isLayoutRtl ? r - l - getPaddingRight() : getPaddingLeft();
1106         final int y = getPaddingTop();
1107 
1108         HomeView homeLayout = mExpandedActionView != null ? mExpandedHomeLayout : mHomeLayout;
1109         final boolean showTitle = mTitleLayout != null && mTitleLayout.getVisibility() != GONE &&
1110                 (mDisplayOptions & ActionBar.DISPLAY_SHOW_TITLE) != 0;
1111         int startOffset = 0;
1112         if (homeLayout.getParent() == mUpGoerFive) {
1113             if (homeLayout.getVisibility() != GONE) {
1114                 startOffset = homeLayout.getStartOffset();
1115             } else if (showTitle) {
1116                 startOffset = homeLayout.getUpWidth();
1117             }
1118         }
1119 
1120         // Position the up container based on where the edge of the home layout should go.
1121         x += positionChild(mUpGoerFive,
1122                 next(x, startOffset, isLayoutRtl), y, contentHeight, isLayoutRtl);
1123         x = next(x, startOffset, isLayoutRtl);
1124 
1125         if (mExpandedActionView == null) {
1126             switch (mNavigationMode) {
1127                 case ActionBar.NAVIGATION_MODE_STANDARD:
1128                     break;
1129                 case ActionBar.NAVIGATION_MODE_LIST:
1130                     if (mListNavLayout != null) {
1131                         if (showTitle) {
1132                             x = next(x, mItemPadding, isLayoutRtl);
1133                         }
1134                         x += positionChild(mListNavLayout, x, y, contentHeight, isLayoutRtl);
1135                         x = next(x, mItemPadding, isLayoutRtl);
1136                     }
1137                     break;
1138                 case ActionBar.NAVIGATION_MODE_TABS:
1139                     if (mTabScrollView != null) {
1140                         if (showTitle) x = next(x, mItemPadding, isLayoutRtl);
1141                         x += positionChild(mTabScrollView, x, y, contentHeight, isLayoutRtl);
1142                         x = next(x, mItemPadding, isLayoutRtl);
1143                     }
1144                     break;
1145             }
1146         }
1147 
1148         if (mMenuView != null && mMenuView.getParent() == this) {
1149             positionChild(mMenuView, menuStart, y, contentHeight, !isLayoutRtl);
1150             menuStart += direction * mMenuView.getMeasuredWidth();
1151         }
1152 
1153         if (mIndeterminateProgressView != null &&
1154                 mIndeterminateProgressView.getVisibility() != GONE) {
1155             positionChild(mIndeterminateProgressView, menuStart, y, contentHeight, !isLayoutRtl);
1156             menuStart += direction * mIndeterminateProgressView.getMeasuredWidth();
1157         }
1158 
1159         View customView = null;
1160         if (mExpandedActionView != null) {
1161             customView = mExpandedActionView;
1162         } else if ((mDisplayOptions & ActionBar.DISPLAY_SHOW_CUSTOM) != 0 &&
1163                 mCustomNavView != null) {
1164             customView = mCustomNavView;
1165         }
1166         if (customView != null) {
1167             final int layoutDirection = getLayoutDirection();
1168             ViewGroup.LayoutParams lp = customView.getLayoutParams();
1169             final ActionBar.LayoutParams ablp = lp instanceof ActionBar.LayoutParams ?
1170                     (ActionBar.LayoutParams) lp : null;
1171             final int gravity = ablp != null ? ablp.gravity : DEFAULT_CUSTOM_GRAVITY;
1172             final int navWidth = customView.getMeasuredWidth();
1173 
1174             int topMargin = 0;
1175             int bottomMargin = 0;
1176             if (ablp != null) {
1177                 x = next(x, ablp.getMarginStart(), isLayoutRtl);
1178                 menuStart += direction * ablp.getMarginEnd();
1179                 topMargin = ablp.topMargin;
1180                 bottomMargin = ablp.bottomMargin;
1181             }
1182 
1183             int hgravity = gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;
1184             // See if we actually have room to truly center; if not push against left or right.
1185             if (hgravity == Gravity.CENTER_HORIZONTAL) {
1186                 final int centeredLeft = ((mRight - mLeft) - navWidth) / 2;
1187                 if (isLayoutRtl) {
1188                     final int centeredStart = centeredLeft + navWidth;
1189                     final int centeredEnd = centeredLeft;
1190                     if (centeredStart > x) {
1191                         hgravity = Gravity.RIGHT;
1192                     } else if (centeredEnd < menuStart) {
1193                         hgravity = Gravity.LEFT;
1194                     }
1195                 } else {
1196                     final int centeredStart = centeredLeft;
1197                     final int centeredEnd = centeredLeft + navWidth;
1198                     if (centeredStart < x) {
1199                         hgravity = Gravity.LEFT;
1200                     } else if (centeredEnd > menuStart) {
1201                         hgravity = Gravity.RIGHT;
1202                     }
1203                 }
1204             } else if (gravity == Gravity.NO_GRAVITY) {
1205                 hgravity = Gravity.START;
1206             }
1207 
1208             int xpos = 0;
1209             switch (Gravity.getAbsoluteGravity(hgravity, layoutDirection)) {
1210                 case Gravity.CENTER_HORIZONTAL:
1211                     xpos = ((mRight - mLeft) - navWidth) / 2;
1212                     break;
1213                 case Gravity.LEFT:
1214                     xpos = isLayoutRtl ? menuStart : x;
1215                     break;
1216                 case Gravity.RIGHT:
1217                     xpos = isLayoutRtl ? x - navWidth : menuStart - navWidth;
1218                     break;
1219             }
1220 
1221             int vgravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;
1222 
1223             if (gravity == Gravity.NO_GRAVITY) {
1224                 vgravity = Gravity.CENTER_VERTICAL;
1225             }
1226 
1227             int ypos = 0;
1228             switch (vgravity) {
1229                 case Gravity.CENTER_VERTICAL:
1230                     final int paddedTop = getPaddingTop();
1231                     final int paddedBottom = mBottom - mTop - getPaddingBottom();
1232                     ypos = ((paddedBottom - paddedTop) - customView.getMeasuredHeight()) / 2;
1233                     break;
1234                 case Gravity.TOP:
1235                     ypos = getPaddingTop() + topMargin;
1236                     break;
1237                 case Gravity.BOTTOM:
1238                     ypos = getHeight() - getPaddingBottom() - customView.getMeasuredHeight()
1239                             - bottomMargin;
1240                     break;
1241             }
1242             final int customWidth = customView.getMeasuredWidth();
1243             customView.layout(xpos, ypos, xpos + customWidth,
1244                     ypos + customView.getMeasuredHeight());
1245             x = next(x, customWidth, isLayoutRtl);
1246         }
1247 
1248         if (mProgressView != null) {
1249             mProgressView.bringToFront();
1250             final int halfProgressHeight = mProgressView.getMeasuredHeight() / 2;
1251             mProgressView.layout(mProgressBarPadding, -halfProgressHeight,
1252                     mProgressBarPadding + mProgressView.getMeasuredWidth(), halfProgressHeight);
1253         }
1254     }
1255 
1256     @Override
generateLayoutParams(AttributeSet attrs)1257     public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
1258         return new ActionBar.LayoutParams(getContext(), attrs);
1259     }
1260 
1261     @Override
generateLayoutParams(ViewGroup.LayoutParams lp)1262     public ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams lp) {
1263         if (lp == null) {
1264             lp = generateDefaultLayoutParams();
1265         }
1266         return lp;
1267     }
1268 
1269     @Override
onSaveInstanceState()1270     public Parcelable onSaveInstanceState() {
1271         Parcelable superState = super.onSaveInstanceState();
1272         SavedState state = new SavedState(superState);
1273 
1274         if (mExpandedMenuPresenter != null && mExpandedMenuPresenter.mCurrentExpandedItem != null) {
1275             state.expandedMenuItemId = mExpandedMenuPresenter.mCurrentExpandedItem.getItemId();
1276         }
1277 
1278         state.isOverflowOpen = isOverflowMenuShowing();
1279 
1280         return state;
1281     }
1282 
1283     @Override
onRestoreInstanceState(Parcelable p)1284     public void onRestoreInstanceState(Parcelable p) {
1285         SavedState state = (SavedState) p;
1286 
1287         super.onRestoreInstanceState(state.getSuperState());
1288 
1289         if (state.expandedMenuItemId != 0 &&
1290                 mExpandedMenuPresenter != null && mOptionsMenu != null) {
1291             final MenuItem item = mOptionsMenu.findItem(state.expandedMenuItemId);
1292             if (item != null) {
1293                 item.expandActionView();
1294             }
1295         }
1296 
1297         if (state.isOverflowOpen) {
1298             postShowOverflowMenu();
1299         }
1300     }
1301 
setNavigationIcon(Drawable indicator)1302     public void setNavigationIcon(Drawable indicator) {
1303         mHomeLayout.setUpIndicator(indicator);
1304     }
1305 
1306     @Override
setDefaultNavigationIcon(Drawable icon)1307     public void setDefaultNavigationIcon(Drawable icon) {
1308         mHomeLayout.setDefaultUpIndicator(icon);
1309     }
1310 
setNavigationIcon(int resId)1311     public void setNavigationIcon(int resId) {
1312         mHomeLayout.setUpIndicator(resId);
1313     }
1314 
setNavigationContentDescription(CharSequence description)1315     public void setNavigationContentDescription(CharSequence description) {
1316         mHomeDescription = description;
1317         updateHomeAccessibility(mUpGoerFive.isEnabled());
1318     }
1319 
setNavigationContentDescription(int resId)1320     public void setNavigationContentDescription(int resId) {
1321         mHomeDescriptionRes = resId;
1322         mHomeDescription = resId != 0 ? getResources().getText(resId) : null;
1323         updateHomeAccessibility(mUpGoerFive.isEnabled());
1324     }
1325 
1326     @Override
setDefaultNavigationContentDescription(int defaultNavigationContentDescription)1327     public void setDefaultNavigationContentDescription(int defaultNavigationContentDescription) {
1328         if (mDefaultUpDescription == defaultNavigationContentDescription) {
1329             return;
1330         }
1331         mDefaultUpDescription = defaultNavigationContentDescription;
1332         updateHomeAccessibility(mUpGoerFive.isEnabled());
1333     }
1334 
1335     @Override
setMenuCallbacks(MenuPresenter.Callback presenterCallback, MenuBuilder.Callback menuBuilderCallback)1336     public void setMenuCallbacks(MenuPresenter.Callback presenterCallback,
1337             MenuBuilder.Callback menuBuilderCallback) {
1338         if (mActionMenuPresenter != null) {
1339             mActionMenuPresenter.setCallback(presenterCallback);
1340         }
1341         if (mOptionsMenu != null) {
1342             mOptionsMenu.setCallback(menuBuilderCallback);
1343         }
1344     }
1345 
1346     @Override
getMenu()1347     public Menu getMenu() {
1348         return mOptionsMenu;
1349     }
1350 
1351     static class SavedState extends BaseSavedState {
1352         int expandedMenuItemId;
1353         boolean isOverflowOpen;
1354 
SavedState(Parcelable superState)1355         SavedState(Parcelable superState) {
1356             super(superState);
1357         }
1358 
SavedState(Parcel in)1359         private SavedState(Parcel in) {
1360             super(in);
1361             expandedMenuItemId = in.readInt();
1362             isOverflowOpen = in.readInt() != 0;
1363         }
1364 
1365         @Override
writeToParcel(Parcel out, int flags)1366         public void writeToParcel(Parcel out, int flags) {
1367             super.writeToParcel(out, flags);
1368             out.writeInt(expandedMenuItemId);
1369             out.writeInt(isOverflowOpen ? 1 : 0);
1370         }
1371 
1372         public static final Parcelable.Creator<SavedState> CREATOR =
1373                 new Parcelable.Creator<SavedState>() {
1374             public SavedState createFromParcel(Parcel in) {
1375                 return new SavedState(in);
1376             }
1377 
1378             public SavedState[] newArray(int size) {
1379                 return new SavedState[size];
1380             }
1381         };
1382     }
1383 
1384     private static class HomeView extends FrameLayout {
1385         private ImageView mUpView;
1386         private ImageView mIconView;
1387         private int mUpWidth;
1388         private int mStartOffset;
1389         private int mUpIndicatorRes;
1390         private Drawable mDefaultUpIndicator;
1391         private Drawable mUpIndicator;
1392 
1393         private static final long DEFAULT_TRANSITION_DURATION = 150;
1394 
HomeView(Context context)1395         public HomeView(Context context) {
1396             this(context, null);
1397         }
1398 
HomeView(Context context, AttributeSet attrs)1399         public HomeView(Context context, AttributeSet attrs) {
1400             super(context, attrs);
1401             LayoutTransition t = getLayoutTransition();
1402             if (t != null) {
1403                 // Set a lower duration than the default
1404                 t.setDuration(DEFAULT_TRANSITION_DURATION);
1405             }
1406         }
1407 
setShowUp(boolean isUp)1408         public void setShowUp(boolean isUp) {
1409             mUpView.setVisibility(isUp ? VISIBLE : GONE);
1410         }
1411 
setShowIcon(boolean showIcon)1412         public void setShowIcon(boolean showIcon) {
1413             mIconView.setVisibility(showIcon ? VISIBLE : GONE);
1414         }
1415 
setIcon(Drawable icon)1416         public void setIcon(Drawable icon) {
1417             mIconView.setImageDrawable(icon);
1418         }
1419 
setUpIndicator(Drawable d)1420         public void setUpIndicator(Drawable d) {
1421             mUpIndicator = d;
1422             mUpIndicatorRes = 0;
1423             updateUpIndicator();
1424         }
1425 
setDefaultUpIndicator(Drawable d)1426         public void setDefaultUpIndicator(Drawable d) {
1427             mDefaultUpIndicator = d;
1428             updateUpIndicator();
1429         }
1430 
setUpIndicator(int resId)1431         public void setUpIndicator(int resId) {
1432             mUpIndicatorRes = resId;
1433             mUpIndicator = null;
1434             updateUpIndicator();
1435         }
1436 
updateUpIndicator()1437         private void updateUpIndicator() {
1438             if (mUpIndicator != null) {
1439                 mUpView.setImageDrawable(mUpIndicator);
1440             } else if (mUpIndicatorRes != 0) {
1441                 mUpView.setImageDrawable(getContext().getDrawable(mUpIndicatorRes));
1442             } else {
1443                 mUpView.setImageDrawable(mDefaultUpIndicator);
1444             }
1445         }
1446 
1447         @Override
onConfigurationChanged(Configuration newConfig)1448         protected void onConfigurationChanged(Configuration newConfig) {
1449             super.onConfigurationChanged(newConfig);
1450             if (mUpIndicatorRes != 0) {
1451                 // Reload for config change
1452                 updateUpIndicator();
1453             }
1454         }
1455 
1456         @Override
dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event)1457         public boolean dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event) {
1458             onPopulateAccessibilityEvent(event);
1459             return true;
1460         }
1461 
1462         @Override
onPopulateAccessibilityEventInternal(AccessibilityEvent event)1463         public void onPopulateAccessibilityEventInternal(AccessibilityEvent event) {
1464             super.onPopulateAccessibilityEventInternal(event);
1465             final CharSequence cdesc = getContentDescription();
1466             if (!TextUtils.isEmpty(cdesc)) {
1467                 event.getText().add(cdesc);
1468             }
1469         }
1470 
1471         @Override
dispatchHoverEvent(MotionEvent event)1472         public boolean dispatchHoverEvent(MotionEvent event) {
1473             // Don't allow children to hover; we want this to be treated as a single component.
1474             return onHoverEvent(event);
1475         }
1476 
1477         @Override
onFinishInflate()1478         protected void onFinishInflate() {
1479             mUpView = (ImageView) findViewById(com.android.internal.R.id.up);
1480             mIconView = (ImageView) findViewById(com.android.internal.R.id.home);
1481             mDefaultUpIndicator = mUpView.getDrawable();
1482         }
1483 
getStartOffset()1484         public int getStartOffset() {
1485             return mUpView.getVisibility() == GONE ? mStartOffset : 0;
1486         }
1487 
getUpWidth()1488         public int getUpWidth() {
1489             return mUpWidth;
1490         }
1491 
1492         @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)1493         protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
1494             measureChildWithMargins(mUpView, widthMeasureSpec, 0, heightMeasureSpec, 0);
1495             final LayoutParams upLp = (LayoutParams) mUpView.getLayoutParams();
1496             final int upMargins = upLp.leftMargin + upLp.rightMargin;
1497             mUpWidth = mUpView.getMeasuredWidth();
1498             mStartOffset = mUpWidth + upMargins;
1499             int width = mUpView.getVisibility() == GONE ? 0 : mStartOffset;
1500             int height = upLp.topMargin + mUpView.getMeasuredHeight() + upLp.bottomMargin;
1501 
1502             if (mIconView.getVisibility() != GONE) {
1503                 measureChildWithMargins(mIconView, widthMeasureSpec, width, heightMeasureSpec, 0);
1504                 final LayoutParams iconLp = (LayoutParams) mIconView.getLayoutParams();
1505                 width += iconLp.leftMargin + mIconView.getMeasuredWidth() + iconLp.rightMargin;
1506                 height = Math.max(height,
1507                         iconLp.topMargin + mIconView.getMeasuredHeight() + iconLp.bottomMargin);
1508             } else if (upMargins < 0) {
1509                 // Remove the measurement effects of negative margins used for offsets
1510                 width -= upMargins;
1511             }
1512 
1513             final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
1514             final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
1515             final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
1516             final int heightSize = MeasureSpec.getSize(heightMeasureSpec);
1517 
1518             switch (widthMode) {
1519                 case MeasureSpec.AT_MOST:
1520                     width = Math.min(width, widthSize);
1521                     break;
1522                 case MeasureSpec.EXACTLY:
1523                     width = widthSize;
1524                     break;
1525                 case MeasureSpec.UNSPECIFIED:
1526                 default:
1527                     break;
1528             }
1529             switch (heightMode) {
1530                 case MeasureSpec.AT_MOST:
1531                     height = Math.min(height, heightSize);
1532                     break;
1533                 case MeasureSpec.EXACTLY:
1534                     height = heightSize;
1535                     break;
1536                 case MeasureSpec.UNSPECIFIED:
1537                 default:
1538                     break;
1539             }
1540             setMeasuredDimension(width, height);
1541         }
1542 
1543         @Override
onLayout(boolean changed, int l, int t, int r, int b)1544         protected void onLayout(boolean changed, int l, int t, int r, int b) {
1545             final int vCenter = (b - t) / 2;
1546             final boolean isLayoutRtl = isLayoutRtl();
1547             final int width = getWidth();
1548             int upOffset = 0;
1549             if (mUpView.getVisibility() != GONE) {
1550                 final LayoutParams upLp = (LayoutParams) mUpView.getLayoutParams();
1551                 final int upHeight = mUpView.getMeasuredHeight();
1552                 final int upWidth = mUpView.getMeasuredWidth();
1553                 upOffset = upLp.leftMargin + upWidth + upLp.rightMargin;
1554                 final int upTop = vCenter - upHeight / 2;
1555                 final int upBottom = upTop + upHeight;
1556                 final int upRight;
1557                 final int upLeft;
1558                 if (isLayoutRtl) {
1559                     upRight = width;
1560                     upLeft = upRight - upWidth;
1561                     r -= upOffset;
1562                 } else {
1563                     upRight = upWidth;
1564                     upLeft = 0;
1565                     l += upOffset;
1566                 }
1567                 mUpView.layout(upLeft, upTop, upRight, upBottom);
1568             }
1569 
1570             final LayoutParams iconLp = (LayoutParams) mIconView.getLayoutParams();
1571             final int iconHeight = mIconView.getMeasuredHeight();
1572             final int iconWidth = mIconView.getMeasuredWidth();
1573             final int hCenter = (r - l) / 2;
1574             final int iconTop = Math.max(iconLp.topMargin, vCenter - iconHeight / 2);
1575             final int iconBottom = iconTop + iconHeight;
1576             final int iconLeft;
1577             final int iconRight;
1578             int marginStart = iconLp.getMarginStart();
1579             final int delta = Math.max(marginStart, hCenter - iconWidth / 2);
1580             if (isLayoutRtl) {
1581                 iconRight = width - upOffset - delta;
1582                 iconLeft = iconRight - iconWidth;
1583             } else {
1584                 iconLeft = upOffset + delta;
1585                 iconRight = iconLeft + iconWidth;
1586             }
1587             mIconView.layout(iconLeft, iconTop, iconRight, iconBottom);
1588         }
1589     }
1590 
1591     private class ExpandedActionViewMenuPresenter implements MenuPresenter {
1592         MenuBuilder mMenu;
1593         MenuItemImpl mCurrentExpandedItem;
1594 
1595         @Override
initForMenu(Context context, MenuBuilder menu)1596         public void initForMenu(Context context, MenuBuilder menu) {
1597             // Clear the expanded action view when menus change.
1598             if (mMenu != null && mCurrentExpandedItem != null) {
1599                 mMenu.collapseItemActionView(mCurrentExpandedItem);
1600             }
1601             mMenu = menu;
1602         }
1603 
1604         @Override
getMenuView(ViewGroup root)1605         public MenuView getMenuView(ViewGroup root) {
1606             return null;
1607         }
1608 
1609         @Override
updateMenuView(boolean cleared)1610         public void updateMenuView(boolean cleared) {
1611             // Make sure the expanded item we have is still there.
1612             if (mCurrentExpandedItem != null) {
1613                 boolean found = false;
1614 
1615                 if (mMenu != null) {
1616                     final int count = mMenu.size();
1617                     for (int i = 0; i < count; i++) {
1618                         final MenuItem item = mMenu.getItem(i);
1619                         if (item == mCurrentExpandedItem) {
1620                             found = true;
1621                             break;
1622                         }
1623                     }
1624                 }
1625 
1626                 if (!found) {
1627                     // The item we had expanded disappeared. Collapse.
1628                     collapseItemActionView(mMenu, mCurrentExpandedItem);
1629                 }
1630             }
1631         }
1632 
1633         @Override
setCallback(Callback cb)1634         public void setCallback(Callback cb) {
1635         }
1636 
1637         @Override
onSubMenuSelected(SubMenuBuilder subMenu)1638         public boolean onSubMenuSelected(SubMenuBuilder subMenu) {
1639             return false;
1640         }
1641 
1642         @Override
onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing)1643         public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
1644         }
1645 
1646         @Override
flagActionItems()1647         public boolean flagActionItems() {
1648             return false;
1649         }
1650 
1651         @Override
expandItemActionView(MenuBuilder menu, MenuItemImpl item)1652         public boolean expandItemActionView(MenuBuilder menu, MenuItemImpl item) {
1653 
1654             mExpandedActionView = item.getActionView();
1655             mExpandedHomeLayout.setIcon(mIcon.getConstantState().newDrawable(getResources()));
1656             mCurrentExpandedItem = item;
1657             if (mExpandedActionView.getParent() != ActionBarView.this) {
1658                 addView(mExpandedActionView);
1659             }
1660             if (mExpandedHomeLayout.getParent() != mUpGoerFive) {
1661                 mUpGoerFive.addView(mExpandedHomeLayout);
1662             }
1663             mHomeLayout.setVisibility(GONE);
1664             if (mTitleLayout != null) mTitleLayout.setVisibility(GONE);
1665             if (mTabScrollView != null) mTabScrollView.setVisibility(GONE);
1666             if (mSpinner != null) mSpinner.setVisibility(GONE);
1667             if (mCustomNavView != null) mCustomNavView.setVisibility(GONE);
1668             setHomeButtonEnabled(false, false);
1669             requestLayout();
1670             item.setActionViewExpanded(true);
1671 
1672             if (mExpandedActionView instanceof CollapsibleActionView) {
1673                 ((CollapsibleActionView) mExpandedActionView).onActionViewExpanded();
1674             }
1675 
1676             return true;
1677         }
1678 
1679         @Override
collapseItemActionView(MenuBuilder menu, MenuItemImpl item)1680         public boolean collapseItemActionView(MenuBuilder menu, MenuItemImpl item) {
1681 
1682             // Do this before detaching the actionview from the hierarchy, in case
1683             // it needs to dismiss the soft keyboard, etc.
1684             if (mExpandedActionView instanceof CollapsibleActionView) {
1685                 ((CollapsibleActionView) mExpandedActionView).onActionViewCollapsed();
1686             }
1687 
1688             removeView(mExpandedActionView);
1689             mUpGoerFive.removeView(mExpandedHomeLayout);
1690             mExpandedActionView = null;
1691             if ((mDisplayOptions & ActionBar.DISPLAY_SHOW_HOME) != 0) {
1692                 mHomeLayout.setVisibility(VISIBLE);
1693             }
1694             if ((mDisplayOptions & ActionBar.DISPLAY_SHOW_TITLE) != 0) {
1695                 if (mTitleLayout == null) {
1696                     initTitle();
1697                 } else {
1698                     mTitleLayout.setVisibility(VISIBLE);
1699                 }
1700             }
1701             if (mTabScrollView != null) mTabScrollView.setVisibility(VISIBLE);
1702             if (mSpinner != null) mSpinner.setVisibility(VISIBLE);
1703             if (mCustomNavView != null) mCustomNavView.setVisibility(VISIBLE);
1704 
1705             mExpandedHomeLayout.setIcon(null);
1706             mCurrentExpandedItem = null;
1707             setHomeButtonEnabled(mWasHomeEnabled); // Set by expandItemActionView above
1708             requestLayout();
1709             item.setActionViewExpanded(false);
1710 
1711             return true;
1712         }
1713 
1714         @Override
getId()1715         public int getId() {
1716             return 0;
1717         }
1718 
1719         @Override
onSaveInstanceState()1720         public Parcelable onSaveInstanceState() {
1721             return null;
1722         }
1723 
1724         @Override
onRestoreInstanceState(Parcelable state)1725         public void onRestoreInstanceState(Parcelable state) {
1726         }
1727     }
1728 }
1729