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