/* * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.launcher3.allapps; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_TAP_ON_PERSONAL_TAB; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_TAP_ON_WORK_TAB; import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_HAS_SHORTCUT_PERMISSION; import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_QUIET_MODE_CHANGE_PERMISSION; import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_QUIET_MODE_ENABLED; import android.animation.ValueAnimator; import android.content.Context; import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Point; import android.graphics.Rect; import android.os.Parcelable; import android.os.Process; import android.text.Selection; import android.text.SpannableStringBuilder; import android.util.AttributeSet; import android.util.Log; import android.util.SparseArray; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.WindowInsets; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.StringRes; import androidx.annotation.VisibleForTesting; import androidx.core.graphics.ColorUtils; import androidx.core.os.BuildCompat; import androidx.recyclerview.widget.DefaultItemAnimator; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; import com.android.launcher3.BaseDraggingActivity; import com.android.launcher3.DeviceProfile; import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener; import com.android.launcher3.DragSource; import com.android.launcher3.DropTarget.DragObject; import com.android.launcher3.Insettable; import com.android.launcher3.InsettableFrameLayout; import com.android.launcher3.R; import com.android.launcher3.Utilities; import com.android.launcher3.allapps.search.SearchAdapterProvider; import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.keyboard.FocusedItemDecorator; import com.android.launcher3.model.data.AppInfo; import com.android.launcher3.testing.TestProtocol; import com.android.launcher3.util.ItemInfoMatcher; import com.android.launcher3.util.Themes; import com.android.launcher3.views.RecyclerViewFastScroller; import com.android.launcher3.views.ScrimView; import com.android.launcher3.views.SpringRelativeLayout; import com.android.launcher3.workprofile.PersonalWorkSlidingTabStrip.OnActivePageChangedListener; /** * The all apps view container. */ public class AllAppsContainerView extends SpringRelativeLayout implements DragSource, Insettable, OnDeviceProfileChangeListener, OnActivePageChangedListener, ScrimView.ScrimDrawingController { public static final float PULL_MULTIPLIER = .02f; public static final float FLING_VELOCITY_MULTIPLIER = 2000f; // Starts the springs after at least 25% of the animation has passed. public static final float FLING_ANIMATION_THRESHOLD = 0.25f; private final Paint mHeaderPaint = new Paint(Paint.ANTI_ALIAS_FLAG); protected final BaseDraggingActivity mLauncher; protected final AdapterHolder[] mAH; private final ItemInfoMatcher mPersonalMatcher = ItemInfoMatcher.ofUser(Process.myUserHandle()); private final ItemInfoMatcher mWorkMatcher = mPersonalMatcher.negate(); private final AllAppsStore mAllAppsStore = new AllAppsStore(); private final RecyclerView.OnScrollListener mScrollListener = new RecyclerView.OnScrollListener() { @Override public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) { updateHeaderScroll(((AllAppsRecyclerView) recyclerView).getCurrentScrollY()); } }; private final Paint mNavBarScrimPaint; private int mNavBarScrimHeight = 0; protected SearchUiManager mSearchUiManager; private View mSearchContainer; private AllAppsPagedView mViewPager; protected FloatingHeaderView mHeader; private float mHeaderTop; private WorkModeSwitch mWorkModeSwitch; private SpannableStringBuilder mSearchQueryBuilder = null; protected boolean mUsingTabs; private boolean mSearchModeWhileUsingTabs = false; protected RecyclerViewFastScroller mTouchHandler; protected final Point mFastScrollerOffset = new Point(); private Rect mInsets = new Rect(); private SearchAdapterProvider mSearchAdapterProvider; private final int mScrimColor; private final int mHeaderProtectionColor; private final float mHeaderThreshold; private ScrimView mScrimView; private int mHeaderColor; public AllAppsContainerView(Context context) { this(context, null); } public AllAppsContainerView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public AllAppsContainerView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); mLauncher = BaseDraggingActivity.fromContext(context); mScrimColor = Themes.getAttrColor(context, R.attr.allAppsScrimColor); mHeaderThreshold = getResources().getDimensionPixelSize( R.dimen.dynamic_grid_cell_border_spacing); mHeaderProtectionColor = Themes.getAttrColor(context, R.attr.allappsHeaderProtectionColor); mLauncher.addOnDeviceProfileChangeListener(this); mSearchAdapterProvider = mLauncher.createSearchAdapterProvider(this); mSearchQueryBuilder = new SpannableStringBuilder(); Selection.setSelection(mSearchQueryBuilder, 0); mAH = new AdapterHolder[2]; mAH[AdapterHolder.MAIN] = new AdapterHolder(false /* isWork */); mAH[AdapterHolder.WORK] = new AdapterHolder(true /* isWork */); mNavBarScrimPaint = new Paint(); mNavBarScrimPaint.setColor(Themes.getAttrColor(context, R.attr.allAppsNavBarScrimColor)); mAllAppsStore.addUpdateListener(this::onAppsUpdated); } @Override protected void dispatchRestoreInstanceState(SparseArray sparseArray) { try { // Many slice view id is not properly assigned, and hence throws null // pointer exception in the underneath method. Catching the exception // simply doesn't restore these slice views. This doesn't have any // user visible effect because because we query them again. super.dispatchRestoreInstanceState(sparseArray); } catch (Exception e) { Log.e("AllAppsContainerView", "restoreInstanceState viewId = 0", e); } } /** * Sets the long click listener for icons */ public void setOnIconLongClickListener(OnLongClickListener listener) { for (AdapterHolder holder : mAH) { holder.adapter.setOnIconLongClickListener(listener); } } public AllAppsStore getAppsStore() { return mAllAppsStore; } public WorkModeSwitch getWorkModeSwitch() { return mWorkModeSwitch; } @Override public void onDeviceProfileChanged(DeviceProfile dp) { for (AdapterHolder holder : mAH) { holder.adapter.setAppsPerRow(dp.numShownAllAppsColumns); if (holder.recyclerView != null) { // Remove all views and clear the pool, while keeping the data same. After this // call, all the viewHolders will be recreated. holder.recyclerView.swapAdapter(holder.recyclerView.getAdapter(), true); holder.recyclerView.getRecycledViewPool().clear(); } } } private void onAppsUpdated() { boolean hasWorkApps = false; for (AppInfo app : mAllAppsStore.getApps()) { if (mWorkMatcher.matches(app, null)) { hasWorkApps = true; break; } } if (!mAH[AdapterHolder.MAIN].appsList.hasFilter()) { rebindAdapters(hasWorkApps); if (hasWorkApps) { resetWorkProfile(); } } } private void resetWorkProfile() { mWorkModeSwitch.updateCurrentState(!mAllAppsStore.hasModelFlag(FLAG_QUIET_MODE_ENABLED)); mAH[AdapterHolder.WORK].setupOverlay(); mAH[AdapterHolder.WORK].applyPadding(); } private void hideInput() { if (!BuildCompat.isAtLeastR() || !FeatureFlags.ENABLE_DEVICE_SEARCH.get()) return; WindowInsets insets = getRootWindowInsets(); if (insets == null) return; if (insets.isVisible(WindowInsets.Type.ime())) { hideIme(); } } protected void hideIme() { getWindowInsetsController().hide(WindowInsets.Type.ime()); } /** * Returns whether the view itself will handle the touch event or not. */ public boolean shouldContainerScroll(MotionEvent ev) { // IF the MotionEvent is inside the search box, and the container keeps on receiving // touch input, container should move down. if (mLauncher.getDragLayer().isEventOverView(mSearchContainer, ev)) { return true; } AllAppsRecyclerView rv = getActiveRecyclerView(); if (rv == null) { return true; } if (rv.getScrollbar().getThumbOffsetY() >= 0 && mLauncher.getDragLayer().isEventOverView(rv.getScrollbar(), ev)) { hideInput(); return false; } return rv.shouldContainerScroll(ev, mLauncher.getDragLayer()); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { if (ev.getAction() == MotionEvent.ACTION_DOWN) { AllAppsRecyclerView rv = getActiveRecyclerView(); if (rv != null && rv.getScrollbar().isHitInParent(ev.getX(), ev.getY(), mFastScrollerOffset)) { mTouchHandler = rv.getScrollbar(); } else { mTouchHandler = null; } } if (mTouchHandler != null) { return mTouchHandler.handleTouchEvent(ev, mFastScrollerOffset); } return false; } @Override public boolean onTouchEvent(MotionEvent ev) { if (ev.getAction() == MotionEvent.ACTION_DOWN) { AllAppsRecyclerView rv = getActiveRecyclerView(); if (rv != null && rv.getScrollbar().isHitInParent(ev.getX(), ev.getY(), mFastScrollerOffset)) { mTouchHandler = rv.getScrollbar(); } else { mTouchHandler = null; } } if (mTouchHandler != null) { mTouchHandler.handleTouchEvent(ev, mFastScrollerOffset); return true; } return false; } public String getDescription() { @StringRes int descriptionRes; if (mUsingTabs) { descriptionRes = mViewPager.getNextPage() == 0 ? R.string.all_apps_button_personal_label : R.string.all_apps_button_work_label; } else { descriptionRes = R.string.all_apps_button_label; } return getContext().getString(descriptionRes); } public AllAppsRecyclerView getActiveRecyclerView() { if (!mUsingTabs || mViewPager.getNextPage() == 0) { return mAH[AdapterHolder.MAIN].recyclerView; } else { return mAH[AdapterHolder.WORK].recyclerView; } } public LayoutInflater getLayoutInflater() { return LayoutInflater.from(getContext()); } /** * Resets the state of AllApps. */ public void reset(boolean animate) { for (int i = 0; i < mAH.length; i++) { if (mAH[i].recyclerView != null) { mAH[i].recyclerView.scrollToTop(); } } if (isHeaderVisible()) { mHeader.reset(animate); } // Reset the search bar and base recycler view after transitioning home mSearchUiManager.resetSearch(); updateHeaderScroll(0); } @Override protected void onFinishInflate() { super.onFinishInflate(); // This is a focus listener that proxies focus from a view into the list view. This is to // work around the search box from getting first focus and showing the cursor. setOnFocusChangeListener((v, hasFocus) -> { if (hasFocus && getActiveRecyclerView() != null) { getActiveRecyclerView().requestFocus(); } }); mHeader = findViewById(R.id.all_apps_header); rebindAdapters(mUsingTabs, true /* force */); mSearchContainer = findViewById(R.id.search_container_all_apps); mSearchUiManager = (SearchUiManager) mSearchContainer; mSearchUiManager.initializeSearch(this); } public SearchUiManager getSearchUiManager() { return mSearchUiManager; } @Override public boolean dispatchKeyEvent(KeyEvent event) { mSearchUiManager.preDispatchKeyEvent(event); return super.dispatchKeyEvent(event); } @Override public void onDropCompleted(View target, DragObject d, boolean success) { } @Override public void setInsets(Rect insets) { mInsets.set(insets); DeviceProfile grid = mLauncher.getDeviceProfile(); int leftRightPadding = grid.desiredWorkspaceLeftRightMarginPx + grid.cellLayoutPaddingLeftRightPx; for (int i = 0; i < mAH.length; i++) { mAH[i].padding.bottom = insets.bottom; mAH[i].padding.left = mAH[i].padding.right = leftRightPadding; mAH[i].applyPadding(); mAH[i].setupOverlay(); } ViewGroup.MarginLayoutParams mlp = (MarginLayoutParams) getLayoutParams(); mlp.leftMargin = insets.left; mlp.rightMargin = insets.right; setLayoutParams(mlp); if (grid.isVerticalBarLayout()) { setPadding(grid.workspacePadding.left, 0, grid.workspacePadding.right, 0); } else { setPadding(0, 0, 0, 0); } InsettableFrameLayout.dispatchInsets(this, insets); } @Override public WindowInsets dispatchApplyWindowInsets(WindowInsets insets) { if (Utilities.ATLEAST_Q) { mNavBarScrimHeight = insets.getTappableElementInsets().bottom - mLauncher.getDeviceProfile().nonOverlappingTaskbarInset; } else { mNavBarScrimHeight = insets.getStableInsetBottom(); } return super.dispatchApplyWindowInsets(insets); } @Override protected void dispatchDraw(Canvas canvas) { super.dispatchDraw(canvas); if (mNavBarScrimHeight > 0) { canvas.drawRect(0, getHeight() - mNavBarScrimHeight, getWidth(), getHeight(), mNavBarScrimPaint); } } private void rebindAdapters(boolean showTabs) { rebindAdapters(showTabs, false /* force */); } protected void rebindAdapters(boolean showTabs, boolean force) { if (showTabs == mUsingTabs && !force) { return; } replaceRVContainer(showTabs); mUsingTabs = showTabs; mAllAppsStore.unregisterIconContainer(mAH[AdapterHolder.MAIN].recyclerView); mAllAppsStore.unregisterIconContainer(mAH[AdapterHolder.WORK].recyclerView); if (mUsingTabs) { setupWorkToggle(); mAH[AdapterHolder.MAIN].setup(mViewPager.getChildAt(0), mPersonalMatcher); mAH[AdapterHolder.WORK].setup(mViewPager.getChildAt(1), mWorkMatcher); mAH[AdapterHolder.WORK].recyclerView.setId(R.id.apps_list_view_work); mViewPager.getPageIndicator().setActiveMarker(AdapterHolder.MAIN); findViewById(R.id.tab_personal) .setOnClickListener((View view) -> { if (mViewPager.snapToPage(AdapterHolder.MAIN)) { mLauncher.getStatsLogManager().logger() .log(LAUNCHER_ALLAPPS_TAP_ON_PERSONAL_TAB); } }); findViewById(R.id.tab_work) .setOnClickListener((View view) -> { if (mViewPager.snapToPage(AdapterHolder.WORK)) { mLauncher.getStatsLogManager().logger() .log(LAUNCHER_ALLAPPS_TAP_ON_WORK_TAB); } }); onActivePageChanged(mViewPager.getNextPage()); } else { mAH[AdapterHolder.MAIN].setup(findViewById(R.id.apps_list_view), null); mAH[AdapterHolder.WORK].recyclerView = null; if (mWorkModeSwitch != null) { ((ViewGroup) mWorkModeSwitch.getParent()).removeView(mWorkModeSwitch); mWorkModeSwitch = null; } } setupHeader(); mAllAppsStore.registerIconContainer(mAH[AdapterHolder.MAIN].recyclerView); mAllAppsStore.registerIconContainer(mAH[AdapterHolder.WORK].recyclerView); } private void setupWorkToggle() { if (Utilities.ATLEAST_P) { mWorkModeSwitch = (WorkModeSwitch) mLauncher.getLayoutInflater().inflate( R.layout.work_mode_fab, this, false); this.addView(mWorkModeSwitch); mWorkModeSwitch.setInsets(mInsets); mWorkModeSwitch.post(this::resetWorkProfile); } } @Override protected void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); View overlay = mAH[AdapterHolder.WORK].getOverlayView(); int v = newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE ? GONE : VISIBLE; overlay.findViewById(R.id.work_apps_paused_title).setVisibility(v); overlay.findViewById(R.id.work_apps_paused_content).setVisibility(v); } private void replaceRVContainer(boolean showTabs) { for (int i = 0; i < mAH.length; i++) { if (mAH[i].recyclerView != null) { mAH[i].recyclerView.setLayoutManager(null); } } View oldView = getRecyclerViewContainer(); int index = indexOfChild(oldView); removeView(oldView); int layout = showTabs ? R.layout.all_apps_tabs : R.layout.all_apps_rv_layout; View newView = getLayoutInflater().inflate(layout, this, false); addView(newView, index); if (TestProtocol.sDebugTracing) { Log.d(TestProtocol.WORK_PROFILE_REMOVED, "should show tabs:" + showTabs, new Exception()); } if (showTabs) { mViewPager = (AllAppsPagedView) newView; mViewPager.initParentViews(this); mViewPager.getPageIndicator().setOnActivePageChangedListener(this); } else { mViewPager = null; } } public View getRecyclerViewContainer() { return mViewPager != null ? mViewPager : findViewById(R.id.apps_list_view); } @Override public void onActivePageChanged(int currentActivePage) { mHeader.setMainActive(currentActivePage == 0); if (mAH[currentActivePage].recyclerView != null) { mAH[currentActivePage].recyclerView.bindFastScrollbar(); } reset(true /* animate */); if (mWorkModeSwitch != null) { mWorkModeSwitch.setWorkTabVisible(currentActivePage == AdapterHolder.WORK && mAllAppsStore.hasModelFlag( FLAG_HAS_SHORTCUT_PERMISSION | FLAG_QUIET_MODE_CHANGE_PERMISSION)); } } // Used by tests only private boolean isDescendantViewVisible(int viewId) { final View view = findViewById(viewId); if (view == null) return false; if (!view.isShown()) return false; return view.getGlobalVisibleRect(new Rect()); } @VisibleForTesting public boolean isPersonalTabVisible() { return isDescendantViewVisible(R.id.tab_personal); } // Used by tests only public boolean isWorkTabVisible() { return isDescendantViewVisible(R.id.tab_work); } public AlphabeticalAppsList getApps() { return mAH[AdapterHolder.MAIN].appsList; } public FloatingHeaderView getFloatingHeaderView() { return mHeader; } public View getSearchView() { return mSearchContainer; } public View getContentView() { return mViewPager == null ? getActiveRecyclerView() : mViewPager; } /** * Handles selection on focused view and returns success */ public boolean launchHighlightedItem() { if (mSearchAdapterProvider == null) return false; return mSearchAdapterProvider.launchHighlightedItem(); } public SearchAdapterProvider getSearchAdapterProvider() { return mSearchAdapterProvider; } public RecyclerViewFastScroller getScrollBar() { AllAppsRecyclerView rv = getActiveRecyclerView(); return rv == null ? null : rv.getScrollbar(); } public void setupHeader() { mHeader.setVisibility(View.VISIBLE); mHeader.setup(mAH, mAH[AllAppsContainerView.AdapterHolder.WORK].recyclerView == null); int padding = mHeader.getMaxTranslation(); for (int i = 0; i < mAH.length; i++) { mAH[i].padding.top = padding; mAH[i].applyPadding(); } mHeaderTop = mHeader.getTop(); } public void setLastSearchQuery(String query) { for (int i = 0; i < mAH.length; i++) { mAH[i].adapter.setLastSearchQuery(query); } if (mUsingTabs) { mSearchModeWhileUsingTabs = true; rebindAdapters(false); // hide tabs } } public void onClearSearchResult() { if (mSearchModeWhileUsingTabs) { rebindAdapters(true); // show tabs mSearchModeWhileUsingTabs = false; } } public void onSearchResultsChanged() { for (int i = 0; i < mAH.length; i++) { if (mAH[i].recyclerView != null) { mAH[i].recyclerView.onSearchResultsChanged(); } } } public void setRecyclerViewVerticalFadingEdgeEnabled(boolean enabled) { for (int i = 0; i < mAH.length; i++) { mAH[i].applyVerticalFadingEdgeEnabled(enabled); } } public void addElevationController(RecyclerView.OnScrollListener scrollListener) { if (!mUsingTabs) { mAH[AdapterHolder.MAIN].recyclerView.addOnScrollListener(scrollListener); } } public boolean isHeaderVisible() { return mHeader != null && mHeader.getVisibility() == View.VISIBLE; } /** * Adds an update listener to {@param animator} that adds springs to the animation. */ public void addSpringFromFlingUpdateListener(ValueAnimator animator, float velocity) { animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { boolean shouldSpring = true; @Override public void onAnimationUpdate(ValueAnimator valueAnimator) { if (shouldSpring && valueAnimator.getAnimatedFraction() >= FLING_ANIMATION_THRESHOLD) { absorbSwipeUpVelocity(Math.max(100, Math.abs( Math.round(velocity * FLING_VELOCITY_MULTIPLIER)))); // calculate the velocity of using the not user controlled interpolator // of when the container reach the end. shouldSpring = false; } } }); } public void onPull(float deltaDistance, float displacement) { absorbPullDeltaDistance(PULL_MULTIPLIER * deltaDistance, PULL_MULTIPLIER * displacement); // ideally, this should be done using EdgeEffect.onPush to create squish effect. // However, until such method is available, launcher to simulate the onPush method. mHeader.setTranslationY(-.5f * mHeaderTop * deltaDistance); getRecyclerViewContainer().setTranslationY(-mHeaderTop * deltaDistance); } public void onRelease() { ValueAnimator anim1 = ValueAnimator.ofFloat(1f, 0f); final float floatingHeaderHeight = getFloatingHeaderView().getTranslationY(); final float recyclerViewHeight = getRecyclerViewContainer().getTranslationY(); anim1.setDuration(200); anim1.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator valueAnimator) { getFloatingHeaderView().setTranslationY( ((float) valueAnimator.getAnimatedValue()) * floatingHeaderHeight); getRecyclerViewContainer().setTranslationY( ((float) valueAnimator.getAnimatedValue()) * recyclerViewHeight); } }); anim1.start(); super.onRelease(); } @Override public void getDrawingRect(Rect outRect) { super.getDrawingRect(outRect); outRect.offset(0, (int) getTranslationY()); } @Override public void setTranslationY(float translationY) { super.setTranslationY(translationY); invalidateHeader(); } public void setScrimView(ScrimView scrimView) { mScrimView = scrimView; } @Override public void drawOnScrim(Canvas canvas) { mHeaderPaint.setColor(mHeaderColor); mHeaderPaint.setAlpha((int) (getAlpha() * Color.alpha(mHeaderColor))); if (mHeaderPaint.getColor() != mScrimColor && mHeaderPaint.getColor() != 0) { canvas.drawRect(0, 0, getWidth(), mSearchContainer.getTop() + getTranslationY(), mHeaderPaint); } } public class AdapterHolder { public static final int MAIN = 0; public static final int WORK = 1; private ItemInfoMatcher mInfoMatcher; private final boolean mIsWork; public final AllAppsGridAdapter adapter; final LinearLayoutManager layoutManager; final AlphabeticalAppsList appsList; final Rect padding = new Rect(); AllAppsRecyclerView recyclerView; boolean verticalFadingEdge; private View mOverlay; boolean mWorkDisabled; AdapterHolder(boolean isWork) { mIsWork = isWork; appsList = new AlphabeticalAppsList(mLauncher, mAllAppsStore, isWork); adapter = new AllAppsGridAdapter(mLauncher, getLayoutInflater(), appsList, mSearchAdapterProvider); appsList.setAdapter(adapter); layoutManager = adapter.getLayoutManager(); } void setup(@NonNull View rv, @Nullable ItemInfoMatcher matcher) { mInfoMatcher = matcher; appsList.updateItemFilter(matcher); recyclerView = (AllAppsRecyclerView) rv; recyclerView.setEdgeEffectFactory(createEdgeEffectFactory()); recyclerView.setApps(appsList); recyclerView.setLayoutManager(layoutManager); recyclerView.setAdapter(adapter); recyclerView.setHasFixedSize(true); // No animations will occur when changes occur to the items in this RecyclerView. recyclerView.setItemAnimator(null); recyclerView.addOnScrollListener(mScrollListener); FocusedItemDecorator focusedItemDecorator = new FocusedItemDecorator(recyclerView); recyclerView.addItemDecoration(focusedItemDecorator); adapter.setIconFocusListener(focusedItemDecorator.getFocusListener()); applyVerticalFadingEdgeEnabled(verticalFadingEdge); applyPadding(); setupOverlay(); if (FeatureFlags.ENABLE_DEVICE_SEARCH.get()) { recyclerView.addItemDecoration(mSearchAdapterProvider.getDecorator()); } } void setupOverlay() { if (!mIsWork || recyclerView == null) return; boolean workDisabled = mAllAppsStore.hasModelFlag(FLAG_QUIET_MODE_ENABLED); if (mWorkDisabled == workDisabled) return; recyclerView.setContentDescription(workDisabled ? mLauncher.getString( R.string.work_apps_paused_content_description) : null); View overlayView = getOverlayView(); recyclerView.setItemAnimator(new DefaultItemAnimator()); if (workDisabled) { overlayView.setAlpha(0); recyclerView.addAutoSizedOverlay(overlayView); overlayView.animate().alpha(1).withEndAction( () -> { appsList.updateItemFilter((info, cn) -> false); recyclerView.setItemAnimator(null); }).start(); } else if (mInfoMatcher != null) { appsList.updateItemFilter(mInfoMatcher); overlayView.animate().alpha(0).withEndAction(() -> { recyclerView.setItemAnimator(null); recyclerView.clearAutoSizedOverlays(); }).start(); } mWorkDisabled = workDisabled; } void applyPadding() { if (recyclerView != null) { Resources res = getResources(); int switchH = res.getDimensionPixelSize(R.dimen.work_profile_footer_padding) * 2 + mInsets.bottom + Utilities.calculateTextHeight( res.getDimension(R.dimen.work_profile_footer_text_size)); int bottomOffset = mWorkModeSwitch != null && mIsWork ? switchH : 0; recyclerView.setPadding(padding.left, padding.top, padding.right, padding.bottom + bottomOffset); recyclerView.scrollToTop(); } } public void applyVerticalFadingEdgeEnabled(boolean enabled) { verticalFadingEdge = enabled; mAH[AdapterHolder.MAIN].recyclerView.setVerticalFadingEdgeEnabled(!mUsingTabs && verticalFadingEdge); } private View getOverlayView() { if (mOverlay == null) { mOverlay = mLauncher.getLayoutInflater().inflate(R.layout.work_apps_paused, null); } return mOverlay; } } protected void updateHeaderScroll(int scrolledOffset) { float prog = Math.max(0, Math.min(1, (float) scrolledOffset / mHeaderThreshold)); int viewBG = ColorUtils.blendARGB(mScrimColor, mHeaderProtectionColor, prog); int headerColor = ColorUtils.setAlphaComponent(viewBG, (int) (getSearchView().getAlpha() * 255)); if (headerColor != mHeaderColor) { mHeaderColor = headerColor; getSearchView().setBackgroundColor(viewBG); getFloatingHeaderView().setHeaderColor(viewBG); invalidateHeader(); } } /** * redraws header protection */ public void invalidateHeader() { if (mScrimView != null && FeatureFlags.ENABLE_DEVICE_SEARCH.get()) { mScrimView.invalidate(); } } }