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