ToolbarWidgetWrapper.java revision fe6d57c366ba78295d3320cb73512f02876eed61
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    private Drawable mDefaultNavigationIcon;
88
89    public ToolbarWidgetWrapper(Toolbar toolbar, boolean style) {
90        this(toolbar, style, R.string.action_bar_up_description);
91    }
92
93    public ToolbarWidgetWrapper(Toolbar toolbar, boolean style,
94            int defaultNavigationContentDescription) {
95        mToolbar = toolbar;
96
97        mTitle = toolbar.getTitle();
98        mSubtitle = toolbar.getSubtitle();
99        mTitleSet = mTitle != null;
100        final TypedArray a = toolbar.getContext().obtainStyledAttributes(null,
101                R.styleable.ActionBar, R.attr.actionBarStyle, 0);
102        mDefaultNavigationIcon = a.getDrawable(R.styleable.ActionBar_homeAsUpIndicator);
103        if (style) {
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            if (mDefaultNavigationIcon != null) {
124                setNavigationIcon(mDefaultNavigationIcon);
125            }
126            setDisplayOptions(a.getInt(R.styleable.ActionBar_displayOptions, 0));
127
128            final int customNavId = a.getResourceId(
129                    R.styleable.ActionBar_customNavigationLayout, 0);
130            if (customNavId != 0) {
131                setCustomView(LayoutInflater.from(mToolbar.getContext()).inflate(customNavId,
132                        mToolbar, false));
133                setDisplayOptions(mDisplayOpts | ActionBar.DISPLAY_SHOW_CUSTOM);
134            }
135
136            final int height = a.getLayoutDimension(R.styleable.ActionBar_height, 0);
137            if (height > 0) {
138                final ViewGroup.LayoutParams lp = mToolbar.getLayoutParams();
139                lp.height = height;
140                mToolbar.setLayoutParams(lp);
141            }
142
143            final int contentInsetStart = a.getDimensionPixelOffset(
144                    R.styleable.ActionBar_contentInsetStart, -1);
145            final int contentInsetEnd = a.getDimensionPixelOffset(
146                    R.styleable.ActionBar_contentInsetEnd, -1);
147            if (contentInsetStart >= 0 || contentInsetEnd >= 0) {
148                mToolbar.setContentInsetsRelative(Math.max(contentInsetStart, 0),
149                        Math.max(contentInsetEnd, 0));
150            }
151
152            final int titleTextStyle = a.getResourceId(R.styleable.ActionBar_titleTextStyle, 0);
153            if (titleTextStyle != 0) {
154                mToolbar.setTitleTextAppearance(mToolbar.getContext(), titleTextStyle);
155            }
156
157            final int subtitleTextStyle = a.getResourceId(
158                    R.styleable.ActionBar_subtitleTextStyle, 0);
159            if (subtitleTextStyle != 0) {
160                mToolbar.setSubtitleTextAppearance(mToolbar.getContext(), subtitleTextStyle);
161            }
162
163            final int popupTheme = a.getResourceId(R.styleable.ActionBar_popupTheme, 0);
164            if (popupTheme != 0) {
165                mToolbar.setPopupTheme(popupTheme);
166            }
167        } else {
168            mDisplayOpts = detectDisplayOptions();
169        }
170        a.recycle();
171
172        setDefaultNavigationContentDescription(defaultNavigationContentDescription);
173        mHomeDescription = mToolbar.getNavigationContentDescription();
174
175        mToolbar.setNavigationOnClickListener(new View.OnClickListener() {
176            final ActionMenuItem mNavItem = new ActionMenuItem(mToolbar.getContext(),
177                    0, android.R.id.home, 0, 0, mTitle);
178            @Override
179            public void onClick(View v) {
180                if (mWindowCallback != null && mMenuPrepared) {
181                    mWindowCallback.onMenuItemSelected(Window.FEATURE_OPTIONS_PANEL, mNavItem);
182                }
183            }
184        });
185    }
186
187    @Override
188    public void setDefaultNavigationContentDescription(int defaultNavigationContentDescription) {
189        if (defaultNavigationContentDescription == mDefaultNavigationContentDescription) {
190            return;
191        }
192        mDefaultNavigationContentDescription = defaultNavigationContentDescription;
193        if (TextUtils.isEmpty(mToolbar.getNavigationContentDescription())) {
194            setNavigationContentDescription(mDefaultNavigationContentDescription);
195        }
196    }
197
198    private int detectDisplayOptions() {
199        int opts = ActionBar.DISPLAY_SHOW_TITLE | ActionBar.DISPLAY_SHOW_HOME |
200                ActionBar.DISPLAY_USE_LOGO;
201        if (mToolbar.getNavigationIcon() != null) {
202            opts |= ActionBar.DISPLAY_HOME_AS_UP;
203            mDefaultNavigationIcon = mToolbar.getNavigationIcon();
204        }
205        return opts;
206    }
207
208    @Override
209    public ViewGroup getViewGroup() {
210        return mToolbar;
211    }
212
213    @Override
214    public Context getContext() {
215        return mToolbar.getContext();
216    }
217
218    @Override
219    public boolean isSplit() {
220        return false;
221    }
222
223    @Override
224    public boolean hasExpandedActionView() {
225        return mToolbar.hasExpandedActionView();
226    }
227
228    @Override
229    public void collapseActionView() {
230        mToolbar.collapseActionView();
231    }
232
233    @Override
234    public void setWindowCallback(Window.Callback cb) {
235        mWindowCallback = cb;
236    }
237
238    @Override
239    public void setWindowTitle(CharSequence title) {
240        // "Real" title always trumps window title.
241        if (!mTitleSet) {
242            setTitleInt(title);
243        }
244    }
245
246    @Override
247    public CharSequence getTitle() {
248        return mToolbar.getTitle();
249    }
250
251    @Override
252    public void setTitle(CharSequence title) {
253        mTitleSet = true;
254        setTitleInt(title);
255    }
256
257    private void setTitleInt(CharSequence title) {
258        mTitle = title;
259        if ((mDisplayOpts & ActionBar.DISPLAY_SHOW_TITLE) != 0) {
260            mToolbar.setTitle(title);
261        }
262    }
263
264    @Override
265    public CharSequence getSubtitle() {
266        return mToolbar.getSubtitle();
267    }
268
269    @Override
270    public void setSubtitle(CharSequence subtitle) {
271        mSubtitle = subtitle;
272        if ((mDisplayOpts & ActionBar.DISPLAY_SHOW_TITLE) != 0) {
273            mToolbar.setSubtitle(subtitle);
274        }
275    }
276
277    @Override
278    public void initProgress() {
279        Log.i(TAG, "Progress display unsupported");
280    }
281
282    @Override
283    public void initIndeterminateProgress() {
284        Log.i(TAG, "Progress display unsupported");
285    }
286
287    @Override
288    public boolean canSplit() {
289        return false;
290    }
291
292    @Override
293    public void setSplitView(ViewGroup splitView) {
294    }
295
296    @Override
297    public void setSplitToolbar(boolean split) {
298        if (split) {
299            throw new UnsupportedOperationException("Cannot split an android.widget.Toolbar");
300        }
301    }
302
303    @Override
304    public void setSplitWhenNarrow(boolean splitWhenNarrow) {
305        // Ignore.
306    }
307
308    @Override
309    public boolean hasIcon() {
310        return mIcon != null;
311    }
312
313    @Override
314    public boolean hasLogo() {
315        return mLogo != null;
316    }
317
318    @Override
319    public void setIcon(int resId) {
320        setIcon(resId != 0 ? getContext().getDrawable(resId) : null);
321    }
322
323    @Override
324    public void setIcon(Drawable d) {
325        mIcon = d;
326        updateToolbarLogo();
327    }
328
329    @Override
330    public void setLogo(int resId) {
331        setLogo(resId != 0 ? getContext().getDrawable(resId) : null);
332    }
333
334    @Override
335    public void setLogo(Drawable d) {
336        mLogo = d;
337        updateToolbarLogo();
338    }
339
340    private void updateToolbarLogo() {
341        Drawable logo = null;
342        if ((mDisplayOpts & ActionBar.DISPLAY_SHOW_HOME) != 0) {
343            if ((mDisplayOpts & ActionBar.DISPLAY_USE_LOGO) != 0) {
344                logo = mLogo != null ? mLogo : mIcon;
345            } else {
346                logo = mIcon;
347            }
348        }
349        mToolbar.setLogo(logo);
350    }
351
352    @Override
353    public boolean canShowOverflowMenu() {
354        return mToolbar.canShowOverflowMenu();
355    }
356
357    @Override
358    public boolean isOverflowMenuShowing() {
359        return mToolbar.isOverflowMenuShowing();
360    }
361
362    @Override
363    public boolean isOverflowMenuShowPending() {
364        return mToolbar.isOverflowMenuShowPending();
365    }
366
367    @Override
368    public boolean showOverflowMenu() {
369        return mToolbar.showOverflowMenu();
370    }
371
372    @Override
373    public boolean hideOverflowMenu() {
374        return mToolbar.hideOverflowMenu();
375    }
376
377    @Override
378    public void setMenuPrepared() {
379        mMenuPrepared = true;
380    }
381
382    @Override
383    public void setMenu(Menu menu, MenuPresenter.Callback cb) {
384        if (mActionMenuPresenter == null) {
385            mActionMenuPresenter = new ActionMenuPresenter(mToolbar.getContext());
386            mActionMenuPresenter.setId(com.android.internal.R.id.action_menu_presenter);
387        }
388        mActionMenuPresenter.setCallback(cb);
389        mToolbar.setMenu((MenuBuilder) menu, mActionMenuPresenter);
390    }
391
392    @Override
393    public void dismissPopupMenus() {
394        mToolbar.dismissPopupMenus();
395    }
396
397    @Override
398    public int getDisplayOptions() {
399        return mDisplayOpts;
400    }
401
402    @Override
403    public void setDisplayOptions(int newOpts) {
404        final int oldOpts = mDisplayOpts;
405        final int changed = oldOpts ^ newOpts;
406        mDisplayOpts = newOpts;
407        if (changed != 0) {
408            if ((changed & ActionBar.DISPLAY_HOME_AS_UP) != 0) {
409                if ((newOpts & ActionBar.DISPLAY_HOME_AS_UP) != 0) {
410                    updateHomeAccessibility();
411                }
412                updateNavigationIcon();
413            }
414
415            if ((changed & AFFECTS_LOGO_MASK) != 0) {
416                updateToolbarLogo();
417            }
418
419            if ((changed & ActionBar.DISPLAY_SHOW_TITLE) != 0) {
420                if ((newOpts & ActionBar.DISPLAY_SHOW_TITLE) != 0) {
421                    mToolbar.setTitle(mTitle);
422                    mToolbar.setSubtitle(mSubtitle);
423                } else {
424                    mToolbar.setTitle(null);
425                    mToolbar.setSubtitle(null);
426                }
427            }
428
429            if ((changed & ActionBar.DISPLAY_SHOW_CUSTOM) != 0 && mCustomView != null) {
430                if ((newOpts & ActionBar.DISPLAY_SHOW_CUSTOM) != 0) {
431                    mToolbar.addView(mCustomView);
432                } else {
433                    mToolbar.removeView(mCustomView);
434                }
435            }
436        }
437    }
438
439    @Override
440    public void setEmbeddedTabView(ScrollingTabContainerView tabView) {
441        if (mTabView != null && mTabView.getParent() == mToolbar) {
442            mToolbar.removeView(mTabView);
443        }
444        mTabView = tabView;
445        if (tabView != null && mNavigationMode == ActionBar.NAVIGATION_MODE_TABS) {
446            mToolbar.addView(mTabView, 0);
447            Toolbar.LayoutParams lp = (Toolbar.LayoutParams) mTabView.getLayoutParams();
448            lp.width = ViewGroup.LayoutParams.WRAP_CONTENT;
449            lp.height = ViewGroup.LayoutParams.WRAP_CONTENT;
450            lp.gravity = Gravity.START | Gravity.BOTTOM;
451            tabView.setAllowCollapse(true);
452        }
453    }
454
455    @Override
456    public boolean hasEmbeddedTabs() {
457        return mTabView != null;
458    }
459
460    @Override
461    public boolean isTitleTruncated() {
462        return mToolbar.isTitleTruncated();
463    }
464
465    @Override
466    public void setCollapsible(boolean collapsible) {
467        mToolbar.setCollapsible(collapsible);
468    }
469
470    @Override
471    public void setHomeButtonEnabled(boolean enable) {
472        // Ignore
473    }
474
475    @Override
476    public int getNavigationMode() {
477        return mNavigationMode;
478    }
479
480    @Override
481    public void setNavigationMode(int mode) {
482        final int oldMode = mNavigationMode;
483        if (mode != oldMode) {
484            switch (oldMode) {
485                case ActionBar.NAVIGATION_MODE_LIST:
486                    if (mSpinner != null && mSpinner.getParent() == mToolbar) {
487                        mToolbar.removeView(mSpinner);
488                    }
489                    break;
490                case ActionBar.NAVIGATION_MODE_TABS:
491                    if (mTabView != null && mTabView.getParent() == mToolbar) {
492                        mToolbar.removeView(mTabView);
493                    }
494                    break;
495            }
496
497            mNavigationMode = mode;
498
499            switch (mode) {
500                case ActionBar.NAVIGATION_MODE_STANDARD:
501                    break;
502                case ActionBar.NAVIGATION_MODE_LIST:
503                    ensureSpinner();
504                    mToolbar.addView(mSpinner, 0);
505                    break;
506                case ActionBar.NAVIGATION_MODE_TABS:
507                    if (mTabView != null) {
508                        mToolbar.addView(mTabView, 0);
509                        Toolbar.LayoutParams lp = (Toolbar.LayoutParams) mTabView.getLayoutParams();
510                        lp.width = ViewGroup.LayoutParams.WRAP_CONTENT;
511                        lp.height = ViewGroup.LayoutParams.WRAP_CONTENT;
512                        lp.gravity = Gravity.START | Gravity.BOTTOM;
513                    }
514                    break;
515                default:
516                    throw new IllegalArgumentException("Invalid navigation mode " + mode);
517            }
518        }
519    }
520
521    private void ensureSpinner() {
522        if (mSpinner == null) {
523            mSpinner = new Spinner(getContext(), null, R.attr.actionDropDownStyle);
524            Toolbar.LayoutParams lp = new Toolbar.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
525                    ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.START | Gravity.CENTER_VERTICAL);
526            mSpinner.setLayoutParams(lp);
527        }
528    }
529
530    @Override
531    public void setDropdownParams(SpinnerAdapter adapter,
532            AdapterView.OnItemSelectedListener listener) {
533        ensureSpinner();
534        mSpinner.setAdapter(adapter);
535        mSpinner.setOnItemSelectedListener(listener);
536    }
537
538    @Override
539    public void setDropdownSelectedPosition(int position) {
540        if (mSpinner == null) {
541            throw new IllegalStateException(
542                    "Can't set dropdown selected position without an adapter");
543        }
544        mSpinner.setSelection(position);
545    }
546
547    @Override
548    public int getDropdownSelectedPosition() {
549        return mSpinner != null ? mSpinner.getSelectedItemPosition() : 0;
550    }
551
552    @Override
553    public int getDropdownItemCount() {
554        return mSpinner != null ? mSpinner.getCount() : 0;
555    }
556
557    @Override
558    public void setCustomView(View view) {
559        if (mCustomView != null && (mDisplayOpts & ActionBar.DISPLAY_SHOW_CUSTOM) != 0) {
560            mToolbar.removeView(mCustomView);
561        }
562        mCustomView = view;
563        if (view != null && (mDisplayOpts & ActionBar.DISPLAY_SHOW_CUSTOM) != 0) {
564            mToolbar.addView(mCustomView);
565        }
566    }
567
568    @Override
569    public View getCustomView() {
570        return mCustomView;
571    }
572
573    @Override
574    public void animateToVisibility(int visibility) {
575        if (visibility == View.GONE) {
576            mToolbar.animate().alpha(0)
577                    .setListener(new AnimatorListenerAdapter() {
578                        private boolean mCanceled = false;
579                        @Override
580                        public void onAnimationEnd(Animator animation) {
581                            if (!mCanceled) {
582                                mToolbar.setVisibility(View.GONE);
583                            }
584                        }
585
586                        @Override
587                        public void onAnimationCancel(Animator animation) {
588                            mCanceled = true;
589                        }
590                    });
591        } else if (visibility == View.VISIBLE) {
592            mToolbar.animate().alpha(1)
593                    .setListener(new AnimatorListenerAdapter() {
594                        @Override
595                        public void onAnimationStart(Animator animation) {
596                            mToolbar.setVisibility(View.VISIBLE);
597                        }
598                    });
599        }
600    }
601
602    @Override
603    public void setNavigationIcon(Drawable icon) {
604        mNavIcon = icon;
605        updateNavigationIcon();
606    }
607
608    @Override
609    public void setNavigationIcon(int resId) {
610        setNavigationIcon(resId != 0 ? mToolbar.getContext().getDrawable(resId) : null);
611    }
612
613    @Override
614    public void setDefaultNavigationIcon(Drawable defaultNavigationIcon) {
615        if (mDefaultNavigationIcon != defaultNavigationIcon) {
616            mDefaultNavigationIcon = defaultNavigationIcon;
617            updateNavigationIcon();
618        }
619    }
620
621    private void updateNavigationIcon() {
622        if ((mDisplayOpts & ActionBar.DISPLAY_HOME_AS_UP) != 0) {
623            mToolbar.setNavigationIcon(mNavIcon != null ? mNavIcon : mDefaultNavigationIcon);
624        } else {
625            mToolbar.setNavigationIcon(null);
626        }
627    }
628
629    @Override
630    public void setNavigationContentDescription(CharSequence description) {
631        mHomeDescription = description;
632        updateHomeAccessibility();
633    }
634
635    @Override
636    public void setNavigationContentDescription(int resId) {
637        setNavigationContentDescription(resId == 0 ? null : getContext().getString(resId));
638    }
639
640    private void updateHomeAccessibility() {
641        if ((mDisplayOpts & ActionBar.DISPLAY_HOME_AS_UP) != 0) {
642            if (TextUtils.isEmpty(mHomeDescription)) {
643                mToolbar.setNavigationContentDescription(mDefaultNavigationContentDescription);
644            } else {
645                mToolbar.setNavigationContentDescription(mHomeDescription);
646            }
647        }
648    }
649
650    @Override
651    public void saveHierarchyState(SparseArray<Parcelable> toolbarStates) {
652        mToolbar.saveHierarchyState(toolbarStates);
653    }
654
655    @Override
656    public void restoreHierarchyState(SparseArray<Parcelable> toolbarStates) {
657        mToolbar.restoreHierarchyState(toolbarStates);
658    }
659
660    @Override
661    public void setBackgroundDrawable(Drawable d) {
662        //noinspection deprecation
663        mToolbar.setBackgroundDrawable(d);
664    }
665
666    @Override
667    public int getHeight() {
668        return mToolbar.getHeight();
669    }
670
671    @Override
672    public void setVisibility(int visible) {
673        mToolbar.setVisibility(visible);
674    }
675
676    @Override
677    public int getVisibility() {
678        return mToolbar.getVisibility();
679    }
680
681    @Override
682    public void setMenuCallbacks(MenuPresenter.Callback presenterCallback,
683            MenuBuilder.Callback menuBuilderCallback) {
684        mToolbar.setMenuCallbacks(presenterCallback, menuBuilderCallback);
685    }
686
687    @Override
688    public Menu getMenu() {
689        return mToolbar.getMenu();
690    }
691
692}
693