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