1 /* 2 * Copyright (C) 2021 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 package com.android.launcher3.taskbar; 17 18 import static android.content.pm.PackageManager.FEATURE_PC; 19 import static android.view.accessibility.AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED; 20 21 import static com.android.launcher3.config.FeatureFlags.ENABLE_CURSOR_HOVER_STATES; 22 import static com.android.launcher3.icons.IconNormalizer.ICON_VISIBLE_AREA_FACTOR; 23 24 import android.content.Context; 25 import android.content.res.Resources; 26 import android.graphics.Canvas; 27 import android.graphics.Rect; 28 import android.os.Bundle; 29 import android.util.AttributeSet; 30 import android.view.LayoutInflater; 31 import android.view.MotionEvent; 32 import android.view.View; 33 import android.view.accessibility.AccessibilityNodeInfo; 34 import android.widget.FrameLayout; 35 36 import androidx.annotation.LayoutRes; 37 import androidx.annotation.NonNull; 38 import androidx.annotation.Nullable; 39 40 import com.android.launcher3.BubbleTextView; 41 import com.android.launcher3.DeviceProfile; 42 import com.android.launcher3.Insettable; 43 import com.android.launcher3.R; 44 import com.android.launcher3.Utilities; 45 import com.android.launcher3.config.FeatureFlags; 46 import com.android.launcher3.folder.FolderIcon; 47 import com.android.launcher3.icons.ThemedIconDrawable; 48 import com.android.launcher3.model.data.FolderInfo; 49 import com.android.launcher3.model.data.ItemInfo; 50 import com.android.launcher3.model.data.WorkspaceItemInfo; 51 import com.android.launcher3.util.DisplayController; 52 import com.android.launcher3.util.LauncherBindableItemsContainer; 53 import com.android.launcher3.util.Themes; 54 import com.android.launcher3.views.ActivityContext; 55 import com.android.launcher3.views.DoubleShadowBubbleTextView; 56 import com.android.launcher3.views.IconButtonView; 57 58 import java.util.function.Predicate; 59 60 /** 61 * Hosts the Taskbar content such as Hotseat and Recent Apps. Drawn on top of other apps. 62 */ 63 public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconParent, Insettable, 64 DeviceProfile.OnDeviceProfileChangeListener { 65 private static final String TAG = TaskbarView.class.getSimpleName(); 66 67 private static final Rect sTmpRect = new Rect(); 68 69 private final int[] mTempOutLocation = new int[2]; 70 private final Rect mIconLayoutBounds; 71 private final int mIconTouchSize; 72 private final int mItemMarginLeftRight; 73 private final int mItemPadding; 74 private final int mFolderLeaveBehindColor; 75 private final boolean mIsRtl; 76 77 private final TaskbarActivityContext mActivityContext; 78 79 // Initialized in init. 80 private TaskbarViewController.TaskbarViewCallbacks mControllerCallbacks; 81 private View.OnClickListener mIconClickListener; 82 private View.OnLongClickListener mIconLongClickListener; 83 84 // Only non-null when the corresponding Folder is open. 85 private @Nullable FolderIcon mLeaveBehindFolderIcon; 86 87 // Only non-null when device supports having an All Apps button. 88 private @Nullable IconButtonView mAllAppsButton; 89 90 // Only non-null when device supports having an All Apps button. 91 private @Nullable IconButtonView mTaskbarDivider; 92 93 private View mQsb; 94 95 private float mTransientTaskbarMinWidth; 96 97 private float mTransientTaskbarAllAppsButtonTranslationXOffset; 98 99 private boolean mShouldTryStartAlign; 100 TaskbarView(@onNull Context context)101 public TaskbarView(@NonNull Context context) { 102 this(context, null); 103 } 104 TaskbarView(@onNull Context context, @Nullable AttributeSet attrs)105 public TaskbarView(@NonNull Context context, @Nullable AttributeSet attrs) { 106 this(context, attrs, 0); 107 } 108 TaskbarView(@onNull Context context, @Nullable AttributeSet attrs, int defStyleAttr)109 public TaskbarView(@NonNull Context context, @Nullable AttributeSet attrs, 110 int defStyleAttr) { 111 this(context, attrs, defStyleAttr, 0); 112 } 113 TaskbarView(@onNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes)114 public TaskbarView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, 115 int defStyleRes) { 116 super(context, attrs, defStyleAttr, defStyleRes); 117 mActivityContext = ActivityContext.lookupContext(context); 118 mIconLayoutBounds = mActivityContext.getTransientTaskbarBounds(); 119 Resources resources = getResources(); 120 boolean isTransientTaskbar = DisplayController.isTransientTaskbar(mActivityContext) 121 && !TaskbarManager.isPhoneMode(mActivityContext.getDeviceProfile()); 122 mIsRtl = Utilities.isRtl(resources); 123 mTransientTaskbarMinWidth = mContext.getResources().getDimension( 124 R.dimen.transient_taskbar_min_width); 125 mTransientTaskbarAllAppsButtonTranslationXOffset = 126 resources.getDimension(isTransientTaskbar 127 ? R.dimen.transient_taskbar_all_apps_button_translation_x_offset 128 : R.dimen.taskbar_all_apps_button_translation_x_offset); 129 130 onDeviceProfileChanged(mActivityContext.getDeviceProfile()); 131 132 int actualMargin = resources.getDimensionPixelSize(R.dimen.taskbar_icon_spacing); 133 int actualIconSize = mActivityContext.getDeviceProfile().taskbarIconSize; 134 int visualIconSize = (int) (actualIconSize * ICON_VISIBLE_AREA_FACTOR); 135 136 mIconTouchSize = Math.max(actualIconSize, 137 resources.getDimensionPixelSize(R.dimen.taskbar_icon_min_touch_size)); 138 139 // We layout the icons to be of mIconTouchSize in width and height 140 mItemMarginLeftRight = actualMargin - (mIconTouchSize - visualIconSize) / 2; 141 mItemPadding = (mIconTouchSize - actualIconSize) / 2; 142 143 mFolderLeaveBehindColor = Themes.getAttrColor(mActivityContext, 144 android.R.attr.textColorTertiary); 145 146 // Needed to draw folder leave-behind when opening one. 147 setWillNotDraw(false); 148 149 if (!mActivityContext.getPackageManager().hasSystemFeature(FEATURE_PC)) { 150 mAllAppsButton = (IconButtonView) LayoutInflater.from(context) 151 .inflate(R.layout.taskbar_all_apps_button, this, false); 152 mAllAppsButton.setIconDrawable(resources.getDrawable(isTransientTaskbar 153 ? R.drawable.ic_transient_taskbar_all_apps_button 154 : R.drawable.ic_taskbar_all_apps_button)); 155 mAllAppsButton.setScaleX(mIsRtl ? -1 : 1); 156 mAllAppsButton.setPadding(mItemPadding, mItemPadding, mItemPadding, mItemPadding); 157 mAllAppsButton.setForegroundTint( 158 mActivityContext.getColor(R.color.all_apps_button_color)); 159 160 if (FeatureFlags.ENABLE_TASKBAR_PINNING.get()) { 161 mTaskbarDivider = (IconButtonView) LayoutInflater.from(context).inflate( 162 R.layout.taskbar_divider, 163 this, false); 164 mTaskbarDivider.setIconDrawable( 165 resources.getDrawable(R.drawable.taskbar_divider_button)); 166 mTaskbarDivider.setPadding(mItemPadding, mItemPadding, mItemPadding, mItemPadding); 167 } 168 } 169 170 // TODO: Disable touch events on QSB otherwise it can crash. 171 mQsb = LayoutInflater.from(context).inflate(R.layout.search_container_hotseat, this, false); 172 } 173 174 @Override onAttachedToWindow()175 protected void onAttachedToWindow() { 176 super.onAttachedToWindow(); 177 mActivityContext.addOnDeviceProfileChangeListener(this); 178 } 179 180 @Override onDetachedFromWindow()181 protected void onDetachedFromWindow() { 182 super.onDetachedFromWindow(); 183 mActivityContext.removeOnDeviceProfileChangeListener(this); 184 } 185 186 @Override onDeviceProfileChanged(DeviceProfile dp)187 public void onDeviceProfileChanged(DeviceProfile dp) { 188 mShouldTryStartAlign = mActivityContext.isThreeButtonNav() && dp.startAlignTaskbar; 189 } 190 191 @Override performAccessibilityActionInternal(int action, Bundle arguments)192 public boolean performAccessibilityActionInternal(int action, Bundle arguments) { 193 if (action == AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS) { 194 announceForAccessibility(mContext.getString(R.string.taskbar_a11y_shown_title)); 195 } else if (action == AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS) { 196 announceForAccessibility(mContext.getString(R.string.taskbar_a11y_hidden_title)); 197 } 198 return super.performAccessibilityActionInternal(action, arguments); 199 200 } 201 announceAccessibilityChanges()202 protected void announceAccessibilityChanges() { 203 this.performAccessibilityAction( 204 isVisibleToUser() ? AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS 205 : AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS, null); 206 207 ActivityContext.lookupContext(getContext()).getDragLayer() 208 .sendAccessibilityEvent(TYPE_WINDOW_CONTENT_CHANGED); 209 } 210 211 /** 212 * Returns the icon touch size. 213 */ getIconTouchSize()214 public int getIconTouchSize() { 215 return mIconTouchSize; 216 } 217 init(TaskbarViewController.TaskbarViewCallbacks callbacks)218 protected void init(TaskbarViewController.TaskbarViewCallbacks callbacks) { 219 // set taskbar pane title so that accessibility service know it window and focuses. 220 setAccessibilityPaneTitle(getContext().getString(R.string.taskbar_a11y_title)); 221 mControllerCallbacks = callbacks; 222 mIconClickListener = mControllerCallbacks.getIconOnClickListener(); 223 mIconLongClickListener = mControllerCallbacks.getIconOnLongClickListener(); 224 225 setOnLongClickListener(mControllerCallbacks.getBackgroundOnLongClickListener()); 226 227 if (mAllAppsButton != null) { 228 mAllAppsButton.setOnClickListener(mControllerCallbacks.getAllAppsButtonClickListener()); 229 } 230 if (mTaskbarDivider != null) { 231 mTaskbarDivider.setOnLongClickListener( 232 mControllerCallbacks.getTaskbarDividerLongClickListener()); 233 } 234 } 235 removeAndRecycle(View view)236 private void removeAndRecycle(View view) { 237 removeView(view); 238 view.setOnClickListener(null); 239 view.setOnLongClickListener(null); 240 if (!(view.getTag() instanceof FolderInfo)) { 241 mActivityContext.getViewCache().recycleView(view.getSourceLayoutResId(), view); 242 } 243 view.setTag(null); 244 } 245 246 /** 247 * Inflates/binds the Hotseat views to show in the Taskbar given their ItemInfos. 248 */ updateHotseatItems(ItemInfo[] hotseatItemInfos)249 protected void updateHotseatItems(ItemInfo[] hotseatItemInfos) { 250 int nextViewIndex = 0; 251 int numViewsAnimated = 0; 252 253 if (mAllAppsButton != null) { 254 removeView(mAllAppsButton); 255 256 if (mTaskbarDivider != null) { 257 removeView(mTaskbarDivider); 258 } 259 } 260 removeView(mQsb); 261 262 263 for (int i = 0; i < hotseatItemInfos.length; i++) { 264 ItemInfo hotseatItemInfo = hotseatItemInfos[i]; 265 if (hotseatItemInfo == null) { 266 continue; 267 } 268 269 // Replace any Hotseat views with the appropriate type if it's not already that type. 270 final int expectedLayoutResId; 271 boolean isFolder = false; 272 if (hotseatItemInfo.isPredictedItem()) { 273 expectedLayoutResId = R.layout.taskbar_predicted_app_icon; 274 } else if (hotseatItemInfo instanceof FolderInfo) { 275 expectedLayoutResId = R.layout.folder_icon; 276 isFolder = true; 277 } else { 278 expectedLayoutResId = R.layout.taskbar_app_icon; 279 } 280 281 View hotseatView = null; 282 while (nextViewIndex < getChildCount()) { 283 hotseatView = getChildAt(nextViewIndex); 284 285 // see if the view can be reused 286 if ((hotseatView.getSourceLayoutResId() != expectedLayoutResId) 287 || (isFolder && (hotseatView.getTag() != hotseatItemInfo))) { 288 // Unlike for BubbleTextView, we can't reapply a new FolderInfo after inflation, 289 // so if the info changes we need to reinflate. This should only happen if a new 290 // folder is dragged to the position that another folder previously existed. 291 removeAndRecycle(hotseatView); 292 hotseatView = null; 293 } else { 294 // View found 295 break; 296 } 297 } 298 299 if (hotseatView == null) { 300 if (isFolder) { 301 FolderInfo folderInfo = (FolderInfo) hotseatItemInfo; 302 FolderIcon folderIcon = FolderIcon.inflateFolderAndIcon(expectedLayoutResId, 303 mActivityContext, this, folderInfo); 304 folderIcon.setTextVisible(false); 305 hotseatView = folderIcon; 306 } else { 307 hotseatView = inflate(expectedLayoutResId); 308 } 309 LayoutParams lp = new LayoutParams(mIconTouchSize, mIconTouchSize); 310 hotseatView.setPadding(mItemPadding, mItemPadding, mItemPadding, mItemPadding); 311 addView(hotseatView, nextViewIndex, lp); 312 } 313 314 // Apply the Hotseat ItemInfos, or hide the view if there is none for a given index. 315 if (hotseatView instanceof BubbleTextView 316 && hotseatItemInfo instanceof WorkspaceItemInfo) { 317 BubbleTextView btv = (BubbleTextView) hotseatView; 318 WorkspaceItemInfo workspaceInfo = (WorkspaceItemInfo) hotseatItemInfo; 319 320 boolean animate = btv.shouldAnimateIconChange((WorkspaceItemInfo) hotseatItemInfo); 321 btv.applyFromWorkspaceItem(workspaceInfo, animate, numViewsAnimated); 322 if (animate) { 323 numViewsAnimated++; 324 } 325 } 326 setClickAndLongClickListenersForIcon(hotseatView); 327 if (ENABLE_CURSOR_HOVER_STATES.get()) { 328 setHoverListenerForIcon(hotseatView); 329 } 330 nextViewIndex++; 331 } 332 // Remove remaining views 333 while (nextViewIndex < getChildCount()) { 334 removeAndRecycle(getChildAt(nextViewIndex)); 335 } 336 337 if (mAllAppsButton != null) { 338 mAllAppsButton.setTranslationXForTaskbarAllAppsIcon(getChildCount() > 0 339 ? mTransientTaskbarAllAppsButtonTranslationXOffset : 0f); 340 addView(mAllAppsButton, mIsRtl ? getChildCount() : 0); 341 342 // if only all apps button present, don't include divider view. 343 if (mTaskbarDivider != null && getChildCount() > 1) { 344 addView(mTaskbarDivider, mIsRtl ? (getChildCount() - 1) : 1); 345 } 346 } 347 if (mActivityContext.getDeviceProfile().isQsbInline) { 348 addView(mQsb, mIsRtl ? getChildCount() : 0); 349 // Always set QSB to invisible after re-adding. 350 mQsb.setVisibility(View.INVISIBLE); 351 } 352 } 353 354 /** 355 * Traverse all the child views and change the background of themeIcons 356 **/ setThemedIconsBackgroundColor(int color)357 public void setThemedIconsBackgroundColor(int color) { 358 for (View icon : getIconViews()) { 359 if (icon instanceof DoubleShadowBubbleTextView) { 360 DoubleShadowBubbleTextView textView = ((DoubleShadowBubbleTextView) icon); 361 if (textView.getIcon() != null 362 && textView.getIcon() instanceof ThemedIconDrawable) { 363 ((ThemedIconDrawable) textView.getIcon()).changeBackgroundColor(color); 364 } 365 } 366 } 367 } 368 369 /** 370 * Sets OnClickListener and OnLongClickListener for the given view. 371 */ setClickAndLongClickListenersForIcon(View icon)372 public void setClickAndLongClickListenersForIcon(View icon) { 373 icon.setOnClickListener(mIconClickListener); 374 icon.setOnLongClickListener(mIconLongClickListener); 375 } 376 377 /** 378 * Sets OnHoverListener for the given view. 379 */ setHoverListenerForIcon(View icon)380 private void setHoverListenerForIcon(View icon) { 381 icon.setOnHoverListener(mControllerCallbacks.getIconOnHoverListener(icon)); 382 } 383 384 @Override onLayout(boolean changed, int left, int top, int right, int bottom)385 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 386 int count = getChildCount(); 387 DeviceProfile deviceProfile = mActivityContext.getDeviceProfile(); 388 int spaceNeeded = getIconLayoutWidth(); 389 int navSpaceNeeded = deviceProfile.hotseatBarEndOffset; 390 boolean layoutRtl = isLayoutRtl(); 391 int centerAlignIconEnd = right - (right - left - spaceNeeded) / 2; 392 int iconEnd; 393 394 if (mShouldTryStartAlign) { 395 // Taskbar is aligned to the start 396 int startSpacingPx = deviceProfile.inlineNavButtonsEndSpacingPx; 397 398 if (layoutRtl) { 399 iconEnd = right - startSpacingPx; 400 } else { 401 iconEnd = startSpacingPx + spaceNeeded; 402 } 403 } else { 404 iconEnd = centerAlignIconEnd; 405 } 406 407 boolean needMoreSpaceForNav = layoutRtl 408 ? navSpaceNeeded > (iconEnd - spaceNeeded) 409 : iconEnd > (right - navSpaceNeeded); 410 if (needMoreSpaceForNav) { 411 // Add offset to account for nav bar when taskbar is centered 412 int offset = layoutRtl 413 ? navSpaceNeeded - (centerAlignIconEnd - spaceNeeded) 414 : (right - navSpaceNeeded) - centerAlignIconEnd; 415 416 iconEnd = centerAlignIconEnd + offset; 417 } 418 419 sTmpRect.set(mIconLayoutBounds); 420 421 // Layout the children 422 mIconLayoutBounds.right = iconEnd; 423 mIconLayoutBounds.top = (bottom - top - mIconTouchSize) / 2; 424 mIconLayoutBounds.bottom = mIconLayoutBounds.top + mIconTouchSize; 425 for (int i = count; i > 0; i--) { 426 View child = getChildAt(i - 1); 427 if (child == mQsb) { 428 int qsbStart; 429 int qsbEnd; 430 if (layoutRtl) { 431 qsbStart = iconEnd + mItemMarginLeftRight; 432 qsbEnd = qsbStart + deviceProfile.hotseatQsbWidth; 433 } else { 434 qsbEnd = iconEnd - mItemMarginLeftRight; 435 qsbStart = qsbEnd - deviceProfile.hotseatQsbWidth; 436 } 437 int qsbTop = (bottom - top - deviceProfile.hotseatQsbHeight) / 2; 438 int qsbBottom = qsbTop + deviceProfile.hotseatQsbHeight; 439 child.layout(qsbStart, qsbTop, qsbEnd, qsbBottom); 440 } else if (child == mTaskbarDivider) { 441 iconEnd += mItemMarginLeftRight; 442 int iconStart = iconEnd - mIconTouchSize; 443 child.layout(iconStart, mIconLayoutBounds.top, iconEnd, mIconLayoutBounds.bottom); 444 iconEnd = iconStart + mItemMarginLeftRight; 445 } else { 446 iconEnd -= mItemMarginLeftRight; 447 int iconStart = iconEnd - mIconTouchSize; 448 child.layout(iconStart, mIconLayoutBounds.top, iconEnd, mIconLayoutBounds.bottom); 449 iconEnd = iconStart - mItemMarginLeftRight; 450 } 451 } 452 453 mIconLayoutBounds.left = iconEnd; 454 455 if (mIconLayoutBounds.right - mIconLayoutBounds.left < mTransientTaskbarMinWidth) { 456 int center = mIconLayoutBounds.centerX(); 457 int distanceFromCenter = (int) mTransientTaskbarMinWidth / 2; 458 mIconLayoutBounds.right = center + distanceFromCenter; 459 mIconLayoutBounds.left = center - distanceFromCenter; 460 } 461 462 if (!sTmpRect.equals(mIconLayoutBounds)) { 463 mControllerCallbacks.notifyIconLayoutBoundsChanged(); 464 } 465 } 466 467 @Override onTouchEvent(MotionEvent event)468 public boolean onTouchEvent(MotionEvent event) { 469 if (mIconLayoutBounds.left <= event.getX() && event.getX() <= mIconLayoutBounds.right) { 470 // Don't allow long pressing between icons, or above/below them. 471 return true; 472 } 473 if (mControllerCallbacks.onTouchEvent(event)) { 474 int oldAction = event.getAction(); 475 try { 476 event.setAction(MotionEvent.ACTION_CANCEL); 477 return super.onTouchEvent(event); 478 } finally { 479 event.setAction(oldAction); 480 } 481 } 482 return super.onTouchEvent(event); 483 } 484 485 /** 486 * Returns whether the given MotionEvent, *in screen coorindates*, is within any Taskbar item's 487 * touch bounds. 488 */ isEventOverAnyItem(MotionEvent ev)489 public boolean isEventOverAnyItem(MotionEvent ev) { 490 getLocationOnScreen(mTempOutLocation); 491 int xInOurCoordinates = (int) ev.getX() - mTempOutLocation[0]; 492 int yInOurCoorindates = (int) ev.getY() - mTempOutLocation[1]; 493 return isShown() && mIconLayoutBounds.contains(xInOurCoordinates, yInOurCoorindates); 494 } 495 getIconLayoutBounds()496 public Rect getIconLayoutBounds() { 497 return mIconLayoutBounds; 498 } 499 500 /** 501 * Returns the space used by the icons 502 */ getIconLayoutWidth()503 public int getIconLayoutWidth() { 504 int countExcludingQsb = getChildCount(); 505 DeviceProfile deviceProfile = mActivityContext.getDeviceProfile(); 506 if (deviceProfile.isQsbInline) { 507 countExcludingQsb--; 508 } 509 int iconLayoutBoundsWidth = 510 countExcludingQsb * (mItemMarginLeftRight * 2 + mIconTouchSize); 511 512 if (FeatureFlags.ENABLE_TASKBAR_PINNING.get() && countExcludingQsb > 1) { 513 // We are removing 4 * mItemMarginLeftRight as there should be no space between 514 // All Apps icon, divider icon, and first app icon in taskbar 515 iconLayoutBoundsWidth -= mItemMarginLeftRight * 4; 516 } 517 return iconLayoutBoundsWidth; 518 } 519 520 /** 521 * Returns the app icons currently shown in the taskbar. 522 */ getIconViews()523 public View[] getIconViews() { 524 final int count = getChildCount(); 525 View[] icons = new View[count]; 526 for (int i = 0; i < count; i++) { 527 icons[i] = getChildAt(i); 528 } 529 return icons; 530 } 531 532 /** 533 * Returns the all apps button in the taskbar. 534 */ 535 @Nullable getAllAppsButtonView()536 public View getAllAppsButtonView() { 537 return mAllAppsButton; 538 } 539 540 /** 541 * Returns the taskbar divider in the taskbar. 542 */ 543 @Nullable getTaskbarDividerView()544 public View getTaskbarDividerView() { 545 return mTaskbarDivider; 546 } 547 548 /** 549 * Returns the QSB in the taskbar. 550 */ getQsb()551 public View getQsb() { 552 return mQsb; 553 } 554 555 // FolderIconParent implemented methods. 556 557 @Override drawFolderLeaveBehindForIcon(FolderIcon child)558 public void drawFolderLeaveBehindForIcon(FolderIcon child) { 559 mLeaveBehindFolderIcon = child; 560 invalidate(); 561 } 562 563 @Override clearFolderLeaveBehind(FolderIcon child)564 public void clearFolderLeaveBehind(FolderIcon child) { 565 mLeaveBehindFolderIcon = null; 566 invalidate(); 567 } 568 569 // End FolderIconParent implemented methods. 570 571 @Override onDraw(Canvas canvas)572 protected void onDraw(Canvas canvas) { 573 super.onDraw(canvas); 574 if (mLeaveBehindFolderIcon != null) { 575 canvas.save(); 576 canvas.translate(mLeaveBehindFolderIcon.getLeft(), mLeaveBehindFolderIcon.getTop()); 577 mLeaveBehindFolderIcon.getFolderBackground().drawLeaveBehind(canvas, 578 mFolderLeaveBehindColor); 579 canvas.restore(); 580 } 581 } 582 inflate(@ayoutRes int layoutResId)583 private View inflate(@LayoutRes int layoutResId) { 584 return mActivityContext.getViewCache().getView(layoutResId, mActivityContext, this); 585 } 586 587 @Override setInsets(Rect insets)588 public void setInsets(Rect insets) { 589 // Ignore, we just implement Insettable to draw behind system insets. 590 } 591 areIconsVisible()592 public boolean areIconsVisible() { 593 // Consider the overall visibility 594 return getVisibility() == VISIBLE; 595 } 596 597 /** 598 * Maps {@code op} over all the child views. 599 */ mapOverItems(LauncherBindableItemsContainer.ItemOperator op)600 public void mapOverItems(LauncherBindableItemsContainer.ItemOperator op) { 601 // map over all the shortcuts on the taskbar 602 for (int i = 0; i < getChildCount(); i++) { 603 View item = getChildAt(i); 604 if (op.evaluate((ItemInfo) item.getTag(), item)) { 605 return; 606 } 607 } 608 } 609 610 /** 611 * Finds the first icon to match one of the given matchers, from highest to lowest priority. 612 * 613 * @return The first match, or All Apps button if no match was found. 614 */ getFirstMatch(Predicate<ItemInfo>.... matchers)615 public View getFirstMatch(Predicate<ItemInfo>... matchers) { 616 for (Predicate<ItemInfo> matcher : matchers) { 617 for (int i = 0; i < getChildCount(); i++) { 618 View item = getChildAt(i); 619 if (!(item.getTag() instanceof ItemInfo)) { 620 // Should only happen for All Apps button. 621 continue; 622 } 623 ItemInfo info = (ItemInfo) item.getTag(); 624 if (matcher.test(info)) { 625 return item; 626 } 627 } 628 } 629 return mAllAppsButton; 630 } 631 } 632