1 /* 2 * Copyright (C) 2008 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.launcher3; 18 19 import android.animation.ObjectAnimator; 20 import android.content.Context; 21 import android.content.res.ColorStateList; 22 import android.content.res.Resources; 23 import android.content.res.TypedArray; 24 import android.graphics.Bitmap; 25 import android.graphics.Canvas; 26 import android.graphics.Paint; 27 import android.graphics.Point; 28 import android.graphics.Rect; 29 import android.graphics.Region; 30 import android.graphics.drawable.Drawable; 31 import android.util.AttributeSet; 32 import android.util.Property; 33 import android.util.TypedValue; 34 import android.view.KeyEvent; 35 import android.view.MotionEvent; 36 import android.view.View; 37 import android.view.ViewConfiguration; 38 import android.view.ViewDebug; 39 import android.view.ViewParent; 40 import android.widget.TextView; 41 42 import com.android.launcher3.IconCache.IconLoadRequest; 43 import com.android.launcher3.IconCache.ItemInfoUpdateReceiver; 44 import com.android.launcher3.badge.BadgeInfo; 45 import com.android.launcher3.badge.BadgeRenderer; 46 import com.android.launcher3.folder.FolderIcon; 47 import com.android.launcher3.graphics.DrawableFactory; 48 import com.android.launcher3.graphics.HolographicOutlineHelper; 49 import com.android.launcher3.graphics.IconPalette; 50 import com.android.launcher3.graphics.PreloadIconDrawable; 51 import com.android.launcher3.model.PackageItemInfo; 52 53 import java.text.NumberFormat; 54 55 /** 56 * TextView that draws a bubble behind the text. We cannot use a LineBackgroundSpan 57 * because we want to make the bubble taller than the text and TextView's clip is 58 * too aggressive. 59 */ 60 public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver { 61 62 // Dimensions in DP 63 private static final float AMBIENT_SHADOW_RADIUS = 2.5f; 64 private static final float KEY_SHADOW_RADIUS = 1f; 65 private static final float KEY_SHADOW_OFFSET = 0.5f; 66 private static final int AMBIENT_SHADOW_COLOR = 0x33000000; 67 private static final int KEY_SHADOW_COLOR = 0x66000000; 68 69 private static final int DISPLAY_WORKSPACE = 0; 70 private static final int DISPLAY_ALL_APPS = 1; 71 private static final int DISPLAY_FOLDER = 2; 72 73 private static final int[] STATE_PRESSED = new int[] {android.R.attr.state_pressed}; 74 75 private final Launcher mLauncher; 76 private Drawable mIcon; 77 private final boolean mCenterVertically; 78 private final Drawable mBackground; 79 private OnLongClickListener mOnLongClickListener; 80 private final CheckLongPressHelper mLongPressHelper; 81 private final HolographicOutlineHelper mOutlineHelper; 82 private final StylusEventHelper mStylusEventHelper; 83 84 private boolean mBackgroundSizeChanged; 85 86 private Bitmap mPressedBackground; 87 88 private float mSlop; 89 90 private final boolean mDeferShadowGenerationOnTouch; 91 private final boolean mCustomShadowsEnabled; 92 private final boolean mLayoutHorizontal; 93 private final int mIconSize; 94 @ViewDebug.ExportedProperty(category = "launcher") 95 private int mTextColor; 96 97 private BadgeInfo mBadgeInfo; 98 private BadgeRenderer mBadgeRenderer; 99 private IconPalette mBadgePalette; 100 private float mBadgeScale; 101 private boolean mForceHideBadge; 102 private Point mTempSpaceForBadgeOffset = new Point(); 103 private Rect mTempIconBounds = new Rect(); 104 105 private static final Property<BubbleTextView, Float> BADGE_SCALE_PROPERTY 106 = new Property<BubbleTextView, Float>(Float.TYPE, "badgeScale") { 107 @Override 108 public Float get(BubbleTextView bubbleTextView) { 109 return bubbleTextView.mBadgeScale; 110 } 111 112 @Override 113 public void set(BubbleTextView bubbleTextView, Float value) { 114 bubbleTextView.mBadgeScale = value; 115 bubbleTextView.invalidate(); 116 } 117 }; 118 119 @ViewDebug.ExportedProperty(category = "launcher") 120 private boolean mStayPressed; 121 @ViewDebug.ExportedProperty(category = "launcher") 122 private boolean mIgnorePressedStateChange; 123 @ViewDebug.ExportedProperty(category = "launcher") 124 private boolean mDisableRelayout = false; 125 126 private IconLoadRequest mIconLoadRequest; 127 BubbleTextView(Context context)128 public BubbleTextView(Context context) { 129 this(context, null, 0); 130 } 131 BubbleTextView(Context context, AttributeSet attrs)132 public BubbleTextView(Context context, AttributeSet attrs) { 133 this(context, attrs, 0); 134 } 135 BubbleTextView(Context context, AttributeSet attrs, int defStyle)136 public BubbleTextView(Context context, AttributeSet attrs, int defStyle) { 137 super(context, attrs, defStyle); 138 mLauncher = Launcher.getLauncher(context); 139 DeviceProfile grid = mLauncher.getDeviceProfile(); 140 141 TypedArray a = context.obtainStyledAttributes(attrs, 142 R.styleable.BubbleTextView, defStyle, 0); 143 mCustomShadowsEnabled = a.getBoolean(R.styleable.BubbleTextView_customShadows, false); 144 mLayoutHorizontal = a.getBoolean(R.styleable.BubbleTextView_layoutHorizontal, false); 145 mDeferShadowGenerationOnTouch = 146 a.getBoolean(R.styleable.BubbleTextView_deferShadowGeneration, false); 147 148 int display = a.getInteger(R.styleable.BubbleTextView_iconDisplay, DISPLAY_WORKSPACE); 149 int defaultIconSize = grid.iconSizePx; 150 if (display == DISPLAY_WORKSPACE) { 151 setTextSize(TypedValue.COMPLEX_UNIT_PX, grid.iconTextSizePx); 152 } else if (display == DISPLAY_ALL_APPS) { 153 setTextSize(TypedValue.COMPLEX_UNIT_PX, grid.allAppsIconTextSizePx); 154 setCompoundDrawablePadding(grid.allAppsIconDrawablePaddingPx); 155 defaultIconSize = grid.allAppsIconSizePx; 156 } else if (display == DISPLAY_FOLDER) { 157 setTextSize(TypedValue.COMPLEX_UNIT_PX, grid.folderChildTextSizePx); 158 setCompoundDrawablePadding(grid.folderChildDrawablePaddingPx); 159 defaultIconSize = grid.folderChildIconSizePx; 160 } 161 mCenterVertically = a.getBoolean(R.styleable.BubbleTextView_centerVertically, false); 162 163 mIconSize = a.getDimensionPixelSize(R.styleable.BubbleTextView_iconSizeOverride, 164 defaultIconSize); 165 a.recycle(); 166 167 if (mCustomShadowsEnabled) { 168 // Draw the background itself as the parent is drawn twice. 169 mBackground = getBackground(); 170 setBackground(null); 171 172 // Set shadow layer as the larger shadow to that the textView does not clip the shadow. 173 float density = getResources().getDisplayMetrics().density; 174 setShadowLayer(density * AMBIENT_SHADOW_RADIUS, 0, 0, AMBIENT_SHADOW_COLOR); 175 } else { 176 mBackground = null; 177 } 178 179 mLongPressHelper = new CheckLongPressHelper(this); 180 mStylusEventHelper = new StylusEventHelper(new SimpleOnStylusPressListener(this), this); 181 182 mOutlineHelper = HolographicOutlineHelper.getInstance(getContext()); 183 setAccessibilityDelegate(mLauncher.getAccessibilityDelegate()); 184 } 185 applyFromShortcutInfo(ShortcutInfo info)186 public void applyFromShortcutInfo(ShortcutInfo info) { 187 applyFromShortcutInfo(info, false); 188 } 189 applyFromShortcutInfo(ShortcutInfo info, boolean promiseStateChanged)190 public void applyFromShortcutInfo(ShortcutInfo info, boolean promiseStateChanged) { 191 applyIconAndLabel(info.iconBitmap, info); 192 setTag(info); 193 if (promiseStateChanged || info.isPromise()) { 194 applyPromiseState(promiseStateChanged); 195 } 196 197 applyBadgeState(info, false /* animate */); 198 } 199 applyFromApplicationInfo(AppInfo info)200 public void applyFromApplicationInfo(AppInfo info) { 201 applyIconAndLabel(info.iconBitmap, info); 202 203 // We don't need to check the info since it's not a ShortcutInfo 204 super.setTag(info); 205 206 // Verify high res immediately 207 verifyHighRes(); 208 209 applyBadgeState(info, false /* animate */); 210 } 211 applyFromPackageItemInfo(PackageItemInfo info)212 public void applyFromPackageItemInfo(PackageItemInfo info) { 213 applyIconAndLabel(info.iconBitmap, info); 214 // We don't need to check the info since it's not a ShortcutInfo 215 super.setTag(info); 216 217 // Verify high res immediately 218 verifyHighRes(); 219 } 220 applyIconAndLabel(Bitmap icon, ItemInfo info)221 private void applyIconAndLabel(Bitmap icon, ItemInfo info) { 222 FastBitmapDrawable iconDrawable = DrawableFactory.get(getContext()).newIcon(icon, info); 223 iconDrawable.setIsDisabled(info.isDisabled()); 224 setIcon(iconDrawable); 225 setText(info.title); 226 if (info.contentDescription != null) { 227 setContentDescription(info.isDisabled() 228 ? getContext().getString(R.string.disabled_app_label, info.contentDescription) 229 : info.contentDescription); 230 } 231 } 232 233 /** 234 * Overrides the default long press timeout. 235 */ setLongPressTimeout(int longPressTimeout)236 public void setLongPressTimeout(int longPressTimeout) { 237 mLongPressHelper.setLongPressTimeout(longPressTimeout); 238 } 239 240 @Override setFrame(int left, int top, int right, int bottom)241 protected boolean setFrame(int left, int top, int right, int bottom) { 242 if (getLeft() != left || getRight() != right || getTop() != top || getBottom() != bottom) { 243 mBackgroundSizeChanged = true; 244 } 245 return super.setFrame(left, top, right, bottom); 246 } 247 248 @Override verifyDrawable(Drawable who)249 protected boolean verifyDrawable(Drawable who) { 250 return who == mBackground || super.verifyDrawable(who); 251 } 252 253 @Override setTag(Object tag)254 public void setTag(Object tag) { 255 if (tag != null) { 256 LauncherModel.checkItemInfo((ItemInfo) tag); 257 } 258 super.setTag(tag); 259 } 260 261 @Override refreshDrawableState()262 public void refreshDrawableState() { 263 if (!mIgnorePressedStateChange) { 264 super.refreshDrawableState(); 265 } 266 } 267 268 @Override onCreateDrawableState(int extraSpace)269 protected int[] onCreateDrawableState(int extraSpace) { 270 final int[] drawableState = super.onCreateDrawableState(extraSpace + 1); 271 if (mStayPressed) { 272 mergeDrawableStates(drawableState, STATE_PRESSED); 273 } 274 return drawableState; 275 } 276 277 /** Returns the icon for this view. */ getIcon()278 public Drawable getIcon() { 279 return mIcon; 280 } 281 282 /** Returns whether the layout is horizontal. */ isLayoutHorizontal()283 public boolean isLayoutHorizontal() { 284 return mLayoutHorizontal; 285 } 286 287 @Override setOnLongClickListener(OnLongClickListener l)288 public void setOnLongClickListener(OnLongClickListener l) { 289 super.setOnLongClickListener(l); 290 mOnLongClickListener = l; 291 } 292 getOnLongClickListener()293 public OnLongClickListener getOnLongClickListener() { 294 return mOnLongClickListener; 295 } 296 297 @Override onTouchEvent(MotionEvent event)298 public boolean onTouchEvent(MotionEvent event) { 299 // Call the superclass onTouchEvent first, because sometimes it changes the state to 300 // isPressed() on an ACTION_UP 301 boolean result = super.onTouchEvent(event); 302 303 // Check for a stylus button press, if it occurs cancel any long press checks. 304 if (mStylusEventHelper.onMotionEvent(event)) { 305 mLongPressHelper.cancelLongPress(); 306 result = true; 307 } 308 309 switch (event.getAction()) { 310 case MotionEvent.ACTION_DOWN: 311 // So that the pressed outline is visible immediately on setStayPressed(), 312 // we pre-create it on ACTION_DOWN (it takes a small but perceptible amount of time 313 // to create it) 314 if (!mDeferShadowGenerationOnTouch && mPressedBackground == null) { 315 mPressedBackground = mOutlineHelper.createMediumDropShadow(this); 316 } 317 318 // If we're in a stylus button press, don't check for long press. 319 if (!mStylusEventHelper.inStylusButtonPressed()) { 320 mLongPressHelper.postCheckForLongPress(); 321 } 322 break; 323 case MotionEvent.ACTION_CANCEL: 324 case MotionEvent.ACTION_UP: 325 // If we've touched down and up on an item, and it's still not "pressed", then 326 // destroy the pressed outline 327 if (!isPressed()) { 328 mPressedBackground = null; 329 } 330 331 mLongPressHelper.cancelLongPress(); 332 break; 333 case MotionEvent.ACTION_MOVE: 334 if (!Utilities.pointInView(this, event.getX(), event.getY(), mSlop)) { 335 mLongPressHelper.cancelLongPress(); 336 } 337 break; 338 } 339 return result; 340 } 341 setStayPressed(boolean stayPressed)342 void setStayPressed(boolean stayPressed) { 343 mStayPressed = stayPressed; 344 if (!stayPressed) { 345 HolographicOutlineHelper.getInstance(getContext()).recycleShadowBitmap(mPressedBackground); 346 mPressedBackground = null; 347 } else { 348 if (mPressedBackground == null) { 349 mPressedBackground = mOutlineHelper.createMediumDropShadow(this); 350 } 351 } 352 353 // Only show the shadow effect when persistent pressed state is set. 354 ViewParent parent = getParent(); 355 if (parent != null && parent.getParent() instanceof BubbleTextShadowHandler) { 356 ((BubbleTextShadowHandler) parent.getParent()).setPressedIcon( 357 this, mPressedBackground); 358 } 359 360 refreshDrawableState(); 361 } 362 clearPressedBackground()363 void clearPressedBackground() { 364 setPressed(false); 365 setStayPressed(false); 366 } 367 368 @Override onKeyDown(int keyCode, KeyEvent event)369 public boolean onKeyDown(int keyCode, KeyEvent event) { 370 if (super.onKeyDown(keyCode, event)) { 371 // Pre-create shadow so show immediately on click. 372 if (mPressedBackground == null) { 373 mPressedBackground = mOutlineHelper.createMediumDropShadow(this); 374 } 375 return true; 376 } 377 return false; 378 } 379 380 @Override onKeyUp(int keyCode, KeyEvent event)381 public boolean onKeyUp(int keyCode, KeyEvent event) { 382 // Unlike touch events, keypress event propagate pressed state change immediately, 383 // without waiting for onClickHandler to execute. Disable pressed state changes here 384 // to avoid flickering. 385 mIgnorePressedStateChange = true; 386 boolean result = super.onKeyUp(keyCode, event); 387 388 mPressedBackground = null; 389 mIgnorePressedStateChange = false; 390 refreshDrawableState(); 391 return result; 392 } 393 394 @Override draw(Canvas canvas)395 public void draw(Canvas canvas) { 396 if (!mCustomShadowsEnabled) { 397 super.draw(canvas); 398 drawBadgeIfNecessary(canvas); 399 return; 400 } 401 402 final Drawable background = mBackground; 403 if (background != null) { 404 final int scrollX = getScrollX(); 405 final int scrollY = getScrollY(); 406 407 if (mBackgroundSizeChanged) { 408 background.setBounds(0, 0, getRight() - getLeft(), getBottom() - getTop()); 409 mBackgroundSizeChanged = false; 410 } 411 412 if ((scrollX | scrollY) == 0) { 413 background.draw(canvas); 414 } else { 415 canvas.translate(scrollX, scrollY); 416 background.draw(canvas); 417 canvas.translate(-scrollX, -scrollY); 418 } 419 } 420 421 // If text is transparent, don't draw any shadow 422 if ((getCurrentTextColor() >> 24) == 0) { 423 getPaint().clearShadowLayer(); 424 super.draw(canvas); 425 drawBadgeIfNecessary(canvas); 426 return; 427 } 428 429 // We enhance the shadow by drawing the shadow twice 430 float density = getResources().getDisplayMetrics().density; 431 getPaint().setShadowLayer(density * AMBIENT_SHADOW_RADIUS, 0, 0, AMBIENT_SHADOW_COLOR); 432 super.draw(canvas); 433 canvas.save(Canvas.CLIP_SAVE_FLAG); 434 canvas.clipRect(getScrollX(), getScrollY() + getExtendedPaddingTop(), 435 getScrollX() + getWidth(), 436 getScrollY() + getHeight(), Region.Op.INTERSECT); 437 getPaint().setShadowLayer( 438 density * KEY_SHADOW_RADIUS, 0.0f, density * KEY_SHADOW_OFFSET, KEY_SHADOW_COLOR); 439 super.draw(canvas); 440 canvas.restore(); 441 442 drawBadgeIfNecessary(canvas); 443 } 444 445 /** 446 * Draws the icon badge in the top right corner of the icon bounds. 447 * @param canvas The canvas to draw to. 448 */ drawBadgeIfNecessary(Canvas canvas)449 private void drawBadgeIfNecessary(Canvas canvas) { 450 if (!mForceHideBadge && (hasBadge() || mBadgeScale > 0)) { 451 getIconBounds(mTempIconBounds); 452 mTempSpaceForBadgeOffset.set((getWidth() - mIconSize) / 2, getPaddingTop()); 453 final int scrollX = getScrollX(); 454 final int scrollY = getScrollY(); 455 canvas.translate(scrollX, scrollY); 456 mBadgeRenderer.draw(canvas, mBadgePalette, mBadgeInfo, mTempIconBounds, mBadgeScale, 457 mTempSpaceForBadgeOffset); 458 canvas.translate(-scrollX, -scrollY); 459 } 460 } 461 forceHideBadge(boolean forceHideBadge)462 public void forceHideBadge(boolean forceHideBadge) { 463 if (mForceHideBadge == forceHideBadge) { 464 return; 465 } 466 mForceHideBadge = forceHideBadge; 467 468 if (forceHideBadge) { 469 invalidate(); 470 } else if (hasBadge()) { 471 ObjectAnimator.ofFloat(this, BADGE_SCALE_PROPERTY, 0, 1).start(); 472 } 473 } 474 hasBadge()475 private boolean hasBadge() { 476 return mBadgeInfo != null; 477 } 478 getIconBounds(Rect outBounds)479 public void getIconBounds(Rect outBounds) { 480 int top = getPaddingTop(); 481 int left = (getWidth() - mIconSize) / 2; 482 int right = left + mIconSize; 483 int bottom = top + mIconSize; 484 outBounds.set(left, top, right, bottom); 485 } 486 487 @Override onAttachedToWindow()488 protected void onAttachedToWindow() { 489 super.onAttachedToWindow(); 490 491 if (mBackground != null) mBackground.setCallback(this); 492 mSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); 493 } 494 495 @Override onMeasure(int widthMeasureSpec, int heightMeasureSpec)496 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 497 if (mCenterVertically) { 498 Paint.FontMetrics fm = getPaint().getFontMetrics(); 499 int cellHeightPx = mIconSize + getCompoundDrawablePadding() + 500 (int) Math.ceil(fm.bottom - fm.top); 501 int height = MeasureSpec.getSize(heightMeasureSpec); 502 setPadding(getPaddingLeft(), (height - cellHeightPx) / 2, getPaddingRight(), 503 getPaddingBottom()); 504 } 505 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 506 } 507 508 @Override onDetachedFromWindow()509 protected void onDetachedFromWindow() { 510 super.onDetachedFromWindow(); 511 if (mBackground != null) mBackground.setCallback(null); 512 } 513 514 @Override setTextColor(int color)515 public void setTextColor(int color) { 516 mTextColor = color; 517 super.setTextColor(color); 518 } 519 520 @Override setTextColor(ColorStateList colors)521 public void setTextColor(ColorStateList colors) { 522 mTextColor = colors.getDefaultColor(); 523 super.setTextColor(colors); 524 } 525 setTextVisibility(boolean visible)526 public void setTextVisibility(boolean visible) { 527 Resources res = getResources(); 528 if (visible) { 529 super.setTextColor(mTextColor); 530 } else { 531 super.setTextColor(res.getColor(android.R.color.transparent)); 532 } 533 } 534 535 @Override cancelLongPress()536 public void cancelLongPress() { 537 super.cancelLongPress(); 538 539 mLongPressHelper.cancelLongPress(); 540 } 541 applyPromiseState(boolean promiseStateChanged)542 public void applyPromiseState(boolean promiseStateChanged) { 543 if (getTag() instanceof ShortcutInfo) { 544 ShortcutInfo info = (ShortcutInfo) getTag(); 545 final boolean isPromise = info.isPromise(); 546 final int progressLevel = isPromise ? 547 ((info.hasStatusFlag(ShortcutInfo.FLAG_INSTALL_SESSION_ACTIVE) ? 548 info.getInstallProgress() : 0)) : 100; 549 550 setContentDescription(progressLevel > 0 ? 551 getContext().getString(R.string.app_downloading_title, info.title, 552 NumberFormat.getPercentInstance().format(progressLevel * 0.01)) : 553 getContext().getString(R.string.app_waiting_download_title, info.title)); 554 555 if (mIcon != null) { 556 final PreloadIconDrawable preloadDrawable; 557 if (mIcon instanceof PreloadIconDrawable) { 558 preloadDrawable = (PreloadIconDrawable) mIcon; 559 } else { 560 preloadDrawable = DrawableFactory.get(getContext()) 561 .newPendingIcon(info.iconBitmap, getContext()); 562 setIcon(preloadDrawable); 563 } 564 565 preloadDrawable.setLevel(progressLevel); 566 if (promiseStateChanged) { 567 preloadDrawable.maybePerformFinishedAnimation(); 568 } 569 } 570 } 571 } 572 applyBadgeState(ItemInfo itemInfo, boolean animate)573 public void applyBadgeState(ItemInfo itemInfo, boolean animate) { 574 if (mIcon instanceof FastBitmapDrawable) { 575 boolean wasBadged = mBadgeInfo != null; 576 mBadgeInfo = mLauncher.getPopupDataProvider().getBadgeInfoForItem(itemInfo); 577 boolean isBadged = mBadgeInfo != null; 578 float newBadgeScale = isBadged ? 1f : 0; 579 mBadgeRenderer = mLauncher.getDeviceProfile().mBadgeRenderer; 580 if (wasBadged || isBadged) { 581 mBadgePalette = IconPalette.getBadgePalette(getResources()); 582 if (mBadgePalette == null) { 583 mBadgePalette = ((FastBitmapDrawable) mIcon).getIconPalette(); 584 } 585 // Animate when a badge is first added or when it is removed. 586 if (animate && (wasBadged ^ isBadged) && isShown()) { 587 ObjectAnimator.ofFloat(this, BADGE_SCALE_PROPERTY, newBadgeScale).start(); 588 } else { 589 mBadgeScale = newBadgeScale; 590 invalidate(); 591 } 592 } 593 } 594 } 595 getBadgePalette()596 public IconPalette getBadgePalette() { 597 return mBadgePalette; 598 } 599 600 /** 601 * Sets the icon for this view based on the layout direction. 602 */ setIcon(Drawable icon)603 private void setIcon(Drawable icon) { 604 mIcon = icon; 605 mIcon.setBounds(0, 0, mIconSize, mIconSize); 606 applyCompoundDrawables(mIcon); 607 } 608 applyCompoundDrawables(Drawable icon)609 protected void applyCompoundDrawables(Drawable icon) { 610 if (mLayoutHorizontal) { 611 setCompoundDrawablesRelative(icon, null, null, null); 612 } else { 613 setCompoundDrawables(null, icon, null, null); 614 } 615 } 616 617 @Override requestLayout()618 public void requestLayout() { 619 if (!mDisableRelayout) { 620 super.requestLayout(); 621 } 622 } 623 624 /** 625 * Applies the item info if it is same as what the view is pointing to currently. 626 */ 627 @Override reapplyItemInfo(ItemInfoWithIcon info)628 public void reapplyItemInfo(ItemInfoWithIcon info) { 629 if (getTag() == info) { 630 mIconLoadRequest = null; 631 mDisableRelayout = true; 632 633 if (info instanceof AppInfo) { 634 applyFromApplicationInfo((AppInfo) info); 635 } else if (info instanceof ShortcutInfo) { 636 applyFromShortcutInfo((ShortcutInfo) info); 637 if ((info.rank < FolderIcon.NUM_ITEMS_IN_PREVIEW) && (info.container >= 0)) { 638 View folderIcon = 639 mLauncher.getWorkspace().getHomescreenIconByItemId(info.container); 640 if (folderIcon != null) { 641 folderIcon.invalidate(); 642 } 643 } 644 } else if (info instanceof PackageItemInfo) { 645 applyFromPackageItemInfo((PackageItemInfo) info); 646 } 647 648 mDisableRelayout = false; 649 } 650 } 651 652 /** 653 * Verifies that the current icon is high-res otherwise posts a request to load the icon. 654 */ verifyHighRes()655 public void verifyHighRes() { 656 if (mIconLoadRequest != null) { 657 mIconLoadRequest.cancel(); 658 mIconLoadRequest = null; 659 } 660 if (getTag() instanceof ItemInfoWithIcon) { 661 ItemInfoWithIcon info = (ItemInfoWithIcon) getTag(); 662 if (info.usingLowResIcon) { 663 mIconLoadRequest = LauncherAppState.getInstance(getContext()).getIconCache() 664 .updateIconInBackground(BubbleTextView.this, info); 665 } 666 } 667 } 668 669 /** 670 * Interface to be implemented by the grand parent to allow click shadow effect. 671 */ 672 public interface BubbleTextShadowHandler { setPressedIcon(BubbleTextView icon, Bitmap background)673 void setPressedIcon(BubbleTextView icon, Bitmap background); 674 } 675 } 676