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 
17 package com.android.browser;
18 
19 import android.animation.Animator;
20 import android.animation.Animator.AnimatorListener;
21 import android.animation.AnimatorSet;
22 import android.animation.ObjectAnimator;
23 import android.app.Activity;
24 import android.content.Context;
25 import android.content.res.Configuration;
26 import android.content.res.Resources;
27 import android.graphics.Bitmap;
28 import android.graphics.BitmapShader;
29 import android.graphics.Canvas;
30 import android.graphics.Matrix;
31 import android.graphics.Paint;
32 import android.graphics.Path;
33 import android.graphics.Shader;
34 import android.graphics.drawable.Drawable;
35 import android.view.Gravity;
36 import android.view.LayoutInflater;
37 import android.view.View;
38 import android.view.View.OnClickListener;
39 import android.widget.ImageButton;
40 import android.widget.ImageView;
41 import android.widget.LinearLayout;
42 import android.widget.TextView;
43 
44 import java.util.HashMap;
45 import java.util.List;
46 import java.util.Map;
47 
48 /**
49  * tabbed title bar for xlarge screen browser
50  */
51 public class TabBar extends LinearLayout implements OnClickListener {
52 
53     private static final int PROGRESS_MAX = 100;
54 
55     private Activity mActivity;
56     private UiController mUiController;
57     private TabControl mTabControl;
58     private XLargeUi mUi;
59 
60     private int mTabWidth;
61 
62     private TabScrollView mTabs;
63 
64     private ImageButton mNewTab;
65     private int mButtonWidth;
66 
67     private Map<Tab, TabView> mTabMap;
68 
69     private int mCurrentTextureWidth = 0;
70     private int mCurrentTextureHeight = 0;
71 
72     private Drawable mActiveDrawable;
73     private Drawable mInactiveDrawable;
74 
75     private final Paint mActiveShaderPaint = new Paint();
76     private final Paint mInactiveShaderPaint = new Paint();
77     private final Paint mFocusPaint = new Paint();
78     private final Matrix mActiveMatrix = new Matrix();
79     private final Matrix mInactiveMatrix = new Matrix();
80 
81     private BitmapShader mActiveShader;
82     private BitmapShader mInactiveShader;
83 
84     private int mTabOverlap;
85     private int mAddTabOverlap;
86     private int mTabSliceWidth;
87     private boolean mUseQuickControls;
88 
TabBar(Activity activity, UiController controller, XLargeUi ui)89     public TabBar(Activity activity, UiController controller, XLargeUi ui) {
90         super(activity);
91         mActivity = activity;
92         mUiController = controller;
93         mTabControl = mUiController.getTabControl();
94         mUi = ui;
95         Resources res = activity.getResources();
96         mTabWidth = (int) res.getDimension(R.dimen.tab_width);
97         mActiveDrawable = res.getDrawable(R.drawable.bg_urlbar);
98         mInactiveDrawable = res.getDrawable(R.drawable.browsertab_inactive);
99 
100         mTabMap = new HashMap<Tab, TabView>();
101         LayoutInflater factory = LayoutInflater.from(activity);
102         factory.inflate(R.layout.tab_bar, this);
103         setPadding(0, (int) res.getDimension(R.dimen.tab_padding_top), 0, 0);
104         mTabs = (TabScrollView) findViewById(R.id.tabs);
105         mNewTab = (ImageButton) findViewById(R.id.newtab);
106         mNewTab.setOnClickListener(this);
107 
108         updateTabs(mUiController.getTabs());
109         mButtonWidth = -1;
110         // tab dimensions
111         mTabOverlap = (int) res.getDimension(R.dimen.tab_overlap);
112         mAddTabOverlap = (int) res.getDimension(R.dimen.tab_addoverlap);
113         mTabSliceWidth = (int) res.getDimension(R.dimen.tab_slice);
114 
115         mActiveShaderPaint.setStyle(Paint.Style.FILL);
116         mActiveShaderPaint.setAntiAlias(true);
117 
118         mInactiveShaderPaint.setStyle(Paint.Style.FILL);
119         mInactiveShaderPaint.setAntiAlias(true);
120 
121         mFocusPaint.setStyle(Paint.Style.STROKE);
122         mFocusPaint.setStrokeWidth(res.getDimension(R.dimen.tab_focus_stroke));
123         mFocusPaint.setAntiAlias(true);
124         mFocusPaint.setColor(res.getColor(R.color.tabFocusHighlight));
125     }
126 
127     @Override
onConfigurationChanged(Configuration config)128     public void onConfigurationChanged(Configuration config) {
129         super.onConfigurationChanged(config);
130         Resources res = mActivity.getResources();
131         mTabWidth = (int) res.getDimension(R.dimen.tab_width);
132         // force update of tab bar
133         mTabs.updateLayout();
134     }
135 
setUseQuickControls(boolean useQuickControls)136     void setUseQuickControls(boolean useQuickControls) {
137         mUseQuickControls = useQuickControls;
138         mNewTab.setVisibility(mUseQuickControls ? View.GONE
139                 : View.VISIBLE);
140     }
141 
getTabCount()142     int getTabCount() {
143         return mTabMap.size();
144     }
145 
updateTabs(List<Tab> tabs)146     void updateTabs(List<Tab> tabs) {
147         mTabs.clearTabs();
148         mTabMap.clear();
149         for (Tab tab : tabs) {
150             TabView tv = buildTabView(tab);
151             mTabs.addTab(tv);
152         }
153         mTabs.setSelectedTab(mTabControl.getCurrentPosition());
154     }
155 
156     @Override
onMeasure(int hspec, int vspec)157     protected void onMeasure(int hspec, int vspec) {
158         super.onMeasure(hspec, vspec);
159         int w = getMeasuredWidth();
160         // adjust for new tab overlap
161         if (!mUseQuickControls) {
162             w -= mAddTabOverlap;
163         }
164         setMeasuredDimension(w, getMeasuredHeight());
165     }
166 
167     @Override
onLayout(boolean changed, int left, int top, int right, int bottom)168     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
169         // use paddingLeft and paddingTop
170         int pl = getPaddingLeft();
171         int pt = getPaddingTop();
172         int sw = mTabs.getMeasuredWidth();
173         int w = right - left - pl;
174         if (mUseQuickControls) {
175             mButtonWidth = 0;
176         } else {
177             mButtonWidth = mNewTab.getMeasuredWidth() - mAddTabOverlap;
178             if (w-sw < mButtonWidth) {
179                 sw = w - mButtonWidth;
180             }
181         }
182         mTabs.layout(pl, pt, pl + sw, bottom - top);
183         // adjust for overlap
184         if (!mUseQuickControls) {
185             mNewTab.layout(pl + sw - mAddTabOverlap, pt,
186                     pl + sw + mButtonWidth - mAddTabOverlap, bottom - top);
187         }
188     }
189 
onClick(View view)190     public void onClick(View view) {
191         if (mNewTab == view) {
192             mUiController.openTabToHomePage();
193         } else if (mTabs.getSelectedTab() == view) {
194             if (mUseQuickControls) {
195                 if (mUi.isTitleBarShowing() && !isLoading()) {
196                     mUi.stopEditingUrl();
197                     mUi.hideTitleBar();
198                 } else {
199                     mUi.stopWebViewScrolling();
200                     mUi.editUrl(false, false);
201                 }
202             } else if (mUi.isTitleBarShowing() && !isLoading()) {
203                 mUi.stopEditingUrl();
204                 mUi.hideTitleBar();
205             } else {
206                 showUrlBar();
207             }
208         } else if (view instanceof TabView) {
209             final Tab tab = ((TabView) view).mTab;
210             int ix = mTabs.getChildIndex(view);
211             if (ix >= 0) {
212                 mTabs.setSelectedTab(ix);
213                 mUiController.switchToTab(tab);
214             }
215         }
216     }
217 
showUrlBar()218     private void showUrlBar() {
219         mUi.stopWebViewScrolling();
220         mUi.showTitleBar();
221     }
222 
buildTabView(Tab tab)223     private TabView buildTabView(Tab tab) {
224         TabView tabview = new TabView(mActivity, tab);
225         mTabMap.put(tab, tabview);
226         tabview.setOnClickListener(this);
227         return tabview;
228     }
229 
getDrawableAsBitmap(Drawable drawable, int width, int height)230     private static Bitmap getDrawableAsBitmap(Drawable drawable, int width, int height) {
231         Bitmap b = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
232         Canvas c = new Canvas(b);
233         drawable.setBounds(0, 0, width, height);
234         drawable.draw(c);
235         c.setBitmap(null);
236         return b;
237     }
238 
239     /**
240      * View used in the tab bar
241      */
242     class TabView extends LinearLayout implements OnClickListener {
243 
244         Tab mTab;
245         View mTabContent;
246         TextView mTitle;
247         View mIncognito;
248         View mSnapshot;
249         ImageView mIconView;
250         ImageView mLock;
251         ImageView mClose;
252         boolean mSelected;
253         Path mPath;
254         Path mFocusPath;
255         int[] mWindowPos;
256 
257         /**
258          * @param context
259          */
TabView(Context context, Tab tab)260         public TabView(Context context, Tab tab) {
261             super(context);
262             setWillNotDraw(false);
263             mPath = new Path();
264             mFocusPath = new Path();
265             mWindowPos = new int[2];
266             mTab = tab;
267             setGravity(Gravity.CENTER_VERTICAL);
268             setOrientation(LinearLayout.HORIZONTAL);
269             setPadding(mTabOverlap, 0, mTabSliceWidth, 0);
270             LayoutInflater inflater = LayoutInflater.from(getContext());
271             mTabContent = inflater.inflate(R.layout.tab_title, this, true);
272             mTitle = (TextView) mTabContent.findViewById(R.id.title);
273             mIconView = (ImageView) mTabContent.findViewById(R.id.favicon);
274             mLock = (ImageView) mTabContent.findViewById(R.id.lock);
275             mClose = (ImageView) mTabContent.findViewById(R.id.close);
276             mClose.setOnClickListener(this);
277             mIncognito = mTabContent.findViewById(R.id.incognito);
278             mSnapshot = mTabContent.findViewById(R.id.snapshot);
279             mSelected = false;
280             // update the status
281             updateFromTab();
282         }
283 
284         @Override
onClick(View v)285         public void onClick(View v) {
286             if (v == mClose) {
287                 closeTab();
288             }
289         }
290 
updateFromTab()291         private void updateFromTab() {
292             String displayTitle = mTab.getTitle();
293             if (displayTitle == null) {
294                 displayTitle = mTab.getUrl();
295             }
296             setDisplayTitle(displayTitle);
297             if (mTab.getFavicon() != null) {
298                 setFavicon(mUi.getFaviconDrawable(mTab.getFavicon()));
299             }
300             updateTabIcons();
301         }
302 
updateTabIcons()303         private void updateTabIcons() {
304             mIncognito.setVisibility(
305                     mTab.isPrivateBrowsingEnabled() ?
306                     View.VISIBLE : View.GONE);
307             mSnapshot.setVisibility(mTab.isSnapshot()
308                     ? View.VISIBLE : View.GONE);
309         }
310 
311         @Override
setActivated(boolean selected)312         public void setActivated(boolean selected) {
313             mSelected = selected;
314             mClose.setVisibility(mSelected ? View.VISIBLE : View.GONE);
315             mIconView.setVisibility(mSelected ? View.GONE : View.VISIBLE);
316             mTitle.setTextAppearance(mActivity, mSelected ?
317                     R.style.TabTitleSelected : R.style.TabTitleUnselected);
318             setHorizontalFadingEdgeEnabled(!mSelected);
319             super.setActivated(selected);
320             updateLayoutParams();
321             setFocusable(!selected);
322             postInvalidate();
323         }
324 
updateLayoutParams()325         public void updateLayoutParams() {
326             LayoutParams lp = (LinearLayout.LayoutParams) getLayoutParams();
327             lp.width = mTabWidth;
328             lp.height =  LayoutParams.MATCH_PARENT;
329             setLayoutParams(lp);
330         }
331 
setDisplayTitle(String title)332         void setDisplayTitle(String title) {
333             mTitle.setText(title);
334         }
335 
setFavicon(Drawable d)336         void setFavicon(Drawable d) {
337             mIconView.setImageDrawable(d);
338         }
339 
setLock(Drawable d)340         void setLock(Drawable d) {
341             if (null == d) {
342                 mLock.setVisibility(View.GONE);
343             } else {
344                 mLock.setImageDrawable(d);
345                 mLock.setVisibility(View.VISIBLE);
346             }
347         }
348 
closeTab()349         private void closeTab() {
350             if (mTab == mTabControl.getCurrentTab()) {
351                 mUiController.closeCurrentTab();
352             } else {
353                 mUiController.closeTab(mTab);
354             }
355         }
356 
357         @Override
onLayout(boolean changed, int l, int t, int r, int b)358         protected void onLayout(boolean changed, int l, int t, int r, int b) {
359             super.onLayout(changed, l, t, r, b);
360             setTabPath(mPath, 0, 0, r - l, b - t);
361             setFocusPath(mFocusPath, 0, 0, r - l, b - t);
362         }
363 
364         @Override
dispatchDraw(Canvas canvas)365         protected void dispatchDraw(Canvas canvas) {
366             if (mCurrentTextureWidth != mUi.getContentWidth() ||
367                     mCurrentTextureHeight != getHeight()) {
368                 mCurrentTextureWidth = mUi.getContentWidth();
369                 mCurrentTextureHeight = getHeight();
370 
371                 if (mCurrentTextureWidth > 0 && mCurrentTextureHeight > 0) {
372                     Bitmap activeTexture = getDrawableAsBitmap(mActiveDrawable,
373                             mCurrentTextureWidth, mCurrentTextureHeight);
374                     Bitmap inactiveTexture = getDrawableAsBitmap(mInactiveDrawable,
375                             mCurrentTextureWidth, mCurrentTextureHeight);
376 
377                     mActiveShader = new BitmapShader(activeTexture,
378                             Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
379                     mActiveShaderPaint.setShader(mActiveShader);
380 
381                     mInactiveShader = new BitmapShader(inactiveTexture,
382                             Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
383                     mInactiveShaderPaint.setShader(mInactiveShader);
384                 }
385             }
386             // add some monkey protection
387             if ((mActiveShader != null) && (mInactiveShader != null)) {
388                 int state = canvas.save();
389                 getLocationInWindow(mWindowPos);
390                 Paint paint = mSelected ? mActiveShaderPaint : mInactiveShaderPaint;
391                 drawClipped(canvas, paint, mPath, mWindowPos[0]);
392                 canvas.restoreToCount(state);
393             }
394             super.dispatchDraw(canvas);
395         }
396 
drawClipped(Canvas canvas, Paint paint, Path clipPath, int left)397         private void drawClipped(Canvas canvas, Paint paint, Path clipPath, int left) {
398             // TODO: We should change the matrix/shader only when needed
399             final Matrix matrix = mSelected ? mActiveMatrix : mInactiveMatrix;
400             matrix.setTranslate(-left, 0.0f);
401             final Shader shader = mSelected ? mActiveShader : mInactiveShader;
402             shader.setLocalMatrix(matrix);
403             paint.setShader(shader);
404             canvas.drawPath(clipPath, paint);
405             if (isFocused()) {
406                 canvas.drawPath(mFocusPath, mFocusPaint);
407             }
408         }
409 
setTabPath(Path path, int l, int t, int r, int b)410         private void setTabPath(Path path, int l, int t, int r, int b) {
411             path.reset();
412             path.moveTo(l, b);
413             path.lineTo(l, t);
414             path.lineTo(r - mTabSliceWidth, t);
415             path.lineTo(r, b);
416             path.close();
417         }
418 
setFocusPath(Path path, int l, int t, int r, int b)419         private void setFocusPath(Path path, int l, int t, int r, int b) {
420             path.reset();
421             path.moveTo(l, b);
422             path.lineTo(l, t);
423             path.lineTo(r - mTabSliceWidth, t);
424             path.lineTo(r, b);
425         }
426 
427     }
428 
animateTabOut(final Tab tab, final TabView tv)429     private void animateTabOut(final Tab tab, final TabView tv) {
430         ObjectAnimator scalex = ObjectAnimator.ofFloat(tv, "scaleX", 1.0f, 0.0f);
431         ObjectAnimator scaley = ObjectAnimator.ofFloat(tv, "scaleY", 1.0f, 0.0f);
432         ObjectAnimator alpha = ObjectAnimator.ofFloat(tv, "alpha", 1.0f, 0.0f);
433         AnimatorSet animator = new AnimatorSet();
434         animator.playTogether(scalex, scaley, alpha);
435         animator.setDuration(150);
436         animator.addListener(new AnimatorListener() {
437 
438             @Override
439             public void onAnimationCancel(Animator animation) {
440             }
441 
442             @Override
443             public void onAnimationEnd(Animator animation) {
444                 mTabs.removeTab(tv);
445                 mTabMap.remove(tab);
446                 mUi.onRemoveTabCompleted(tab);
447             }
448 
449             @Override
450             public void onAnimationRepeat(Animator animation) {
451             }
452 
453             @Override
454             public void onAnimationStart(Animator animation) {
455             }
456 
457         });
458         animator.start();
459     }
460 
animateTabIn(final Tab tab, final TabView tv)461     private void animateTabIn(final Tab tab, final TabView tv) {
462         ObjectAnimator scalex = ObjectAnimator.ofFloat(tv, "scaleX", 0.0f, 1.0f);
463         scalex.setDuration(150);
464         scalex.addListener(new AnimatorListener() {
465 
466             @Override
467             public void onAnimationCancel(Animator animation) {
468             }
469 
470             @Override
471             public void onAnimationEnd(Animator animation) {
472                 mUi.onAddTabCompleted(tab);
473             }
474 
475             @Override
476             public void onAnimationRepeat(Animator animation) {
477             }
478 
479             @Override
480             public void onAnimationStart(Animator animation) {
481                 mTabs.addTab(tv);
482             }
483 
484         });
485         scalex.start();
486     }
487 
488     // TabChangeListener implementation
489 
onSetActiveTab(Tab tab)490     public void onSetActiveTab(Tab tab) {
491         mTabs.setSelectedTab(mTabControl.getTabPosition(tab));
492     }
493 
onFavicon(Tab tab, Bitmap favicon)494     public void onFavicon(Tab tab, Bitmap favicon) {
495         TabView tv = mTabMap.get(tab);
496         if (tv != null) {
497             tv.setFavicon(mUi.getFaviconDrawable(favicon));
498         }
499     }
500 
onNewTab(Tab tab)501     public void onNewTab(Tab tab) {
502         TabView tv = buildTabView(tab);
503         animateTabIn(tab, tv);
504     }
505 
onRemoveTab(Tab tab)506     public void onRemoveTab(Tab tab) {
507         TabView tv = mTabMap.get(tab);
508         if (tv != null) {
509             animateTabOut(tab, tv);
510         } else {
511             mTabMap.remove(tab);
512         }
513     }
514 
onUrlAndTitle(Tab tab, String url, String title)515     public void onUrlAndTitle(Tab tab, String url, String title) {
516         TabView tv = mTabMap.get(tab);
517         if (tv != null) {
518             if (title != null) {
519                 tv.setDisplayTitle(title);
520             } else if (url != null) {
521                 tv.setDisplayTitle(UrlUtils.stripUrl(url));
522             }
523             tv.updateTabIcons();
524         }
525     }
526 
isLoading()527     private boolean isLoading() {
528         Tab tab = mTabControl.getCurrentTab();
529         if (tab != null) {
530             return tab.inPageLoad();
531         } else {
532             return false;
533         }
534     }
535 
536 }
537