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