1 /* 2 * Copyright (C) 2022 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.allapps; 17 18 import static com.android.launcher3.Flags.enableExpandingPauseWorkButton; 19 import static com.android.launcher3.allapps.ActivityAllAppsContainerView.AdapterHolder.MAIN; 20 import static com.android.launcher3.allapps.ActivityAllAppsContainerView.AdapterHolder.SEARCH; 21 import static com.android.launcher3.allapps.ActivityAllAppsContainerView.AdapterHolder.WORK; 22 import static com.android.launcher3.allapps.BaseAllAppsAdapter.VIEW_TYPE_PRIVATE_SPACE_HEADER; 23 import static com.android.launcher3.allapps.BaseAllAppsAdapter.VIEW_TYPE_WORK_DISABLED_CARD; 24 import static com.android.launcher3.allapps.BaseAllAppsAdapter.VIEW_TYPE_WORK_EDU_CARD; 25 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_COUNT; 26 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_TAP_ON_PERSONAL_TAB; 27 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_TAP_ON_WORK_TAB; 28 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; 29 import static com.android.launcher3.util.ScrollableLayoutManager.PREDICTIVE_BACK_MIN_SCALE; 30 import static com.android.launcher3.views.RecyclerViewFastScroller.FastScrollerLocation.ALL_APPS_SCROLLER; 31 import static com.android.window.flags.Flags.predictiveBackThreeButtonNav; 32 33 import android.animation.Animator; 34 import android.animation.AnimatorListenerAdapter; 35 import android.animation.ValueAnimator; 36 import android.content.Context; 37 import android.graphics.Canvas; 38 import android.graphics.Color; 39 import android.graphics.Outline; 40 import android.graphics.Paint; 41 import android.graphics.Path; 42 import android.graphics.Path.Direction; 43 import android.graphics.Point; 44 import android.graphics.Rect; 45 import android.graphics.RectF; 46 import android.os.Bundle; 47 import android.os.Parcelable; 48 import android.os.Process; 49 import android.os.UserManager; 50 import android.util.AttributeSet; 51 import android.util.Log; 52 import android.util.SparseArray; 53 import android.view.KeyEvent; 54 import android.view.LayoutInflater; 55 import android.view.MotionEvent; 56 import android.view.View; 57 import android.view.ViewGroup; 58 import android.view.ViewOutlineProvider; 59 import android.view.WindowInsets; 60 import android.widget.Button; 61 import android.widget.RelativeLayout; 62 63 import androidx.annotation.NonNull; 64 import androidx.annotation.Nullable; 65 import androidx.annotation.Px; 66 import androidx.annotation.VisibleForTesting; 67 import androidx.constraintlayout.widget.ConstraintLayout; 68 import androidx.core.graphics.ColorUtils; 69 import androidx.recyclerview.widget.RecyclerView; 70 71 import com.android.launcher3.DeviceProfile; 72 import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener; 73 import com.android.launcher3.DragSource; 74 import com.android.launcher3.DropTarget.DragObject; 75 import com.android.launcher3.Flags; 76 import com.android.launcher3.Insettable; 77 import com.android.launcher3.InsettableFrameLayout; 78 import com.android.launcher3.R; 79 import com.android.launcher3.Utilities; 80 import com.android.launcher3.allapps.BaseAllAppsAdapter.AdapterItem; 81 import com.android.launcher3.allapps.search.AllAppsSearchUiDelegate; 82 import com.android.launcher3.allapps.search.SearchAdapterProvider; 83 import com.android.launcher3.config.FeatureFlags; 84 import com.android.launcher3.keyboard.FocusedItemDecorator; 85 import com.android.launcher3.keyboard.ViewGroupFocusHelper; 86 import com.android.launcher3.model.StringCache; 87 import com.android.launcher3.model.data.ItemInfo; 88 import com.android.launcher3.pm.UserCache; 89 import com.android.launcher3.recyclerview.AllAppsRecyclerViewPool; 90 import com.android.launcher3.util.ItemInfoMatcher; 91 import com.android.launcher3.util.Preconditions; 92 import com.android.launcher3.util.Themes; 93 import com.android.launcher3.views.ActivityContext; 94 import com.android.launcher3.views.BaseDragLayer; 95 import com.android.launcher3.views.RecyclerViewFastScroller; 96 import com.android.launcher3.views.ScrimView; 97 import com.android.launcher3.views.SpringRelativeLayout; 98 import com.android.launcher3.workprofile.PersonalWorkSlidingTabStrip; 99 import com.android.systemui.plugins.AllAppsRow; 100 101 import java.util.ArrayList; 102 import java.util.Arrays; 103 import java.util.List; 104 import java.util.Optional; 105 import java.util.function.Predicate; 106 import java.util.stream.Stream; 107 108 /** 109 * All apps container view with search support for use in a dragging activity. 110 * 111 * @param <T> Type of context inflating all apps. 112 */ 113 public class ActivityAllAppsContainerView<T extends Context & ActivityContext> 114 extends SpringRelativeLayout implements DragSource, Insettable, 115 OnDeviceProfileChangeListener, PersonalWorkSlidingTabStrip.OnActivePageChangedListener, 116 ScrimView.ScrimDrawingController { 117 118 119 private static final String TAG = "ActivityAllAppsContainerView"; 120 public static final float PULL_MULTIPLIER = .02f; 121 public static final float FLING_VELOCITY_MULTIPLIER = 1200f; 122 protected static final String BUNDLE_KEY_CURRENT_PAGE = "launcher.allapps.current_page"; 123 private static final long DEFAULT_SEARCH_TRANSITION_DURATION_MS = 300; 124 // Render the header protection at all times to debug clipping issues. 125 private static final boolean DEBUG_HEADER_PROTECTION = false; 126 /** Context of an activity or window that is inflating this container. */ 127 128 protected final T mActivityContext; 129 protected final List<AdapterHolder> mAH; 130 protected final Predicate<ItemInfo> mPersonalMatcher = ItemInfoMatcher.ofUser( 131 Process.myUserHandle()); 132 protected WorkProfileManager mWorkManager; 133 protected final PrivateProfileManager mPrivateProfileManager; 134 protected final Point mFastScrollerOffset = new Point(); 135 protected final int mScrimColor; 136 protected final float mHeaderThreshold; 137 protected final AllAppsSearchUiDelegate mSearchUiDelegate; 138 139 // Used to animate Search results out and A-Z apps in, or vice-versa. 140 private final SearchTransitionController mSearchTransitionController; 141 private final Paint mHeaderPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 142 private final Rect mInsets = new Rect(); 143 private final AllAppsStore<T> mAllAppsStore; 144 private final RecyclerView.OnScrollListener mScrollListener = 145 new RecyclerView.OnScrollListener() { 146 @Override 147 public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) { 148 updateHeaderScroll(recyclerView.computeVerticalScrollOffset()); 149 } 150 }; 151 private final Paint mNavBarScrimPaint; 152 private final int mHeaderProtectionColor; 153 private final int mPrivateSpaceBottomExtraSpace; 154 private final Path mTmpPath = new Path(); 155 private final RectF mTmpRectF = new RectF(); 156 protected AllAppsPagedView mViewPager; 157 protected FloatingHeaderView mHeader; 158 protected final List<AllAppsRow> mAdditionalHeaderRows = new ArrayList<>(); 159 protected View mBottomSheetBackground; 160 protected RecyclerViewFastScroller mFastScroller; 161 private ConstraintLayout mFastScrollLetterLayout; 162 163 /** 164 * View that defines the search box. Result is rendered inside {@link #mSearchRecyclerView}. 165 */ 166 protected View mSearchContainer; 167 protected SearchUiManager mSearchUiManager; 168 protected boolean mUsingTabs; 169 protected RecyclerViewFastScroller mTouchHandler; 170 171 /** {@code true} when rendered view is in search state instead of the scroll state. */ 172 private boolean mIsSearching; 173 private boolean mRebindAdaptersAfterSearchAnimation; 174 private int mNavBarScrimHeight = 0; 175 private SearchRecyclerView mSearchRecyclerView; 176 protected SearchAdapterProvider<?> mMainAdapterProvider; 177 private View mBottomSheetHandleArea; 178 private boolean mHasWorkApps; 179 private boolean mHasPrivateApps; 180 private float[] mBottomSheetCornerRadii; 181 private ScrimView mScrimView; 182 private int mHeaderColor; 183 private int mBottomSheetBackgroundColor; 184 private float mBottomSheetBackgroundAlpha = 1f; 185 private int mTabsProtectionAlpha; 186 @Nullable private AllAppsTransitionController mAllAppsTransitionController; 187 ActivityAllAppsContainerView(Context context)188 public ActivityAllAppsContainerView(Context context) { 189 this(context, null); 190 } 191 ActivityAllAppsContainerView(Context context, AttributeSet attrs)192 public ActivityAllAppsContainerView(Context context, AttributeSet attrs) { 193 this(context, attrs, 0); 194 } 195 ActivityAllAppsContainerView(Context context, AttributeSet attrs, int defStyleAttr)196 public ActivityAllAppsContainerView(Context context, AttributeSet attrs, int defStyleAttr) { 197 super(context, attrs, defStyleAttr); 198 mActivityContext = ActivityContext.lookupContext(context); 199 mAllAppsStore = new AllAppsStore<>(mActivityContext); 200 201 mScrimColor = Themes.getAttrColor(context, R.attr.allAppsScrimColor); 202 mHeaderThreshold = getResources().getDimensionPixelSize( 203 R.dimen.dynamic_grid_cell_border_spacing); 204 mHeaderProtectionColor = Themes.getAttrColor(context, R.attr.allappsHeaderProtectionColor); 205 206 mWorkManager = new WorkProfileManager( 207 mActivityContext.getSystemService(UserManager.class), 208 this, 209 mActivityContext.getStatsLogManager(), 210 UserCache.INSTANCE.get(mActivityContext)); 211 mPrivateProfileManager = new PrivateProfileManager( 212 mActivityContext.getSystemService(UserManager.class), 213 this, 214 mActivityContext.getStatsLogManager(), 215 UserCache.INSTANCE.get(mActivityContext)); 216 mPrivateSpaceBottomExtraSpace = context.getResources().getDimensionPixelSize( 217 R.dimen.ps_extra_bottom_padding); 218 mAH = Arrays.asList(null, null, null); 219 mNavBarScrimPaint = new Paint(); 220 mNavBarScrimPaint.setColor(Themes.getNavBarScrimColor(mActivityContext)); 221 222 AllAppsStore.OnUpdateListener onAppsUpdated = this::onAppsUpdated; 223 mAllAppsStore.addUpdateListener(onAppsUpdated); 224 225 // This is a focus listener that proxies focus from a view into the list view. This is to 226 // work around the search box from getting first focus and showing the cursor. 227 setOnFocusChangeListener((v, hasFocus) -> { 228 if (hasFocus && getActiveRecyclerView() != null) { 229 getActiveRecyclerView().requestFocus(); 230 } 231 }); 232 mSearchUiDelegate = createSearchUiDelegate(); 233 initContent(); 234 235 mSearchTransitionController = new SearchTransitionController(this); 236 } 237 238 /** Creates the delegate for initializing search. */ createSearchUiDelegate()239 protected AllAppsSearchUiDelegate createSearchUiDelegate() { 240 return new AllAppsSearchUiDelegate(this); 241 } 242 getSearchUiDelegate()243 public AllAppsSearchUiDelegate getSearchUiDelegate() { 244 return mSearchUiDelegate; 245 } 246 247 /** 248 * Initializes the view hierarchy and internal variables. Any initialization which actually uses 249 * these members should be done in {@link #onFinishInflate()}. 250 * In terms of subclass initialization, the following would be parallel order for activity: 251 * initContent -> onPreCreate 252 * constructor/init -> onCreate 253 * onFinishInflate -> onPostCreate 254 */ initContent()255 protected void initContent() { 256 mMainAdapterProvider = mSearchUiDelegate.createMainAdapterProvider(); 257 258 mAH.set(AdapterHolder.MAIN, new AdapterHolder(AdapterHolder.MAIN, 259 new AlphabeticalAppsList<>(mActivityContext, 260 mAllAppsStore, 261 null, 262 mPrivateProfileManager))); 263 mAH.set(AdapterHolder.WORK, new AdapterHolder(AdapterHolder.WORK, 264 new AlphabeticalAppsList<>(mActivityContext, mAllAppsStore, mWorkManager, null))); 265 mAH.set(SEARCH, new AdapterHolder(SEARCH, 266 new AlphabeticalAppsList<>(mActivityContext, null, null, null))); 267 268 getLayoutInflater().inflate(R.layout.all_apps_content, this); 269 mHeader = findViewById(R.id.all_apps_header); 270 mAdditionalHeaderRows.clear(); 271 mAdditionalHeaderRows.addAll(getAdditionalHeaderRows()); 272 mBottomSheetBackground = findViewById(R.id.bottom_sheet_background); 273 mBottomSheetHandleArea = findViewById(R.id.bottom_sheet_handle_area); 274 mSearchRecyclerView = findViewById(R.id.search_results_list_view); 275 mFastScroller = findViewById(R.id.fast_scroller); 276 mFastScroller.setPopupView(findViewById(R.id.fast_scroller_popup)); 277 mFastScrollLetterLayout = findViewById(R.id.scroll_letter_layout); 278 setClipChildren(false); 279 280 mSearchContainer = inflateSearchBar(); 281 if (!isSearchBarFloating()) { 282 // Add the search box above everything else in this container (if the flag is enabled, 283 // it's added to drag layer in onAttach instead). 284 addView(mSearchContainer); 285 // The search container is visually at the top of the all apps UI, and should thus be 286 // focused by default. It's added to end of the children list, so it needs to be 287 // explicitly marked as focused by default. 288 mSearchContainer.setFocusedByDefault(true); 289 } 290 mSearchUiManager = (SearchUiManager) mSearchContainer; 291 } 292 getAdditionalHeaderRows()293 public List<AllAppsRow> getAdditionalHeaderRows() { 294 return List.of(); 295 } 296 297 @Override onFinishInflate()298 protected void onFinishInflate() { 299 super.onFinishInflate(); 300 301 mAH.get(SEARCH).setup(mSearchRecyclerView, 302 /* Filter out A-Z apps */ itemInfo -> false); 303 rebindAdapters(true /* force */); 304 float cornerRadius = Themes.getDialogCornerRadius(getContext()); 305 mBottomSheetCornerRadii = new float[]{ 306 cornerRadius, 307 cornerRadius, // Top left radius in px 308 cornerRadius, 309 cornerRadius, // Top right radius in px 310 0, 311 0, // Bottom right 312 0, 313 0 // Bottom left 314 }; 315 if (Flags.allAppsBlur()) { 316 int resId = Utilities.isDarkTheme(getContext()) 317 ? android.R.color.system_accent1_800 : android.R.color.system_accent1_100; 318 int layerAbove = ColorUtils.setAlphaComponent(getResources().getColor(resId, null), 319 (int) (0.4f * 255)); 320 int layerBelow = ColorUtils.setAlphaComponent(Color.WHITE, (int) (0.1f * 255)); 321 mBottomSheetBackgroundColor = ColorUtils.compositeColors(layerAbove, layerBelow); 322 } else { 323 mBottomSheetBackgroundColor = getContext().getColor(R.color.materialColorSurfaceDim); 324 } 325 mBottomSheetBackgroundAlpha = Color.alpha(mBottomSheetBackgroundColor) / 255.0f; 326 updateBackgroundVisibility(mActivityContext.getDeviceProfile()); 327 mSearchUiManager.initializeSearch(this); 328 } 329 330 @Override onAttachedToWindow()331 protected void onAttachedToWindow() { 332 super.onAttachedToWindow(); 333 if (isSearchBarFloating()) { 334 // Note: for Taskbar this is removed in TaskbarAllAppsController#cleanUpOverlay when the 335 // panel is closed. Can't do so in onDetach because we are also a child of drag layer 336 // so can't remove its views during that dispatch. 337 mActivityContext.getDragLayer().addView(mSearchContainer); 338 mSearchUiDelegate.onInitializeSearchBar(); 339 } 340 mActivityContext.addOnDeviceProfileChangeListener(this); 341 } 342 343 @Override onDetachedFromWindow()344 protected void onDetachedFromWindow() { 345 super.onDetachedFromWindow(); 346 mActivityContext.removeOnDeviceProfileChangeListener(this); 347 } 348 getSearchUiManager()349 public SearchUiManager getSearchUiManager() { 350 return mSearchUiManager; 351 } 352 getSearchView()353 public View getSearchView() { 354 return mSearchContainer; 355 } 356 357 /** Invoke when the current search session is finished. */ onClearSearchResult()358 public void onClearSearchResult() { 359 getMainAdapterProvider().clearHighlightedItem(); 360 animateToSearchState(false); 361 rebindAdapters(); 362 } 363 364 /** 365 * Sets results list for search 366 */ setSearchResults(ArrayList<AdapterItem> results)367 public void setSearchResults(ArrayList<AdapterItem> results) { 368 getMainAdapterProvider().clearHighlightedItem(); 369 if (getSearchResultList().setSearchResults(results)) { 370 getSearchRecyclerView().onSearchResultsChanged(); 371 } 372 if (results != null) { 373 animateToSearchState(true); 374 } 375 } 376 377 /** 378 * Sets results list for search. 379 * 380 * @param searchResultCode indicates if the result is final or intermediate for a given query 381 * since we can get search results from multiple sources. 382 */ setSearchResults(ArrayList<AdapterItem> results, int searchResultCode)383 public void setSearchResults(ArrayList<AdapterItem> results, int searchResultCode) { 384 setSearchResults(results); 385 mSearchUiDelegate.onSearchResultsChanged(results, searchResultCode); 386 } 387 animateToSearchState(boolean goingToSearch)388 private void animateToSearchState(boolean goingToSearch) { 389 animateToSearchState(goingToSearch, DEFAULT_SEARCH_TRANSITION_DURATION_MS); 390 } 391 setAllAppsTransitionController( AllAppsTransitionController allAppsTransitionController)392 public void setAllAppsTransitionController( 393 AllAppsTransitionController allAppsTransitionController) { 394 mAllAppsTransitionController = allAppsTransitionController; 395 } 396 animateToSearchState(boolean goingToSearch, long durationMs)397 void animateToSearchState(boolean goingToSearch, long durationMs) { 398 if (!mSearchTransitionController.isRunning() && goingToSearch == isSearching()) { 399 return; 400 } 401 mFastScroller.setVisibility(goingToSearch ? INVISIBLE : VISIBLE); 402 if (goingToSearch) { 403 // Fade out the button to pause work apps. 404 mWorkManager.onActivePageChanged(SEARCH); 405 } else if (mAllAppsTransitionController != null) { 406 // If exiting search, revert predictive back scale on all apps 407 mAllAppsTransitionController.animateAllAppsToNoScale(); 408 } 409 mSearchTransitionController.animateToState(goingToSearch, durationMs, 410 /* onEndRunnable = */ () -> { 411 mIsSearching = goingToSearch; 412 updateSearchResultsVisibility(); 413 int previousPage = getCurrentPage(); 414 if (mRebindAdaptersAfterSearchAnimation) { 415 rebindAdapters(false); 416 mRebindAdaptersAfterSearchAnimation = false; 417 } 418 419 if (goingToSearch) { 420 mSearchUiDelegate.onAnimateToSearchStateCompleted(); 421 } else { 422 setSearchResults(null); 423 if (mViewPager != null) { 424 mViewPager.setCurrentPage(previousPage); 425 } 426 onActivePageChanged(previousPage); 427 } 428 }); 429 } 430 shouldContainerScroll(MotionEvent ev)431 public boolean shouldContainerScroll(MotionEvent ev) { 432 BaseDragLayer dragLayer = mActivityContext.getDragLayer(); 433 // IF the MotionEvent is inside the search box or handle area, and the container keeps on 434 // receiving touch input, container should move down. 435 if (dragLayer.isEventOverView(mSearchContainer, ev) 436 || dragLayer.isEventOverView(mBottomSheetHandleArea, ev)) { 437 return true; 438 } 439 AllAppsRecyclerView rv = getActiveRecyclerView(); 440 if (rv == null) { 441 return true; 442 } 443 if (rv.getScrollbar() != null 444 && rv.getScrollbar().getThumbOffsetY() >= 0 445 && dragLayer.isEventOverView(rv.getScrollbar(), ev)) { 446 return false; 447 } 448 // Scroll if not within the container view (e.g. over large-screen scrim). 449 if (!dragLayer.isEventOverView(getVisibleContainerView(), ev)) { 450 return true; 451 } 452 return rv.shouldContainerScroll(ev, dragLayer); 453 } 454 455 /** 456 * Resets the UI to be ready for fresh interactions in the future. Exits search and returns to 457 * A-Z apps list. 458 * 459 * @param animate Whether to animate the header during the reset (e.g. switching profile tabs). 460 */ reset(boolean animate)461 public void reset(boolean animate) { 462 reset(animate, true); 463 } 464 465 /** 466 * Resets the UI to be ready for fresh interactions in the future. 467 * 468 * @param animate Whether to animate the header during the reset (e.g. switching profile tabs). 469 * @param exitSearch Whether to force exit the search state and return to A-Z apps list. 470 */ reset(boolean animate, boolean exitSearch)471 public void reset(boolean animate, boolean exitSearch) { 472 // Scroll Main and Work RV to top. Search RV is done in `resetSearch`. 473 for (int i = 0; i < mAH.size(); i++) { 474 if (i != SEARCH && mAH.get(i).mRecyclerView != null) { 475 mAH.get(i).mRecyclerView.scrollToTop(); 476 } 477 } 478 if (mTouchHandler != null) { 479 mTouchHandler.endFastScrolling(); 480 } 481 if (mHeader != null && mHeader.getVisibility() == VISIBLE) { 482 mHeader.reset(animate); 483 } 484 updateBackgroundVisibility(mActivityContext.getDeviceProfile()); 485 // Reset the base recycler view after transitioning home. 486 updateHeaderScroll(0); 487 if (exitSearch) { 488 // Reset the search bar and search RV after transitioning home. 489 MAIN_EXECUTOR.getHandler().post(mSearchUiManager::resetSearch); 490 } 491 if (isSearching()) { 492 mWorkManager.reset(); 493 } 494 } 495 496 /** 497 * Exits search and returns to A-Z apps list. Scroll to the private space header. 498 */ resetAndScrollToPrivateSpaceHeader()499 public void resetAndScrollToPrivateSpaceHeader() { 500 // Animate to A-Z with 0 time to reset the animation with proper state management. 501 // We can't rely on `animateToSearchState` with delay inside `resetSearch` because that will 502 // conflict with following scrolling to bottom, so we need it with 0 time here. 503 animateToSearchState(false, 0); 504 505 MAIN_EXECUTOR.getHandler().post(() -> { 506 // Reset the search bar after transitioning home. 507 // When `resetSearch` is called after `animateToSearchState` is finished, the inside 508 // `animateToSearchState` with delay is a just no-op and return early. 509 mSearchUiManager.resetSearch(); 510 // Switch to the main tab 511 switchToTab(ActivityAllAppsContainerView.AdapterHolder.MAIN); 512 // Scroll to bottom 513 if (mPrivateProfileManager != null) { 514 mPrivateProfileManager.scrollForHeaderToBeVisibleInContainer( 515 getActiveAppsRecyclerView(), 516 getPersonalAppList().getAdapterItems(), 517 mPrivateProfileManager.getPsHeaderHeight(), 518 mActivityContext.getDeviceProfile().allAppsCellHeightPx); 519 } 520 }); 521 } 522 523 @Override dispatchKeyEvent(KeyEvent event)524 public boolean dispatchKeyEvent(KeyEvent event) { 525 mSearchUiManager.preDispatchKeyEvent(event); 526 return super.dispatchKeyEvent(event); 527 } 528 getDescription()529 public String getDescription() { 530 if (!mUsingTabs && isSearching()) { 531 return getContext().getString(R.string.all_apps_search_results); 532 } else { 533 StringCache cache = mActivityContext.getStringCache(); 534 if (mUsingTabs) { 535 if (cache != null) { 536 return isPersonalTab() 537 ? cache.allAppsPersonalTabAccessibility 538 : cache.allAppsWorkTabAccessibility; 539 } else { 540 return isPersonalTab() 541 ? getContext().getString(R.string.all_apps_button_personal_label) 542 : getContext().getString(R.string.all_apps_button_work_label); 543 } 544 } 545 return getContext().getString(R.string.all_apps_button_label); 546 } 547 } 548 isSearching()549 public boolean isSearching() { 550 return mIsSearching; 551 } 552 553 @Override onActivePageChanged(int currentActivePage)554 public void onActivePageChanged(int currentActivePage) { 555 if (mSearchTransitionController.isRunning()) { 556 // Will be called at the end of the animation. 557 return; 558 } 559 if (currentActivePage != SEARCH) { 560 mActivityContext.hideKeyboard(); 561 } 562 if (mAH.get(currentActivePage).mRecyclerView != null) { 563 mAH.get(currentActivePage).mRecyclerView.bindFastScrollbar(mFastScroller, 564 ALL_APPS_SCROLLER); 565 } 566 // Header keeps track of active recycler view to properly render header protection. 567 mHeader.setActiveRV(currentActivePage); 568 reset(true /* animate */, !isSearching() /* exitSearch */); 569 570 mWorkManager.onActivePageChanged(currentActivePage); 571 } 572 rebindAdapters()573 protected void rebindAdapters() { 574 rebindAdapters(false /* force */); 575 } 576 rebindAdapters(boolean force)577 protected void rebindAdapters(boolean force) { 578 Log.d(TAG, "rebindAdapters: force: " + force); 579 if (mSearchTransitionController.isRunning()) { 580 mRebindAdaptersAfterSearchAnimation = true; 581 return; 582 } 583 updateSearchResultsVisibility(); 584 585 boolean showTabs = shouldShowTabs(); 586 if (showTabs == mUsingTabs && !force) { 587 Log.d(TAG, "rebindAdapters: Not needed."); 588 return; 589 } 590 591 // replaceAppsRVcontainer() needs to use both mUsingTabs value to remove the old view AND 592 // showTabs value to create new view. Hence the mUsingTabs new value assignment MUST happen 593 // after this call. 594 replaceAppsRVContainer(showTabs); 595 mUsingTabs = showTabs; 596 597 mAllAppsStore.unregisterIconContainer(mAH.get(AdapterHolder.MAIN).mRecyclerView); 598 mAllAppsStore.unregisterIconContainer(mAH.get(AdapterHolder.WORK).mRecyclerView); 599 mAllAppsStore.unregisterIconContainer(mAH.get(AdapterHolder.SEARCH).mRecyclerView); 600 601 final AllAppsRecyclerView mainRecyclerView; 602 final AllAppsRecyclerView workRecyclerView; 603 if (mUsingTabs) { 604 mainRecyclerView = (AllAppsRecyclerView) mViewPager.getChildAt(0); 605 workRecyclerView = (AllAppsRecyclerView) mViewPager.getChildAt(1); 606 mAH.get(AdapterHolder.MAIN).setup(mainRecyclerView, mPersonalMatcher); 607 mAH.get(AdapterHolder.WORK).setup(workRecyclerView, mWorkManager.getItemInfoMatcher()); 608 workRecyclerView.setId(R.id.apps_list_view_work); 609 if (enableExpandingPauseWorkButton() 610 || FeatureFlags.ENABLE_EXPANDING_PAUSE_WORK_BUTTON.get()) { 611 mAH.get(AdapterHolder.WORK).mRecyclerView.addOnScrollListener( 612 mWorkManager.newScrollListener()); 613 } 614 mViewPager.getPageIndicator().setActiveMarker(AdapterHolder.MAIN); 615 findViewById(R.id.tab_personal) 616 .setOnClickListener((View view) -> { 617 Log.d(TAG, "rebindAdapters: " + "Clicked personal tab."); 618 if (mViewPager.snapToPage(AdapterHolder.MAIN)) { 619 mActivityContext.getStatsLogManager().logger() 620 .log(LAUNCHER_ALLAPPS_TAP_ON_PERSONAL_TAB); 621 } 622 }); 623 findViewById(R.id.tab_work) 624 .setOnClickListener((View view) -> { 625 Log.d(TAG, "rebindAdapters: " + "Clicked work tab."); 626 if (mViewPager.snapToPage(AdapterHolder.WORK)) { 627 mActivityContext.getStatsLogManager().logger() 628 .log(LAUNCHER_ALLAPPS_TAP_ON_WORK_TAB); 629 } 630 }); 631 setDeviceManagementResources(); 632 if (mHeader.isSetUp()) { 633 onActivePageChanged(mViewPager.getNextPage()); 634 } 635 } else { 636 mainRecyclerView = findViewById(R.id.apps_list_view); 637 workRecyclerView = null; 638 mAH.get(AdapterHolder.MAIN).setup(mainRecyclerView, mPersonalMatcher); 639 mAH.get(AdapterHolder.WORK).mRecyclerView = null; 640 } 641 setUpCustomRecyclerViewPool( 642 mainRecyclerView, 643 workRecyclerView, 644 mAllAppsStore.getRecyclerViewPool()); 645 setupHeader(); 646 647 if (isSearchBarFloating()) { 648 // Keep the scroller above the search bar. 649 RelativeLayout.LayoutParams scrollerLayoutParams = 650 (LayoutParams) mFastScroller.getLayoutParams(); 651 scrollerLayoutParams.bottomMargin = mSearchContainer.getHeight() 652 + getResources().getDimensionPixelSize( 653 R.dimen.fastscroll_bottom_margin_floating_search); 654 } 655 656 mAllAppsStore.registerIconContainer(mAH.get(AdapterHolder.MAIN).mRecyclerView); 657 mAllAppsStore.registerIconContainer(mAH.get(AdapterHolder.WORK).mRecyclerView); 658 mAllAppsStore.registerIconContainer(mAH.get(AdapterHolder.SEARCH).mRecyclerView); 659 } 660 661 /** 662 * If {@link ENABLE_ALL_APPS_RV_PREINFLATION} is enabled, wire custom 663 * {@link RecyclerView.RecycledViewPool} to main and work {@link AllAppsRecyclerView}. 664 * 665 * Then if {@link ALL_APPS_GONE_VISIBILITY} is enabled, update max pool size. This is because 666 * all apps rv's hidden visibility is changed to {@link View#GONE} from {@link View#INVISIBLE), 667 * thus we cannot rely on layout pass to update pool size. 668 */ setUpCustomRecyclerViewPool( @onNull AllAppsRecyclerView mainRecyclerView, @Nullable AllAppsRecyclerView workRecyclerView, @NonNull AllAppsRecyclerViewPool recycledViewPool)669 private static void setUpCustomRecyclerViewPool( 670 @NonNull AllAppsRecyclerView mainRecyclerView, 671 @Nullable AllAppsRecyclerView workRecyclerView, 672 @NonNull AllAppsRecyclerViewPool recycledViewPool) { 673 final boolean hasWorkProfile = workRecyclerView != null; 674 recycledViewPool.setHasWorkProfile(hasWorkProfile); 675 mainRecyclerView.setRecycledViewPool(recycledViewPool); 676 if (workRecyclerView != null) { 677 workRecyclerView.setRecycledViewPool(recycledViewPool); 678 } 679 mainRecyclerView.updatePoolSize(hasWorkProfile); 680 } 681 replaceAppsRVContainer(boolean showTabs)682 private void replaceAppsRVContainer(boolean showTabs) { 683 Log.d(TAG, "replaceAppsRVContainer: showTabs: " + showTabs); 684 for (int i = AdapterHolder.MAIN; i <= AdapterHolder.WORK; i++) { 685 AdapterHolder adapterHolder = mAH.get(i); 686 if (adapterHolder.mRecyclerView != null) { 687 adapterHolder.mRecyclerView.setLayoutManager(null); 688 adapterHolder.mRecyclerView.setAdapter(null); 689 } 690 } 691 View oldView = getAppsRecyclerViewContainer(); 692 int index = indexOfChild(oldView); 693 removeView(oldView); 694 int layout = showTabs ? R.layout.all_apps_tabs : R.layout.all_apps_rv_layout; 695 final View rvContainer = getLayoutInflater().inflate(layout, this, false); 696 addView(rvContainer, index); 697 if (showTabs) { 698 mViewPager = (AllAppsPagedView) rvContainer; 699 mViewPager.initParentViews(this); 700 mViewPager.getPageIndicator().setOnActivePageChangedListener(this); 701 mViewPager.setOutlineProvider(new ViewOutlineProvider() { 702 @Override 703 public void getOutline(View view, Outline outline) { 704 @Px final int bottomOffsetPx = 705 (int) (ActivityAllAppsContainerView.this.getMeasuredHeight() 706 * PREDICTIVE_BACK_MIN_SCALE); 707 outline.setRect( 708 0, 709 0, 710 view.getMeasuredWidth(), 711 view.getMeasuredHeight() + bottomOffsetPx); 712 } 713 }); 714 715 mWorkManager.reset(); 716 post(() -> mAH.get(AdapterHolder.WORK).applyPadding()); 717 } else { 718 mWorkManager.detachWorkUtilityViews(); 719 mViewPager = null; 720 } 721 722 removeCustomRules(rvContainer); 723 removeCustomRules(getSearchRecyclerView()); 724 if (isSearchBarFloating()) { 725 alignParentTop(rvContainer, showTabs); 726 alignParentTop(getSearchRecyclerView(), /* tabs= */ false); 727 } else { 728 layoutBelowSearchContainer(rvContainer, showTabs); 729 layoutBelowSearchContainer(getSearchRecyclerView(), /* tabs= */ false); 730 } 731 732 updateSearchResultsVisibility(); 733 } 734 setupHeader()735 void setupHeader() { 736 mAdditionalHeaderRows.forEach(row -> mHeader.onPluginDisconnected(row)); 737 738 mHeader.setVisibility(View.VISIBLE); 739 boolean tabsHidden = !mUsingTabs; 740 mHeader.setup( 741 mAH.get(AdapterHolder.MAIN).mRecyclerView, 742 mAH.get(AdapterHolder.WORK).mRecyclerView, 743 (SearchRecyclerView) mAH.get(SEARCH).mRecyclerView, 744 getCurrentPage(), 745 tabsHidden); 746 747 int padding = mHeader.getMaxTranslation(); 748 mAH.forEach(adapterHolder -> { 749 adapterHolder.mPadding.top = padding; 750 adapterHolder.applyPadding(); 751 if (adapterHolder.mRecyclerView != null) { 752 adapterHolder.mRecyclerView.scrollToTop(); 753 } 754 }); 755 mAdditionalHeaderRows.forEach(row -> mHeader.onPluginConnected(row, mActivityContext)); 756 757 removeCustomRules(mHeader); 758 if (isSearchBarFloating()) { 759 alignParentTop(mHeader, false /* includeTabsMargin */); 760 } else { 761 layoutBelowSearchContainer(mHeader, false /* includeTabsMargin */); 762 } 763 } 764 765 /** 766 * Force header height update with an offset. Used by {@link UniversalSearchInputView} to 767 * request {@link FloatingHeaderView} to update its maxTranslation for multiline search bar. 768 */ forceUpdateHeaderHeight(int offset)769 public void forceUpdateHeaderHeight(int offset) { 770 if (Flags.multilineSearchBar()) { 771 mHeader.updateSearchBarOffset(offset); 772 } 773 } 774 775 @Override addChildrenForAccessibility(ArrayList<View> arrayList)776 public void addChildrenForAccessibility(ArrayList<View> arrayList) { 777 super.addChildrenForAccessibility(arrayList); 778 if (!Flags.floatingSearchBar()) { 779 // Searchbox container is visually at the top of the all apps UI but it's present in 780 // end of the children list. 781 // We need to move the searchbox to the top in a11y tree for a11y services to read the 782 // all apps screen in same as visual order. 783 arrayList.stream().filter(v -> v.getId() == R.id.search_container_all_apps) 784 .findFirst().ifPresent(v -> { 785 arrayList.remove(v); 786 arrayList.add(0, v); 787 }); 788 } 789 } 790 updateHeaderScroll(int scrolledOffset)791 protected void updateHeaderScroll(int scrolledOffset) { 792 float prog1 = Utilities.boundToRange((float) scrolledOffset / mHeaderThreshold, 0f, 1f); 793 int headerColor = getHeaderColor(prog1); 794 int tabsAlpha = mHeader.getPeripheralProtectionHeight(/* expectedHeight */ false) == 0 ? 0 795 : (int) (Utilities.boundToRange( 796 (scrolledOffset + mHeader.mSnappedScrolledY) / mHeaderThreshold, 0f, 1f) 797 * 255); 798 if (headerColor != mHeaderColor || mTabsProtectionAlpha != tabsAlpha) { 799 mHeaderColor = headerColor; 800 mTabsProtectionAlpha = tabsAlpha; 801 invalidateHeader(); 802 } 803 if (mSearchUiManager.getEditText() == null) { 804 return; 805 } 806 807 float prog = Utilities.boundToRange((float) scrolledOffset / mHeaderThreshold, 0f, 1f); 808 boolean bgVisible = mSearchUiManager.getBackgroundVisibility(); 809 if (scrolledOffset == 0 && !isSearching()) { 810 bgVisible = true; 811 } else if (scrolledOffset > mHeaderThreshold) { 812 bgVisible = false; 813 } 814 mSearchUiManager.setBackgroundVisibility(bgVisible, 1 - prog); 815 } 816 getHeaderColor(float blendRatio)817 protected int getHeaderColor(float blendRatio) { 818 return ColorUtils.setAlphaComponent( 819 ColorUtils.blendARGB(mScrimColor, mHeaderProtectionColor, blendRatio), 820 (int) (mSearchContainer.getAlpha() * 255)); 821 } 822 823 /** 824 * @return true if the search bar is floating above this container (at the bottom of the screen) 825 */ isSearchBarFloating()826 protected boolean isSearchBarFloating() { 827 return mSearchUiDelegate.isSearchBarFloating(); 828 } 829 830 /** 831 * Whether the <em>floating</em> search bar should appear as a small pill when not focused. 832 * <p> 833 * Note: This method mirrors one in LauncherState. For subclasses that use Launcher, it likely 834 * makes sense to use that method to derive an appropriate value for the current/target state. 835 */ shouldFloatingSearchBarBePillWhenUnfocused()836 public boolean shouldFloatingSearchBarBePillWhenUnfocused() { 837 return false; 838 } 839 840 /** 841 * How far from the bottom of the screen the <em>floating</em> search bar should rest when the 842 * IME is not present. 843 * <p> 844 * To hide offscreen, use a negative value. 845 * <p> 846 * Note: if the provided value is non-negative but less than the current bottom insets, the 847 * insets will be applied. As such, you can use 0 to default to this. 848 * <p> 849 * Note: This method mirrors one in LauncherState. For subclasses that use Launcher, it likely 850 * makes sense to use that method to derive an appropriate value for the current/target state. 851 */ getFloatingSearchBarRestingMarginBottom()852 public int getFloatingSearchBarRestingMarginBottom() { 853 return 0; 854 } 855 856 /** 857 * How far from the start of the screen the <em>floating</em> search bar should rest. 858 * <p> 859 * To use original margin, return a negative value. 860 * <p> 861 * Note: This method mirrors one in LauncherState. For subclasses that use Launcher, it likely 862 * makes sense to use that method to derive an appropriate value for the current/target state. 863 */ getFloatingSearchBarRestingMarginStart()864 public int getFloatingSearchBarRestingMarginStart() { 865 DeviceProfile dp = mActivityContext.getDeviceProfile(); 866 return dp.allAppsLeftRightMargin + dp.getAllAppsIconStartMargin(mActivityContext); 867 } 868 869 /** 870 * How far from the end of the screen the <em>floating</em> search bar should rest. 871 * <p> 872 * To use original margin, return a negative value. 873 * <p> 874 * Note: This method mirrors one in LauncherState. For subclasses that use Launcher, it likely 875 * makes sense to use that method to derive an appropriate value for the current/target state. 876 */ getFloatingSearchBarRestingMarginEnd()877 public int getFloatingSearchBarRestingMarginEnd() { 878 DeviceProfile dp = mActivityContext.getDeviceProfile(); 879 return dp.allAppsLeftRightMargin + dp.getAllAppsIconStartMargin(mActivityContext); 880 } 881 layoutBelowSearchContainer(View v, boolean includeTabsMargin)882 private void layoutBelowSearchContainer(View v, boolean includeTabsMargin) { 883 if (!(v.getLayoutParams() instanceof RelativeLayout.LayoutParams)) { 884 return; 885 } 886 887 RelativeLayout.LayoutParams layoutParams = (LayoutParams) v.getLayoutParams(); 888 layoutParams.addRule(RelativeLayout.ALIGN_TOP, R.id.search_container_all_apps); 889 890 int topMargin = getContext().getResources().getDimensionPixelSize( 891 R.dimen.all_apps_header_top_margin); 892 if (includeTabsMargin) { 893 topMargin += getContext().getResources().getDimensionPixelSize( 894 R.dimen.all_apps_header_pill_height); 895 } 896 layoutParams.topMargin = topMargin; 897 } 898 alignParentTop(View v, boolean includeTabsMargin)899 private void alignParentTop(View v, boolean includeTabsMargin) { 900 if (!(v.getLayoutParams() instanceof RelativeLayout.LayoutParams)) { 901 return; 902 } 903 904 RelativeLayout.LayoutParams layoutParams = (LayoutParams) v.getLayoutParams(); 905 layoutParams.addRule(RelativeLayout.ALIGN_PARENT_TOP); 906 layoutParams.topMargin = 907 includeTabsMargin 908 ? getContext().getResources().getDimensionPixelSize( 909 R.dimen.all_apps_header_pill_height) 910 : 0; 911 } 912 removeCustomRules(View v)913 private void removeCustomRules(View v) { 914 if (!(v.getLayoutParams() instanceof RelativeLayout.LayoutParams)) { 915 return; 916 } 917 918 RelativeLayout.LayoutParams layoutParams = (LayoutParams) v.getLayoutParams(); 919 layoutParams.removeRule(RelativeLayout.ABOVE); 920 layoutParams.removeRule(RelativeLayout.ALIGN_TOP); 921 layoutParams.removeRule(RelativeLayout.ALIGN_PARENT_TOP); 922 } 923 createAdapter(AlphabeticalAppsList<T> appsList)924 protected BaseAllAppsAdapter<T> createAdapter(AlphabeticalAppsList<T> appsList) { 925 return new AllAppsGridAdapter<>(mActivityContext, getLayoutInflater(), appsList, 926 mMainAdapterProvider); 927 } 928 isInAllApps()929 public boolean isInAllApps() { 930 // TODO: Make this abstract 931 return true; 932 } 933 934 /** 935 * Inflates the search bar 936 */ inflateSearchBar()937 protected View inflateSearchBar() { 938 return mSearchUiDelegate.inflateSearchBar(); 939 } 940 941 /** The adapter provider for the main section. */ getMainAdapterProvider()942 public final SearchAdapterProvider<?> getMainAdapterProvider() { 943 return mMainAdapterProvider; 944 } 945 946 @Override dispatchRestoreInstanceState(SparseArray<Parcelable> sparseArray)947 protected void dispatchRestoreInstanceState(SparseArray<Parcelable> sparseArray) { 948 try { 949 // Many slice view id is not properly assigned, and hence throws null 950 // pointer exception in the underneath method. Catching the exception 951 // simply doesn't restore these slice views. This doesn't have any 952 // user visible effect because because we query them again. 953 super.dispatchRestoreInstanceState(sparseArray); 954 } catch (Exception e) { 955 Log.e("AllAppsContainerView", "restoreInstanceState viewId = 0", e); 956 } 957 958 Bundle state = (Bundle) sparseArray.get(R.id.work_tab_state_id, null); 959 if (state != null) { 960 int currentPage = state.getInt(BUNDLE_KEY_CURRENT_PAGE, 0); 961 if (currentPage == AdapterHolder.WORK && mViewPager != null) { 962 mViewPager.setCurrentPage(currentPage); 963 rebindAdapters(); 964 } else { 965 reset(true); 966 } 967 } 968 } 969 970 @Override dispatchSaveInstanceState(SparseArray<Parcelable> container)971 protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) { 972 super.dispatchSaveInstanceState(container); 973 Bundle state = new Bundle(); 974 state.putInt(BUNDLE_KEY_CURRENT_PAGE, getCurrentPage()); 975 container.put(R.id.work_tab_state_id, state); 976 } 977 getAppsStore()978 public AllAppsStore<T> getAppsStore() { 979 return mAllAppsStore; 980 } 981 getWorkManager()982 public WorkProfileManager getWorkManager() { 983 return mWorkManager; 984 } 985 986 /** Returns whether Private Profile has been setup. */ hasPrivateProfile()987 public boolean hasPrivateProfile() { 988 return mHasPrivateApps; 989 } 990 991 @Override onDeviceProfileChanged(DeviceProfile dp)992 public void onDeviceProfileChanged(DeviceProfile dp) { 993 for (AdapterHolder holder : mAH) { 994 holder.mAdapter.setAppsPerRow(dp.numShownAllAppsColumns); 995 holder.mAppsList.setNumAppsPerRowAllApps(dp.numShownAllAppsColumns); 996 if (holder.mRecyclerView != null) { 997 // Remove all views and clear the pool, while keeping the data same. After this 998 // call, all the viewHolders will be recreated. 999 holder.mRecyclerView.swapAdapter(holder.mRecyclerView.getAdapter(), true); 1000 holder.mRecyclerView.getRecycledViewPool().clear(); 1001 } 1002 } 1003 updateBackgroundVisibility(dp); 1004 1005 int navBarScrimColor = Themes.getNavBarScrimColor(mActivityContext); 1006 if (mNavBarScrimPaint.getColor() != navBarScrimColor) { 1007 mNavBarScrimPaint.setColor(navBarScrimColor); 1008 invalidate(); 1009 } 1010 } 1011 updateBackgroundVisibility(DeviceProfile deviceProfile)1012 protected void updateBackgroundVisibility(DeviceProfile deviceProfile) { 1013 mBottomSheetBackground.setVisibility( 1014 deviceProfile.shouldShowAllAppsOnSheet() ? View.VISIBLE : View.GONE); 1015 // Note: The opaque sheet background and header protection are added in drawOnScrim. 1016 // For the taskbar entrypoint, the scrim is drawn by its abstract slide in view container, 1017 // so its header protection is derived from this scrim instead. 1018 } 1019 1020 @VisibleForTesting onAppsUpdated()1021 public void onAppsUpdated() { 1022 Log.d(TAG, "onAppsUpdated; number of apps: " + mAllAppsStore.getApps().length); 1023 mHasWorkApps = Stream.of(mAllAppsStore.getApps()) 1024 .anyMatch(mWorkManager.getItemInfoMatcher()); 1025 mHasPrivateApps = Stream.of(mAllAppsStore.getApps()) 1026 .anyMatch(mPrivateProfileManager.getItemInfoMatcher()); 1027 if (!isSearching()) { 1028 rebindAdapters(); 1029 } 1030 if (mHasWorkApps) { 1031 mWorkManager.reset(); 1032 } 1033 if (mHasPrivateApps) { 1034 mPrivateProfileManager.reset(); 1035 } 1036 1037 mActivityContext.getStatsLogManager().logger() 1038 .withCardinality(mAllAppsStore.getApps().length) 1039 .log(LAUNCHER_ALLAPPS_COUNT); 1040 } 1041 1042 @Override onInterceptTouchEvent(MotionEvent ev)1043 public boolean onInterceptTouchEvent(MotionEvent ev) { 1044 // The AllAppsContainerView houses the QSB and is hence visible from the Workspace 1045 // Overview states. We shouldn't intercept for the scrubber in these cases. 1046 if (!isInAllApps()) { 1047 mTouchHandler = null; 1048 return false; 1049 } 1050 1051 if (ev.getAction() == MotionEvent.ACTION_DOWN) { 1052 AllAppsRecyclerView rv = getActiveRecyclerView(); 1053 if (rv != null && rv.getScrollbar() != null 1054 && rv.getScrollbar().isHitInParent(ev.getX(), ev.getY(), mFastScrollerOffset)) { 1055 mTouchHandler = rv.getScrollbar(); 1056 } else { 1057 mTouchHandler = null; 1058 } 1059 } 1060 if (mTouchHandler != null) { 1061 return mTouchHandler.handleTouchEvent(ev, mFastScrollerOffset); 1062 } 1063 return false; 1064 } 1065 1066 @Override onTouchEvent(MotionEvent ev)1067 public boolean onTouchEvent(MotionEvent ev) { 1068 if (!isInAllApps()) { 1069 return false; 1070 } 1071 1072 if (ev.getAction() == MotionEvent.ACTION_DOWN) { 1073 AllAppsRecyclerView rv = getActiveRecyclerView(); 1074 if (rv != null && rv.getScrollbar() != null 1075 && rv.getScrollbar().isHitInParent(ev.getX(), ev.getY(), mFastScrollerOffset)) { 1076 mTouchHandler = rv.getScrollbar(); 1077 } else { 1078 mTouchHandler = null; 1079 1080 } 1081 } 1082 if (mTouchHandler != null) { 1083 mTouchHandler.handleTouchEvent(ev, mFastScrollerOffset); 1084 return true; 1085 } 1086 if (isSearching() 1087 && mActivityContext.getDragLayer().isEventOverView(getVisibleContainerView(), ev)) { 1088 // if in search state, consume touch event. 1089 return true; 1090 } 1091 return false; 1092 } 1093 1094 /** The current active recycler view (A-Z list from one of the profiles, or search results). */ getActiveRecyclerView()1095 public AllAppsRecyclerView getActiveRecyclerView() { 1096 if (isSearching()) { 1097 return getSearchRecyclerView(); 1098 } 1099 return getActiveAppsRecyclerView(); 1100 } 1101 1102 /** The current focus change listener in the search container. */ getSearchFocusChangeListener()1103 public OnFocusChangeListener getSearchFocusChangeListener() { 1104 return mAH.get(AdapterHolder.SEARCH).mOnFocusChangeListener; 1105 } 1106 1107 /** The current apps recycler view in the container. */ getActiveAppsRecyclerView()1108 private AllAppsRecyclerView getActiveAppsRecyclerView() { 1109 if (!mUsingTabs || isPersonalTab()) { 1110 return mAH.get(AdapterHolder.MAIN).mRecyclerView; 1111 } else { 1112 return mAH.get(AdapterHolder.WORK).mRecyclerView; 1113 } 1114 } 1115 1116 /** 1117 * The container for A-Z apps (the ViewPager for main+work tabs, or main RV). This is currently 1118 * hidden while searching. 1119 */ getAppsRecyclerViewContainer()1120 public ViewGroup getAppsRecyclerViewContainer() { 1121 return mViewPager != null ? mViewPager : findViewById(R.id.apps_list_view); 1122 } 1123 1124 /** The RV for search results, which is hidden while A-Z apps are visible. */ getSearchRecyclerView()1125 public SearchRecyclerView getSearchRecyclerView() { 1126 return mSearchRecyclerView; 1127 } 1128 isPersonalTab()1129 protected boolean isPersonalTab() { 1130 return mViewPager == null || mViewPager.getNextPage() == 0; 1131 } 1132 1133 /** 1134 * Switches the current page to the provided {@code tab} if tabs are supported, otherwise does 1135 * nothing. 1136 */ switchToTab(int tab)1137 public void switchToTab(int tab) { 1138 if (mUsingTabs) { 1139 mViewPager.setCurrentPage(tab); 1140 } 1141 } 1142 getLayoutInflater()1143 public LayoutInflater getLayoutInflater() { 1144 return mSearchUiDelegate.getLayoutInflater(); 1145 } 1146 1147 @Override onDropCompleted(View target, DragObject d, boolean success)1148 public void onDropCompleted(View target, DragObject d, boolean success) {} 1149 1150 @Override setInsets(Rect insets)1151 public void setInsets(Rect insets) { 1152 mInsets.set(insets); 1153 DeviceProfile grid = mActivityContext.getDeviceProfile(); 1154 1155 applyAdapterSideAndBottomPaddings(grid); 1156 1157 MarginLayoutParams mlp = (MarginLayoutParams) getLayoutParams(); 1158 // Ignore left/right insets on bottom sheet because we are already centered in-screen. 1159 if (grid.shouldShowAllAppsOnSheet()) { 1160 mlp.leftMargin = mlp.rightMargin = 0; 1161 } else { 1162 mlp.leftMargin = insets.left; 1163 mlp.rightMargin = insets.right; 1164 } 1165 setLayoutParams(mlp); 1166 1167 if (!grid.isVerticalBarLayout() || FeatureFlags.enableResponsiveWorkspace()) { 1168 int topPadding = grid.allAppsPadding.top; 1169 if (isSearchBarFloating() && !grid.shouldShowAllAppsOnSheet()) { 1170 topPadding += getResources().getDimensionPixelSize( 1171 R.dimen.all_apps_additional_top_padding_floating_search); 1172 } 1173 setPadding(grid.allAppsLeftRightMargin, topPadding, grid.allAppsLeftRightMargin, 0); 1174 } 1175 InsettableFrameLayout.dispatchInsets(this, insets); 1176 } 1177 1178 /** 1179 * Returns a padding in case a scrim is shown on the bottom of the view and a padding is needed. 1180 */ computeNavBarScrimHeight(WindowInsets insets)1181 protected int computeNavBarScrimHeight(WindowInsets insets) { 1182 return 0; 1183 } 1184 1185 /** 1186 * Returns the current height of nav bar scrim 1187 */ getNavBarScrimHeight()1188 public int getNavBarScrimHeight() { 1189 return mNavBarScrimHeight; 1190 } 1191 1192 @Override dispatchApplyWindowInsets(WindowInsets insets)1193 public WindowInsets dispatchApplyWindowInsets(WindowInsets insets) { 1194 mNavBarScrimHeight = computeNavBarScrimHeight(insets); 1195 applyAdapterSideAndBottomPaddings(mActivityContext.getDeviceProfile()); 1196 return super.dispatchApplyWindowInsets(insets); 1197 } 1198 1199 @Override dispatchDraw(Canvas canvas)1200 protected void dispatchDraw(Canvas canvas) { 1201 super.dispatchDraw(canvas); 1202 1203 if (mNavBarScrimHeight > 0) { 1204 float left = (getWidth() - getWidth() / getScaleX()) / 2; 1205 float top = getHeight() / 2f + (getHeight() / 2f - mNavBarScrimHeight) / getScaleY(); 1206 canvas.drawRect(left, top, getWidth() / getScaleX(), 1207 top + mNavBarScrimHeight / getScaleY(), mNavBarScrimPaint); 1208 } 1209 } 1210 updateSearchResultsVisibility()1211 protected void updateSearchResultsVisibility() { 1212 if (isSearching()) { 1213 getSearchRecyclerView().setVisibility(VISIBLE); 1214 getAppsRecyclerViewContainer().setVisibility(GONE); 1215 mHeader.setVisibility(GONE); 1216 } else { 1217 getSearchRecyclerView().setVisibility(GONE); 1218 getAppsRecyclerViewContainer().setVisibility(VISIBLE); 1219 mHeader.setVisibility(VISIBLE); 1220 } 1221 if (mHeader.isSetUp()) { 1222 mHeader.setActiveRV(getCurrentPage()); 1223 } 1224 } 1225 applyAdapterSideAndBottomPaddings(DeviceProfile grid)1226 private void applyAdapterSideAndBottomPaddings(DeviceProfile grid) { 1227 int bottomPadding = Math.max(mInsets.bottom, mNavBarScrimHeight); 1228 mAH.forEach(adapterHolder -> { 1229 adapterHolder.mPadding.bottom = bottomPadding; 1230 adapterHolder.mPadding.left = grid.allAppsPadding.left; 1231 adapterHolder.mPadding.right = grid.allAppsPadding.right; 1232 adapterHolder.applyPadding(); 1233 }); 1234 } 1235 setDeviceManagementResources()1236 private void setDeviceManagementResources() { 1237 if (mActivityContext.getStringCache() != null) { 1238 Button personalTab = findViewById(R.id.tab_personal); 1239 personalTab.setText(mActivityContext.getStringCache().allAppsPersonalTab); 1240 1241 Button workTab = findViewById(R.id.tab_work); 1242 workTab.setText(mActivityContext.getStringCache().allAppsWorkTab); 1243 } 1244 } 1245 1246 /** 1247 * Returns true if the container has work apps. 1248 */ shouldShowTabs()1249 public boolean shouldShowTabs() { 1250 return mHasWorkApps; 1251 } 1252 1253 // Used by tests only isDescendantViewVisible(int viewId)1254 private boolean isDescendantViewVisible(int viewId) { 1255 final View view = findViewById(viewId); 1256 if (view == null) return false; 1257 1258 if (!view.isShown()) return false; 1259 1260 return view.getGlobalVisibleRect(new Rect()); 1261 } 1262 1263 /** Called in Launcher#bindStringCache() to update the UI when cache is updated. */ updateWorkUI()1264 public void updateWorkUI() { 1265 setDeviceManagementResources(); 1266 if (mWorkManager.getWorkUtilityView() != null) { 1267 mWorkManager.getWorkUtilityView().updateStringFromCache(); 1268 } 1269 inflateWorkCardsIfNeeded(); 1270 } 1271 inflateWorkCardsIfNeeded()1272 private void inflateWorkCardsIfNeeded() { 1273 AllAppsRecyclerView workRV = mAH.get(AdapterHolder.WORK).mRecyclerView; 1274 if (workRV != null) { 1275 for (int i = 0; i < workRV.getChildCount(); i++) { 1276 View currentView = workRV.getChildAt(i); 1277 int currentItemViewType = workRV.getChildViewHolder(currentView).getItemViewType(); 1278 if (currentItemViewType == VIEW_TYPE_WORK_EDU_CARD) { 1279 ((WorkEduCard) currentView).updateStringFromCache(); 1280 } else if (currentItemViewType == VIEW_TYPE_WORK_DISABLED_CARD) { 1281 ((WorkPausedCard) currentView).updateStringFromCache(); 1282 } 1283 } 1284 } 1285 } 1286 1287 @VisibleForTesting setWorkManager(WorkProfileManager workManager)1288 public void setWorkManager(WorkProfileManager workManager) { 1289 mWorkManager = workManager; 1290 } 1291 1292 @VisibleForTesting isPersonalTabVisible()1293 public boolean isPersonalTabVisible() { 1294 return isDescendantViewVisible(R.id.tab_personal); 1295 } 1296 1297 @VisibleForTesting isWorkTabVisible()1298 public boolean isWorkTabVisible() { 1299 return isDescendantViewVisible(R.id.tab_work); 1300 } 1301 getSearchResultList()1302 public AlphabeticalAppsList<T> getSearchResultList() { 1303 return mAH.get(SEARCH).mAppsList; 1304 } 1305 getPersonalAppList()1306 public AlphabeticalAppsList<T> getPersonalAppList() { 1307 return mAH.get(MAIN).mAppsList; 1308 } 1309 getWorkAppList()1310 public AlphabeticalAppsList<T> getWorkAppList() { 1311 return mAH.get(WORK).mAppsList; 1312 } 1313 getFloatingHeaderView()1314 public FloatingHeaderView getFloatingHeaderView() { 1315 return mHeader; 1316 } 1317 1318 @VisibleForTesting getContentView()1319 public View getContentView() { 1320 return isSearching() ? getSearchRecyclerView() : getAppsRecyclerViewContainer(); 1321 } 1322 1323 /** The current page visible in all apps. */ getCurrentPage()1324 public int getCurrentPage() { 1325 return isSearching() 1326 ? SEARCH 1327 : mViewPager == null ? AdapterHolder.MAIN : mViewPager.getNextPage(); 1328 } 1329 getPrivateProfileManager()1330 public PrivateProfileManager getPrivateProfileManager() { 1331 return mPrivateProfileManager; 1332 } 1333 1334 /** 1335 * Adds an update listener to animator that adds springs to the animation. 1336 */ addSpringFromFlingUpdateListener(ValueAnimator animator, float velocity , float progress )1337 public void addSpringFromFlingUpdateListener(ValueAnimator animator, 1338 float velocity /* release velocity */, 1339 float progress /* portion of the distance to travel*/) { 1340 animator.addListener(new AnimatorListenerAdapter() { 1341 @Override 1342 public void onAnimationStart(Animator animator) { 1343 float distance = (1 - progress) * getHeight(); // px 1344 float settleVelocity = Math.min(0, distance 1345 / (AllAppsTransitionController.INTERP_COEFF * animator.getDuration()) 1346 + velocity); 1347 absorbSwipeUpVelocity(Math.max(1000, Math.abs( 1348 Math.round(settleVelocity * FLING_VELOCITY_MULTIPLIER)))); 1349 } 1350 }); 1351 } 1352 1353 /** Invoked when the container is pulled. */ onPull(float deltaDistance, float displacement)1354 public void onPull(float deltaDistance, float displacement) { 1355 absorbPullDeltaDistance(PULL_MULTIPLIER * deltaDistance, PULL_MULTIPLIER * displacement); 1356 // Current motion spec is to actually push and not pull 1357 // on this surface. However, until EdgeEffect.onPush (b/190612804) is 1358 // implemented at view level, we will simply pull 1359 } 1360 1361 @Override getDrawingRect(Rect outRect)1362 public void getDrawingRect(Rect outRect) { 1363 super.getDrawingRect(outRect); 1364 outRect.offset(0, (int) getTranslationY()); 1365 } 1366 1367 @Override setTranslationY(float translationY)1368 public void setTranslationY(float translationY) { 1369 super.setTranslationY(translationY); 1370 invalidateHeader(); 1371 } 1372 1373 @Override setScaleY(float scaleY)1374 public void setScaleY(float scaleY) { 1375 super.setScaleY(scaleY); 1376 if (predictiveBackThreeButtonNav() && mNavBarScrimHeight > 0) { 1377 // Call invalidate to prevent navbar scrim from scaling. The navbar scrim is drawn 1378 // directly onto the canvas. To prevent it from being scaled with the canvas, there's a 1379 // counter scale applied in dispatchDraw. 1380 invalidate(20, getHeight() - mNavBarScrimHeight, getWidth(), getHeight()); 1381 } 1382 } 1383 1384 /** 1385 * Set {@link Animator.AnimatorListener} on {@link mAllAppsTransitionController} to observe 1386 * animation of backing out of all apps search view to all apps view. 1387 */ setAllAppsSearchBackAnimatorListener(Animator.AnimatorListener listener)1388 public void setAllAppsSearchBackAnimatorListener(Animator.AnimatorListener listener) { 1389 Preconditions.assertNotNull(mAllAppsTransitionController); 1390 if (mAllAppsTransitionController == null) { 1391 return; 1392 } 1393 mAllAppsTransitionController.setAllAppsSearchBackAnimationListener(listener); 1394 } 1395 setScrimView(ScrimView scrimView)1396 public void setScrimView(ScrimView scrimView) { 1397 mScrimView = scrimView; 1398 } 1399 1400 @Override drawOnScrimWithScaleAndBottomOffset( Canvas canvas, float scale, @Px int bottomOffsetPx)1401 public void drawOnScrimWithScaleAndBottomOffset( 1402 Canvas canvas, float scale, @Px int bottomOffsetPx) { 1403 final View panel = mBottomSheetBackground; 1404 final boolean hasBottomSheet = panel.getVisibility() == VISIBLE; 1405 final float translationY = ((View) panel.getParent()).getTranslationY(); 1406 1407 final float horizontalScaleOffset = (1 - scale) * panel.getWidth() / 2; 1408 final float verticalScaleOffset = (1 - scale) * (panel.getHeight() - getHeight() / 2); 1409 1410 final float topNoScale = panel.getTop() + translationY; 1411 final float topWithScale = topNoScale + verticalScaleOffset; 1412 final float leftWithScale = panel.getLeft() + horizontalScaleOffset; 1413 final float rightWithScale = panel.getRight() - horizontalScaleOffset; 1414 final float bottomWithOffset = panel.getBottom() + bottomOffsetPx; 1415 // Draw full background panel for tablets. 1416 if (hasBottomSheet) { 1417 mHeaderPaint.setColor(mBottomSheetBackgroundColor); 1418 mHeaderPaint.setAlpha((int) (mBottomSheetBackgroundAlpha * 255)); 1419 1420 mTmpRectF.set( 1421 leftWithScale, 1422 topWithScale, 1423 rightWithScale, 1424 bottomWithOffset); 1425 mTmpPath.reset(); 1426 mTmpPath.addRoundRect(mTmpRectF, mBottomSheetCornerRadii, Direction.CW); 1427 canvas.drawPath(mTmpPath, mHeaderPaint); 1428 } 1429 1430 if (DEBUG_HEADER_PROTECTION) { 1431 mHeaderPaint.setColor(Color.MAGENTA); 1432 mHeaderPaint.setAlpha(255); 1433 } else { 1434 mHeaderPaint.setColor(mHeaderColor); 1435 mHeaderPaint.setAlpha((int) (getAlpha() * Color.alpha(mHeaderColor))); 1436 } 1437 if (mHeaderPaint.getColor() == mScrimColor || mHeaderPaint.getColor() == 0) { 1438 return; 1439 } 1440 1441 if (hasBottomSheet) { 1442 mHeaderPaint.setAlpha((int) (mHeaderPaint.getAlpha() * mBottomSheetBackgroundAlpha)); 1443 } 1444 1445 // Draw header on background panel 1446 final float headerBottomNoScale = 1447 getHeaderBottom() + getVisibleContainerView().getPaddingTop(); 1448 final float headerHeightNoScale = headerBottomNoScale - topNoScale; 1449 final float headerBottomWithScaleOnTablet = topWithScale + headerHeightNoScale * scale; 1450 final float headerBottomOffset = (getVisibleContainerView().getHeight() * (1 - scale) / 2); 1451 final float headerBottomWithScaleOnPhone = headerBottomNoScale * scale + headerBottomOffset; 1452 final FloatingHeaderView headerView = getFloatingHeaderView(); 1453 if (hasBottomSheet) { 1454 // Start adding header protection if search bar or tabs will attach to the top. 1455 if (!isSearchBarFloating() || mUsingTabs) { 1456 mTmpRectF.set( 1457 leftWithScale, 1458 topWithScale, 1459 rightWithScale, 1460 headerBottomWithScaleOnTablet); 1461 mTmpPath.reset(); 1462 mTmpPath.addRoundRect(mTmpRectF, mBottomSheetCornerRadii, Direction.CW); 1463 canvas.drawPath(mTmpPath, mHeaderPaint); 1464 } 1465 } else { 1466 canvas.drawRect(0, 0, canvas.getWidth(), headerBottomWithScaleOnPhone, mHeaderPaint); 1467 } 1468 1469 // If tab exist (such as work profile), extend header with tab height 1470 final int tabsHeight = headerView.getPeripheralProtectionHeight(/* expectedHeight */ false); 1471 if (mTabsProtectionAlpha > 0 && tabsHeight != 0) { 1472 if (DEBUG_HEADER_PROTECTION) { 1473 mHeaderPaint.setColor(Color.BLUE); 1474 mHeaderPaint.setAlpha(255); 1475 } else { 1476 float tabAlpha = getAlpha() * mTabsProtectionAlpha; 1477 if (hasBottomSheet) { 1478 tabAlpha *= mBottomSheetBackgroundAlpha; 1479 } 1480 mHeaderPaint.setAlpha((int) tabAlpha); 1481 } 1482 float left = 0f; 1483 float right = canvas.getWidth(); 1484 if (hasBottomSheet) { 1485 left = mBottomSheetBackground.getLeft() + horizontalScaleOffset; 1486 right = mBottomSheetBackground.getRight() - horizontalScaleOffset; 1487 } 1488 1489 final float tabTopWithScale = hasBottomSheet 1490 ? headerBottomWithScaleOnTablet 1491 : headerBottomWithScaleOnPhone; 1492 final float tabBottomWithScale = tabTopWithScale + tabsHeight * scale; 1493 1494 canvas.drawRect( 1495 left, 1496 tabTopWithScale, 1497 right, 1498 tabBottomWithScale, 1499 mHeaderPaint); 1500 } 1501 } 1502 1503 /** 1504 * The height of the header protection as if the user scrolled down the app list. 1505 */ getHeaderProtectionHeight()1506 float getHeaderProtectionHeight() { 1507 float headerBottom = getHeaderBottom() - getTranslationY(); 1508 if (mUsingTabs) { 1509 return headerBottom + mHeader.getPeripheralProtectionHeight(/* expectedHeight */ true); 1510 } else { 1511 return headerBottom; 1512 } 1513 } 1514 getFastScrollerLetterList()1515 ConstraintLayout getFastScrollerLetterList() { 1516 return mFastScrollLetterLayout; 1517 } 1518 1519 /** 1520 * redraws header protection 1521 */ invalidateHeader()1522 public void invalidateHeader() { 1523 if (mScrimView != null) { 1524 mScrimView.invalidate(); 1525 } 1526 } 1527 1528 /** Returns the position of the bottom edge of the header */ getHeaderBottom()1529 public int getHeaderBottom() { 1530 int bottom = (int) getTranslationY() + mHeader.getClipTop(); 1531 if (isSearchBarFloating()) { 1532 if (mActivityContext.getDeviceProfile().shouldShowAllAppsOnSheet()) { 1533 return bottom + mBottomSheetBackground.getTop(); 1534 } 1535 return bottom; 1536 } 1537 return bottom + mHeader.getTop(); 1538 } 1539 isUsingTabs()1540 boolean isUsingTabs() { 1541 return mUsingTabs; 1542 } 1543 1544 /** 1545 * Returns a view that denotes the visible part of all apps container view. 1546 */ getVisibleContainerView()1547 public View getVisibleContainerView() { 1548 return mBottomSheetBackground.getVisibility() == VISIBLE ? mBottomSheetBackground : this; 1549 } 1550 onInitializeRecyclerView(RecyclerView rv)1551 protected void onInitializeRecyclerView(RecyclerView rv) { 1552 rv.addOnScrollListener(mScrollListener); 1553 mSearchUiDelegate.onInitializeRecyclerView(rv); 1554 } 1555 1556 /** Returns the instance of @{code SearchTransitionController}. */ getSearchTransitionController()1557 public SearchTransitionController getSearchTransitionController() { 1558 return mSearchTransitionController; 1559 } 1560 1561 /** Holds a {@link BaseAllAppsAdapter} and related fields. */ 1562 public class AdapterHolder { 1563 public static final int MAIN = 0; 1564 public static final int WORK = 1; 1565 public static final int SEARCH = 2; 1566 1567 private final int mType; 1568 public final BaseAllAppsAdapter<T> mAdapter; 1569 final RecyclerView.LayoutManager mLayoutManager; 1570 final AlphabeticalAppsList<T> mAppsList; 1571 final Rect mPadding = new Rect(); 1572 AllAppsRecyclerView mRecyclerView; 1573 private OnFocusChangeListener mOnFocusChangeListener; 1574 AdapterHolder(int type, AlphabeticalAppsList<T> appsList)1575 AdapterHolder(int type, AlphabeticalAppsList<T> appsList) { 1576 mType = type; 1577 mAppsList = appsList; 1578 mAdapter = createAdapter(mAppsList); 1579 mAppsList.setAdapter(mAdapter); 1580 mLayoutManager = mAdapter.getLayoutManager(); 1581 } 1582 setup(@onNull View rv, @Nullable Predicate<ItemInfo> matcher)1583 void setup(@NonNull View rv, @Nullable Predicate<ItemInfo> matcher) { 1584 mAppsList.updateItemFilter(matcher); 1585 mRecyclerView = (AllAppsRecyclerView) rv; 1586 mRecyclerView.bindFastScrollbar(mFastScroller, ALL_APPS_SCROLLER); 1587 mRecyclerView.setEdgeEffectFactory(createEdgeEffectFactory()); 1588 mRecyclerView.setApps(mAppsList); 1589 mRecyclerView.setLayoutManager(mLayoutManager); 1590 mRecyclerView.setAdapter(mAdapter); 1591 mRecyclerView.setHasFixedSize(true); 1592 // No animations will occur when changes occur to the items in this RecyclerView. 1593 mRecyclerView.setItemAnimator(null); 1594 onInitializeRecyclerView(mRecyclerView); 1595 // Use ViewGroupFocusHelper for SearchRecyclerView to draw focus outline for the 1596 // buttons in the view (e.g. query builder button and setting button) 1597 FocusedItemDecorator focusedItemDecorator = isSearch() ? new FocusedItemDecorator( 1598 new ViewGroupFocusHelper(mRecyclerView)) : new FocusedItemDecorator( 1599 mRecyclerView); 1600 mRecyclerView.addItemDecoration(focusedItemDecorator); 1601 mOnFocusChangeListener = focusedItemDecorator.getFocusListener(); 1602 mAdapter.setIconFocusListener(mOnFocusChangeListener); 1603 applyPadding(); 1604 } 1605 applyPadding()1606 void applyPadding() { 1607 if (mRecyclerView != null) { 1608 int bottomOffset = 0; 1609 if (isWork() && mWorkManager.getWorkUtilityView() != null) { 1610 bottomOffset = mInsets.bottom + mWorkManager.getWorkUtilityView().getHeight(); 1611 } else if (isMain() && mPrivateProfileManager != null) { 1612 Optional<AdapterItem> privateSpaceHeaderItem = mAppsList.getAdapterItems() 1613 .stream() 1614 .filter(item -> item.viewType == VIEW_TYPE_PRIVATE_SPACE_HEADER) 1615 .findFirst(); 1616 if (privateSpaceHeaderItem.isPresent()) { 1617 bottomOffset = mPrivateSpaceBottomExtraSpace; 1618 } 1619 } 1620 if (isSearchBarFloating()) { 1621 bottomOffset += mSearchContainer.getHeight(); 1622 } 1623 mRecyclerView.setPadding(mPadding.left, mPadding.top, mPadding.right, 1624 mPadding.bottom + bottomOffset); 1625 } 1626 } 1627 isWork()1628 private boolean isWork() { 1629 return mType == WORK; 1630 } 1631 isSearch()1632 private boolean isSearch() { 1633 return mType == SEARCH; 1634 } 1635 isMain()1636 private boolean isMain() { 1637 return mType == MAIN; 1638 } 1639 } 1640 } 1641