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