1 /*
2  * Copyright (C) 2015 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.tv.menu;
18 
19 import android.content.Context;
20 import android.content.Intent;
21 import android.content.pm.ApplicationInfo;
22 import android.content.pm.PackageManager;
23 import android.graphics.Bitmap;
24 import android.graphics.Canvas;
25 import android.graphics.drawable.BitmapDrawable;
26 import android.graphics.drawable.Drawable;
27 import android.support.annotation.Nullable;
28 import android.support.v7.graphics.Palette;
29 import android.text.TextUtils;
30 import android.util.AttributeSet;
31 import android.util.Log;
32 import android.view.View;
33 import android.view.ViewGroup;
34 import android.widget.ImageView;
35 import android.widget.TextView;
36 
37 import com.android.tv.MainActivity;
38 import com.android.tv.R;
39 import com.android.tv.data.Channel;
40 import com.android.tv.util.BitmapUtils;
41 import com.android.tv.util.ImageLoader;
42 import com.android.tv.util.TvInputManagerHelper;
43 import com.android.tv.util.Utils;
44 
45 /**
46  * A view to render an app link card.
47  */
48 public class AppLinkCardView extends BaseCardView<Channel> {
49     private static final String TAG = MenuView.TAG;
50     private static final boolean DEBUG = MenuView.DEBUG;
51 
52     private final float mCardHeight;
53     private final float mExtendedCardHeight;
54     private final float mTextViewHeight;
55     private final float mExtendedTextViewCardHeight;
56     private final int mCardImageWidth;
57     private final int mCardImageHeight;
58     private final int mIconWidth;
59     private final int mIconHeight;
60     private final int mIconPadding;
61     private final int mIconColorFilter;
62 
63     private ImageView mImageView;
64     private View mGradientView;
65     private TextView mAppInfoView;
66     private TextView mMetaViewFocused;
67     private TextView mMetaViewUnfocused;
68     private View mMetaViewHolder;
69     private Channel mChannel;
70     private Intent mIntent;
71     private boolean mExtendViewOnFocus;
72     private final PackageManager mPackageManager;
73     private final TvInputManagerHelper mTvInputManagerHelper;
74 
AppLinkCardView(Context context)75     public AppLinkCardView(Context context) {
76         this(context, null);
77     }
78 
AppLinkCardView(Context context, AttributeSet attrs)79     public AppLinkCardView(Context context, AttributeSet attrs) {
80         this(context, attrs, 0);
81     }
82 
AppLinkCardView(Context context, AttributeSet attrs, int defStyle)83     public AppLinkCardView(Context context, AttributeSet attrs, int defStyle) {
84         super(context, attrs, defStyle);
85 
86         mCardImageWidth = getResources().getDimensionPixelSize(R.dimen.card_image_layout_width);
87         mCardImageHeight = getResources().getDimensionPixelSize(R.dimen.card_image_layout_height);
88         mCardHeight = getResources().getDimensionPixelSize(R.dimen.card_layout_height);
89         mExtendedCardHeight = getResources().getDimensionPixelOffset(
90                 R.dimen.card_layout_height_extended);
91         mIconWidth = getResources().getDimensionPixelSize(R.dimen.app_link_card_icon_width);
92         mIconHeight = getResources().getDimensionPixelSize(R.dimen.app_link_card_icon_height);
93         mIconPadding = getResources().getDimensionPixelOffset(R.dimen.app_link_card_icon_padding);
94         mPackageManager = context.getPackageManager();
95         mTvInputManagerHelper = ((MainActivity) context).getTvInputManagerHelper();
96         mTextViewHeight = getResources().getDimensionPixelSize(
97                 R.dimen.card_meta_layout_height);
98         mExtendedTextViewCardHeight = getResources().getDimensionPixelOffset(
99                 R.dimen.card_meta_layout_height_extended);
100         mIconColorFilter = Utils.getColor(getResources(), R.color.app_link_card_icon_color_filter);
101     }
102 
103     /**
104      * Returns the intent which will be started once this card is clicked.
105      */
getIntent()106     public Intent getIntent() {
107         return mIntent;
108     }
109 
110     @Override
onBind(Channel channel, boolean selected)111     public void onBind(Channel channel, boolean selected) {
112         if (DEBUG) {
113             Log.d(TAG, "onBind(channelName=" + channel.getDisplayName() + ", selected=" + selected
114                     + ")");
115         }
116         mChannel = channel;
117         ApplicationInfo appInfo = mTvInputManagerHelper.getTvInputAppInfo(mChannel.getInputId());
118         int linkType = mChannel.getAppLinkType(getContext());
119         mIntent = mChannel.getAppLinkIntent(getContext());
120 
121         switch (linkType) {
122             case Channel.APP_LINK_TYPE_CHANNEL:
123                 setMetaViewText(mChannel.getAppLinkText());
124                 mAppInfoView.setVisibility(VISIBLE);
125                 mGradientView.setVisibility(VISIBLE);
126                 mAppInfoView.setCompoundDrawablePadding(mIconPadding);
127                 mAppInfoView.setCompoundDrawables(null, null, null, null);
128                 mAppInfoView.setText(mPackageManager.getApplicationLabel(appInfo));
129                 if (!TextUtils.isEmpty(mChannel.getAppLinkIconUri())) {
130                     mChannel.loadBitmap(getContext(), Channel.LOAD_IMAGE_TYPE_APP_LINK_ICON,
131                             mIconWidth, mIconHeight, createChannelLogoCallback(this, mChannel,
132                                     Channel.LOAD_IMAGE_TYPE_APP_LINK_ICON));
133                 } else if (appInfo.icon != 0) {
134                     Drawable appIcon = mPackageManager.getApplicationIcon(appInfo);
135                     BitmapUtils.setColorFilterToDrawable(mIconColorFilter, appIcon);
136                     appIcon.setBounds(0, 0, mIconWidth, mIconHeight);
137                     mAppInfoView.setCompoundDrawables(appIcon, null, null, null);
138                 }
139                 break;
140             case Channel.APP_LINK_TYPE_APP:
141                 setMetaViewText(getContext().getString(
142                         R.string.channels_item_app_link_app_launcher,
143                         mPackageManager.getApplicationLabel(appInfo)));
144                 mAppInfoView.setVisibility(GONE);
145                 mGradientView.setVisibility(GONE);
146                 break;
147             default:
148                 mAppInfoView.setVisibility(GONE);
149                 mGradientView.setVisibility(GONE);
150                 Log.d(TAG, "Should not be here.");
151         }
152 
153         if (mChannel.getAppLinkColor() == 0) {
154             mMetaViewHolder.setBackgroundResource(R.color.channel_card_meta_background);
155         } else {
156             mMetaViewHolder.setBackgroundColor(mChannel.getAppLinkColor());
157         }
158 
159         if (!TextUtils.isEmpty(mChannel.getAppLinkPosterArtUri())) {
160             mImageView.setImageResource(R.drawable.ic_recent_thumbnail_default);
161             mChannel.loadBitmap(getContext(), Channel.LOAD_IMAGE_TYPE_APP_LINK_POSTER_ART,
162                     mCardImageWidth, mCardImageHeight, createChannelLogoCallback(this, mChannel,
163                             Channel.LOAD_IMAGE_TYPE_APP_LINK_POSTER_ART));
164         } else {
165             setCardImageWithBanner(appInfo);
166         }
167 
168         mMetaViewFocused.measure(MeasureSpec.makeMeasureSpec(mCardImageWidth, MeasureSpec.EXACTLY),
169                 MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
170         mExtendViewOnFocus = mMetaViewFocused.getLineCount() > 1;
171         if (mExtendViewOnFocus) {
172             setMetaViewFocusedAlpha(selected ? 1f : 0f);
173         } else {
174             setMetaViewFocusedAlpha(1f);
175         }
176 
177         // Call super.onBind() at the end in order to make getCardHeight() return a proper value.
178         super.onBind(channel, selected);
179     }
180 
createChannelLogoCallback( AppLinkCardView cardView, final Channel channel, final int type)181     private static ImageLoader.ImageLoaderCallback<AppLinkCardView> createChannelLogoCallback(
182             AppLinkCardView cardView, final Channel channel, final int type) {
183         return new ImageLoader.ImageLoaderCallback<AppLinkCardView>(cardView) {
184             @Override
185             public void onBitmapLoaded(AppLinkCardView cardView, @Nullable Bitmap bitmap) {
186                 // mChannel can be changed before the image load finished.
187                 if (!cardView.mChannel.hasSameReadOnlyInfo(channel)) {
188                     return;
189                 }
190                 cardView.updateChannelLogo(bitmap, type);
191             }
192         };
193     }
194 
195     private void updateChannelLogo(@Nullable Bitmap bitmap, int type) {
196         if (type == Channel.LOAD_IMAGE_TYPE_APP_LINK_ICON) {
197             BitmapDrawable drawable = null;
198             if (bitmap != null) {
199                 drawable = new BitmapDrawable(getResources(), bitmap);
200                 if (bitmap.getWidth() > bitmap.getHeight()) {
201                     drawable.setBounds(0, 0, mIconWidth,
202                             mIconWidth * bitmap.getHeight() / bitmap.getWidth());
203                 } else {
204                     drawable.setBounds(0, 0,
205                             mIconHeight * bitmap.getWidth() / bitmap.getHeight(),
206                             mIconHeight);
207                 }
208             }
209             BitmapUtils.setColorFilterToDrawable(mIconColorFilter, drawable);
210             mAppInfoView.setCompoundDrawables(drawable, null, null, null);
211         } else if (type == Channel.LOAD_IMAGE_TYPE_APP_LINK_POSTER_ART) {
212             if (bitmap == null) {
213                 setCardImageWithBanner(
214                         mTvInputManagerHelper.getTvInputAppInfo(mChannel.getInputId()));
215             } else {
216                 mImageView.setImageBitmap(bitmap);
217                 if (mChannel.getAppLinkColor() == 0) {
218                     extractAndSetMetaViewBackgroundColor(bitmap);
219                 }
220             }
221         }
222     }
223 
224     @Override
225     protected void onFinishInflate() {
226         super.onFinishInflate();
227         mImageView = (ImageView) findViewById(R.id.image);
228         mGradientView = findViewById(R.id.image_gradient);
229         mAppInfoView = (TextView) findViewById(R.id.app_info);
230         mMetaViewHolder = findViewById(R.id.app_link_text_holder);
231         mMetaViewFocused = (TextView) findViewById(R.id.app_link_text_focused);
232         mMetaViewUnfocused = (TextView) findViewById(R.id.app_link_text_unfocused);
233     }
234 
235     @Override
236     protected void onFocusAnimationStart(boolean selected) {
237         if (mExtendViewOnFocus) {
238             setMetaViewFocusedAlpha(selected ? 1f : 0f);
239         }
240     }
241 
242     @Override
243     protected void onSetFocusAnimatedValue(float animatedValue) {
244         super.onSetFocusAnimatedValue(animatedValue);
245         if (mExtendViewOnFocus) {
246             ViewGroup.LayoutParams params = mMetaViewUnfocused.getLayoutParams();
247             params.height = Math.round(mTextViewHeight
248                     + (mExtendedTextViewCardHeight - mTextViewHeight) * animatedValue);
249             setMetaViewLayoutParams(params);
250             setMetaViewFocusedAlpha(animatedValue);
251         }
252     }
253 
254     @Override
255     protected float getCardHeight() {
256         return (mExtendViewOnFocus && isFocused()) ? mExtendedCardHeight : mCardHeight;
257     }
258 
259     // Try to set the card image with following order:
260     // 1) Provided poster art image, 2) Activity banner, 3) Activity icon, 4) Application banner,
261     // 5) Application icon, and 6) default image.
262     private void setCardImageWithBanner(ApplicationInfo appInfo) {
263         Drawable banner = null;
264         if (mIntent != null) {
265             try {
266                 banner = mPackageManager.getActivityBanner(mIntent);
267                 if (banner == null) {
268                     banner = mPackageManager.getActivityIcon(mIntent);
269                 }
270             } catch (PackageManager.NameNotFoundException e) {
271                 // do nothing.
272             }
273         }
274 
275         if (banner == null && appInfo != null) {
276             if (appInfo.banner != 0) {
277                 banner = mPackageManager.getApplicationBanner(appInfo);
278             }
279             if (banner == null && appInfo.icon != 0) {
280                 banner = mPackageManager.getApplicationIcon(appInfo);
281             }
282         }
283 
284         if (banner == null) {
285             mImageView.setImageResource(R.drawable.ic_recent_thumbnail_default);
286             mImageView.setBackgroundResource(R.color.channel_card);
287         } else {
288             Bitmap bitmap =
289                     Bitmap.createBitmap(mCardImageWidth, mCardImageHeight, Bitmap.Config.ARGB_8888);
290             Canvas canvas = new Canvas(bitmap);
291             banner.setBounds(0, 0, mCardImageWidth, mCardImageHeight);
292             banner.draw(canvas);
293             mImageView.setImageDrawable(banner);
294             if (mChannel.getAppLinkColor() == 0) {
295                 extractAndSetMetaViewBackgroundColor(bitmap);
296             }
297         }
298     }
299 
300     private void extractAndSetMetaViewBackgroundColor(Bitmap bitmap) {
301         new Palette.Builder(bitmap).generate(new Palette.PaletteAsyncListener() {
302             @Override
303             public void onGenerated(Palette palette) {
304                 mMetaViewHolder.setBackgroundColor(palette.getDarkVibrantColor(
305                         Utils.getColor(getResources(), R.color.channel_card_meta_background)));
306             }
307         });
308     }
309 
310     private void setMetaViewLayoutParams(ViewGroup.LayoutParams params) {
311         mMetaViewFocused.setLayoutParams(params);
312         mMetaViewUnfocused.setLayoutParams(params);
313     }
314 
315     private void setMetaViewText(String text) {
316         mMetaViewFocused.setText(text);
317         mMetaViewUnfocused.setText(text);
318     }
319 
320     private void setMetaViewFocusedAlpha(float focusedAlpha) {
321         mMetaViewFocused.setAlpha(focusedAlpha);
322         mMetaViewUnfocused.setAlpha(1f - focusedAlpha);
323     }
324 }
325