1 /* This Source Code Form is subject to the terms of the Mozilla Public
2  * License, v. 2.0. If a copy of the MPL was not distributed with this
3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 
5 package org.mozilla.gecko.tabs;
6 
7 import org.mozilla.gecko.R;
8 import org.mozilla.gecko.Tab;
9 import org.mozilla.gecko.Tabs;
10 import org.mozilla.gecko.icons.IconResponse;
11 import org.mozilla.gecko.icons.Icons;
12 import org.mozilla.gecko.widget.FaviconView;
13 import org.mozilla.gecko.widget.HoverDelegateWithReset;
14 import org.mozilla.gecko.widget.TabThumbnailWrapper;
15 import org.mozilla.gecko.widget.TouchDelegateWithReset;
16 import org.mozilla.gecko.widget.themed.ThemedRelativeLayout;
17 
18 import android.content.Context;
19 import android.content.res.Resources;
20 import android.graphics.Rect;
21 import android.graphics.drawable.Drawable;
22 import android.support.v7.widget.ViewUtils;
23 import android.text.TextUtils;
24 import android.util.AttributeSet;
25 import android.util.TypedValue;
26 import android.view.MotionEvent;
27 import android.view.View;
28 import android.widget.Checkable;
29 import android.widget.ImageView;
30 import android.widget.LinearLayout;
31 import android.widget.TextView;
32 
33 import java.util.concurrent.Future;
34 
35 public class TabsLayoutItemView extends LinearLayout
36                                 implements Checkable {
37     private static final String LOGTAG = "Gecko" + TabsLayoutItemView.class.getSimpleName();
38     private static final int[] STATE_CHECKED = { android.R.attr.state_checked };
39     private boolean mChecked;
40 
41     private int mTabId;
42     private TextView mTitle;
43     private TabsPanelThumbnailView mThumbnail;
44     private ImageView mCloseButton;
45     private TabThumbnailWrapper mThumbnailWrapper;
46     private HoverDelegateWithReset mHoverDelegate;
47 
48     private FaviconView mFaviconView;
49     private Future<IconResponse> mOngoingIconLoad;
50 
TabsLayoutItemView(Context context, AttributeSet attrs)51     public TabsLayoutItemView(Context context, AttributeSet attrs) {
52         super(context, attrs);
53     }
54 
55     @Override
onCreateDrawableState(int extraSpace)56     public int[] onCreateDrawableState(int extraSpace) {
57         final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
58 
59         if (mChecked) {
60             mergeDrawableStates(drawableState, STATE_CHECKED);
61         }
62 
63         return drawableState;
64     }
65 
66     @Override
isEnabled()67     public boolean isEnabled() {
68         return true;
69     }
70 
71     @Override
isChecked()72     public boolean isChecked() {
73         return mChecked;
74     }
75 
76     @Override
setChecked(boolean checked)77     public void setChecked(boolean checked) {
78         if (mChecked == checked) {
79             return;
80         }
81 
82         mChecked = checked;
83         refreshDrawableState();
84 
85         int count = getChildCount();
86         for (int i = 0; i < count; i++) {
87             final View child = getChildAt(i);
88             if (child instanceof Checkable) {
89                 ((Checkable) child).setChecked(checked);
90             }
91         }
92     }
93 
94     @Override
toggle()95     public void toggle() {
96         mChecked = !mChecked;
97     }
98 
setCloseOnClickListener(OnClickListener mOnClickListener)99     public void setCloseOnClickListener(OnClickListener mOnClickListener) {
100         mCloseButton.setOnClickListener(mOnClickListener);
101     }
102 
103     @Override
onFinishInflate()104     protected void onFinishInflate() {
105         super.onFinishInflate();
106         mTitle = (TextView) findViewById(R.id.title);
107         mThumbnail = (TabsPanelThumbnailView) findViewById(R.id.thumbnail);
108         mCloseButton = (ImageView) findViewById(R.id.close);
109         mThumbnailWrapper = (TabThumbnailWrapper) findViewById(R.id.wrapper);
110         mFaviconView = (FaviconView) findViewById(R.id.favicon);
111 
112         growCloseButtonHitArea();
113     }
114 
growCloseButtonHitArea()115     private void growCloseButtonHitArea() {
116         addOnLayoutChangeListener(new OnLayoutChangeListener() {
117             @Override
118             public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
119                 // Ideally we want the close button hit area to be 40x40dp but we are constrained by the height of the parent, so
120                 // we make it as tall as the parent view and 40dp across.
121                 final int targetHitArea = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 40, getResources().getDisplayMetrics());
122 
123                 final Rect hitRect = getHitRectRelatively(targetHitArea);
124 
125                 setTouchDelegate(new TouchDelegateWithReset(hitRect, mCloseButton));
126                 setHoverDelegate(new HoverDelegateWithReset(hitRect, mCloseButton));
127             }
128         });
129     }
130 
getHitRectRelatively(int targetHitArea)131     private Rect getHitRectRelatively(int targetHitArea) {
132         final boolean isRtl = ViewUtils.isLayoutRtl(this);
133         final Rect hitRect = new Rect();
134         hitRect.top = 0;
135         hitRect.right = isRtl ? targetHitArea : getWidth();
136         hitRect.left = isRtl ? 0 : getWidth() - targetHitArea;
137         hitRect.bottom = targetHitArea;
138         return hitRect;
139     }
140 
141     /**
142      * Sets the HoverDelegate for this View.
143      */
setHoverDelegate(HoverDelegateWithReset delegate)144     public void setHoverDelegate(HoverDelegateWithReset delegate) {
145         mHoverDelegate = delegate;
146     }
147 
148     @Override
onHoverEvent(MotionEvent event)149     public boolean onHoverEvent(MotionEvent event) {
150         if (mHoverDelegate != null) {
151             if (mHoverDelegate.onHoverEvent(event)) {
152                 return true;
153             }
154         }
155 
156         return super.onHoverEvent(event);
157     }
158 
assignValues(Tab tab)159     protected void assignValues(Tab tab)  {
160         if (tab == null) {
161             return;
162         }
163 
164         mTabId = tab.getId();
165 
166         setChecked(Tabs.getInstance().isSelectedTab(tab));
167 
168         Drawable thumbnailImage = tab.getThumbnail();
169         mThumbnail.setImageDrawable(thumbnailImage);
170 
171         mThumbnail.setPrivateMode(tab.isPrivate());
172 
173         if (mThumbnailWrapper != null) {
174             mThumbnailWrapper.setRecording(tab.isRecording());
175         }
176 
177         final String tabTitle = tab.getDisplayTitle();
178         mTitle.setText(tabTitle);
179         mCloseButton.setTag(this);
180 
181         if (tab.isAudioPlaying()) {
182             mFaviconView.setImageResource(R.drawable.tab_audio_playing);
183             final String tabTitleWithAudio =
184                     getResources().getString(R.string.tab_title_prefix_is_playing_audio, tabTitle);
185             mTitle.setContentDescription(tabTitleWithAudio);
186         } else {
187             final String url = tab.getURL();
188             if (TextUtils.isEmpty(url)) {
189                 // Ignore loading favicon without url.
190                 return;
191             }
192 
193             if (mOngoingIconLoad != null) {
194                 mOngoingIconLoad.cancel(true);
195             }
196 
197             final Resources resources = getResources();
198             final int iconSize = resources.getDimensionPixelSize(R.dimen.tab_favicon_size);
199             final float textSize = resources.getDimensionPixelSize(R.dimen.tab_favicon_text_size);
200 
201             final Context appContext = getContext().getApplicationContext();
202             mOngoingIconLoad = Icons.with(appContext)
203                                        .pageUrl(url)
204                                        .skipNetwork()
205                                        .targetSize(iconSize)
206                                        .textSize(textSize)
207                                        .build()
208                                        .execute(mFaviconView.createIconCallback());
209 
210             mTitle.setContentDescription(tabTitle);
211         }
212     }
213 
getTabId()214     public int getTabId() {
215         return mTabId;
216     }
217 
setThumbnail(Drawable thumbnail)218     public void setThumbnail(Drawable thumbnail) {
219         mThumbnail.setImageDrawable(thumbnail);
220     }
221 
setCloseVisible(boolean visible)222     public void setCloseVisible(boolean visible) {
223         mCloseButton.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
224     }
225 
setPrivateMode(boolean isPrivate)226     public void setPrivateMode(boolean isPrivate) {
227         ((ThemedRelativeLayout) findViewById(R.id.wrapper)).setPrivateMode(isPrivate);
228     }
229 }
230