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 android.animation.ValueAnimator; 19 import android.content.Context; 20 import android.graphics.Canvas; 21 import android.graphics.Paint; 22 import android.graphics.Point; 23 import android.graphics.Rect; 24 import android.os.Bundle; 25 import android.os.Process; 26 import android.text.Selection; 27 import android.text.SpannableStringBuilder; 28 import android.util.AttributeSet; 29 import android.view.KeyEvent; 30 import android.view.LayoutInflater; 31 import android.view.MotionEvent; 32 import android.view.View; 33 import android.view.ViewGroup; 34 import android.view.WindowInsets; 35 import androidx.annotation.NonNull; 36 import androidx.annotation.Nullable; 37 import androidx.annotation.StringRes; 38 import androidx.dynamicanimation.animation.DynamicAnimation; 39 import androidx.recyclerview.widget.LinearLayoutManager; 40 import androidx.recyclerview.widget.RecyclerView; 41 import com.android.launcher3.AppInfo; 42 import com.android.launcher3.DeviceProfile; 43 import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener; 44 import com.android.launcher3.DragSource; 45 import com.android.launcher3.DropTarget.DragObject; 46 import com.android.launcher3.Insettable; 47 import com.android.launcher3.InsettableFrameLayout; 48 import com.android.launcher3.ItemInfo; 49 import com.android.launcher3.Launcher; 50 import com.android.launcher3.LauncherState; 51 import com.android.launcher3.R; 52 import com.android.launcher3.Utilities; 53 import com.android.launcher3.compat.AccessibilityManagerCompat; 54 import com.android.launcher3.config.FeatureFlags; 55 import com.android.launcher3.keyboard.FocusedItemDecorator; 56 import com.android.launcher3.testing.TestProtocol; 57 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType; 58 import com.android.launcher3.userevent.nano.LauncherLogProto.Target; 59 import com.android.launcher3.util.ItemInfoMatcher; 60 import com.android.launcher3.util.MultiValueAlpha; 61 import com.android.launcher3.util.MultiValueAlpha.AlphaProperty; 62 import com.android.launcher3.util.Themes; 63 import com.android.launcher3.views.BottomUserEducationView; 64 import com.android.launcher3.views.RecyclerViewFastScroller; 65 import com.android.launcher3.views.SpringRelativeLayout; 66 67 /** 68 * The all apps view container. 69 */ 70 public class AllAppsContainerView extends SpringRelativeLayout implements DragSource, 71 Insettable, OnDeviceProfileChangeListener { 72 73 private static final float FLING_VELOCITY_MULTIPLIER = 135f; 74 // Starts the springs after at least 55% of the animation has passed. 75 private static final float FLING_ANIMATION_THRESHOLD = 0.55f; 76 private static final int ALPHA_CHANNEL_COUNT = 2; 77 78 private final Launcher mLauncher; 79 private final AdapterHolder[] mAH; 80 private final ItemInfoMatcher mPersonalMatcher = ItemInfoMatcher.ofUser(Process.myUserHandle()); 81 private final ItemInfoMatcher mWorkMatcher = ItemInfoMatcher.not(mPersonalMatcher); 82 private final AllAppsStore mAllAppsStore = new AllAppsStore(); 83 84 private final Paint mNavBarScrimPaint; 85 private int mNavBarScrimHeight = 0; 86 87 private SearchUiManager mSearchUiManager; 88 private View mSearchContainer; 89 private AllAppsPagedView mViewPager; 90 private FloatingHeaderView mHeader; 91 92 private SpannableStringBuilder mSearchQueryBuilder = null; 93 94 private boolean mUsingTabs; 95 private boolean mSearchModeWhileUsingTabs = false; 96 97 private RecyclerViewFastScroller mTouchHandler; 98 private final Point mFastScrollerOffset = new Point(); 99 100 private final MultiValueAlpha mMultiValueAlpha; 101 AllAppsContainerView(Context context)102 public AllAppsContainerView(Context context) { 103 this(context, null); 104 } 105 AllAppsContainerView(Context context, AttributeSet attrs)106 public AllAppsContainerView(Context context, AttributeSet attrs) { 107 this(context, attrs, 0); 108 } 109 AllAppsContainerView(Context context, AttributeSet attrs, int defStyleAttr)110 public AllAppsContainerView(Context context, AttributeSet attrs, int defStyleAttr) { 111 super(context, attrs, defStyleAttr); 112 113 mLauncher = Launcher.getLauncher(context); 114 mLauncher.addOnDeviceProfileChangeListener(this); 115 116 mSearchQueryBuilder = new SpannableStringBuilder(); 117 Selection.setSelection(mSearchQueryBuilder, 0); 118 119 mAH = new AdapterHolder[2]; 120 mAH[AdapterHolder.MAIN] = new AdapterHolder(false /* isWork */); 121 mAH[AdapterHolder.WORK] = new AdapterHolder(true /* isWork */); 122 123 mNavBarScrimPaint = new Paint(); 124 mNavBarScrimPaint.setColor(Themes.getAttrColor(context, R.attr.allAppsNavBarScrimColor)); 125 126 mAllAppsStore.addUpdateListener(this::onAppsUpdated); 127 128 addSpringView(R.id.all_apps_header); 129 addSpringView(R.id.apps_list_view); 130 addSpringView(R.id.all_apps_tabs_view_pager); 131 132 mMultiValueAlpha = new MultiValueAlpha(this, ALPHA_CHANNEL_COUNT); 133 } 134 getAppsStore()135 public AllAppsStore getAppsStore() { 136 return mAllAppsStore; 137 } 138 getAlphaProperty(int index)139 public AlphaProperty getAlphaProperty(int index) { 140 return mMultiValueAlpha.getProperty(index); 141 } 142 143 @Override setDampedScrollShift(float shift)144 protected void setDampedScrollShift(float shift) { 145 // Bound the shift amount to avoid content from drawing on top (Y-val) of the QSB. 146 float maxShift = getSearchView().getHeight() / 2f; 147 super.setDampedScrollShift(Utilities.boundToRange(shift, -maxShift, maxShift)); 148 } 149 150 @Override onDeviceProfileChanged(DeviceProfile dp)151 public void onDeviceProfileChanged(DeviceProfile dp) { 152 for (AdapterHolder holder : mAH) { 153 if (holder.recyclerView != null) { 154 // Remove all views and clear the pool, while keeping the data same. After this 155 // call, all the viewHolders will be recreated. 156 holder.recyclerView.swapAdapter(holder.recyclerView.getAdapter(), true); 157 holder.recyclerView.getRecycledViewPool().clear(); 158 } 159 } 160 } 161 onAppsUpdated()162 private void onAppsUpdated() { 163 if (FeatureFlags.ALL_APPS_TABS_ENABLED) { 164 boolean hasWorkApps = false; 165 for (AppInfo app : mAllAppsStore.getApps()) { 166 if (mWorkMatcher.matches(app, null)) { 167 hasWorkApps = true; 168 break; 169 } 170 } 171 rebindAdapters(hasWorkApps); 172 } 173 } 174 175 /** 176 * Returns whether the view itself will handle the touch event or not. 177 */ shouldContainerScroll(MotionEvent ev)178 public boolean shouldContainerScroll(MotionEvent ev) { 179 // IF the MotionEvent is inside the search box, and the container keeps on receiving 180 // touch input, container should move down. 181 if (mLauncher.getDragLayer().isEventOverView(mSearchContainer, ev)) { 182 return true; 183 } 184 AllAppsRecyclerView rv = getActiveRecyclerView(); 185 if (rv == null) { 186 return true; 187 } 188 if (rv.getScrollbar().getThumbOffsetY() >= 0 && 189 mLauncher.getDragLayer().isEventOverView(rv.getScrollbar(), ev)) { 190 return false; 191 } 192 return rv.shouldContainerScroll(ev, mLauncher.getDragLayer()); 193 } 194 195 @Override onInterceptTouchEvent(MotionEvent ev)196 public boolean onInterceptTouchEvent(MotionEvent ev) { 197 198 // The AllAppsContainerView houses the QSB and is hence visible from the Workspace 199 // Overview states. We shouldn't intercept for the scrubber in these cases. 200 if (!mLauncher.isInState(LauncherState.ALL_APPS)) { 201 mTouchHandler = null; 202 return false; 203 } 204 205 if (ev.getAction() == MotionEvent.ACTION_DOWN) { 206 AllAppsRecyclerView rv = getActiveRecyclerView(); 207 if (rv != null && 208 rv.getScrollbar().isHitInParent(ev.getX(), ev.getY(), mFastScrollerOffset)) { 209 mTouchHandler = rv.getScrollbar(); 210 } else { 211 mTouchHandler = null; 212 } 213 } 214 if (mTouchHandler != null) { 215 return mTouchHandler.handleTouchEvent(ev, mFastScrollerOffset); 216 } 217 return false; 218 } 219 220 @Override onTouchEvent(MotionEvent ev)221 public boolean onTouchEvent(MotionEvent ev) { 222 if (ev.getAction() == MotionEvent.ACTION_DOWN) { 223 AllAppsRecyclerView rv = getActiveRecyclerView(); 224 if (rv != null && rv.getScrollbar() 225 .isHitInParent(ev.getX(), ev.getY(), mFastScrollerOffset)) { 226 mTouchHandler = rv.getScrollbar(); 227 } else { 228 mTouchHandler = null; 229 } 230 } 231 232 if (mTouchHandler != null) { 233 mTouchHandler.handleTouchEvent(ev, mFastScrollerOffset); 234 return true; 235 } 236 return false; 237 } 238 getDescription()239 public String getDescription() { 240 @StringRes int descriptionRes; 241 if (mUsingTabs) { 242 descriptionRes = 243 mViewPager.getNextPage() == 0 244 ? R.string.all_apps_button_personal_label 245 : R.string.all_apps_button_work_label; 246 } else { 247 descriptionRes = R.string.all_apps_button_label; 248 } 249 return getContext().getString(descriptionRes); 250 } 251 getActiveRecyclerView()252 public AllAppsRecyclerView getActiveRecyclerView() { 253 if (!mUsingTabs || mViewPager.getNextPage() == 0) { 254 return mAH[AdapterHolder.MAIN].recyclerView; 255 } else { 256 return mAH[AdapterHolder.WORK].recyclerView; 257 } 258 } 259 260 /** 261 * Resets the state of AllApps. 262 */ reset(boolean animate)263 public void reset(boolean animate) { 264 for (int i = 0; i < mAH.length; i++) { 265 if (mAH[i].recyclerView != null) { 266 mAH[i].recyclerView.scrollToTop(); 267 } 268 } 269 if (isHeaderVisible()) { 270 mHeader.reset(animate); 271 } 272 // Reset the search bar and base recycler view after transitioning home 273 mSearchUiManager.resetSearch(); 274 } 275 276 @Override onFinishInflate()277 protected void onFinishInflate() { 278 super.onFinishInflate(); 279 280 // This is a focus listener that proxies focus from a view into the list view. This is to 281 // work around the search box from getting first focus and showing the cursor. 282 setOnFocusChangeListener((v, hasFocus) -> { 283 if (hasFocus && getActiveRecyclerView() != null) { 284 getActiveRecyclerView().requestFocus(); 285 } 286 }); 287 288 mHeader = findViewById(R.id.all_apps_header); 289 rebindAdapters(mUsingTabs, true /* force */); 290 291 mSearchContainer = findViewById(R.id.search_container_all_apps); 292 mSearchUiManager = (SearchUiManager) mSearchContainer; 293 mSearchUiManager.initialize(this); 294 } 295 getSearchUiManager()296 public SearchUiManager getSearchUiManager() { 297 return mSearchUiManager; 298 } 299 300 @Override dispatchKeyEvent(KeyEvent event)301 public boolean dispatchKeyEvent(KeyEvent event) { 302 mSearchUiManager.preDispatchKeyEvent(event); 303 return super.dispatchKeyEvent(event); 304 } 305 306 @Override onDropCompleted(View target, DragObject d, boolean success)307 public void onDropCompleted(View target, DragObject d, boolean success) { } 308 309 @Override fillInLogContainerData(View v, ItemInfo info, Target target, Target targetParent)310 public void fillInLogContainerData(View v, ItemInfo info, Target target, Target targetParent) { 311 if (getApps().hasFilter()) { 312 targetParent.containerType = ContainerType.SEARCHRESULT; 313 } else { 314 targetParent.containerType = ContainerType.ALLAPPS; 315 } 316 } 317 318 @Override setInsets(Rect insets)319 public void setInsets(Rect insets) { 320 DeviceProfile grid = mLauncher.getDeviceProfile(); 321 int leftRightPadding = grid.desiredWorkspaceLeftRightMarginPx 322 + grid.cellLayoutPaddingLeftRightPx; 323 324 for (int i = 0; i < mAH.length; i++) { 325 mAH[i].adapter.setAppsPerRow(grid.inv.numAllAppsColumns); 326 mAH[i].padding.bottom = insets.bottom; 327 mAH[i].padding.left = mAH[i].padding.right = leftRightPadding; 328 mAH[i].applyPadding(); 329 } 330 331 ViewGroup.MarginLayoutParams mlp = (MarginLayoutParams) getLayoutParams(); 332 if (grid.isVerticalBarLayout()) { 333 mlp.leftMargin = insets.left; 334 mlp.rightMargin = insets.right; 335 setPadding(grid.workspacePadding.left, 0, grid.workspacePadding.right, 0); 336 } else { 337 mlp.leftMargin = mlp.rightMargin = 0; 338 setPadding(0, 0, 0, 0); 339 } 340 setLayoutParams(mlp); 341 342 InsettableFrameLayout.dispatchInsets(this, insets); 343 mLauncher.getAllAppsController() 344 .setScrollRangeDelta(mSearchUiManager.getScrollRangeDelta(insets)); 345 } 346 347 @Override dispatchApplyWindowInsets(WindowInsets insets)348 public WindowInsets dispatchApplyWindowInsets(WindowInsets insets) { 349 if (Utilities.ATLEAST_Q) { 350 mNavBarScrimHeight = insets.getTappableElementInsets().bottom; 351 } else { 352 mNavBarScrimHeight = insets.getStableInsetBottom(); 353 } 354 return super.dispatchApplyWindowInsets(insets); 355 } 356 357 @Override dispatchDraw(Canvas canvas)358 protected void dispatchDraw(Canvas canvas) { 359 super.dispatchDraw(canvas); 360 361 if (mNavBarScrimHeight > 0) { 362 canvas.drawRect(0, getHeight() - mNavBarScrimHeight, getWidth(), getHeight(), 363 mNavBarScrimPaint); 364 } 365 } 366 367 @Override getCanvasClipTopForOverscroll()368 public int getCanvasClipTopForOverscroll() { 369 // Do not clip if the QSB is attached to the spring, otherwise the QSB will get clipped. 370 return mSpringViews.get(getSearchView().getId()) ? 0 : mHeader.getTop(); 371 } 372 rebindAdapters(boolean showTabs)373 private void rebindAdapters(boolean showTabs) { 374 rebindAdapters(showTabs, false /* force */); 375 } 376 rebindAdapters(boolean showTabs, boolean force)377 private void rebindAdapters(boolean showTabs, boolean force) { 378 if (showTabs == mUsingTabs && !force) { 379 return; 380 } 381 replaceRVContainer(showTabs); 382 mUsingTabs = showTabs; 383 384 mAllAppsStore.unregisterIconContainer(mAH[AdapterHolder.MAIN].recyclerView); 385 mAllAppsStore.unregisterIconContainer(mAH[AdapterHolder.WORK].recyclerView); 386 387 if (mUsingTabs) { 388 mAH[AdapterHolder.MAIN].setup(mViewPager.getChildAt(0), mPersonalMatcher); 389 mAH[AdapterHolder.WORK].setup(mViewPager.getChildAt(1), mWorkMatcher); 390 onTabChanged(mViewPager.getNextPage()); 391 } else { 392 mAH[AdapterHolder.MAIN].setup(findViewById(R.id.apps_list_view), null); 393 mAH[AdapterHolder.WORK].recyclerView = null; 394 } 395 setupHeader(); 396 397 mAllAppsStore.registerIconContainer(mAH[AdapterHolder.MAIN].recyclerView); 398 mAllAppsStore.registerIconContainer(mAH[AdapterHolder.WORK].recyclerView); 399 } 400 replaceRVContainer(boolean showTabs)401 private void replaceRVContainer(boolean showTabs) { 402 for (int i = 0; i < mAH.length; i++) { 403 if (mAH[i].recyclerView != null) { 404 mAH[i].recyclerView.setLayoutManager(null); 405 } 406 } 407 View oldView = getRecyclerViewContainer(); 408 int index = indexOfChild(oldView); 409 removeView(oldView); 410 int layout = showTabs ? R.layout.all_apps_tabs : R.layout.all_apps_rv_layout; 411 View newView = LayoutInflater.from(getContext()).inflate(layout, this, false); 412 addView(newView, index); 413 if (showTabs) { 414 mViewPager = (AllAppsPagedView) newView; 415 mViewPager.initParentViews(this); 416 mViewPager.getPageIndicator().setContainerView(this); 417 } else { 418 mViewPager = null; 419 } 420 } 421 getRecyclerViewContainer()422 public View getRecyclerViewContainer() { 423 return mViewPager != null ? mViewPager : findViewById(R.id.apps_list_view); 424 } 425 onTabChanged(int pos)426 public void onTabChanged(int pos) { 427 mHeader.setMainActive(pos == 0); 428 reset(true /* animate */); 429 if (mAH[pos].recyclerView != null) { 430 mAH[pos].recyclerView.bindFastScrollbar(); 431 432 findViewById(R.id.tab_personal) 433 .setOnClickListener((View view) -> mViewPager.snapToPage(AdapterHolder.MAIN)); 434 findViewById(R.id.tab_work) 435 .setOnClickListener((View view) -> mViewPager.snapToPage(AdapterHolder.WORK)); 436 437 } 438 if (pos == AdapterHolder.WORK) { 439 BottomUserEducationView.showIfNeeded(mLauncher); 440 } 441 } 442 443 // Used by tests only isDescendantViewVisible(int viewId)444 private boolean isDescendantViewVisible(int viewId) { 445 final View view = findViewById(viewId); 446 if (view == null) return false; 447 448 if (!view.isShown()) return false; 449 450 return view.getGlobalVisibleRect(new Rect()); 451 } 452 453 // Used by tests only isPersonalTabVisible()454 public boolean isPersonalTabVisible() { 455 return isDescendantViewVisible(R.id.tab_personal); 456 } 457 458 // Used by tests only isWorkTabVisible()459 public boolean isWorkTabVisible() { 460 return isDescendantViewVisible(R.id.tab_work); 461 } 462 getApps()463 public AlphabeticalAppsList getApps() { 464 return mAH[AdapterHolder.MAIN].appsList; 465 } 466 getFloatingHeaderView()467 public FloatingHeaderView getFloatingHeaderView() { 468 return mHeader; 469 } 470 getSearchView()471 public View getSearchView() { 472 return mSearchContainer; 473 } 474 getContentView()475 public View getContentView() { 476 return mViewPager == null ? getActiveRecyclerView() : mViewPager; 477 } 478 getScrollBar()479 public RecyclerViewFastScroller getScrollBar() { 480 AllAppsRecyclerView rv = getActiveRecyclerView(); 481 return rv == null ? null : rv.getScrollbar(); 482 } 483 setupHeader()484 public void setupHeader() { 485 mHeader.setVisibility(View.VISIBLE); 486 mHeader.setup(mAH, mAH[AllAppsContainerView.AdapterHolder.WORK].recyclerView == null); 487 488 int padding = mHeader.getMaxTranslation(); 489 for (int i = 0; i < mAH.length; i++) { 490 mAH[i].padding.top = padding; 491 mAH[i].applyPadding(); 492 } 493 } 494 setLastSearchQuery(String query)495 public void setLastSearchQuery(String query) { 496 for (int i = 0; i < mAH.length; i++) { 497 mAH[i].adapter.setLastSearchQuery(query); 498 } 499 if (mUsingTabs) { 500 mSearchModeWhileUsingTabs = true; 501 rebindAdapters(false); // hide tabs 502 } 503 } 504 onClearSearchResult()505 public void onClearSearchResult() { 506 if (mSearchModeWhileUsingTabs) { 507 rebindAdapters(true); // show tabs 508 mSearchModeWhileUsingTabs = false; 509 } 510 } 511 onSearchResultsChanged()512 public void onSearchResultsChanged() { 513 for (int i = 0; i < mAH.length; i++) { 514 if (mAH[i].recyclerView != null) { 515 mAH[i].recyclerView.onSearchResultsChanged(); 516 } 517 } 518 } 519 setRecyclerViewVerticalFadingEdgeEnabled(boolean enabled)520 public void setRecyclerViewVerticalFadingEdgeEnabled(boolean enabled) { 521 for (int i = 0; i < mAH.length; i++) { 522 mAH[i].applyVerticalFadingEdgeEnabled(enabled); 523 } 524 } 525 addElevationController(RecyclerView.OnScrollListener scrollListener)526 public void addElevationController(RecyclerView.OnScrollListener scrollListener) { 527 if (!mUsingTabs) { 528 mAH[AdapterHolder.MAIN].recyclerView.addOnScrollListener(scrollListener); 529 } 530 } 531 isHeaderVisible()532 public boolean isHeaderVisible() { 533 return mHeader != null && mHeader.getVisibility() == View.VISIBLE; 534 } 535 onScrollUpEnd()536 public void onScrollUpEnd() { 537 highlightWorkTabIfNecessary(); 538 } 539 highlightWorkTabIfNecessary()540 void highlightWorkTabIfNecessary() { 541 if (mUsingTabs) { 542 ((PersonalWorkSlidingTabStrip) findViewById(R.id.tabs)) 543 .highlightWorkTabIfNecessary(); 544 } 545 } 546 547 /** 548 * Adds an update listener to {@param animator} that adds springs to the animation. 549 */ addSpringFromFlingUpdateListener(ValueAnimator animator, float velocity)550 public void addSpringFromFlingUpdateListener(ValueAnimator animator, float velocity) { 551 animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 552 boolean shouldSpring = true; 553 554 @Override 555 public void onAnimationUpdate(ValueAnimator valueAnimator) { 556 if (shouldSpring 557 && valueAnimator.getAnimatedFraction() >= FLING_ANIMATION_THRESHOLD) { 558 int searchViewId = getSearchView().getId(); 559 addSpringView(searchViewId); 560 561 finishWithShiftAndVelocity(1, velocity * FLING_VELOCITY_MULTIPLIER, 562 new DynamicAnimation.OnAnimationEndListener() { 563 @Override 564 public void onAnimationEnd(DynamicAnimation animation, 565 boolean canceled, float value, float velocity) { 566 removeSpringView(searchViewId); 567 } 568 }); 569 570 shouldSpring = false; 571 } 572 } 573 }); 574 } 575 576 @Override getDrawingRect(Rect outRect)577 public void getDrawingRect(Rect outRect) { 578 super.getDrawingRect(outRect); 579 outRect.offset(0, (int) getTranslationY()); 580 } 581 582 public class AdapterHolder { 583 public static final int MAIN = 0; 584 public static final int WORK = 1; 585 586 public final AllAppsGridAdapter adapter; 587 final LinearLayoutManager layoutManager; 588 final AlphabeticalAppsList appsList; 589 final Rect padding = new Rect(); 590 AllAppsRecyclerView recyclerView; 591 boolean verticalFadingEdge; 592 AdapterHolder(boolean isWork)593 AdapterHolder(boolean isWork) { 594 appsList = new AlphabeticalAppsList(mLauncher, mAllAppsStore, isWork); 595 adapter = new AllAppsGridAdapter(mLauncher, appsList); 596 appsList.setAdapter(adapter); 597 layoutManager = adapter.getLayoutManager(); 598 } 599 setup(@onNull View rv, @Nullable ItemInfoMatcher matcher)600 void setup(@NonNull View rv, @Nullable ItemInfoMatcher matcher) { 601 appsList.updateItemFilter(matcher); 602 recyclerView = (AllAppsRecyclerView) rv; 603 recyclerView.setEdgeEffectFactory(createEdgeEffectFactory()); 604 recyclerView.setApps(appsList, mUsingTabs); 605 recyclerView.setLayoutManager(layoutManager); 606 recyclerView.setAdapter(adapter); 607 recyclerView.setHasFixedSize(true); 608 // No animations will occur when changes occur to the items in this RecyclerView. 609 recyclerView.setItemAnimator(null); 610 FocusedItemDecorator focusedItemDecorator = new FocusedItemDecorator(recyclerView); 611 recyclerView.addItemDecoration(focusedItemDecorator); 612 adapter.setIconFocusListener(focusedItemDecorator.getFocusListener()); 613 applyVerticalFadingEdgeEnabled(verticalFadingEdge); 614 applyPadding(); 615 } 616 applyPadding()617 void applyPadding() { 618 if (recyclerView != null) { 619 recyclerView.setPadding(padding.left, padding.top, padding.right, padding.bottom); 620 } 621 } 622 applyVerticalFadingEdgeEnabled(boolean enabled)623 public void applyVerticalFadingEdgeEnabled(boolean enabled) { 624 verticalFadingEdge = enabled; 625 mAH[AdapterHolder.MAIN].recyclerView.setVerticalFadingEdgeEnabled(!mUsingTabs 626 && verticalFadingEdge); 627 } 628 } 629 630 @Override performAccessibilityAction(int action, Bundle arguments)631 public boolean performAccessibilityAction(int action, Bundle arguments) { 632 if (AccessibilityManagerCompat.processTestRequest( 633 mLauncher, TestProtocol.GET_SCROLL_MESSAGE, action, arguments, 634 response -> 635 response.putInt(TestProtocol.SCROLL_Y_FIELD, 636 getActiveRecyclerView().getCurrentScrollY()))) { 637 return true; 638 } 639 640 return super.performAccessibilityAction(action, arguments); 641 } 642 } 643