TabBar.java revision 541f55a0178da7c09fb32aa5385effae031ea071
1/*
2 * Copyright (C) 2010 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
17package com.android.browser;
18
19import com.android.browser.BrowserWebView.ScrollListener;
20
21import android.animation.Animator;
22import android.animation.Animator.AnimatorListener;
23import android.animation.AnimatorSet;
24import android.animation.ObjectAnimator;
25import android.app.Activity;
26import android.content.Context;
27import android.content.res.Configuration;
28import android.content.res.Resources;
29import android.graphics.Bitmap;
30import android.graphics.BitmapShader;
31import android.graphics.Canvas;
32import android.graphics.Matrix;
33import android.graphics.Paint;
34import android.graphics.Path;
35import android.graphics.Shader;
36import android.graphics.drawable.BitmapDrawable;
37import android.graphics.drawable.Drawable;
38import android.graphics.drawable.LayerDrawable;
39import android.graphics.drawable.PaintDrawable;
40import android.view.ContextMenu;
41import android.view.Gravity;
42import android.view.LayoutInflater;
43import android.view.MenuInflater;
44import android.view.View;
45import android.view.View.OnClickListener;
46import android.webkit.WebView;
47import android.widget.ImageButton;
48import android.widget.ImageView;
49import android.widget.LinearLayout;
50import android.widget.TextView;
51
52import java.util.HashMap;
53import java.util.List;
54import java.util.Map;
55
56/**
57 * tabbed title bar for xlarge screen browser
58 */
59public class TabBar extends LinearLayout
60        implements ScrollListener, OnClickListener {
61
62    private static final int PROGRESS_MAX = 100;
63
64    private Activity mActivity;
65    private UiController mUiController;
66    private TabControl mTabControl;
67    private XLargeUi mUi;
68
69    private int mTabWidthSelected;
70    private int mTabWidthUnselected;
71
72    private TabScrollView mTabs;
73
74    private ImageButton mNewTab;
75    private int mButtonWidth;
76
77    private Map<Tab, TabView> mTabMap;
78
79    private Drawable mGenericFavicon;
80
81    private int mCurrentTextureWidth = 0;
82    private int mCurrentTextureHeight = 0;
83
84    private Drawable mActiveDrawable;
85    private Drawable mInactiveDrawable;
86
87    private final Paint mActiveShaderPaint = new Paint();
88    private final Paint mInactiveShaderPaint = new Paint();
89    private final Paint mFocusPaint = new Paint();
90    private final Matrix mActiveMatrix = new Matrix();
91    private final Matrix mInactiveMatrix = new Matrix();
92
93    private BitmapShader mActiveShader;
94    private BitmapShader mInactiveShader;
95
96    private int mTabOverlap;
97    private int mAddTabOverlap;
98    private int mTabSliceWidth;
99    private boolean mUseQuickControls;
100
101    public TabBar(Activity activity, UiController controller, XLargeUi ui) {
102        super(activity);
103        mActivity = activity;
104        mUiController = controller;
105        mTabControl = mUiController.getTabControl();
106        mUi = ui;
107        Resources res = activity.getResources();
108        mTabWidthSelected = (int) res.getDimension(R.dimen.tab_width_selected);
109        mTabWidthUnselected = (int) res.getDimension(R.dimen.tab_width_unselected);
110        mActiveDrawable = res.getDrawable(R.drawable.bg_urlbar);
111        mInactiveDrawable = res.getDrawable(R.drawable.browsertab_inactive);
112
113        mTabMap = new HashMap<Tab, TabView>();
114        Resources resources = activity.getResources();
115        LayoutInflater factory = LayoutInflater.from(activity);
116        factory.inflate(R.layout.tab_bar, this);
117        setPadding(0, (int) res.getDimension(R.dimen.tab_padding_top), 0, 0);
118        mTabs = (TabScrollView) findViewById(R.id.tabs);
119        mNewTab = (ImageButton) findViewById(R.id.newtab);
120        mNewTab.setOnClickListener(this);
121        mGenericFavicon = res.getDrawable(R.drawable.app_web_browser_sm);
122
123        updateTabs(mUiController.getTabs());
124        mButtonWidth = -1;
125        // tab dimensions
126        mTabOverlap = (int) res.getDimension(R.dimen.tab_overlap);
127        mAddTabOverlap = (int) res.getDimension(R.dimen.tab_addoverlap);
128        mTabSliceWidth = (int) res.getDimension(R.dimen.tab_slice);
129
130        mActiveShaderPaint.setStyle(Paint.Style.FILL);
131        mActiveShaderPaint.setAntiAlias(true);
132
133        mInactiveShaderPaint.setStyle(Paint.Style.FILL);
134        mInactiveShaderPaint.setAntiAlias(true);
135
136        mFocusPaint.setStyle(Paint.Style.STROKE);
137        mFocusPaint.setStrokeWidth(res.getDimension(R.dimen.tab_focus_stroke));
138        mFocusPaint.setAntiAlias(true);
139        mFocusPaint.setColor(res.getColor(R.color.tabFocusHighlight));
140    }
141
142    @Override
143    public void onConfigurationChanged(Configuration config) {
144        super.onConfigurationChanged(config);
145        Resources res = mActivity.getResources();
146        mTabWidthSelected = (int) res.getDimension(R.dimen.tab_width_selected);
147        mTabWidthUnselected = (int) res.getDimension(R.dimen.tab_width_unselected);
148        // force update of tab bar
149        mTabs.updateLayout();
150    }
151
152    void setUseQuickControls(boolean useQuickControls) {
153        mUseQuickControls = useQuickControls;
154        mNewTab.setVisibility(mUseQuickControls ? View.GONE
155                : View.VISIBLE);
156    }
157
158    int getTabCount() {
159        return mTabMap.size();
160    }
161
162    void updateTabs(List<Tab> tabs) {
163        mTabs.clearTabs();
164        mTabMap.clear();
165        for (Tab tab : tabs) {
166            TabView tv = buildTabView(tab);
167            mTabs.addTab(tv);
168        }
169        mTabs.setSelectedTab(mTabControl.getCurrentPosition());
170    }
171
172    @Override
173    protected void onMeasure(int hspec, int vspec) {
174        super.onMeasure(hspec, vspec);
175        int w = getMeasuredWidth();
176        // adjust for new tab overlap
177        if (!mUseQuickControls) {
178            w -= mAddTabOverlap;
179        }
180        setMeasuredDimension(w, getMeasuredHeight());
181    }
182
183    @Override
184    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
185        // use paddingLeft and paddingTop
186        int pl = getPaddingLeft();
187        int pt = getPaddingTop();
188        int sw = mTabs.getMeasuredWidth();
189        int w = right - left - pl;
190        if (mUseQuickControls) {
191            mButtonWidth = 0;
192        } else {
193            mButtonWidth = mNewTab.getMeasuredWidth() - mAddTabOverlap;
194            if (w-sw < mButtonWidth) {
195                sw = w - mButtonWidth;
196            }
197        }
198        mTabs.layout(pl, pt, pl + sw, bottom - top);
199        // adjust for overlap
200        if (!mUseQuickControls) {
201            mNewTab.layout(pl + sw - mAddTabOverlap, pt,
202                    pl + sw + mButtonWidth - mAddTabOverlap, bottom - top);
203        }
204    }
205
206    public void onClick(View view) {
207        if (mNewTab == view) {
208            mUiController.openTabToHomePage();
209        } else if (mTabs.getSelectedTab() == view) {
210            if (mUseQuickControls) {
211                if (mUi.isTitleBarShowing() && !isLoading()) {
212                    mUi.stopEditingUrl();
213                    mUi.hideTitleBar();
214                } else {
215                    mUi.stopWebViewScrolling();
216                    mUi.editUrl(false);
217                }
218            } else if (mUi.isTitleBarShowing() && !isLoading()) {
219                mUi.stopEditingUrl();
220                mUi.hideTitleBar();
221            } else {
222                showUrlBar();
223            }
224        } else if (view instanceof TabView) {
225            final Tab tab = ((TabView) view).mTab;
226            int ix = mTabs.getChildIndex(view);
227            if (ix >= 0) {
228                mTabs.setSelectedTab(ix);
229                mUiController.switchToTab(tab);
230            }
231        }
232    }
233
234    private void showUrlBar() {
235        mUi.stopWebViewScrolling();
236        mUi.showTitleBar();
237    }
238
239    void showTitleBarIndicator(boolean show) {
240        Tab tab = mTabControl.getCurrentTab();
241        if (tab != null && !tab.isSnapshot()) {
242            TabView tv = mTabMap.get(tab);
243            if (tv != null) {
244                tv.showIndicator(show);
245            }
246        }
247    }
248
249    boolean showsTitleBarIndicator() {
250        Tab tab = mTabControl.getCurrentTab();
251        if (tab != null) {
252            TabView tv = mTabMap.get(tab);
253            if (tv != null) {
254                return tv.showsIndicator();
255            }
256        }
257        return false;
258    }
259
260    // callback after fake titlebar is shown
261    void onShowTitleBar() {
262        showTitleBarIndicator(false);
263    }
264
265    // callback after fake titlebar is hidden
266    void onHideTitleBar() {
267        Tab tab = mTabControl.getCurrentTab();
268        WebView w = tab.getWebView();
269        if (w != null) {
270            showTitleBarIndicator(w.getVisibleTitleHeight() == 0);
271        }
272    }
273
274    // webview scroll listener
275
276    @Override
277    public void onScroll(int visibleTitleHeight, boolean userInitiated) {
278        if (mUseQuickControls) return;
279        // isLoading is using the current tab, which initially might not be set yet
280        if (mTabControl.getCurrentTab() != null
281                && !isLoading()) {
282            if (visibleTitleHeight == 0) {
283                if (!showsTitleBarIndicator()
284                        && (!mUi.isEditingUrl() || userInitiated)) {
285                    mUi.hideTitleBar();
286                    showTitleBarIndicator(true);
287                }
288            } else {
289                if (showsTitleBarIndicator()) {
290                    showTitleBarIndicator(false);
291                }
292            }
293        }
294    }
295
296    @Override
297    public void createContextMenu(ContextMenu menu) {
298        MenuInflater inflater = mActivity.getMenuInflater();
299        inflater.inflate(R.menu.title_context, menu);
300        mActivity.onCreateContextMenu(menu, this, null);
301    }
302
303    private TabView buildTabView(Tab tab) {
304        TabView tabview = new TabView(mActivity, tab);
305        mTabMap.put(tab, tabview);
306        tabview.setOnClickListener(this);
307        return tabview;
308    }
309
310    private static Bitmap getDrawableAsBitmap(Drawable drawable, int width, int height) {
311        Bitmap b = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
312        Canvas c = new Canvas(b);
313        drawable.setBounds(0, 0, width, height);
314        drawable.draw(c);
315        return b;
316    }
317
318    /**
319     * View used in the tab bar
320     */
321    class TabView extends LinearLayout implements OnClickListener {
322
323        Tab mTab;
324        View mTabContent;
325        TextView mTitle;
326        View mIndicator;
327        View mIncognito;
328        View mSnapshot;
329        ImageView mIconView;
330        ImageView mLock;
331        ImageView mClose;
332        boolean mSelected;
333        boolean mInLoad;
334        Path mPath;
335        Path mFocusPath;
336        int[] mWindowPos;
337
338        /**
339         * @param context
340         */
341        public TabView(Context context, Tab tab) {
342            super(context);
343            setWillNotDraw(false);
344            mPath = new Path();
345            mFocusPath = new Path();
346            mWindowPos = new int[2];
347            mTab = tab;
348            setGravity(Gravity.CENTER_VERTICAL);
349            setOrientation(LinearLayout.HORIZONTAL);
350            setPadding(mTabOverlap, 0, mTabSliceWidth, 0);
351            LayoutInflater inflater = LayoutInflater.from(getContext());
352            mTabContent = inflater.inflate(R.layout.tab_title, this, true);
353            mTitle = (TextView) mTabContent.findViewById(R.id.title);
354            mIconView = (ImageView) mTabContent.findViewById(R.id.favicon);
355            mLock = (ImageView) mTabContent.findViewById(R.id.lock);
356            mClose = (ImageView) mTabContent.findViewById(R.id.close);
357            mClose.setOnClickListener(this);
358            mIncognito = mTabContent.findViewById(R.id.incognito);
359            mSnapshot = mTabContent.findViewById(R.id.snapshot);
360            mIndicator = mTabContent.findViewById(R.id.chevron);
361            mSelected = false;
362            mInLoad = false;
363            // update the status
364            updateFromTab();
365        }
366
367        void showIndicator(boolean show) {
368            if (mSelected) {
369                mIndicator.setVisibility(show ? View.VISIBLE : View.GONE);
370                LayoutParams lp = (LinearLayout.LayoutParams) getLayoutParams();
371                if (show) {
372                    lp.width = mTabWidthSelected + mIndicator.getWidth();
373                } else {
374                    lp.width = mTabWidthSelected;
375                }
376                lp.height =  LayoutParams.MATCH_PARENT;
377                setLayoutParams(lp);
378            } else {
379                mIndicator.setVisibility(View.GONE);
380            }
381        }
382
383        boolean showsIndicator() {
384            return (mIndicator.getVisibility() == View.VISIBLE);
385        }
386
387        @Override
388        public void onClick(View v) {
389            if (v == mClose) {
390                closeTab();
391            }
392        }
393
394        private void updateFromTab() {
395            String displayTitle = mTab.getTitle();
396            if (displayTitle == null) {
397                displayTitle = mTab.getUrl();
398            }
399            setDisplayTitle(displayTitle);
400            setProgress(mTab.getLoadProgress());
401            if (mTab.getFavicon() != null) {
402                setFavicon(renderFavicon(mTab.getFavicon()));
403            }
404            updateTabIcons();
405        }
406
407        private void updateTabIcons() {
408            mIncognito.setVisibility(
409                    mTab.isPrivateBrowsingEnabled() ?
410                    View.VISIBLE : View.GONE);
411            mSnapshot.setVisibility(mTab.isSnapshot()
412                    ? View.VISIBLE : View.GONE);
413        }
414
415        @Override
416        public void setActivated(boolean selected) {
417            mSelected = selected;
418            mClose.setVisibility(mSelected ? View.VISIBLE : View.GONE);
419            mIndicator.setVisibility(View.GONE);
420            mTitle.setTextAppearance(mActivity, mSelected ?
421                    R.style.TabTitleSelected : R.style.TabTitleUnselected);
422            setHorizontalFadingEdgeEnabled(!mSelected);
423            super.setActivated(selected);
424            updateLayoutParams();
425            setFocusable(!selected);
426            postInvalidate();
427        }
428
429        public void updateLayoutParams() {
430            LayoutParams lp = (LinearLayout.LayoutParams) getLayoutParams();
431            lp.width = mSelected ? mTabWidthSelected : mTabWidthUnselected;
432            lp.height =  LayoutParams.MATCH_PARENT;
433            setLayoutParams(lp);
434        }
435
436        void setDisplayTitle(String title) {
437            mTitle.setText(title);
438        }
439
440        void setFavicon(Drawable d) {
441            mIconView.setImageDrawable(d);
442        }
443
444        void setLock(Drawable d) {
445            if (null == d) {
446                mLock.setVisibility(View.GONE);
447            } else {
448                mLock.setImageDrawable(d);
449                mLock.setVisibility(View.VISIBLE);
450            }
451        }
452
453        void setProgress(int newProgress) {
454            if (newProgress >= PROGRESS_MAX) {
455                mInLoad = false;
456            } else {
457                if (!mInLoad && getWindowToken() != null) {
458                    mInLoad = true;
459                }
460            }
461        }
462
463        private void closeTab() {
464            if (mTab == mTabControl.getCurrentTab()) {
465                mUiController.closeCurrentTab();
466            } else {
467                mUiController.closeTab(mTab);
468            }
469        }
470
471        @Override
472        protected void onLayout(boolean changed, int l, int t, int r, int b) {
473            super.onLayout(changed, l, t, r, b);
474            setTabPath(mPath, 0, 0, r - l, b - t);
475            setFocusPath(mFocusPath, 0, 0, r - l, b - t);
476        }
477
478        @Override
479        protected void dispatchDraw(Canvas canvas) {
480            if (mCurrentTextureWidth != mUi.getContentWidth() ||
481                    mCurrentTextureHeight != getHeight()) {
482                mCurrentTextureWidth = mUi.getContentWidth();
483                mCurrentTextureHeight = getHeight();
484
485                if (mCurrentTextureWidth > 0 && mCurrentTextureHeight > 0) {
486                    Bitmap activeTexture = getDrawableAsBitmap(mActiveDrawable,
487                            mCurrentTextureWidth, mCurrentTextureHeight);
488                    Bitmap inactiveTexture = getDrawableAsBitmap(mInactiveDrawable,
489                            mCurrentTextureWidth, mCurrentTextureHeight);
490
491                    mActiveShader = new BitmapShader(activeTexture,
492                            Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
493                    mActiveShaderPaint.setShader(mActiveShader);
494
495                    mInactiveShader = new BitmapShader(inactiveTexture,
496                            Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
497                    mInactiveShaderPaint.setShader(mInactiveShader);
498                }
499            }
500            // add some monkey protection
501            if ((mActiveShader != null) && (mInactiveShader != null)) {
502                int state = canvas.save();
503                getLocationInWindow(mWindowPos);
504                Paint paint = mSelected ? mActiveShaderPaint : mInactiveShaderPaint;
505                drawClipped(canvas, paint, mPath, mWindowPos[0]);
506                canvas.restoreToCount(state);
507            }
508            super.dispatchDraw(canvas);
509        }
510
511        private void drawClipped(Canvas canvas, Paint paint, Path clipPath, int left) {
512            // TODO: We should change the matrix/shader only when needed
513            final Matrix matrix = mSelected ? mActiveMatrix : mInactiveMatrix;
514            matrix.setTranslate(-left, 0.0f);
515            (mSelected ? mActiveShader : mInactiveShader).setLocalMatrix(matrix);
516            canvas.drawPath(clipPath, paint);
517            if (isFocused()) {
518                canvas.drawPath(mFocusPath, mFocusPaint);
519            }
520        }
521
522        private void setTabPath(Path path, int l, int t, int r, int b) {
523            path.reset();
524            path.moveTo(l, b);
525            path.lineTo(l, t);
526            path.lineTo(r - mTabSliceWidth, t);
527            path.lineTo(r, b);
528            path.close();
529        }
530
531        private void setFocusPath(Path path, int l, int t, int r, int b) {
532            path.reset();
533            path.moveTo(l, b);
534            path.lineTo(l, t);
535            path.lineTo(r - mTabSliceWidth, t);
536            path.lineTo(r, b);
537        }
538
539    }
540
541    static Drawable createFaviconBackground(Context context) {
542        PaintDrawable faviconBackground = new PaintDrawable();
543        Resources res = context.getResources();
544        faviconBackground.getPaint().setColor(context.getResources()
545                .getColor(R.color.tabFaviconBackground));
546        faviconBackground.setCornerRadius(
547                res.getDimension(R.dimen.tab_favicon_corner_radius));
548        return faviconBackground;
549    }
550
551    private Drawable renderFavicon(Bitmap icon) {
552        Drawable[] array = new Drawable[2];
553        array[0] = createFaviconBackground(getContext());
554        if (icon == null) {
555            array[1] = mGenericFavicon;
556        } else {
557            array[1] = new BitmapDrawable(icon);
558        }
559        LayerDrawable d = new LayerDrawable(array);
560        d.setLayerInset(1, 2, 2, 2, 2);
561        return d;
562    }
563
564    private void animateTabOut(final Tab tab, final TabView tv) {
565        ObjectAnimator scalex = ObjectAnimator.ofFloat(tv, "scaleX", 1.0f, 0.0f);
566        ObjectAnimator scaley = ObjectAnimator.ofFloat(tv, "scaleY", 1.0f, 0.0f);
567        ObjectAnimator alpha = ObjectAnimator.ofFloat(tv, "alpha", 1.0f, 0.0f);
568        AnimatorSet animator = new AnimatorSet();
569        animator.playTogether(scalex, scaley, alpha);
570        animator.setDuration(150);
571        animator.addListener(new AnimatorListener() {
572
573            @Override
574            public void onAnimationCancel(Animator animation) {
575            }
576
577            @Override
578            public void onAnimationEnd(Animator animation) {
579                mTabs.removeTab(tv);
580                mTabMap.remove(tab);
581                mUi.onRemoveTabCompleted(tab);
582            }
583
584            @Override
585            public void onAnimationRepeat(Animator animation) {
586            }
587
588            @Override
589            public void onAnimationStart(Animator animation) {
590            }
591
592        });
593        animator.start();
594    }
595
596    private void animateTabIn(final Tab tab, final TabView tv) {
597        ObjectAnimator scalex = ObjectAnimator.ofFloat(tv, "scaleX", 0.0f, 1.0f);
598        scalex.setDuration(150);
599        scalex.addListener(new AnimatorListener() {
600
601            @Override
602            public void onAnimationCancel(Animator animation) {
603            }
604
605            @Override
606            public void onAnimationEnd(Animator animation) {
607                mUi.onAddTabCompleted(tab);
608            }
609
610            @Override
611            public void onAnimationRepeat(Animator animation) {
612            }
613
614            @Override
615            public void onAnimationStart(Animator animation) {
616                mTabs.addTab(tv);
617            }
618
619        });
620        scalex.start();
621    }
622
623    // TabChangeListener implementation
624
625    public void onSetActiveTab(Tab tab) {
626        mTabs.setSelectedTab(mTabControl.getTabPosition(tab));
627        TabView tv = mTabMap.get(tab);
628        if (tv != null) {
629            tv.setProgress(tv.mTab.getLoadProgress());
630            // update the scroll state
631            WebView webview = tab.getWebView();
632            if (webview != null) {
633                int h = webview.getVisibleTitleHeight();
634                onScroll(h, true);
635            }
636        }
637    }
638
639    public void onFavicon(Tab tab, Bitmap favicon) {
640        TabView tv = mTabMap.get(tab);
641        if (tv != null) {
642            tv.setFavicon(renderFavicon(favicon));
643        }
644    }
645
646    public void onNewTab(Tab tab) {
647        TabView tv = buildTabView(tab);
648        animateTabIn(tab, tv);
649    }
650
651    public void onProgress(Tab tab, int progress) {
652        TabView tv = mTabMap.get(tab);
653        if (tv != null) {
654            tv.setProgress(progress);
655        }
656    }
657
658    public void onRemoveTab(Tab tab) {
659        TabView tv = mTabMap.get(tab);
660        if (tv != null) {
661            animateTabOut(tab, tv);
662        } else {
663            mTabMap.remove(tab);
664        }
665    }
666
667    public void onUrlAndTitle(Tab tab, String url, String title) {
668        TabView tv = mTabMap.get(tab);
669        if (tv != null) {
670            if (title != null) {
671                tv.setDisplayTitle(title);
672            } else if (url != null) {
673                tv.setDisplayTitle(UrlUtils.stripUrl(url));
674            }
675            tv.updateTabIcons();
676        }
677    }
678
679    private boolean isLoading() {
680        TabView tv = mTabMap.get(mTabControl.getCurrentTab());
681        if (tv != null) {
682            return tv.mInLoad;
683        } else {
684            return false;
685        }
686    }
687
688}
689