• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.os.AsyncTask;
28 import android.support.annotation.Nullable;
29 import android.support.v7.graphics.Palette;
30 import android.text.TextUtils;
31 import android.util.AttributeSet;
32 import android.util.Log;
33 import android.view.View;
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 
44 import java.util.Objects;
45 
46 /**
47  * A view to render an app link card.
48  */
49 public class AppLinkCardView extends BaseCardView<ChannelsRowItem> {
50     private static final String TAG = MenuView.TAG;
51     private static final boolean DEBUG = MenuView.DEBUG;
52 
53     private final int mCardImageWidth;
54     private final int mCardImageHeight;
55     private final int mIconWidth;
56     private final int mIconHeight;
57     private final int mIconPadding;
58     private final int mIconColorFilter;
59     private final Drawable mDefaultDrawable;
60 
61     private ImageView mImageView;
62     private TextView mAppInfoView;
63     private View mMetaViewHolder;
64     private Channel mChannel;
65     private Intent mIntent;
66     private final PackageManager mPackageManager;
67     private final TvInputManagerHelper mTvInputManagerHelper;
68 
AppLinkCardView(Context context)69     public AppLinkCardView(Context context) {
70         this(context, null);
71     }
72 
AppLinkCardView(Context context, AttributeSet attrs)73     public AppLinkCardView(Context context, AttributeSet attrs) {
74         this(context, attrs, 0);
75     }
76 
AppLinkCardView(Context context, AttributeSet attrs, int defStyle)77     public AppLinkCardView(Context context, AttributeSet attrs, int defStyle) {
78         super(context, attrs, defStyle);
79 
80         mCardImageWidth = getResources().getDimensionPixelSize(R.dimen.card_image_layout_width);
81         mCardImageHeight = getResources().getDimensionPixelSize(R.dimen.card_image_layout_height);
82         mIconWidth = getResources().getDimensionPixelSize(R.dimen.app_link_card_icon_width);
83         mIconHeight = getResources().getDimensionPixelSize(R.dimen.app_link_card_icon_height);
84         mIconPadding = getResources().getDimensionPixelOffset(R.dimen.app_link_card_icon_padding);
85         mPackageManager = context.getPackageManager();
86         mTvInputManagerHelper = ((MainActivity) context).getTvInputManagerHelper();
87         mIconColorFilter = getResources().getColor(R.color.app_link_card_icon_color_filter, null);
88         mDefaultDrawable = getResources().getDrawable(R.drawable.ic_recent_thumbnail_default, null);
89     }
90 
91     /**
92      * Returns the intent which will be started once this card is clicked.
93      */
getIntent()94     public Intent getIntent() {
95         return mIntent;
96     }
97 
98     @Override
onBind(ChannelsRowItem item, boolean selected)99     public void onBind(ChannelsRowItem item, boolean selected) {
100         Channel newChannel = item.getChannel();
101         boolean channelChanged = !Objects.equals(mChannel, newChannel);
102         String previousPosterArtUri = mChannel == null ? null : mChannel.getAppLinkPosterArtUri();
103         boolean posterArtChanged = previousPosterArtUri == null
104                 || newChannel.getAppLinkPosterArtUri() == null
105                 || !TextUtils.equals(previousPosterArtUri, newChannel.getAppLinkPosterArtUri());
106         mChannel = newChannel;
107         if (DEBUG) {
108             Log.d(TAG, "onBind(channelName=" + mChannel.getDisplayName() + ", selected=" + selected
109                     + ")");
110         }
111         ApplicationInfo appInfo = mTvInputManagerHelper.getTvInputAppInfo(mChannel.getInputId());
112         if (channelChanged) {
113             int linkType = mChannel.getAppLinkType(getContext());
114             mIntent = mChannel.getAppLinkIntent(getContext());
115 
116             CharSequence appLabel;
117             switch (linkType) {
118                 case Channel.APP_LINK_TYPE_CHANNEL:
119                     setText(mChannel.getAppLinkText());
120                     mAppInfoView.setVisibility(VISIBLE);
121                     mAppInfoView.setCompoundDrawablePadding(mIconPadding);
122                     mAppInfoView.setCompoundDrawablesRelative(null, null, null, null);
123                     appLabel = mTvInputManagerHelper
124                             .getTvInputApplicationLabel(mChannel.getInputId());
125                     if (appLabel != null) {
126                         mAppInfoView.setText(appLabel);
127                     } else {
128                         new AsyncTask<Void, Void, CharSequence>() {
129                             private final String mLoadTvInputId = mChannel.getInputId();
130 
131                             @Override
132                             protected CharSequence doInBackground(Void... params) {
133                                 if (appInfo != null) {
134                                     return mPackageManager.getApplicationLabel(appInfo);
135                                 }
136                                 return null;
137                             }
138 
139                             @Override
140                             protected void onPostExecute(CharSequence appLabel) {
141                                 mTvInputManagerHelper.setTvInputApplicationLabel(
142                                         mLoadTvInputId, appLabel);
143                                 if (mLoadTvInputId != mChannel.getInputId()
144                                         || !isAttachedToWindow()) {
145                                     return;
146                                 }
147                                 mAppInfoView.setText(appLabel);
148                             }
149                         }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
150                     }
151                     if (!TextUtils.isEmpty(mChannel.getAppLinkIconUri())) {
152                         mChannel.loadBitmap(getContext(), Channel.LOAD_IMAGE_TYPE_APP_LINK_ICON,
153                                 mIconWidth, mIconHeight, createChannelLogoCallback(
154                                         this, mChannel, Channel.LOAD_IMAGE_TYPE_APP_LINK_ICON));
155                     } else if (appInfo.icon != 0) {
156                         Drawable appIcon = mTvInputManagerHelper
157                                 .getTvInputApplicationIcon(mChannel.getInputId());
158                         if (appIcon != null) {
159                             BitmapUtils.setColorFilterToDrawable(mIconColorFilter, appIcon);
160                             appIcon.setBounds(0, 0, mIconWidth, mIconHeight);
161                             mAppInfoView.setCompoundDrawablesRelative(appIcon, null, null, null);
162                         } else {
163                             new AsyncTask<Void, Void, Drawable>() {
164                                 private final String mLoadTvInputId = mChannel.getInputId();
165 
166                                 @Override
167                                 protected Drawable doInBackground(Void... params) {
168                                     return mPackageManager.getApplicationIcon(appInfo);
169                                 }
170 
171                                 @Override
172                                 protected void onPostExecute(Drawable appIcon) {
173                                     mTvInputManagerHelper.setTvInputApplicationIcon(
174                                             mLoadTvInputId, appIcon);
175                                     if (!mLoadTvInputId.equals(mChannel.getInputId())
176                                             || !isAttachedToWindow()) {
177                                         return;
178                                     }
179                                     BitmapUtils.setColorFilterToDrawable(mIconColorFilter, appIcon);
180                                     appIcon.setBounds(0, 0, mIconWidth, mIconHeight);
181                                     mAppInfoView.setCompoundDrawablesRelative(
182                                             appIcon, null, null, null);
183                                 }
184                             }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
185                         }
186                     }
187                     break;
188                 case Channel.APP_LINK_TYPE_APP:
189                     appLabel = mTvInputManagerHelper
190                         .getTvInputApplicationLabel(mChannel.getInputId());
191                     if (appLabel != null) {
192                         setText(getContext()
193                             .getString(R.string.channels_item_app_link_app_launcher, appLabel));
194                     } else {
195                         new AsyncTask<Void, Void, CharSequence>() {
196                             private final String mLoadTvInputId = mChannel.getInputId();
197 
198                             @Override
199                             protected CharSequence doInBackground(Void... params) {
200                                 if (appInfo != null) {
201                                     return mPackageManager.getApplicationLabel(appInfo);
202                                 }
203                                 return null;
204                             }
205 
206                             @Override
207                             protected void onPostExecute(CharSequence appLabel) {
208                                 mTvInputManagerHelper.setTvInputApplicationLabel(
209                                     mLoadTvInputId, appLabel);
210                                 if (!mLoadTvInputId.equals(mChannel.getInputId())
211                                     || !isAttachedToWindow()) {
212                                     return;
213                                 }
214                                 setText(getContext()
215                                     .getString(
216                                         R.string.channels_item_app_link_app_launcher,
217                                         appLabel));
218                             }
219                         }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
220                     }
221                     mAppInfoView.setVisibility(GONE);
222                     break;
223                 default:
224                     mAppInfoView.setVisibility(GONE);
225                     Log.d(TAG, "Should not be here.");
226             }
227 
228             if (mChannel.getAppLinkColor() == 0) {
229                 mMetaViewHolder.setBackgroundResource(R.color.channel_card_meta_background);
230             } else {
231                 mMetaViewHolder.setBackgroundColor(mChannel.getAppLinkColor());
232             }
233         }
234         if (posterArtChanged) {
235             mImageView.setImageDrawable(mDefaultDrawable);
236             mImageView.setForeground(null);
237             if (!TextUtils.isEmpty(mChannel.getAppLinkPosterArtUri())) {
238                 mChannel.loadBitmap(getContext(), Channel.LOAD_IMAGE_TYPE_APP_LINK_POSTER_ART,
239                         mCardImageWidth, mCardImageHeight, createChannelLogoCallback(this, mChannel,
240                                 Channel.LOAD_IMAGE_TYPE_APP_LINK_POSTER_ART));
241             } else {
242                 setCardImageWithBanner(appInfo);
243             }
244         }
245         super.onBind(item, selected);
246     }
247 
createChannelLogoCallback( AppLinkCardView cardView, final Channel channel, final int type)248     private static ImageLoader.ImageLoaderCallback<AppLinkCardView> createChannelLogoCallback(
249             AppLinkCardView cardView, final Channel channel, final int type) {
250         return new ImageLoader.ImageLoaderCallback<AppLinkCardView>(cardView) {
251             @Override
252             public void onBitmapLoaded(AppLinkCardView cardView, @Nullable Bitmap bitmap) {
253                 // mChannel can be changed before the image load finished.
254                 if (!cardView.mChannel.hasSameReadOnlyInfo(channel)) {
255                     return;
256                 }
257                 cardView.updateChannelLogo(bitmap, type);
258             }
259         };
260     }
261 
262     private void updateChannelLogo(@Nullable Bitmap bitmap, int type) {
263         if (type == Channel.LOAD_IMAGE_TYPE_APP_LINK_ICON) {
264             BitmapDrawable drawable = null;
265             if (bitmap != null) {
266                 drawable = new BitmapDrawable(getResources(), bitmap);
267                 if (bitmap.getWidth() > bitmap.getHeight()) {
268                     drawable.setBounds(0, 0, mIconWidth,
269                             mIconWidth * bitmap.getHeight() / bitmap.getWidth());
270                 } else {
271                     drawable.setBounds(0, 0,
272                             mIconHeight * bitmap.getWidth() / bitmap.getHeight(),
273                             mIconHeight);
274                 }
275             }
276             BitmapUtils.setColorFilterToDrawable(mIconColorFilter, drawable);
277             mAppInfoView.setCompoundDrawablesRelative(drawable, null, null, null);
278         } else if (type == Channel.LOAD_IMAGE_TYPE_APP_LINK_POSTER_ART) {
279             if (bitmap == null) {
280                 setCardImageWithBanner(
281                         mTvInputManagerHelper.getTvInputAppInfo(mChannel.getInputId()));
282             } else {
283                 mImageView.setImageBitmap(bitmap);
284                 mImageView.setForeground(getContext().getDrawable(R.drawable.card_image_gradient));
285                 if (mChannel.getAppLinkColor() == 0) {
286                     extractAndSetMetaViewBackgroundColor(bitmap);
287                 }
288             }
289         }
290     }
291 
292     @Override
293     protected void onFinishInflate() {
294         super.onFinishInflate();
295         mImageView = (ImageView) findViewById(R.id.image);
296         mAppInfoView = (TextView) findViewById(R.id.app_info);
297         mMetaViewHolder = findViewById(R.id.app_link_text_holder);
298     }
299 
300     // Try to set the card image with following order:
301     // 1) Provided poster art image, 2) Activity banner, 3) Activity icon, 4) Application banner,
302     // 5) Application icon, and 6) default image.
303     private void setCardImageWithBanner(ApplicationInfo appInfo) {
304         new AsyncTask<Void, Void, Drawable>() {
305             private String mLoadTvInputId = mChannel.getInputId();
306             @Override
307             protected Drawable doInBackground(Void... params) {
308                 Drawable banner = null;
309                 if (mIntent != null) {
310                     try {
311                         banner = mPackageManager.getActivityBanner(mIntent);
312                         if (banner == null) {
313                             banner = mPackageManager.getActivityIcon(mIntent);
314                         }
315                     } catch (PackageManager.NameNotFoundException e) {
316                         // do nothing.
317                     }
318                 }
319                 return banner;
320             }
321 
322             @Override
323             protected void onPostExecute(Drawable banner) {
324                 if (mLoadTvInputId != mChannel.getInputId() || !isAttachedToWindow()) {
325                     return;
326                 }
327                 if (banner != null) {
328                     setCardImageWithBannerInternal(banner);
329                 } else {
330                     setCardImageWithApplicationInfoBanner(appInfo);
331                 }
332             }
333         }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
334     }
335 
336     private void setCardImageWithApplicationInfoBanner(ApplicationInfo appInfo) {
337         Drawable appBanner =
338                 mTvInputManagerHelper.getTvInputApplicationBanner(mChannel.getInputId());
339         if (appBanner != null) {
340             setCardImageWithBannerInternal(appBanner);
341         } else {
342             new AsyncTask<Void, Void, Drawable>() {
343                 private final String mLoadTvInputId = mChannel.getInputId();
344                 @Override
345                 protected Drawable doInBackground(Void... params) {
346                     Drawable banner = null;
347                     if (appInfo != null) {
348                         if (appInfo.banner != 0) {
349                             banner = mPackageManager.getApplicationBanner(appInfo);
350                         }
351                         if (banner == null && appInfo.icon != 0) {
352                             banner = mPackageManager.getApplicationIcon(appInfo);
353                         }
354                     }
355                     return banner;
356                 }
357 
358                 @Override
359                 protected void onPostExecute(Drawable banner) {
360                     mTvInputManagerHelper.setTvInputApplicationBanner(mLoadTvInputId, banner);
361                     if (!TextUtils.equals(mLoadTvInputId, mChannel.getInputId())
362                             || !isAttachedToWindow()) {
363                         return;
364                     }
365                     setCardImageWithBannerInternal(banner);
366                 }
367             }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
368         }
369     }
370 
371     private void setCardImageWithBannerInternal(Drawable banner) {
372         if (banner == null) {
373             mImageView.setImageDrawable(mDefaultDrawable);
374             mImageView.setBackgroundResource(R.color.channel_card);
375         } else {
376             Bitmap bitmap = Bitmap.createBitmap(
377                     mCardImageWidth, mCardImageHeight, Bitmap.Config.ARGB_8888);
378             Canvas canvas = new Canvas(bitmap);
379             banner.setBounds(0, 0, mCardImageWidth, mCardImageHeight);
380             banner.draw(canvas);
381             mImageView.setImageDrawable(banner);
382             mImageView.setForeground(getContext().getDrawable(R.drawable.card_image_gradient));
383             if (mChannel.getAppLinkColor() == 0) {
384                 extractAndSetMetaViewBackgroundColor(bitmap);
385             }
386         }
387     }
388 
389     private void extractAndSetMetaViewBackgroundColor(Bitmap bitmap) {
390         new Palette.Builder(bitmap).generate(new Palette.PaletteAsyncListener() {
391             @Override
392             public void onGenerated(Palette palette) {
393                 mMetaViewHolder.setBackgroundColor(palette.getDarkVibrantColor(
394                         getResources().getColor(R.color.channel_card_meta_background, null)));
395             }
396         });
397     }
398 }
399