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