1 /* 2 * Copyright (C) 2015 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.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_TAP_ON_PERSONAL_TAB; 19 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_TAP_ON_WORK_TAB; 20 21 import android.animation.Animator; 22 import android.animation.AnimatorListenerAdapter; 23 import android.animation.ValueAnimator; 24 import android.content.Context; 25 import android.graphics.Canvas; 26 import android.graphics.Color; 27 import android.graphics.Paint; 28 import android.graphics.Point; 29 import android.graphics.Rect; 30 import android.os.Bundle; 31 import android.os.Parcelable; 32 import android.os.Process; 33 import android.os.UserManager; 34 import android.text.SpannableStringBuilder; 35 import android.util.AttributeSet; 36 import android.util.Log; 37 import android.util.SparseArray; 38 import android.view.KeyEvent; 39 import android.view.LayoutInflater; 40 import android.view.MotionEvent; 41 import android.view.View; 42 import android.view.ViewGroup; 43 import android.view.WindowInsets; 44 45 import androidx.annotation.NonNull; 46 import androidx.annotation.Nullable; 47 import androidx.annotation.StringRes; 48 import androidx.annotation.VisibleForTesting; 49 import androidx.core.graphics.ColorUtils; 50 import androidx.recyclerview.widget.LinearLayoutManager; 51 import androidx.recyclerview.widget.RecyclerView; 52 53 import com.android.launcher3.BaseDraggingActivity; 54 import com.android.launcher3.DeviceProfile; 55 import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener; 56 import com.android.launcher3.DragSource; 57 import com.android.launcher3.DropTarget.DragObject; 58 import com.android.launcher3.Insettable; 59 import com.android.launcher3.InsettableFrameLayout; 60 import com.android.launcher3.R; 61 import com.android.launcher3.Utilities; 62 import com.android.launcher3.allapps.search.SearchAdapterProvider; 63 import com.android.launcher3.config.FeatureFlags; 64 import com.android.launcher3.keyboard.FocusedItemDecorator; 65 import com.android.launcher3.model.data.AppInfo; 66 import com.android.launcher3.util.ItemInfoMatcher; 67 import com.android.launcher3.util.Themes; 68 import com.android.launcher3.views.RecyclerViewFastScroller; 69 import com.android.launcher3.views.ScrimView; 70 import com.android.launcher3.views.SpringRelativeLayout; 71 import com.android.launcher3.workprofile.PersonalWorkSlidingTabStrip.OnActivePageChangedListener; 72 73 /** 74 * The all apps view container. 75 */ 76 public class AllAppsContainerView extends SpringRelativeLayout implements DragSource, 77 Insettable, OnDeviceProfileChangeListener, OnActivePageChangedListener, 78 ScrimView.ScrimDrawingController { 79 80 private static final String BUNDLE_KEY_CURRENT_PAGE = "launcher.allapps.current_page"; 81 82 public static final float PULL_MULTIPLIER = .02f; 83 public static final float FLING_VELOCITY_MULTIPLIER = 1200f; 84 85 private final Paint mHeaderPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 86 private final Rect mInsets = new Rect(); 87 88 protected final BaseDraggingActivity mLauncher; 89 protected final AdapterHolder[] mAH; 90 protected final ItemInfoMatcher mPersonalMatcher = ItemInfoMatcher.ofUser( 91 Process.myUserHandle()); 92 private final AllAppsStore mAllAppsStore = new AllAppsStore(); 93 94 private final RecyclerView.OnScrollListener mScrollListener = 95 new RecyclerView.OnScrollListener() { 96 @Override 97 public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) { 98 updateHeaderScroll(((AllAppsRecyclerView) recyclerView).getCurrentScrollY()); 99 } 100 }; 101 private final WorkProfileManager mWorkManager; 102 103 104 private final Paint mNavBarScrimPaint; 105 private int mNavBarScrimHeight = 0; 106 107 protected SearchUiManager mSearchUiManager; 108 private View mSearchContainer; 109 private AllAppsPagedView mViewPager; 110 111 protected FloatingHeaderView mHeader; 112 113 114 private SpannableStringBuilder mSearchQueryBuilder = null; 115 116 protected boolean mUsingTabs; 117 private boolean mIsSearching; 118 private boolean mHasWorkApps; 119 120 protected RecyclerViewFastScroller mTouchHandler; 121 protected final Point mFastScrollerOffset = new Point(); 122 123 private SearchAdapterProvider mSearchAdapterProvider; 124 private final int mScrimColor; 125 private final int mHeaderProtectionColor; 126 private final float mHeaderThreshold; 127 private ScrimView mScrimView; 128 private int mHeaderColor; 129 private int mTabsProtectionAlpha; 130 AllAppsContainerView(Context context)131 public AllAppsContainerView(Context context) { 132 this(context, null); 133 } 134 AllAppsContainerView(Context context, AttributeSet attrs)135 public AllAppsContainerView(Context context, AttributeSet attrs) { 136 this(context, attrs, 0); 137 } 138 AllAppsContainerView(Context context, AttributeSet attrs, int defStyleAttr)139 public AllAppsContainerView(Context context, AttributeSet attrs, int defStyleAttr) { 140 super(context, attrs, defStyleAttr); 141 142 mLauncher = BaseDraggingActivity.fromContext(context); 143 144 mScrimColor = Themes.getAttrColor(context, R.attr.allAppsScrimColor); 145 mHeaderThreshold = getResources().getDimensionPixelSize( 146 R.dimen.dynamic_grid_cell_border_spacing); 147 mHeaderProtectionColor = Themes.getAttrColor(context, R.attr.allappsHeaderProtectionColor); 148 149 mLauncher.addOnDeviceProfileChangeListener(this); 150 151 mSearchAdapterProvider = mLauncher.createSearchAdapterProvider(this); 152 153 mAH = new AdapterHolder[2]; 154 155 mWorkManager = new WorkProfileManager(mLauncher.getSystemService(UserManager.class), this, 156 Utilities.getPrefs(mLauncher)); 157 mAH[AdapterHolder.MAIN] = new AdapterHolder(false /* isWork */); 158 mAH[AdapterHolder.WORK] = new AdapterHolder(true /* isWork */); 159 160 mNavBarScrimPaint = new Paint(); 161 mNavBarScrimPaint.setColor(Themes.getAttrColor(context, R.attr.allAppsNavBarScrimColor)); 162 163 mAllAppsStore.addUpdateListener(this::onAppsUpdated); 164 } 165 166 @Override dispatchRestoreInstanceState(SparseArray<Parcelable> sparseArray)167 protected void dispatchRestoreInstanceState(SparseArray<Parcelable> sparseArray) { 168 try { 169 // Many slice view id is not properly assigned, and hence throws null 170 // pointer exception in the underneath method. Catching the exception 171 // simply doesn't restore these slice views. This doesn't have any 172 // user visible effect because because we query them again. 173 super.dispatchRestoreInstanceState(sparseArray); 174 } catch (Exception e) { 175 Log.e("AllAppsContainerView", "restoreInstanceState viewId = 0", e); 176 } 177 178 Bundle state = (Bundle) sparseArray.get(R.id.work_tab_state_id, null); 179 if (state != null) { 180 int currentPage = state.getInt(BUNDLE_KEY_CURRENT_PAGE, 0); 181 if (currentPage != 0 && mViewPager != null) { 182 mViewPager.setCurrentPage(currentPage); 183 rebindAdapters(); 184 } else { 185 reset(true); 186 } 187 } 188 189 } 190 191 @Override dispatchSaveInstanceState(SparseArray<Parcelable> container)192 protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) { 193 super.dispatchSaveInstanceState(container); 194 Bundle state = new Bundle(); 195 state.putInt(BUNDLE_KEY_CURRENT_PAGE, getCurrentPage()); 196 container.put(R.id.work_tab_state_id, state); 197 } 198 199 /** 200 * Sets the long click listener for icons 201 */ setOnIconLongClickListener(OnLongClickListener listener)202 public void setOnIconLongClickListener(OnLongClickListener listener) { 203 for (AdapterHolder holder : mAH) { 204 holder.adapter.setOnIconLongClickListener(listener); 205 } 206 } 207 getAppsStore()208 public AllAppsStore getAppsStore() { 209 return mAllAppsStore; 210 } 211 getWorkManager()212 public WorkProfileManager getWorkManager() { 213 return mWorkManager; 214 } 215 216 @Override onDeviceProfileChanged(DeviceProfile dp)217 public void onDeviceProfileChanged(DeviceProfile dp) { 218 for (AdapterHolder holder : mAH) { 219 holder.adapter.setAppsPerRow(dp.numShownAllAppsColumns); 220 if (holder.recyclerView != null) { 221 // Remove all views and clear the pool, while keeping the data same. After this 222 // call, all the viewHolders will be recreated. 223 holder.recyclerView.swapAdapter(holder.recyclerView.getAdapter(), true); 224 holder.recyclerView.getRecycledViewPool().clear(); 225 } 226 } 227 } 228 onAppsUpdated()229 private void onAppsUpdated() { 230 boolean hasWorkApps = false; 231 for (AppInfo app : mAllAppsStore.getApps()) { 232 if (mWorkManager.getMatcher().matches(app, null)) { 233 hasWorkApps = true; 234 break; 235 } 236 } 237 mHasWorkApps = hasWorkApps; 238 if (!mAH[AdapterHolder.MAIN].appsList.hasFilter()) { 239 rebindAdapters(); 240 if (hasWorkApps) { 241 mWorkManager.reset(); 242 } 243 } 244 } 245 246 /** 247 * Returns whether the view itself will handle the touch event or not. 248 */ shouldContainerScroll(MotionEvent ev)249 public boolean shouldContainerScroll(MotionEvent ev) { 250 // IF the MotionEvent is inside the search box, and the container keeps on receiving 251 // touch input, container should move down. 252 if (mLauncher.getDragLayer().isEventOverView(mSearchContainer, ev)) { 253 return true; 254 } 255 AllAppsRecyclerView rv = getActiveRecyclerView(); 256 if (rv == null) { 257 return true; 258 } 259 if (rv.getScrollbar().getThumbOffsetY() >= 0 && 260 mLauncher.getDragLayer().isEventOverView(rv.getScrollbar(), ev)) { 261 return false; 262 } 263 return rv.shouldContainerScroll(ev, mLauncher.getDragLayer()); 264 } 265 266 @Override onInterceptTouchEvent(MotionEvent ev)267 public boolean onInterceptTouchEvent(MotionEvent ev) { 268 if (ev.getAction() == MotionEvent.ACTION_DOWN) { 269 AllAppsRecyclerView rv = getActiveRecyclerView(); 270 if (rv != null && 271 rv.getScrollbar().isHitInParent(ev.getX(), ev.getY(), mFastScrollerOffset)) { 272 mTouchHandler = rv.getScrollbar(); 273 } else { 274 mTouchHandler = null; 275 } 276 } 277 if (mTouchHandler != null) { 278 return mTouchHandler.handleTouchEvent(ev, mFastScrollerOffset); 279 } 280 return false; 281 } 282 283 @Override onTouchEvent(MotionEvent ev)284 public boolean onTouchEvent(MotionEvent ev) { 285 if (ev.getAction() == MotionEvent.ACTION_DOWN) { 286 AllAppsRecyclerView rv = getActiveRecyclerView(); 287 if (rv != null && rv.getScrollbar().isHitInParent(ev.getX(), ev.getY(), 288 mFastScrollerOffset)) { 289 mTouchHandler = rv.getScrollbar(); 290 } else { 291 mTouchHandler = null; 292 293 } 294 } 295 if (mTouchHandler != null) { 296 mTouchHandler.handleTouchEvent(ev, mFastScrollerOffset); 297 return true; 298 } 299 return false; 300 } 301 getDescription()302 public String getDescription() { 303 @StringRes int descriptionRes; 304 if (mUsingTabs) { 305 descriptionRes = 306 mViewPager.getNextPage() == 0 307 ? R.string.all_apps_button_personal_label 308 : R.string.all_apps_button_work_label; 309 } else if (mIsSearching) { 310 descriptionRes = R.string.all_apps_search_results; 311 } else { 312 descriptionRes = R.string.all_apps_button_label; 313 } 314 return getContext().getString(descriptionRes); 315 } 316 getActiveRecyclerView()317 public AllAppsRecyclerView getActiveRecyclerView() { 318 if (!mUsingTabs || mViewPager.getNextPage() == 0) { 319 return mAH[AdapterHolder.MAIN].recyclerView; 320 } else { 321 return mAH[AdapterHolder.WORK].recyclerView; 322 } 323 } 324 getLayoutInflater()325 public LayoutInflater getLayoutInflater() { 326 return LayoutInflater.from(getContext()); 327 } 328 329 /** 330 * Resets the state of AllApps. 331 */ reset(boolean animate)332 public void reset(boolean animate) { 333 for (int i = 0; i < mAH.length; i++) { 334 if (mAH[i].recyclerView != null) { 335 mAH[i].recyclerView.scrollToTop(); 336 } 337 } 338 if (isHeaderVisible()) { 339 mHeader.reset(animate); 340 } 341 // Reset the search bar and base recycler view after transitioning home 342 mSearchUiManager.resetSearch(); 343 updateHeaderScroll(0); 344 } 345 346 @Override onFinishInflate()347 protected void onFinishInflate() { 348 super.onFinishInflate(); 349 350 // This is a focus listener that proxies focus from a view into the list view. This is to 351 // work around the search box from getting first focus and showing the cursor. 352 setOnFocusChangeListener((v, hasFocus) -> { 353 if (hasFocus && getActiveRecyclerView() != null) { 354 getActiveRecyclerView().requestFocus(); 355 } 356 }); 357 358 mHeader = findViewById(R.id.all_apps_header); 359 rebindAdapters(true /* force */); 360 361 mSearchContainer = findViewById(R.id.search_container_all_apps); 362 mSearchUiManager = (SearchUiManager) mSearchContainer; 363 mSearchUiManager.initializeSearch(this); 364 } 365 getSearchUiManager()366 public SearchUiManager getSearchUiManager() { 367 return mSearchUiManager; 368 } 369 370 @Override dispatchKeyEvent(KeyEvent event)371 public boolean dispatchKeyEvent(KeyEvent event) { 372 mSearchUiManager.preDispatchKeyEvent(event); 373 return super.dispatchKeyEvent(event); 374 } 375 376 @Override onDropCompleted(View target, DragObject d, boolean success)377 public void onDropCompleted(View target, DragObject d, boolean success) { 378 } 379 380 @Override setInsets(Rect insets)381 public void setInsets(Rect insets) { 382 mInsets.set(insets); 383 DeviceProfile grid = mLauncher.getDeviceProfile(); 384 385 for (int i = 0; i < mAH.length; i++) { 386 mAH[i].padding.bottom = insets.bottom; 387 mAH[i].padding.left = mAH[i].padding.right = grid.allAppsLeftRightPadding; 388 mAH[i].applyPadding(); 389 } 390 391 ViewGroup.MarginLayoutParams mlp = (MarginLayoutParams) getLayoutParams(); 392 mlp.leftMargin = insets.left; 393 mlp.rightMargin = insets.right; 394 setLayoutParams(mlp); 395 396 if (grid.isVerticalBarLayout()) { 397 setPadding(grid.workspacePadding.left, 0, grid.workspacePadding.right, 0); 398 } else { 399 setPadding(0, 0, 0, 0); 400 } 401 402 InsettableFrameLayout.dispatchInsets(this, insets); 403 } 404 405 @Override dispatchApplyWindowInsets(WindowInsets insets)406 public WindowInsets dispatchApplyWindowInsets(WindowInsets insets) { 407 if (Utilities.ATLEAST_Q) { 408 mNavBarScrimHeight = insets.getTappableElementInsets().bottom; 409 } else { 410 mNavBarScrimHeight = insets.getStableInsetBottom(); 411 } 412 return super.dispatchApplyWindowInsets(insets); 413 } 414 415 @Override dispatchDraw(Canvas canvas)416 protected void dispatchDraw(Canvas canvas) { 417 super.dispatchDraw(canvas); 418 419 if (mNavBarScrimHeight > 0) { 420 canvas.drawRect(0, getHeight() - mNavBarScrimHeight, getWidth(), getHeight(), 421 mNavBarScrimPaint); 422 } 423 } 424 rebindAdapters()425 private void rebindAdapters() { 426 rebindAdapters(false /* force */); 427 } 428 rebindAdapters(boolean force)429 protected void rebindAdapters(boolean force) { 430 boolean showTabs = mHasWorkApps && !mIsSearching; 431 if (showTabs == mUsingTabs && !force) { 432 return; 433 } 434 replaceRVContainer(showTabs); 435 mUsingTabs = showTabs; 436 437 mAllAppsStore.unregisterIconContainer(mAH[AdapterHolder.MAIN].recyclerView); 438 mAllAppsStore.unregisterIconContainer(mAH[AdapterHolder.WORK].recyclerView); 439 440 if (mUsingTabs) { 441 mAH[AdapterHolder.MAIN].setup(mViewPager.getChildAt(0), mPersonalMatcher); 442 mAH[AdapterHolder.WORK].setup(mViewPager.getChildAt(1), mWorkManager.getMatcher()); 443 mAH[AdapterHolder.WORK].recyclerView.setId(R.id.apps_list_view_work); 444 mViewPager.getPageIndicator().setActiveMarker(AdapterHolder.MAIN); 445 findViewById(R.id.tab_personal) 446 .setOnClickListener((View view) -> { 447 if (mViewPager.snapToPage(AdapterHolder.MAIN)) { 448 mLauncher.getStatsLogManager().logger() 449 .log(LAUNCHER_ALLAPPS_TAP_ON_PERSONAL_TAB); 450 } 451 }); 452 findViewById(R.id.tab_work) 453 .setOnClickListener((View view) -> { 454 if (mViewPager.snapToPage(AdapterHolder.WORK)) { 455 mLauncher.getStatsLogManager().logger() 456 .log(LAUNCHER_ALLAPPS_TAP_ON_WORK_TAB); 457 } 458 }); 459 onActivePageChanged(mViewPager.getNextPage()); 460 } else { 461 mAH[AdapterHolder.MAIN].setup(findViewById(R.id.apps_list_view), null); 462 mAH[AdapterHolder.WORK].recyclerView = null; 463 } 464 setupHeader(); 465 466 mAllAppsStore.registerIconContainer(mAH[AdapterHolder.MAIN].recyclerView); 467 mAllAppsStore.registerIconContainer(mAH[AdapterHolder.WORK].recyclerView); 468 } 469 470 replaceRVContainer(boolean showTabs)471 private void replaceRVContainer(boolean showTabs) { 472 for (AdapterHolder adapterHolder : mAH) { 473 if (adapterHolder.recyclerView != null) { 474 adapterHolder.recyclerView.setLayoutManager(null); 475 adapterHolder.recyclerView.setAdapter(null); 476 } 477 } 478 View oldView = getRecyclerViewContainer(); 479 int index = indexOfChild(oldView); 480 removeView(oldView); 481 int layout = showTabs ? R.layout.all_apps_tabs : R.layout.all_apps_rv_layout; 482 View newView = getLayoutInflater().inflate(layout, this, false); 483 addView(newView, index); 484 if (showTabs) { 485 mViewPager = (AllAppsPagedView) newView; 486 mViewPager.initParentViews(this); 487 mViewPager.getPageIndicator().setOnActivePageChangedListener(this); 488 if (mWorkManager.attachWorkModeSwitch()) { 489 mWorkManager.getWorkModeSwitch().post(() -> mAH[AdapterHolder.WORK].applyPadding()); 490 } 491 } else { 492 mWorkManager.detachWorkModeSwitch(); 493 mViewPager = null; 494 } 495 } 496 getRecyclerViewContainer()497 public View getRecyclerViewContainer() { 498 return mViewPager != null ? mViewPager : findViewById(R.id.apps_list_view); 499 } 500 501 @Override onActivePageChanged(int currentActivePage)502 public void onActivePageChanged(int currentActivePage) { 503 mHeader.setMainActive(currentActivePage == AdapterHolder.MAIN); 504 if (mAH[currentActivePage].recyclerView != null) { 505 mAH[currentActivePage].recyclerView.bindFastScrollbar(); 506 } 507 reset(true /* animate */); 508 509 mWorkManager.onActivePageChanged(currentActivePage); 510 } 511 512 // Used by tests only isDescendantViewVisible(int viewId)513 private boolean isDescendantViewVisible(int viewId) { 514 final View view = findViewById(viewId); 515 if (view == null) return false; 516 517 if (!view.isShown()) return false; 518 519 return view.getGlobalVisibleRect(new Rect()); 520 } 521 522 @VisibleForTesting isPersonalTabVisible()523 public boolean isPersonalTabVisible() { 524 return isDescendantViewVisible(R.id.tab_personal); 525 } 526 527 // Used by tests only isWorkTabVisible()528 public boolean isWorkTabVisible() { 529 return isDescendantViewVisible(R.id.tab_work); 530 } 531 getApps()532 public AlphabeticalAppsList getApps() { 533 return mAH[AdapterHolder.MAIN].appsList; 534 } 535 getFloatingHeaderView()536 public FloatingHeaderView getFloatingHeaderView() { 537 return mHeader; 538 } 539 getSearchView()540 public View getSearchView() { 541 return mSearchContainer; 542 } 543 getContentView()544 public View getContentView() { 545 return mViewPager == null ? getActiveRecyclerView() : mViewPager; 546 } 547 getCurrentPage()548 public int getCurrentPage() { 549 return mViewPager != null ? mViewPager.getCurrentPage() : AdapterHolder.MAIN; 550 } 551 552 /** 553 * Handles selection on focused view and returns success 554 */ launchHighlightedItem()555 public boolean launchHighlightedItem() { 556 if (mSearchAdapterProvider == null) return false; 557 return mSearchAdapterProvider.launchHighlightedItem(); 558 } 559 getSearchAdapterProvider()560 public SearchAdapterProvider getSearchAdapterProvider() { 561 return mSearchAdapterProvider; 562 } 563 getScrollBar()564 public RecyclerViewFastScroller getScrollBar() { 565 AllAppsRecyclerView rv = getActiveRecyclerView(); 566 return rv == null ? null : rv.getScrollbar(); 567 } 568 setupHeader()569 public void setupHeader() { 570 mHeader.setVisibility(View.VISIBLE); 571 mHeader.setup(mAH, mAH[AllAppsContainerView.AdapterHolder.WORK].recyclerView == null); 572 573 int padding = mHeader.getMaxTranslation(); 574 for (int i = 0; i < mAH.length; i++) { 575 mAH[i].padding.top = padding; 576 mAH[i].applyPadding(); 577 if (mAH[i].recyclerView != null) { 578 mAH[i].recyclerView.scrollToTop(); 579 } 580 } 581 } 582 setLastSearchQuery(String query)583 public void setLastSearchQuery(String query) { 584 for (int i = 0; i < mAH.length; i++) { 585 mAH[i].adapter.setLastSearchQuery(query); 586 } 587 mIsSearching = true; 588 rebindAdapters(); 589 mHeader.setCollapsed(true); 590 } 591 onClearSearchResult()592 public void onClearSearchResult() { 593 mIsSearching = false; 594 mHeader.setCollapsed(false); 595 rebindAdapters(); 596 mHeader.reset(false); 597 } 598 onSearchResultsChanged()599 public void onSearchResultsChanged() { 600 for (int i = 0; i < mAH.length; i++) { 601 if (mAH[i].recyclerView != null) { 602 mAH[i].recyclerView.onSearchResultsChanged(); 603 } 604 } 605 } 606 setRecyclerViewVerticalFadingEdgeEnabled(boolean enabled)607 public void setRecyclerViewVerticalFadingEdgeEnabled(boolean enabled) { 608 for (int i = 0; i < mAH.length; i++) { 609 mAH[i].applyVerticalFadingEdgeEnabled(enabled); 610 } 611 } 612 addElevationController(RecyclerView.OnScrollListener scrollListener)613 public void addElevationController(RecyclerView.OnScrollListener scrollListener) { 614 if (!mUsingTabs) { 615 mAH[AdapterHolder.MAIN].recyclerView.addOnScrollListener(scrollListener); 616 } 617 } 618 isHeaderVisible()619 public boolean isHeaderVisible() { 620 return mHeader != null && mHeader.getVisibility() == View.VISIBLE; 621 } 622 623 /** 624 * Adds an update listener to {@param animator} that adds springs to the animation. 625 */ addSpringFromFlingUpdateListener(ValueAnimator animator, float velocity , float progress )626 public void addSpringFromFlingUpdateListener(ValueAnimator animator, 627 float velocity /* release velocity */, 628 float progress /* portion of the distance to travel*/) { 629 animator.addListener(new AnimatorListenerAdapter() { 630 @Override 631 public void onAnimationStart(Animator animator) { 632 float distance = (float) ((1 - progress) * getHeight()); // px 633 float settleVelocity = Math.min(0, distance 634 / (AllAppsTransitionController.INTERP_COEFF * animator.getDuration()) 635 + velocity); 636 absorbSwipeUpVelocity(Math.max(1000, Math.abs( 637 Math.round(settleVelocity * FLING_VELOCITY_MULTIPLIER)))); 638 } 639 }); 640 } 641 onPull(float deltaDistance, float displacement)642 public void onPull(float deltaDistance, float displacement) { 643 absorbPullDeltaDistance(PULL_MULTIPLIER * deltaDistance, PULL_MULTIPLIER * displacement); 644 // Current motion spec is to actually push and not pull 645 // on this surface. However, until EdgeEffect.onPush (b/190612804) is 646 // implemented at view level, we will simply pull 647 } 648 649 @Override getDrawingRect(Rect outRect)650 public void getDrawingRect(Rect outRect) { 651 super.getDrawingRect(outRect); 652 outRect.offset(0, (int) getTranslationY()); 653 } 654 655 @Override setTranslationY(float translationY)656 public void setTranslationY(float translationY) { 657 super.setTranslationY(translationY); 658 invalidateHeader(); 659 } 660 setScrimView(ScrimView scrimView)661 public void setScrimView(ScrimView scrimView) { 662 mScrimView = scrimView; 663 } 664 665 @Override drawOnScrim(Canvas canvas)666 public void drawOnScrim(Canvas canvas) { 667 if (!mHeader.isHeaderProtectionSupported()) return; 668 mHeaderPaint.setColor(mHeaderColor); 669 mHeaderPaint.setAlpha((int) (getAlpha() * Color.alpha(mHeaderColor))); 670 if (mHeaderPaint.getColor() != mScrimColor && mHeaderPaint.getColor() != 0) { 671 int bottom = (int) (mSearchContainer.getBottom() + getTranslationY()); 672 canvas.drawRect(0, 0, canvas.getWidth(), bottom, mHeaderPaint); 673 int tabsHeight = getFloatingHeaderView().getPeripheralProtectionHeight(); 674 if (mTabsProtectionAlpha > 0 && tabsHeight != 0) { 675 mHeaderPaint.setAlpha((int) (getAlpha() * mTabsProtectionAlpha)); 676 canvas.drawRect(0, bottom, canvas.getWidth(), bottom + tabsHeight, mHeaderPaint); 677 } 678 } 679 } 680 681 public class AdapterHolder { 682 public static final int MAIN = 0; 683 public static final int WORK = 1; 684 685 private final boolean mIsWork; 686 public final AllAppsGridAdapter adapter; 687 final LinearLayoutManager layoutManager; 688 final AlphabeticalAppsList appsList; 689 final Rect padding = new Rect(); 690 AllAppsRecyclerView recyclerView; 691 boolean verticalFadingEdge; 692 693 AdapterHolder(boolean isWork)694 AdapterHolder(boolean isWork) { 695 mIsWork = isWork; 696 appsList = new AlphabeticalAppsList(mLauncher, mAllAppsStore, 697 isWork ? mWorkManager.getAdapterProvider() : null); 698 699 BaseAdapterProvider[] adapterProviders = 700 isWork ? new BaseAdapterProvider[]{mSearchAdapterProvider, 701 mWorkManager.getAdapterProvider()} 702 : new BaseAdapterProvider[]{mSearchAdapterProvider}; 703 704 adapter = new AllAppsGridAdapter(mLauncher, getLayoutInflater(), appsList, 705 adapterProviders); 706 appsList.setAdapter(adapter); 707 layoutManager = adapter.getLayoutManager(); 708 } 709 setup(@onNull View rv, @Nullable ItemInfoMatcher matcher)710 void setup(@NonNull View rv, @Nullable ItemInfoMatcher matcher) { 711 appsList.updateItemFilter(matcher); 712 recyclerView = (AllAppsRecyclerView) rv; 713 recyclerView.setEdgeEffectFactory(createEdgeEffectFactory()); 714 recyclerView.setApps(appsList); 715 recyclerView.setLayoutManager(layoutManager); 716 recyclerView.setAdapter(adapter); 717 recyclerView.setHasFixedSize(true); 718 // No animations will occur when changes occur to the items in this RecyclerView. 719 recyclerView.setItemAnimator(null); 720 recyclerView.addOnScrollListener(mScrollListener); 721 FocusedItemDecorator focusedItemDecorator = new FocusedItemDecorator(recyclerView); 722 recyclerView.addItemDecoration(focusedItemDecorator); 723 adapter.setIconFocusListener(focusedItemDecorator.getFocusListener()); 724 applyVerticalFadingEdgeEnabled(verticalFadingEdge); 725 applyPadding(); 726 if (FeatureFlags.ENABLE_DEVICE_SEARCH.get()) { 727 recyclerView.addItemDecoration(mSearchAdapterProvider.getDecorator()); 728 } 729 } 730 applyPadding()731 void applyPadding() { 732 if (recyclerView != null) { 733 int bottomOffset = 0; 734 if (mIsWork && mWorkManager.getWorkModeSwitch() != null) { 735 bottomOffset = mInsets.bottom + mWorkManager.getWorkModeSwitch().getHeight(); 736 } 737 recyclerView.setPadding(padding.left, padding.top, padding.right, 738 padding.bottom + bottomOffset); 739 } 740 } 741 applyVerticalFadingEdgeEnabled(boolean enabled)742 public void applyVerticalFadingEdgeEnabled(boolean enabled) { 743 verticalFadingEdge = enabled; 744 mAH[AdapterHolder.MAIN].recyclerView.setVerticalFadingEdgeEnabled(!mUsingTabs 745 && verticalFadingEdge); 746 } 747 } 748 749 updateHeaderScroll(int scrolledOffset)750 protected void updateHeaderScroll(int scrolledOffset) { 751 752 float prog = Utilities.boundToRange((float) scrolledOffset / mHeaderThreshold, 0f, 1f); 753 int viewBG = ColorUtils.blendARGB(mScrimColor, mHeaderProtectionColor, prog); 754 int headerColor = ColorUtils.setAlphaComponent(viewBG, 755 (int) (getSearchView().getAlpha() * 255)); 756 int tabsAlpha = mHeader.getPeripheralProtectionHeight() == 0 ? 0 757 : (int) (Utilities.boundToRange( 758 (scrolledOffset + mHeader.mSnappedScrolledY) / mHeaderThreshold, 0f, 1f) 759 * 255); 760 if (headerColor != mHeaderColor || mTabsProtectionAlpha != tabsAlpha) { 761 mHeaderColor = headerColor; 762 mTabsProtectionAlpha = tabsAlpha; 763 invalidateHeader(); 764 } 765 if (mSearchUiManager.getEditText() != null) { 766 boolean bgVisible = mSearchUiManager.getBackgroundVisibility(); 767 if (scrolledOffset == 0 && !mIsSearching) { 768 bgVisible = true; 769 } else if (scrolledOffset > mHeaderThreshold) { 770 bgVisible = false; 771 } 772 mSearchUiManager.setBackgroundVisibility(bgVisible, 1 - prog); 773 } 774 } 775 776 /** 777 * redraws header protection 778 */ invalidateHeader()779 public void invalidateHeader() { 780 if (mScrimView != null && mHeader.isHeaderProtectionSupported()) { 781 mScrimView.invalidate(); 782 } 783 } 784 } 785