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