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 75 public AppLinkCardView(Context context) { 76 this(context, null); 77 } 78 79 public AppLinkCardView(Context context, AttributeSet attrs) { 80 this(context, attrs, 0); 81 } 82 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 */ 106 public Intent getIntent() { 107 return mIntent; 108 } 109 110 @Override 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 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