• 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.view.accessibility.AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED;
19 import static android.window.DesktopModeFlags.ENABLE_TASKBAR_RECENTS_LAYOUT_TRANSITION;
20 
21 import static com.android.launcher3.BubbleTextView.DISPLAY_TASKBAR;
22 import static com.android.launcher3.Flags.enableCursorHoverStates;
23 import static com.android.launcher3.Flags.enableRecentsInTaskbar;
24 import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR;
25 import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_FOLDER;
26 import static com.android.launcher3.config.FeatureFlags.enableTaskbarPinning;
27 import static com.android.launcher3.icons.IconNormalizer.ICON_VISIBLE_AREA_FACTOR;
28 
29 import android.content.Context;
30 import android.content.res.Resources;
31 import android.graphics.Canvas;
32 import android.graphics.Rect;
33 import android.graphics.drawable.Drawable;
34 import android.util.ArraySet;
35 import android.util.AttributeSet;
36 import android.view.DisplayCutout;
37 import android.view.InputDevice;
38 import android.view.LayoutInflater;
39 import android.view.MotionEvent;
40 import android.view.View;
41 import android.widget.FrameLayout;
42 
43 import androidx.annotation.LayoutRes;
44 import androidx.annotation.NonNull;
45 import androidx.annotation.Nullable;
46 
47 import com.android.launcher3.BubbleTextView;
48 import com.android.launcher3.DeviceProfile;
49 import com.android.launcher3.Flags;
50 import com.android.launcher3.Insettable;
51 import com.android.launcher3.R;
52 import com.android.launcher3.Utilities;
53 import com.android.launcher3.apppairs.AppPairIcon;
54 import com.android.launcher3.folder.FolderIcon;
55 import com.android.launcher3.folder.PreviewBackground;
56 import com.android.launcher3.model.data.AppPairInfo;
57 import com.android.launcher3.model.data.CollectionInfo;
58 import com.android.launcher3.model.data.FolderInfo;
59 import com.android.launcher3.model.data.ItemInfo;
60 import com.android.launcher3.model.data.WorkspaceItemInfo;
61 import com.android.launcher3.taskbar.customization.TaskbarAllAppsButtonContainer;
62 import com.android.launcher3.taskbar.customization.TaskbarDividerContainer;
63 import com.android.launcher3.uioverrides.PredictedAppIcon;
64 import com.android.launcher3.util.Themes;
65 import com.android.launcher3.views.ActivityContext;
66 import com.android.quickstep.util.GroupTask;
67 import com.android.quickstep.util.SingleTask;
68 import com.android.quickstep.views.TaskViewType;
69 import com.android.systemui.shared.recents.model.Task;
70 import com.android.wm.shell.shared.bubbles.BubbleBarLocation;
71 
72 import java.util.Arrays;
73 import java.util.Collections;
74 import java.util.List;
75 import java.util.Objects;
76 import java.util.Set;
77 
78 /**
79  * Hosts the Taskbar content such as Hotseat and Recent Apps. Drawn on top of other apps.
80  */
81 public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconParent, Insettable,
82         DeviceProfile.OnDeviceProfileChangeListener {
83     private static final Rect sTmpRect = new Rect();
84 
85     private final int[] mTempOutLocation = new int[2];
86     private final Rect mIconLayoutBounds;
87     private final int mIconTouchSize;
88     private final int mItemMarginLeftRight;
89     private final int mItemPadding;
90     private final int mFolderLeaveBehindColor;
91     private final boolean mIsRtl;
92 
93     private final TaskbarActivityContext mActivityContext;
94     @Nullable private BubbleBarLocation mBubbleBarLocation = null;
95 
96     // Initialized in init.
97     private TaskbarViewCallbacks mControllerCallbacks;
98     private View.OnClickListener mIconClickListener;
99     private View.OnLongClickListener mIconLongClickListener;
100 
101     // Only non-null when the corresponding Folder is open.
102     @Nullable private FolderIcon mLeaveBehindFolderIcon;
103 
104     // Only non-null when device supports having an All Apps button.
105     private final TaskbarAllAppsButtonContainer mAllAppsButtonContainer;
106 
107     // Only non-null when device supports having a Divider button.
108     @Nullable private TaskbarDividerContainer mTaskbarDividerContainer;
109 
110     // Only non-null when device supports having a Taskbar Overflow button.
111     @Nullable private TaskbarOverflowView mTaskbarOverflowView;
112 
113     private int mNextViewIndex;
114 
115     /**
116      * Whether the divider is between Hotseat icons and Recents,
117      * instead of between All Apps button and Hotseat.
118      */
119     private boolean mAddedDividerForRecents;
120 
121     private final View mQsb;
122 
123     private final float mTransientTaskbarMinWidth;
124 
125     private boolean mShouldTryStartAlign;
126 
127     private int mMaxNumIcons = 0;
128     private int mIdealNumIcons = 0;
129 
130     private final int mAllAppsButtonTranslationOffset;
131 
132     private int mNumStaticViews;
133 
134     private Set<GroupTask> mPrevRecentTasks = Collections.emptySet();
135     private Set<GroupTask> mPrevOverflowTasks = Collections.emptySet();
136 
TaskbarView(@onNull Context context)137     public TaskbarView(@NonNull Context context) {
138         this(context, null);
139     }
140 
TaskbarView(@onNull Context context, @Nullable AttributeSet attrs)141     public TaskbarView(@NonNull Context context, @Nullable AttributeSet attrs) {
142         this(context, attrs, 0);
143     }
144 
TaskbarView(@onNull Context context, @Nullable AttributeSet attrs, int defStyleAttr)145     public TaskbarView(@NonNull Context context, @Nullable AttributeSet attrs,
146             int defStyleAttr) {
147         this(context, attrs, defStyleAttr, 0);
148     }
149 
TaskbarView(@onNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes)150     public TaskbarView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr,
151             int defStyleRes) {
152         super(context, attrs, defStyleAttr, defStyleRes);
153         mActivityContext = ActivityContext.lookupContext(context);
154         mIconLayoutBounds = mActivityContext.getTransientTaskbarBounds();
155         Resources resources = getResources();
156         mIsRtl = Utilities.isRtl(resources);
157         mTransientTaskbarMinWidth = resources.getDimension(R.dimen.transient_taskbar_min_width);
158 
159         onDeviceProfileChanged(mActivityContext.getDeviceProfile());
160 
161         int actualMargin = resources.getDimensionPixelSize(R.dimen.taskbar_icon_spacing);
162         int actualIconSize = mActivityContext.getDeviceProfile().taskbarIconSize;
163         if (enableTaskbarPinning() && !mActivityContext.isThreeButtonNav()) {
164             DeviceProfile deviceProfile = mActivityContext.getTransientTaskbarDeviceProfile();
165             actualIconSize = deviceProfile.taskbarIconSize;
166         }
167         int visualIconSize = (int) (actualIconSize * ICON_VISIBLE_AREA_FACTOR);
168 
169         mIconTouchSize = Math.max(actualIconSize,
170                 resources.getDimensionPixelSize(R.dimen.taskbar_icon_min_touch_size));
171 
172         // We layout the icons to be of mIconTouchSize in width and height
173         mItemMarginLeftRight = actualMargin - (mIconTouchSize - visualIconSize) / 2;
174 
175         // We always layout taskbar as a transient taskbar when we have taskbar pinning feature on,
176         // then we scale and translate the icons to match persistent taskbar designs, so we use
177         // taskbar icon size from current device profile to calculate correct item padding.
178         mItemPadding = (mIconTouchSize - mActivityContext.getDeviceProfile().taskbarIconSize) / 2;
179         mFolderLeaveBehindColor = Themes.getAttrColor(mActivityContext,
180                 android.R.attr.textColorTertiary);
181 
182         // Needed to draw folder leave-behind when opening one.
183         setWillNotDraw(false);
184 
185         mAllAppsButtonContainer = new TaskbarAllAppsButtonContainer(context);
186         mAllAppsButtonTranslationOffset = (int) getResources().getDimension(
187                 mAllAppsButtonContainer.getAllAppsButtonTranslationXOffset(
188                         mActivityContext.isTransientTaskbar()));
189 
190         if (enableTaskbarPinning() || enableRecentsInTaskbar()) {
191             mTaskbarDividerContainer = new TaskbarDividerContainer(context);
192         }
193 
194         if (Flags.taskbarOverflow()) {
195             mTaskbarOverflowView = TaskbarOverflowView.inflateIcon(
196                     R.layout.taskbar_overflow_view, this,
197                     mIconTouchSize, mItemPadding);
198         }
199 
200         // TODO: Disable touch events on QSB otherwise it can crash.
201         mQsb = LayoutInflater.from(context).inflate(R.layout.search_container_hotseat, this, false);
202     }
203 
204     /**
205      * @return the maximum number of 'icons' that can fit in the taskbar.
206      */
calculateMaxNumIcons()207     private int calculateMaxNumIcons() {
208         DeviceProfile deviceProfile = mActivityContext.getDeviceProfile();
209         int availableWidth = deviceProfile.widthPx;
210         int defaultEdgeMargin =
211                 (int) getResources().getDimension(deviceProfile.inv.inlineNavButtonsEndSpacing);
212         int spaceForBubbleBar =
213                 Math.round(mControllerCallbacks.getBubbleBarMaxCollapsedWidthIfVisible());
214 
215         // Reserve space required for edge margins, or for navbar if shown. If task bar needs to be
216         // center aligned with nav bar shown, reserve space on both sides.
217         availableWidth -=
218                 Math.max(defaultEdgeMargin + spaceForBubbleBar, deviceProfile.hotseatBarEndOffset);
219         availableWidth -= Math.max(
220                 defaultEdgeMargin + (mShouldTryStartAlign ? 0 : spaceForBubbleBar),
221                 mShouldTryStartAlign ? 0 : deviceProfile.hotseatBarEndOffset);
222 
223         // The space taken by an item icon used during layout.
224         int iconSize = 2 * mItemMarginLeftRight + mIconTouchSize;
225 
226         int additionalIcons = 0;
227 
228         if (mTaskbarDividerContainer != null) {
229             // Space for divider icon is reduced during layout compared to normal icon size, reserve
230             // space for the divider separately.
231             availableWidth -= iconSize - 4 * mItemMarginLeftRight;
232             ++additionalIcons;
233         }
234 
235         // All apps icon takes less space compared to normal icon size, reserve space for the icon
236         // separately.
237         boolean forceTransientTaskbarSize =
238                 enableTaskbarPinning() && !mActivityContext.isThreeButtonNav();
239         availableWidth -= iconSize - (int) getResources().getDimension(
240                 mAllAppsButtonContainer.getAllAppsButtonTranslationXOffset(
241                         forceTransientTaskbarSize || mActivityContext.isTransientTaskbar()));
242         ++additionalIcons;
243 
244         return Math.floorDiv(availableWidth, iconSize) + additionalIcons;
245     }
246 
247     /**
248      * Recalculates the max number of icons the taskbar view can show without entering overflow.
249      * Returns whether the max number of icons changed and the change affects the number of icons
250      * that should be shown in the taskbar.
251      */
updateMaxNumIcons()252     boolean updateMaxNumIcons() {
253         if (!Flags.taskbarOverflow()) {
254             return false;
255         }
256         int oldMaxNumIcons = mMaxNumIcons;
257         mMaxNumIcons = calculateMaxNumIcons();
258         return oldMaxNumIcons != mMaxNumIcons
259                 && (mIdealNumIcons > oldMaxNumIcons || mIdealNumIcons > mMaxNumIcons);
260     }
261 
262     /**
263      * Pre-adds views that are always children of this view for LayoutTransition support.
264      * <p>
265      * Normally these views are removed and re-added when updating hotseat and recents. This
266      * approach does not behave well with LayoutTransition, so we instead need to add them
267      * initially and avoid removing them during updates.
268      */
addStaticViews()269     private int addStaticViews() {
270         int numStaticViews = 1;
271         addView(mAllAppsButtonContainer);
272         if (mActivityContext.getDeviceProfile().isQsbInline) {
273             addView(mQsb, mIsRtl ? 1 : 0);
274             mQsb.setVisibility(View.INVISIBLE);
275             numStaticViews++;
276         }
277         return numStaticViews;
278     }
279 
280     @Override
setVisibility(int visibility)281     public void setVisibility(int visibility) {
282         boolean changed = getVisibility() != visibility;
283         super.setVisibility(visibility);
284         if (changed && mControllerCallbacks != null) {
285             mControllerCallbacks.notifyVisibilityChanged();
286         }
287     }
288 
289     @Override
onAttachedToWindow()290     protected void onAttachedToWindow() {
291         super.onAttachedToWindow();
292         mActivityContext.addOnDeviceProfileChangeListener(this);
293     }
294 
295     @Override
onDetachedFromWindow()296     protected void onDetachedFromWindow() {
297         super.onDetachedFromWindow();
298         mActivityContext.removeOnDeviceProfileChangeListener(this);
299     }
300 
301     @Override
onDeviceProfileChanged(DeviceProfile dp)302     public void onDeviceProfileChanged(DeviceProfile dp) {
303         mShouldTryStartAlign = mActivityContext.shouldStartAlignTaskbar();
304     }
305 
announceTaskbarShown()306     private void announceTaskbarShown() {
307         BubbleBarLocation bubbleBarLocation = mControllerCallbacks.getBubbleBarLocationIfVisible();
308         if (bubbleBarLocation == null) {
309             announceForAccessibility(mContext.getString(R.string.taskbar_a11y_shown_title));
310         } else if (bubbleBarLocation.isOnLeft(isLayoutRtl())) {
311             announceForAccessibility(
312                     mContext.getString(R.string.taskbar_a11y_shown_with_bubbles_left_title));
313         } else {
314             announceForAccessibility(
315                     mContext.getString(R.string.taskbar_a11y_shown_with_bubbles_right_title));
316         }
317     }
318 
announceAccessibilityChanges()319     protected void announceAccessibilityChanges() {
320         // Only announce taskbar window shown. Window disappearing is generally not announce.
321         // This also aligns with talkback guidelines and unnecessary announcement to users.
322         if (isVisibleToUser()) {
323             announceTaskbarShown();
324         }
325         ActivityContext.lookupContext(getContext()).getDragLayer()
326                 .sendAccessibilityEvent(TYPE_WINDOW_CONTENT_CHANGED);
327     }
328 
329     /**
330      * Returns the icon touch size.
331      */
getIconTouchSize()332     public int getIconTouchSize() {
333         return mIconTouchSize;
334     }
335 
init(TaskbarViewCallbacks callbacks)336     protected void init(TaskbarViewCallbacks callbacks) {
337         // set taskbar pane title so that accessibility service know it window and focuses.
338         setAccessibilityPaneTitle(getContext().getString(R.string.taskbar_a11y_title));
339         mControllerCallbacks = callbacks;
340         mIconClickListener = mControllerCallbacks.getIconOnClickListener();
341         mIconLongClickListener = mControllerCallbacks.getIconOnLongClickListener();
342 
343         mAllAppsButtonContainer.setUpCallbacks(callbacks);
344         if (mTaskbarDividerContainer != null
345                 && mActivityContext.getTaskbarFeatureEvaluator().getSupportsPinningPopup()) {
346             mTaskbarDividerContainer.setUpCallbacks(callbacks);
347         }
348         if (mTaskbarOverflowView != null) {
349             mTaskbarOverflowView.setOnClickListener(
350                     mControllerCallbacks.getOverflowOnClickListener());
351             mTaskbarOverflowView.setOnLongClickListener(
352                     mControllerCallbacks.getOverflowOnLongClickListener());
353         }
354         if (Flags.showTaskbarPinningPopupFromAnywhere()
355                 && mActivityContext.getTaskbarFeatureEvaluator().getSupportsPinningPopup()) {
356             setOnTouchListener(mControllerCallbacks.getTaskbarTouchListener());
357         }
358 
359         if (Flags.taskbarOverflow()) {
360             mMaxNumIcons = calculateMaxNumIcons();
361         }
362     }
363 
removeAndRecycle(View view)364     private void removeAndRecycle(View view) {
365         removeView(view);
366         view.setOnClickListener(null);
367         view.setOnLongClickListener(null);
368         if (!(view.getTag() instanceof CollectionInfo)) {
369             mActivityContext.getViewCache().recycleView(view.getSourceLayoutResId(), view);
370         }
371         view.setTag(null);
372     }
373 
374     /** Inflates/binds the hotseat items and recent tasks to the view. */
updateItems(ItemInfo[] hotseatItemInfos, List<GroupTask> recentTasks)375     protected void updateItems(ItemInfo[] hotseatItemInfos, List<GroupTask> recentTasks) {
376         if (mActivityContext.isDestroyed()) return;
377         // Filter out unsupported items.
378         hotseatItemInfos = Arrays.stream(hotseatItemInfos)
379                 .filter(Objects::nonNull)
380                 .toArray(ItemInfo[]::new);
381         // TODO(b/343289567 and b/316004172): support app pairs and desktop mode.
382         recentTasks = recentTasks.stream().filter(it -> it instanceof SingleTask).toList();
383 
384         if (ENABLE_TASKBAR_RECENTS_LAYOUT_TRANSITION.isTrue()) {
385             updateItemsWithLayoutTransition(hotseatItemInfos, recentTasks);
386         } else {
387             updateItemsWithoutLayoutTransition(hotseatItemInfos, recentTasks);
388         }
389     }
390 
updateItemsWithoutLayoutTransition( ItemInfo[] hotseatItemInfos, List<GroupTask> recentTasks)391     private void updateItemsWithoutLayoutTransition(
392             ItemInfo[] hotseatItemInfos, List<GroupTask> recentTasks) {
393 
394         mNextViewIndex = 0;
395         mAddedDividerForRecents = false;
396 
397         removeView(mAllAppsButtonContainer);
398 
399         if (mTaskbarDividerContainer != null) {
400             removeView(mTaskbarDividerContainer);
401         }
402         if (mTaskbarOverflowView != null) {
403             removeView(mTaskbarOverflowView);
404         }
405         removeView(mQsb);
406 
407         updateHotseatItems(hotseatItemInfos);
408 
409         if (mTaskbarDividerContainer != null && !recentTasks.isEmpty()) {
410             addView(mTaskbarDividerContainer, mNextViewIndex++);
411             mAddedDividerForRecents = true;
412         }
413 
414         updateRecents(recentTasks, hotseatItemInfos.length);
415 
416         addView(mAllAppsButtonContainer, mIsRtl ? hotseatItemInfos.length : 0);
417 
418         // If there are no recent tasks, add divider after All Apps (unless it's the only view).
419         if (!mAddedDividerForRecents
420                 && mTaskbarDividerContainer != null
421                 && getChildCount() > 1) {
422             addView(mTaskbarDividerContainer, mIsRtl ? (getChildCount() - 1) : 1);
423         }
424 
425         if (mActivityContext.getDeviceProfile().isQsbInline) {
426             addView(mQsb, mIsRtl ? getChildCount() : 0);
427             // Always set QSB to invisible after re-adding.
428             mQsb.setVisibility(View.INVISIBLE);
429         }
430     }
431 
updateItemsWithLayoutTransition( ItemInfo[] hotseatItemInfos, List<GroupTask> recentTasks)432     private void updateItemsWithLayoutTransition(
433             ItemInfo[] hotseatItemInfos, List<GroupTask> recentTasks) {
434         if (mNumStaticViews == 0) {
435             mNumStaticViews = addStaticViews();
436         }
437 
438         // Skip static views and potential All Apps divider, if they are on the left.
439         mNextViewIndex = mIsRtl ? 0 : mNumStaticViews;
440         if (getChildAt(mNextViewIndex) == mTaskbarDividerContainer && !mAddedDividerForRecents) {
441             mNextViewIndex++;
442         }
443 
444         // Update left section.
445         if (mIsRtl) {
446             updateRecents(recentTasks.reversed(), hotseatItemInfos.length);
447         } else {
448             updateHotseatItems(hotseatItemInfos);
449         }
450 
451         // Now at theoretical position for recent apps divider.
452         updateRecentsDivider(!recentTasks.isEmpty());
453         if (getChildAt(mNextViewIndex) == mTaskbarDividerContainer) {
454             mNextViewIndex++;
455         }
456 
457         // Update right section.
458         if (mIsRtl) {
459             updateHotseatItems(hotseatItemInfos);
460         } else {
461             updateRecents(recentTasks, hotseatItemInfos.length);
462         }
463 
464         // Recents divider takes priority.
465         if (!mAddedDividerForRecents && !mActivityContext.isInDesktopMode()) {
466             updateAllAppsDivider();
467         }
468     }
469 
updateRecentsDivider(boolean hasRecents)470     private void updateRecentsDivider(boolean hasRecents) {
471         if (hasRecents && !mAddedDividerForRecents) {
472             mAddedDividerForRecents = true;
473 
474             // Remove possible All Apps divider.
475             if (getChildAt(mNumStaticViews) == mTaskbarDividerContainer) {
476                 mNextViewIndex--; // All Apps divider on the left. Need to account for removing it.
477             }
478             removeView(mTaskbarDividerContainer);
479 
480             addView(mTaskbarDividerContainer, mNextViewIndex);
481         } else if (!hasRecents && mAddedDividerForRecents) {
482             mAddedDividerForRecents = false;
483             removeViewAt(mNextViewIndex);
484         }
485     }
486 
updateAllAppsDivider()487     private void updateAllAppsDivider() {
488         // Index where All Apps divider would be if it is already in Taskbar.
489         final int expectedAllAppsDividerIndex =
490                 mIsRtl ? getChildCount() - mNumStaticViews - 1 : mNumStaticViews;
491         if (getChildAt(expectedAllAppsDividerIndex) == mTaskbarDividerContainer
492                 && getChildCount() == mNumStaticViews + 1) {
493             // Only static views with divider so remove divider.
494             removeView(mTaskbarDividerContainer);
495         } else if (getChildAt(expectedAllAppsDividerIndex) != mTaskbarDividerContainer
496                 && getChildCount() >= mNumStaticViews + 1) {
497             // Static views with at least one app icon so add divider. For RTL, add it after the
498             // icon that is at the expected index.
499             addView(
500                     mTaskbarDividerContainer,
501                     mIsRtl ? expectedAllAppsDividerIndex + 1 : expectedAllAppsDividerIndex);
502         }
503     }
504 
updateHotseatItems(ItemInfo[] hotseatItemInfos)505     private void updateHotseatItems(ItemInfo[] hotseatItemInfos) {
506         int numViewsAnimated = 0;
507 
508         for (ItemInfo hotseatItemInfo : hotseatItemInfos) {
509             // Replace any Hotseat views with the appropriate type if it's not already that type.
510             final int expectedLayoutResId;
511             boolean isCollection = false;
512             if (hotseatItemInfo.isPredictedItem()) {
513                 expectedLayoutResId = R.layout.taskbar_predicted_app_icon;
514             } else if (hotseatItemInfo instanceof CollectionInfo ci) {
515                 expectedLayoutResId = ci.itemType == ITEM_TYPE_APP_PAIR
516                         ? R.layout.app_pair_icon
517                         : R.layout.folder_icon;
518                 isCollection = true;
519             } else {
520                 expectedLayoutResId = R.layout.taskbar_app_icon;
521             }
522 
523             View hotseatView = null;
524             while (isNextViewInSection(ItemInfo.class)) {
525                 hotseatView = getChildAt(mNextViewIndex);
526 
527                 // see if the view can be reused
528                 if ((hotseatView.getSourceLayoutResId() != expectedLayoutResId)
529                         || (isCollection && (hotseatView.getTag() != hotseatItemInfo))) {
530                     // Unlike for BubbleTextView, we can't reapply a new FolderInfo after inflation,
531                     // so if the info changes we need to reinflate. This should only happen if a new
532                     // folder is dragged to the position that another folder previously existed.
533                     removeAndRecycle(hotseatView);
534                     hotseatView = null;
535                 } else {
536                     // View found
537                     break;
538                 }
539             }
540 
541             if (hotseatView == null) {
542                 if (isCollection) {
543                     CollectionInfo collectionInfo = (CollectionInfo) hotseatItemInfo;
544                     switch (hotseatItemInfo.itemType) {
545                         case ITEM_TYPE_FOLDER:
546                             hotseatView = FolderIcon.inflateFolderAndIcon(
547                                     expectedLayoutResId, mActivityContext, this,
548                                     (FolderInfo) collectionInfo);
549                             ((FolderIcon) hotseatView).setTextVisible(false);
550                             break;
551                         case ITEM_TYPE_APP_PAIR:
552                             hotseatView = AppPairIcon.inflateIcon(
553                                     expectedLayoutResId, mActivityContext, this,
554                                     (AppPairInfo) collectionInfo, DISPLAY_TASKBAR);
555                             ((AppPairIcon) hotseatView).setTextVisible(false);
556                             break;
557                         default:
558                             throw new IllegalStateException(
559                                     "Unexpected item type: " + hotseatItemInfo.itemType);
560                     }
561                 } else {
562                     hotseatView = inflate(expectedLayoutResId);
563                 }
564                 LayoutParams lp = new LayoutParams(mIconTouchSize, mIconTouchSize);
565                 hotseatView.setPadding(mItemPadding, mItemPadding, mItemPadding, mItemPadding);
566                 addView(hotseatView, mNextViewIndex, lp);
567             } else if (hotseatView instanceof FolderIcon fi) {
568                 fi.onItemsChanged(false);
569                 fi.getFolder().reapplyItemInfo();
570             }
571 
572             // Apply the Hotseat ItemInfos, or hide the view if there is none for a given index.
573             if (hotseatView instanceof BubbleTextView btv
574                     && hotseatItemInfo instanceof WorkspaceItemInfo workspaceInfo) {
575                 if (btv instanceof PredictedAppIcon pai) {
576                     if (pai.applyFromWorkspaceItemWithAnimation(workspaceInfo, numViewsAnimated)) {
577                         numViewsAnimated++;
578                     }
579                 } else {
580                     btv.applyFromWorkspaceItem(workspaceInfo);
581                 }
582             }
583             setClickAndLongClickListenersForIcon(hotseatView);
584             if (enableCursorHoverStates()) {
585                 setHoverListenerForIcon(hotseatView);
586             }
587             mNextViewIndex++;
588         }
589 
590         while (isNextViewInSection(ItemInfo.class)) {
591             removeAndRecycle(getChildAt(mNextViewIndex));
592         }
593     }
594 
updateRecents(List<GroupTask> recentTasks, int hotseatSize)595     private void updateRecents(List<GroupTask> recentTasks, int hotseatSize) {
596         boolean supportsOverflow = Flags.taskbarOverflow() && recentTasks.size() > 1;
597         int overflowSize = 0;
598         boolean hasOverflow = false;
599         if (supportsOverflow && mTaskbarOverflowView != null) {
600             // Need to account for All Apps and the divider. If we need to have an overflow, we will
601             // have a divider for recents.
602             final int nonTaskIconsToBeAdded = 2;
603             mIdealNumIcons = hotseatSize + recentTasks.size() + nonTaskIconsToBeAdded;
604             overflowSize = mIdealNumIcons - mMaxNumIcons;
605             hasOverflow = overflowSize > 0;
606 
607             if (!ENABLE_TASKBAR_RECENTS_LAYOUT_TRANSITION.isTrue() && hasOverflow) {
608                 addView(mTaskbarOverflowView, mNextViewIndex++);
609             } else if (ENABLE_TASKBAR_RECENTS_LAYOUT_TRANSITION.isTrue()) {
610                 // RTL case is handled after we add the recent icons, because the button needs to
611                 // then be to the right of them.
612                 if (hasOverflow && !mIsRtl) {
613                     if (mPrevOverflowTasks.isEmpty()) addView(mTaskbarOverflowView, mNextViewIndex);
614                     // NOTE: If overflow already existed, assume the overflow view is already
615                     // at the correct position.
616                     mNextViewIndex++;
617                 } else if (!hasOverflow && !mPrevOverflowTasks.isEmpty()) {
618                     removeView(mTaskbarOverflowView);
619                     mTaskbarOverflowView.clearItems();
620                 }
621             } else {
622                 mTaskbarOverflowView.clearItems();
623             }
624         }
625 
626         // An extra item needs to be added to overflow button to account for the space taken up by
627         // the overflow button.
628         final int itemsToAddToOverflow =
629                 hasOverflow ? Math.min(overflowSize + 1, recentTasks.size()) : 0;
630         final Set<GroupTask> overflownRecentsSet;
631         if (hasOverflow && mTaskbarOverflowView != null) {
632             final int startIndex = mIsRtl ? recentTasks.size() - itemsToAddToOverflow : 0;
633             final int endIndex = mIsRtl ? recentTasks.size() : itemsToAddToOverflow;
634             final List<GroupTask> overflownRecents = recentTasks.subList(startIndex, endIndex);
635             mTaskbarOverflowView.setItems(
636                     overflownRecents.stream().map(t -> ((SingleTask) t).getTask()).toList());
637             overflownRecentsSet = new ArraySet<>(overflownRecents);
638         } else {
639             overflownRecentsSet = Collections.emptySet();
640         }
641 
642         // Add Recent/Running icons.
643         final Set<GroupTask> recentTasksSet = new ArraySet<>(recentTasks);
644         final int startIndex = mIsRtl ? 0 : itemsToAddToOverflow;
645         final int endIndex =
646                 mIsRtl ? recentTasks.size() - itemsToAddToOverflow : recentTasks.size();
647         for (GroupTask task : recentTasks.subList(startIndex, endIndex)) {
648             // Replace any Recent views with the appropriate type if it's not already that type.
649             final int expectedLayoutResId;
650             boolean isCollection = false;
651             if (!(task instanceof SingleTask)) {
652                 if (task.taskViewType == TaskViewType.DESKTOP) {
653                     // TODO(b/316004172): use Desktop tile layout.
654                     expectedLayoutResId = -1;
655                 } else {
656                     // TODO(b/343289567): use R.layout.app_pair_icon
657                     expectedLayoutResId = -1;
658                 }
659                 isCollection = true;
660             } else {
661                 expectedLayoutResId = R.layout.taskbar_app_icon;
662             }
663 
664             View recentIcon = null;
665             // If a task is new, we should not reuse a view so that it animates in when it is added.
666             final boolean canReuseView = !ENABLE_TASKBAR_RECENTS_LAYOUT_TRANSITION.isTrue()
667                     || (mPrevRecentTasks.contains(task) && !mPrevOverflowTasks.contains(task));
668             while (canReuseView && isNextViewInSection(GroupTask.class)) {
669                 recentIcon = getChildAt(mNextViewIndex);
670                 GroupTask tag = (GroupTask) recentIcon.getTag();
671 
672                 // see if the view can be reused
673                 if ((recentIcon.getSourceLayoutResId() != expectedLayoutResId)
674                         || (isCollection && tag != task)
675                         // Remove view corresponding to removed task so that it animates out.
676                         || (ENABLE_TASKBAR_RECENTS_LAYOUT_TRANSITION.isTrue()
677                                 && (!recentTasksSet.contains(tag)
678                                         || overflownRecentsSet.contains(tag)))) {
679                     removeAndRecycle(recentIcon);
680                     recentIcon = null;
681                 } else {
682                     // View found
683                     break;
684                 }
685             }
686 
687             if (recentIcon == null) {
688                 // TODO(b/343289567 and b/316004172): support app pairs and desktop mode.
689                 recentIcon = inflate(expectedLayoutResId);
690                 LayoutParams lp = new LayoutParams(mIconTouchSize, mIconTouchSize);
691                 recentIcon.setPadding(mItemPadding, mItemPadding, mItemPadding, mItemPadding);
692                 addView(recentIcon, mNextViewIndex, lp);
693             }
694 
695             if (recentIcon instanceof BubbleTextView btv) {
696                 applyGroupTaskToBubbleTextView(btv, task);
697             }
698             setClickAndLongClickListenersForIcon(recentIcon);
699             if (enableCursorHoverStates()) {
700                 setHoverListenerForIcon(recentIcon);
701             }
702             mNextViewIndex++;
703         }
704 
705         while (isNextViewInSection(GroupTask.class)) {
706             removeAndRecycle(getChildAt(mNextViewIndex));
707         }
708 
709         if (ENABLE_TASKBAR_RECENTS_LAYOUT_TRANSITION.isTrue() && mIsRtl && hasOverflow) {
710             if (mPrevOverflowTasks.isEmpty()) {
711                 addView(mTaskbarOverflowView, mNextViewIndex);
712             }
713             mNextViewIndex++;
714         }
715 
716         mPrevRecentTasks = recentTasksSet;
717         mPrevOverflowTasks = overflownRecentsSet;
718     }
719 
isNextViewInSection(Class<?> tagClass)720     private boolean isNextViewInSection(Class<?> tagClass) {
721         return mNextViewIndex < getChildCount()
722                 && tagClass.isInstance(getChildAt(mNextViewIndex).getTag());
723     }
724 
725     /** Binds the SingleTask to the BubbleTextView to be ready to present to the user. */
applyGroupTaskToBubbleTextView(BubbleTextView btv, GroupTask groupTask)726     public void applyGroupTaskToBubbleTextView(BubbleTextView btv, GroupTask groupTask) {
727         if (!(groupTask instanceof SingleTask singleTask)) {
728             // TODO(b/343289567 and b/316004172): support app pairs and desktop mode.
729             return;
730         }
731 
732         Task task = singleTask.getTask();
733         // TODO(b/344038728): use FastBitmapDrawable instead of Drawable, to get disabled state
734         //  while dragging.
735         Drawable taskIcon = task.icon;
736         if (taskIcon != null) {
737             taskIcon = taskIcon.getConstantState().newDrawable().mutate();
738         }
739         btv.applyIconAndLabel(taskIcon, task.titleDescription);
740         btv.setTag(singleTask);
741     }
742 
743     /**
744      * Sets OnClickListener and OnLongClickListener for the given view.
745      */
setClickAndLongClickListenersForIcon(View icon)746     public void setClickAndLongClickListenersForIcon(View icon) {
747         icon.setOnClickListener(mIconClickListener);
748         icon.setOnLongClickListener(mIconLongClickListener);
749         // Add right-click support to btv icons.
750         icon.setOnTouchListener((v, event) -> {
751             if (event.isFromSource(InputDevice.SOURCE_MOUSE)
752                     && (event.getButtonState() & MotionEvent.BUTTON_SECONDARY) != 0
753                     && v instanceof BubbleTextView) {
754                 mActivityContext.showPopupMenuForIcon((BubbleTextView) v);
755                 return true;
756             }
757             return false;
758         });
759     }
760 
761     /**
762      * Sets OnHoverListener for the given view.
763      */
setHoverListenerForIcon(View icon)764     private void setHoverListenerForIcon(View icon) {
765         icon.setOnHoverListener(mControllerCallbacks.getIconOnHoverListener(icon));
766     }
767 
768     /** Updates taskbar icons accordingly to the new bubble bar location. */
onBubbleBarLocationUpdated(BubbleBarLocation location)769     public void onBubbleBarLocationUpdated(BubbleBarLocation location) {
770         if (mBubbleBarLocation == location) return;
771         mBubbleBarLocation = location;
772         requestLayout();
773     }
774 
775     /**
776      * Returns translation X for the taskbar icons for provided {@link BubbleBarLocation}. If the
777      * bubble bar is not enabled, or location of the bubble bar is the same, or taskbar is not start
778      * aligned - returns 0.
779      */
getTranslationXForBubbleBarPosition(BubbleBarLocation location)780     public float getTranslationXForBubbleBarPosition(BubbleBarLocation location) {
781         if (!mControllerCallbacks.isBubbleBarEnabled()
782                 || location == mBubbleBarLocation
783                 || !mActivityContext.shouldStartAlignTaskbar()
784         ) {
785             return 0;
786         }
787         Rect iconsBounds = getTransientTaskbarIconLayoutBoundsInParent();
788         return getTaskBarIconsEndForBubbleBarLocation(location) - iconsBounds.right;
789     }
790 
791     @Override
onLayout(boolean changed, int left, int top, int right, int bottom)792     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
793         int spaceNeeded = getIconLayoutWidth();
794         boolean layoutRtl = isLayoutRtl();
795         DeviceProfile deviceProfile = mActivityContext.getDeviceProfile();
796         int navSpaceNeeded = deviceProfile.hotseatBarEndOffset;
797         int centerAlignIconEnd = (right + left + spaceNeeded) / 2;
798         int iconEnd = centerAlignIconEnd;
799         if (mShouldTryStartAlign) {
800             int startSpacingPx = deviceProfile.inlineNavButtonsEndSpacingPx;
801             if (mControllerCallbacks.isBubbleBarEnabled()
802                     && mBubbleBarLocation != null
803                     && mActivityContext.shouldStartAlignTaskbar()) {
804                 iconEnd = (int) getTaskBarIconsEndForBubbleBarLocation(mBubbleBarLocation);
805             } else {
806                 if (layoutRtl) {
807                     iconEnd = right - startSpacingPx;
808                 } else {
809                     iconEnd = startSpacingPx + spaceNeeded;
810                 }
811                 boolean needMoreSpaceForNav = layoutRtl
812                         ? navSpaceNeeded > (iconEnd - spaceNeeded)
813                         : iconEnd > (right - navSpaceNeeded);
814                 if (needMoreSpaceForNav) {
815                     // Add offset to account for nav bar when taskbar is centered
816                     int offset = layoutRtl
817                             ? navSpaceNeeded - (centerAlignIconEnd - spaceNeeded)
818                             : (right - navSpaceNeeded) - centerAlignIconEnd;
819                     iconEnd = centerAlignIconEnd + offset;
820                 }
821             }
822         }
823 
824         // Currently, we support only one device with display cutout and we only are concern about
825         // it when the bottom rect is present and non empty
826         DisplayCutout displayCutout = getDisplay().getCutout();
827         if (displayCutout != null && !displayCutout.getBoundingRectBottom().isEmpty()) {
828             Rect cutoutBottomRect = displayCutout.getBoundingRectBottom();
829             // when cutout present at the bottom of screen align taskbar icons to cutout offset
830             // if taskbar icon overlaps with cutout
831             int taskbarIconLeftBound = iconEnd - spaceNeeded;
832             int taskbarIconRightBound = iconEnd;
833 
834             boolean doesTaskbarIconsOverlapWithCutout =
835                     taskbarIconLeftBound <= cutoutBottomRect.centerX()
836                             && cutoutBottomRect.centerX() <= taskbarIconRightBound;
837 
838             if (doesTaskbarIconsOverlapWithCutout) {
839                 if (!layoutRtl) {
840                     iconEnd = spaceNeeded + cutoutBottomRect.width();
841                 } else {
842                     iconEnd = right - cutoutBottomRect.width();
843                 }
844             }
845         }
846 
847         sTmpRect.set(mIconLayoutBounds);
848 
849         // Layout the children
850         mIconLayoutBounds.right = iconEnd;
851         mIconLayoutBounds.top = (bottom - top - mIconTouchSize) / 2;
852         mIconLayoutBounds.bottom = mIconLayoutBounds.top + mIconTouchSize;
853 
854         // With rtl layout, the all apps button will be translated by `allAppsButtonOffset` after
855         // layout completion (by `TaskbarViewController`). Offset the icon end by the same amount
856         // when laying out icons, so the taskbar content remains centered after all apps button
857         // translation.
858         if (layoutRtl) {
859             iconEnd += mAllAppsButtonTranslationOffset;
860         }
861 
862         mControllerCallbacks.onPreLayoutChildren();
863 
864         int count = getChildCount();
865         for (int i = count; i > 0; i--) {
866             View child = getChildAt(i - 1);
867             if (child == mQsb) {
868                 int qsbStart;
869                 int qsbEnd;
870                 if (layoutRtl) {
871                     qsbStart = iconEnd + mItemMarginLeftRight;
872                     qsbEnd = qsbStart + deviceProfile.hotseatQsbWidth;
873                 } else {
874                     qsbEnd = iconEnd - mItemMarginLeftRight;
875                     qsbStart = qsbEnd - deviceProfile.hotseatQsbWidth;
876                 }
877                 int qsbTop = (bottom - top - deviceProfile.hotseatQsbHeight) / 2;
878                 int qsbBottom = qsbTop + deviceProfile.hotseatQsbHeight;
879                 child.layout(qsbStart, qsbTop, qsbEnd, qsbBottom);
880             } else if (child == mTaskbarDividerContainer) {
881                 iconEnd += mItemMarginLeftRight;
882                 int iconStart = iconEnd - mIconTouchSize;
883                 child.layout(iconStart, mIconLayoutBounds.top, iconEnd, mIconLayoutBounds.bottom);
884                 iconEnd = iconStart + mItemMarginLeftRight;
885             } else {
886                 iconEnd -= mItemMarginLeftRight;
887                 int iconStart = iconEnd - mIconTouchSize;
888                 child.layout(iconStart, mIconLayoutBounds.top, iconEnd, mIconLayoutBounds.bottom);
889                 iconEnd = iconStart - mItemMarginLeftRight;
890             }
891         }
892 
893         mIconLayoutBounds.left = iconEnd;
894 
895         // Adjust the icon layout bounds by the amount by which all apps button will be translated
896         // post layout to maintain margin between all apps button and the edge of the transient
897         // taskbar background. Done for ltr layout only - for rtl layout, the offset needs to be
898         // adjusted on the right, which is done by offsetting `iconEnd` after setting
899         // `mIconLayoutBounds.right`.
900         if (!layoutRtl) {
901             mIconLayoutBounds.left += mAllAppsButtonTranslationOffset;
902         }
903 
904         if (mIconLayoutBounds.right - mIconLayoutBounds.left < mTransientTaskbarMinWidth) {
905             int center = mIconLayoutBounds.centerX();
906             int distanceFromCenter = (int) mTransientTaskbarMinWidth / 2;
907             mIconLayoutBounds.right = center + distanceFromCenter;
908             mIconLayoutBounds.left = center - distanceFromCenter;
909         }
910 
911         if (!sTmpRect.equals(mIconLayoutBounds)) {
912             mControllerCallbacks.notifyIconLayoutBoundsChanged();
913         }
914     }
915 
916     /**
917      * Returns whether the given MotionEvent, *in screen coordinates*, is within any Taskbar item's
918      * touch bounds.
919      */
isEventOverAnyItem(MotionEvent ev)920     public boolean isEventOverAnyItem(MotionEvent ev) {
921         getLocationOnScreen(mTempOutLocation);
922         int xInOurCoordinates = (int) ev.getRawX() - mTempOutLocation[0];
923         int yInOurCoordinates = (int) ev.getRawY() - mTempOutLocation[1];
924         return isShown() && getTaskbarIconsActualBounds().contains(xInOurCoordinates,
925                 yInOurCoordinates);
926     }
927 
928     /**
929      * Returns the current visual taskbar icons bounds (unlike `mIconLayoutBounds` which contains
930      * bounds for transient mode only).
931      */
getTaskbarIconsActualBounds()932     private Rect getTaskbarIconsActualBounds() {
933         View[] iconViews = getIconViews();
934         if (iconViews.length == 0) {
935             return new Rect();
936         }
937 
938         int[] firstIconViewLocation = new int[2];
939         int[] lastIconViewLocation = new int[2];
940         iconViews[0].getLocationOnScreen(firstIconViewLocation);
941         iconViews[iconViews.length - 1].getLocationOnScreen(lastIconViewLocation);
942 
943         return new Rect(firstIconViewLocation[0], 0, lastIconViewLocation[0] + mIconTouchSize,
944                 getHeight());
945     }
946 
947     /**
948      * Gets visual bounds of the taskbar view. The visual bounds correspond to the taskbar touch
949      * area, rather than layout placement in the parent view.
950      */
getTransientTaskbarIconLayoutBounds()951     public Rect getTransientTaskbarIconLayoutBounds() {
952         return new Rect(mIconLayoutBounds);
953     }
954 
955     /** Gets taskbar layout bounds in parent view. */
getTransientTaskbarIconLayoutBoundsInParent()956     public Rect getTransientTaskbarIconLayoutBoundsInParent() {
957         Rect actualBounds = new Rect(mIconLayoutBounds);
958         actualBounds.top = getTop();
959         actualBounds.bottom = getBottom();
960         return actualBounds;
961     }
962 
963     /**
964      * Returns the space used by the icons
965      */
getIconLayoutWidth()966     private int getIconLayoutWidth() {
967         int countExcludingQsb = getChildCount();
968         DeviceProfile deviceProfile = mActivityContext.getDeviceProfile();
969         if (deviceProfile.isQsbInline) {
970             countExcludingQsb--;
971         }
972 
973         int iconLayoutBoundsWidth =
974                 countExcludingQsb * (mItemMarginLeftRight * 2 + mIconTouchSize);
975 
976         if (enableTaskbarPinning() && countExcludingQsb > 1) {
977             // We are removing 4 * mItemMarginLeftRight as there should be no space between
978             // All Apps icon, divider icon, and first app icon in taskbar
979             iconLayoutBoundsWidth -= mItemMarginLeftRight * 4;
980         }
981 
982         // The all apps button container gets offset horizontally, reducing the overall taskbar
983         // view size.
984         iconLayoutBoundsWidth -= mAllAppsButtonTranslationOffset;
985 
986         return iconLayoutBoundsWidth;
987     }
988 
989     /**
990      * Returns the app icons currently shown in the taskbar. The returned list does not include qsb,
991      * but it includes all apps button and icon divider views.
992      */
getIconViews()993     public View[] getIconViews() {
994         final int count = getChildCount();
995         if (count == 0) {
996             return new View[0];
997         }
998         View[] icons = new View[count - (mActivityContext.getDeviceProfile().isQsbInline ? 1 : 0)];
999         int insertionPoint = 0;
1000         for (int i = 0; i < count; i++) {
1001             if (getChildAt(i)  == mQsb) continue;
1002             icons[insertionPoint++] = getChildAt(i);
1003         }
1004         return icons;
1005     }
1006 
1007     /**
1008      * The max number of icon views the taskbar can have when taskbar overflow is enabled.
1009      */
getMaxNumIconViews()1010     int getMaxNumIconViews() {
1011         return mMaxNumIcons;
1012     }
1013 
1014     /**
1015      * Returns the all apps button in the taskbar.
1016      */
getAllAppsButtonContainer()1017     public TaskbarAllAppsButtonContainer getAllAppsButtonContainer() {
1018         return mAllAppsButtonContainer;
1019     }
1020 
1021     /**
1022      * Returns the taskbar divider in the taskbar.
1023      */
1024     @Nullable
getTaskbarDividerViewContainer()1025     public TaskbarDividerContainer getTaskbarDividerViewContainer() {
1026         return mTaskbarDividerContainer;
1027     }
1028 
1029     /**
1030      * Returns the taskbar overflow view in the taskbar.
1031      */
1032     @Nullable
getTaskbarOverflowView()1033     public TaskbarOverflowView getTaskbarOverflowView() {
1034         return mTaskbarOverflowView;
1035     }
1036 
1037     /**
1038      * Returns whether the divider is between Hotseat icons and Recents,
1039      * instead of between All Apps button and Hotseat.
1040      */
isDividerForRecents()1041     public boolean isDividerForRecents() {
1042         return mAddedDividerForRecents;
1043     }
1044 
1045     /**
1046      * Returns the QSB in the taskbar.
1047      */
getQsb()1048     public View getQsb() {
1049         return mQsb;
1050     }
1051 
1052     // FolderIconParent implemented methods.
1053 
1054     @Override
drawFolderLeaveBehindForIcon(FolderIcon child)1055     public void drawFolderLeaveBehindForIcon(FolderIcon child) {
1056         mLeaveBehindFolderIcon = child;
1057         invalidate();
1058     }
1059 
1060     @Override
clearFolderLeaveBehind(FolderIcon child)1061     public void clearFolderLeaveBehind(FolderIcon child) {
1062         mLeaveBehindFolderIcon = null;
1063         invalidate();
1064     }
1065 
1066     // End FolderIconParent implemented methods.
1067 
1068     @Override
onDraw(Canvas canvas)1069     protected void onDraw(Canvas canvas) {
1070         super.onDraw(canvas);
1071         if (mLeaveBehindFolderIcon != null) {
1072             canvas.save();
1073             canvas.translate(
1074                     mLeaveBehindFolderIcon.getLeft() + mLeaveBehindFolderIcon.getTranslationX(),
1075                     mLeaveBehindFolderIcon.getTop());
1076             PreviewBackground previewBackground = mLeaveBehindFolderIcon.getFolderBackground();
1077             previewBackground.drawLeaveBehind(canvas, mFolderLeaveBehindColor);
1078             canvas.restore();
1079         }
1080     }
1081 
inflate(@ayoutRes int layoutResId)1082     private View inflate(@LayoutRes int layoutResId) {
1083         return mActivityContext.getViewCache().getView(layoutResId, mActivityContext, this);
1084     }
1085 
1086     @Override
setInsets(Rect insets)1087     public void setInsets(Rect insets) {
1088         // Ignore, we just implement Insettable to draw behind system insets.
1089     }
1090 
areIconsVisible()1091     public boolean areIconsVisible() {
1092         // Consider the overall visibility
1093         return getVisibility() == VISIBLE;
1094     }
1095 
1096     /**
1097      * @return The all apps button horizontal offset used to calculate the taskbar contents width
1098      * during layout.
1099      */
getAllAppsButtonTranslationXOffsetUsedForLayout()1100     public int getAllAppsButtonTranslationXOffsetUsedForLayout() {
1101         return mAllAppsButtonTranslationOffset;
1102     }
1103 
1104     /**
1105      * This method only works for bubble bar enabled in persistent task bar and the taskbar is start
1106      * aligned.
1107      */
getTaskBarIconsEndForBubbleBarLocation(BubbleBarLocation location)1108     private float getTaskBarIconsEndForBubbleBarLocation(BubbleBarLocation location) {
1109         DeviceProfile deviceProfile = mActivityContext.getDeviceProfile();
1110         boolean navbarOnRight = location.isOnLeft(isLayoutRtl());
1111         int navSpaceNeeded = deviceProfile.hotseatBarEndOffset;
1112         if (navbarOnRight) {
1113             return getWidth() - navSpaceNeeded;
1114         } else {
1115             return navSpaceNeeded + getIconLayoutWidth();
1116         }
1117     }
1118 }
1119