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