ToolbarWidgetWrapper.java revision 2bbad0cddce213ffd3d1e0909f2dc74dd0038ba7
1/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17
18package com.android.internal.widget;
19
20import android.animation.Animator;
21import android.animation.AnimatorListenerAdapter;
22import android.app.ActionBar;
23import android.content.Context;
24import android.content.res.TypedArray;
25import android.graphics.drawable.Drawable;
26import android.os.Parcelable;
27import android.text.TextUtils;
28import android.util.Log;
29import android.util.SparseArray;
30import android.util.TypedValue;
31import android.view.ContextThemeWrapper;
32import android.view.Gravity;
33import android.view.LayoutInflater;
34import android.view.Menu;
35import android.view.View;
36import android.view.ViewGroup;
37import android.view.Window;
38import android.widget.ActionMenuPresenter;
39import android.widget.AdapterView;
40import android.widget.Spinner;
41import android.widget.SpinnerAdapter;
42import android.widget.Toolbar;
43import com.android.internal.R;
44import com.android.internal.view.menu.ActionMenuItem;
45import com.android.internal.view.menu.MenuBuilder;
46import com.android.internal.view.menu.MenuPresenter;
47
48/**
49 * Internal class used to interact with the Toolbar widget without
50 * exposing interface methods to the public API.
51 *
52 * <p>ToolbarWidgetWrapper manages the differences between Toolbar and ActionBarView
53 * so that either variant acting as a
54 * {@link com.android.internal.app.WindowDecorActionBar WindowDecorActionBar} can behave
55 * in the same way.</p>
56 *
57 * @hide
58 */
59public class ToolbarWidgetWrapper implements DecorToolbar {
60    private static final String TAG = "ToolbarWidgetWrapper";
61
62    private static final int AFFECTS_LOGO_MASK =
63            ActionBar.DISPLAY_SHOW_HOME | ActionBar.DISPLAY_USE_LOGO;
64
65    private Toolbar mToolbar;
66
67    private int mDisplayOpts;
68    private View mTabView;
69    private Spinner mSpinner;
70    private View mCustomView;
71
72    private Drawable mIcon;
73    private Drawable mLogo;
74    private Drawable mNavIcon;
75
76    private boolean mTitleSet;
77    private CharSequence mTitle;
78    private CharSequence mSubtitle;
79    private CharSequence mHomeDescription;
80
81    private Window.Callback mWindowCallback;
82    private boolean mMenuPrepared;
83    private ActionMenuPresenter mActionMenuPresenter;
84
85    private int mNavigationMode = ActionBar.NAVIGATION_MODE_STANDARD;
86    private int mDefaultNavigationContentDescription = 0;
87
88    public ToolbarWidgetWrapper(Toolbar toolbar, boolean style) {
89        this(toolbar, style, R.string.action_bar_up_description);
90    }
91
92    public ToolbarWidgetWrapper(Toolbar toolbar, boolean style,
93            int defaultNavigationContentDescription) {
94        mToolbar = toolbar;
95
96        mTitle = toolbar.getTitle();
97        mSubtitle = toolbar.getSubtitle();
98        mTitleSet = mTitle != null;
99
100        if (style) {
101            final TypedArray a = toolbar.getContext().obtainStyledAttributes(null,
102                    R.styleable.ActionBar, R.attr.actionBarStyle, 0);
103
104            final CharSequence title = a.getText(R.styleable.ActionBar_title);
105            if (!TextUtils.isEmpty(title)) {
106                setTitle(title);
107            }
108
109            final CharSequence subtitle = a.getText(R.styleable.ActionBar_subtitle);
110            if (!TextUtils.isEmpty(subtitle)) {
111                setSubtitle(subtitle);
112            }
113
114            final Drawable logo = a.getDrawable(R.styleable.ActionBar_logo);
115            if (logo != null) {
116                setLogo(logo);
117            }
118
119            final Drawable icon = a.getDrawable(R.styleable.ActionBar_icon);
120            if (icon != null) {
121                setIcon(icon);
122            }
123
124            final Drawable navIcon = a.getDrawable(R.styleable.ActionBar_homeAsUpIndicator);
125            if (navIcon != null) {
126                setNavigationIcon(navIcon);
127            }
128
129            setDisplayOptions(a.getInt(R.styleable.ActionBar_displayOptions, 0));
130
131            final int customNavId = a.getResourceId(
132                    R.styleable.ActionBar_customNavigationLayout, 0);
133            if (customNavId != 0) {
134                setCustomView(LayoutInflater.from(mToolbar.getContext()).inflate(customNavId,
135                        mToolbar, false));
136                setDisplayOptions(mDisplayOpts | ActionBar.DISPLAY_SHOW_CUSTOM);
137            }
138
139            final int height = a.getLayoutDimension(R.styleable.ActionBar_height, 0);
140            if (height > 0) {
141                final ViewGroup.LayoutParams lp = mToolbar.getLayoutParams();
142                lp.height = height;
143                mToolbar.setLayoutParams(lp);
144            }
145
146            final int contentInsetStart = a.getDimensionPixelOffset(
147                    R.styleable.ActionBar_contentInsetStart, -1);
148            final int contentInsetEnd = a.getDimensionPixelOffset(
149                    R.styleable.ActionBar_contentInsetEnd, -1);
150            if (contentInsetStart >= 0 || contentInsetEnd >= 0) {
151                mToolbar.setContentInsetsRelative(Math.max(contentInsetStart, 0),
152                        Math.max(contentInsetEnd, 0));
153            }
154
155            final int titleTextStyle = a.getResourceId(R.styleable.ActionBar_titleTextStyle, 0);
156            if (titleTextStyle != 0) {
157                mToolbar.setTitleTextAppearance(mToolbar.getContext(), titleTextStyle);
158            }
159
160            final int subtitleTextStyle = a.getResourceId(
161                    R.styleable.ActionBar_subtitleTextStyle, 0);
162            if (subtitleTextStyle != 0) {
163                mToolbar.setSubtitleTextAppearance(mToolbar.getContext(), subtitleTextStyle);
164            }
165
166            final int popupTheme = a.getResourceId(R.styleable.ActionBar_popupTheme, 0);
167            if (popupTheme != 0) {
168                mToolbar.setPopupTheme(popupTheme);
169            }
170
171            a.recycle();
172        } else {
173            mDisplayOpts = detectDisplayOptions();
174        }
175
176        setDefaultNavigationContentDescription(defaultNavigationContentDescription);
177        mHomeDescription = mToolbar.getNavigationContentDescription();
178
179        mToolbar.setNavigationOnClickListener(new View.OnClickListener() {
180            final ActionMenuItem mNavItem = new ActionMenuItem(mToolbar.getContext(),
181                    0, android.R.id.home, 0, 0, mTitle);
182            @Override
183            public void onClick(View v) {
184                if (mWindowCallback != null && mMenuPrepared) {
185                    mWindowCallback.onMenuItemSelected(Window.FEATURE_OPTIONS_PANEL, mNavItem);
186                }
187            }
188        });
189    }
190
191    @Override
192    public void setDefaultNavigationContentDescription(int defaultNavigationContentDescription) {
193        if (defaultNavigationContentDescription == mDefaultNavigationContentDescription) {
194            return;
195        }
196        mDefaultNavigationContentDescription = defaultNavigationContentDescription;
197        if (TextUtils.isEmpty(mToolbar.getNavigationContentDescription())) {
198            setNavigationContentDescription(mDefaultNavigationContentDescription);
199        }
200    }
201
202    private int detectDisplayOptions() {
203        int opts = ActionBar.DISPLAY_SHOW_TITLE | ActionBar.DISPLAY_SHOW_HOME |
204                ActionBar.DISPLAY_USE_LOGO;
205        if (mToolbar.getNavigationIcon() != null) {
206            opts |= ActionBar.DISPLAY_HOME_AS_UP;
207        }
208        return opts;
209    }
210
211    @Override
212    public ViewGroup getViewGroup() {
213        return mToolbar;
214    }
215
216    @Override
217    public Context getContext() {
218        return mToolbar.getContext();
219    }
220
221    @Override
222    public boolean isSplit() {
223        return false;
224    }
225
226    @Override
227    public boolean hasExpandedActionView() {
228        return mToolbar.hasExpandedActionView();
229    }
230
231    @Override
232    public void collapseActionView() {
233        mToolbar.collapseActionView();
234    }
235
236    @Override
237    public void setWindowCallback(Window.Callback cb) {
238        mWindowCallback = cb;
239    }
240
241    @Override
242    public void setWindowTitle(CharSequence title) {
243        // "Real" title always trumps window title.
244        if (!mTitleSet) {
245            setTitleInt(title);
246        }
247    }
248
249    @Override
250    public CharSequence getTitle() {
251        return mToolbar.getTitle();
252    }
253
254    @Override
255    public void setTitle(CharSequence title) {
256        mTitleSet = true;
257        setTitleInt(title);
258    }
259
260    private void setTitleInt(CharSequence title) {
261        mTitle = title;
262        if ((mDisplayOpts & ActionBar.DISPLAY_SHOW_TITLE) != 0) {
263            mToolbar.setTitle(title);
264        }
265    }
266
267    @Override
268    public CharSequence getSubtitle() {
269        return mToolbar.getSubtitle();
270    }
271
272    @Override
273    public void setSubtitle(CharSequence subtitle) {
274        mSubtitle = subtitle;
275        if ((mDisplayOpts & ActionBar.DISPLAY_SHOW_TITLE) != 0) {
276            mToolbar.setSubtitle(subtitle);
277        }
278    }
279
280    @Override
281    public void initProgress() {
282        Log.i(TAG, "Progress display unsupported");
283    }
284
285    @Override
286    public void initIndeterminateProgress() {
287        Log.i(TAG, "Progress display unsupported");
288    }
289
290    @Override
291    public boolean canSplit() {
292        return false;
293    }
294
295    @Override
296    public void setSplitView(ViewGroup splitView) {
297    }
298
299    @Override
300    public void setSplitToolbar(boolean split) {
301        if (split) {
302            throw new UnsupportedOperationException("Cannot split an android.widget.Toolbar");
303        }
304    }
305
306    @Override
307    public void setSplitWhenNarrow(boolean splitWhenNarrow) {
308        // Ignore.
309    }
310
311    @Override
312    public boolean hasIcon() {
313        return mIcon != null;
314    }
315
316    @Override
317    public boolean hasLogo() {
318        return mLogo != null;
319    }
320
321    @Override
322    public void setIcon(int resId) {
323        setIcon(resId != 0 ? getContext().getDrawable(resId) : null);
324    }
325
326    @Override
327    public void setIcon(Drawable d) {
328        mIcon = d;
329        updateToolbarLogo();
330    }
331
332    @Override
333    public void setLogo(int resId) {
334        setLogo(resId != 0 ? getContext().getDrawable(resId) : null);
335    }
336
337    @Override
338    public void setLogo(Drawable d) {
339        mLogo = d;
340        updateToolbarLogo();
341    }
342
343    private void updateToolbarLogo() {
344        Drawable logo = null;
345        if ((mDisplayOpts & ActionBar.DISPLAY_SHOW_HOME) != 0) {
346            if ((mDisplayOpts & ActionBar.DISPLAY_USE_LOGO) != 0) {
347                logo = mLogo != null ? mLogo : mIcon;
348            } else {
349                logo = mIcon;
350            }
351        }
352        mToolbar.setLogo(logo);
353    }
354
355    @Override
356    public boolean canShowOverflowMenu() {
357        return mToolbar.canShowOverflowMenu();
358    }
359
360    @Override
361    public boolean isOverflowMenuShowing() {
362        return mToolbar.isOverflowMenuShowing();
363    }
364
365    @Override
366    public boolean isOverflowMenuShowPending() {
367        return mToolbar.isOverflowMenuShowPending();
368    }
369
370    @Override
371    public boolean showOverflowMenu() {
372        return mToolbar.showOverflowMenu();
373    }
374
375    @Override
376    public boolean hideOverflowMenu() {
377        return mToolbar.hideOverflowMenu();
378    }
379
380    @Override
381    public void setMenuPrepared() {
382        mMenuPrepared = true;
383    }
384
385    @Override
386    public void setMenu(Menu menu, MenuPresenter.Callback cb) {
387        if (mActionMenuPresenter == null) {
388            mActionMenuPresenter = new ActionMenuPresenter(mToolbar.getContext());
389            mActionMenuPresenter.setId(com.android.internal.R.id.action_menu_presenter);
390        }
391        mActionMenuPresenter.setCallback(cb);
392        mToolbar.setMenu((MenuBuilder) menu, mActionMenuPresenter);
393    }
394
395    @Override
396    public void dismissPopupMenus() {
397        mToolbar.dismissPopupMenus();
398    }
399
400    @Override
401    public int getDisplayOptions() {
402        return mDisplayOpts;
403    }
404
405    @Override
406    public void setDisplayOptions(int newOpts) {
407        final int oldOpts = mDisplayOpts;
408        final int changed = oldOpts ^ newOpts;
409        mDisplayOpts = newOpts;
410        if (changed != 0) {
411            if ((changed & ActionBar.DISPLAY_HOME_AS_UP) != 0) {
412                if ((newOpts & ActionBar.DISPLAY_HOME_AS_UP) != 0) {
413                    mToolbar.setNavigationIcon(mNavIcon);
414                    updateHomeAccessibility();
415                } else {
416                    mToolbar.setNavigationIcon(null);
417                }
418            }
419
420            if ((changed & AFFECTS_LOGO_MASK) != 0) {
421                updateToolbarLogo();
422            }
423
424            if ((changed & ActionBar.DISPLAY_SHOW_TITLE) != 0) {
425                if ((newOpts & ActionBar.DISPLAY_SHOW_TITLE) != 0) {
426                    mToolbar.setTitle(mTitle);
427                    mToolbar.setSubtitle(mSubtitle);
428                } else {
429                    mToolbar.setTitle(null);
430                    mToolbar.setSubtitle(null);
431                }
432            }
433
434            if ((changed & ActionBar.DISPLAY_SHOW_CUSTOM) != 0 && mCustomView != null) {
435                if ((newOpts & ActionBar.DISPLAY_SHOW_CUSTOM) != 0) {
436                    mToolbar.addView(mCustomView);
437                } else {
438                    mToolbar.removeView(mCustomView);
439                }
440            }
441        }
442    }
443
444    @Override
445    public void setEmbeddedTabView(ScrollingTabContainerView tabView) {
446        if (mTabView != null && mTabView.getParent() == mToolbar) {
447            mToolbar.removeView(mTabView);
448        }
449        mTabView = tabView;
450        if (tabView != null && mNavigationMode == ActionBar.NAVIGATION_MODE_TABS) {
451            mToolbar.addView(mTabView, 0);
452            Toolbar.LayoutParams lp = (Toolbar.LayoutParams) mTabView.getLayoutParams();
453            lp.width = ViewGroup.LayoutParams.WRAP_CONTENT;
454            lp.height = ViewGroup.LayoutParams.WRAP_CONTENT;
455            lp.gravity = Gravity.START | Gravity.BOTTOM;
456            tabView.setAllowCollapse(true);
457        }
458    }
459
460    @Override
461    public boolean hasEmbeddedTabs() {
462        return mTabView != null;
463    }
464
465    @Override
466    public boolean isTitleTruncated() {
467        return mToolbar.isTitleTruncated();
468    }
469
470    @Override
471    public void setCollapsible(boolean collapsible) {
472        mToolbar.setCollapsible(collapsible);
473    }
474
475    @Override
476    public void setHomeButtonEnabled(boolean enable) {
477        // Ignore
478    }
479
480    @Override
481    public int getNavigationMode() {
482        return mNavigationMode;
483    }
484
485    @Override
486    public void setNavigationMode(int mode) {
487        final int oldMode = mNavigationMode;
488        if (mode != oldMode) {
489            switch (oldMode) {
490                case ActionBar.NAVIGATION_MODE_LIST:
491                    if (mSpinner != null && mSpinner.getParent() == mToolbar) {
492                        mToolbar.removeView(mSpinner);
493                    }
494                    break;
495                case ActionBar.NAVIGATION_MODE_TABS:
496                    if (mTabView != null && mTabView.getParent() == mToolbar) {
497                        mToolbar.removeView(mTabView);
498                    }
499                    break;
500            }
501
502            mNavigationMode = mode;
503
504            switch (mode) {
505                case ActionBar.NAVIGATION_MODE_STANDARD:
506                    break;
507                case ActionBar.NAVIGATION_MODE_LIST:
508                    ensureSpinner();
509                    mToolbar.addView(mSpinner, 0);
510                    break;
511                case ActionBar.NAVIGATION_MODE_TABS:
512                    if (mTabView != null) {
513                        mToolbar.addView(mTabView, 0);
514                        Toolbar.LayoutParams lp = (Toolbar.LayoutParams) mTabView.getLayoutParams();
515                        lp.width = ViewGroup.LayoutParams.WRAP_CONTENT;
516                        lp.height = ViewGroup.LayoutParams.WRAP_CONTENT;
517                        lp.gravity = Gravity.START | Gravity.BOTTOM;
518                    }
519                    break;
520                default:
521                    throw new IllegalArgumentException("Invalid navigation mode " + mode);
522            }
523        }
524    }
525
526    private void ensureSpinner() {
527        if (mSpinner == null) {
528            mSpinner = new Spinner(getContext(), null, R.attr.actionDropDownStyle);
529            Toolbar.LayoutParams lp = new Toolbar.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
530                    ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.START | Gravity.CENTER_VERTICAL);
531            mSpinner.setLayoutParams(lp);
532        }
533    }
534
535    @Override
536    public void setDropdownParams(SpinnerAdapter adapter,
537            AdapterView.OnItemSelectedListener listener) {
538        ensureSpinner();
539        mSpinner.setAdapter(adapter);
540        mSpinner.setOnItemSelectedListener(listener);
541    }
542
543    @Override
544    public void setDropdownSelectedPosition(int position) {
545        if (mSpinner == null) {
546            throw new IllegalStateException(
547                    "Can't set dropdown selected position without an adapter");
548        }
549        mSpinner.setSelection(position);
550    }
551
552    @Override
553    public int getDropdownSelectedPosition() {
554        return mSpinner != null ? mSpinner.getSelectedItemPosition() : 0;
555    }
556
557    @Override
558    public int getDropdownItemCount() {
559        return mSpinner != null ? mSpinner.getCount() : 0;
560    }
561
562    @Override
563    public void setCustomView(View view) {
564        if (mCustomView != null && (mDisplayOpts & ActionBar.DISPLAY_SHOW_CUSTOM) != 0) {
565            mToolbar.removeView(mCustomView);
566        }
567        mCustomView = view;
568        if (view != null && (mDisplayOpts & ActionBar.DISPLAY_SHOW_CUSTOM) != 0) {
569            mToolbar.addView(mCustomView);
570        }
571    }
572
573    @Override
574    public View getCustomView() {
575        return mCustomView;
576    }
577
578    @Override
579    public void animateToVisibility(int visibility) {
580        if (visibility == View.GONE) {
581            mToolbar.animate().alpha(0)
582                    .setListener(new AnimatorListenerAdapter() {
583                        private boolean mCanceled = false;
584                        @Override
585                        public void onAnimationEnd(Animator animation) {
586                            if (!mCanceled) {
587                                mToolbar.setVisibility(View.GONE);
588                            }
589                        }
590
591                        @Override
592                        public void onAnimationCancel(Animator animation) {
593                            mCanceled = true;
594                        }
595                    });
596        } else if (visibility == View.VISIBLE) {
597            mToolbar.animate().alpha(1)
598                    .setListener(new AnimatorListenerAdapter() {
599                        @Override
600                        public void onAnimationStart(Animator animation) {
601                            mToolbar.setVisibility(View.VISIBLE);
602                        }
603                    });
604        }
605    }
606
607    @Override
608    public void setNavigationIcon(Drawable icon) {
609        mNavIcon = icon;
610        if ((mDisplayOpts & ActionBar.DISPLAY_HOME_AS_UP) != 0) {
611            mToolbar.setNavigationIcon(icon);
612        }
613    }
614
615    @Override
616    public void setNavigationIcon(int resId) {
617        setNavigationIcon(resId != 0 ? mToolbar.getContext().getDrawable(resId) : null);
618    }
619
620    @Override
621    public void setNavigationContentDescription(CharSequence description) {
622        mHomeDescription = description;
623        updateHomeAccessibility();
624    }
625
626    @Override
627    public void setNavigationContentDescription(int resId) {
628        setNavigationContentDescription(resId == 0 ? null : getContext().getString(resId));
629    }
630
631    private void updateHomeAccessibility() {
632        if ((mDisplayOpts & ActionBar.DISPLAY_HOME_AS_UP) != 0) {
633            if (TextUtils.isEmpty(mHomeDescription)) {
634                mToolbar.setNavigationContentDescription(mDefaultNavigationContentDescription);
635            } else {
636                mToolbar.setNavigationContentDescription(mHomeDescription);
637            }
638        }
639    }
640
641    @Override
642    public void saveHierarchyState(SparseArray<Parcelable> toolbarStates) {
643        mToolbar.saveHierarchyState(toolbarStates);
644    }
645
646    @Override
647    public void restoreHierarchyState(SparseArray<Parcelable> toolbarStates) {
648        mToolbar.restoreHierarchyState(toolbarStates);
649    }
650
651}
652