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