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