• 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 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