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 
     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 
     69     public AppLinkCardView(Context context) {
     70         this(context, null);
     71     }
     72 
     73     public AppLinkCardView(Context context, AttributeSet attrs) {
     74         this(context, attrs, 0);
     75     }
     76 
     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      */
     94     public Intent getIntent() {
     95         return mIntent;
     96     }
     97 
     98     @Override
     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 
    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