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