/* * Copyright (C) 2022 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 android.content.Context; import android.content.Intent; import android.util.AttributeSet; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.View; import android.widget.RelativeLayout; import androidx.core.graphics.ColorUtils; import androidx.recyclerview.widget.RecyclerView; import com.android.launcher3.DeviceProfile.DeviceProfileListenable; 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.util.PackageManagerHelper; import com.android.launcher3.views.AppLauncher; import java.util.Objects; /** * All apps container view with search support for use in a dragging activity. * * @param Type of context inflating all apps. */ public class ActivityAllAppsContainerView extends BaseAllAppsContainerView { protected SearchUiManager mSearchUiManager; /** * View that defines the search box. Result is rendered inside the recycler view defined in the * base class. */ private View mSearchContainer; /** {@code true} when rendered view is in search state instead of the scroll state. */ private boolean mIsSearching; public ActivityAllAppsContainerView(Context context) { this(context, null); } public ActivityAllAppsContainerView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public ActivityAllAppsContainerView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } public SearchUiManager getSearchUiManager() { return mSearchUiManager; } public View getSearchView() { return mSearchContainer; } /** Updates all apps container with the latest search query. */ public void setLastSearchQuery(String query) { Intent marketSearchIntent = PackageManagerHelper.getMarketSearchIntent( mActivityContext, query); OnClickListener marketSearchClickListener = (v) -> mActivityContext.startActivitySafely(v, marketSearchIntent, null); for (int i = 0; i < mAH.size(); i++) { mAH.get(i).adapter.setLastSearchQuery(query, marketSearchClickListener); } mIsSearching = true; rebindAdapters(); mHeader.setCollapsed(true); } /** Invoke when the current search session is finished. */ public void onClearSearchResult() { mIsSearching = false; mHeader.setCollapsed(false); rebindAdapters(); mHeader.reset(false); } /** Invoke when the search results change. */ public void onSearchResultsChanged() { for (int i = 0; i < mAH.size(); i++) { if (mAH.get(i).mRecyclerView != null) { mAH.get(i).mRecyclerView.onSearchResultsChanged(); } } } @Override protected final SearchAdapterProvider createMainAdapterProvider() { return mActivityContext.createSearchAdapterProvider(this); } @Override 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 (mActivityContext.getDragLayer().isEventOverView(mSearchContainer, ev)) { return true; } return super.shouldContainerScroll(ev); } @Override public void reset(boolean animate) { super.reset(animate); // Reset the search bar after transitioning home. mSearchUiManager.resetSearch(); } @Override protected void onFinishInflate() { super.onFinishInflate(); mSearchContainer = findViewById(R.id.search_container_all_apps); mSearchUiManager = (SearchUiManager) mSearchContainer; mSearchUiManager.initializeSearch(this); } @Override public boolean dispatchKeyEvent(KeyEvent event) { mSearchUiManager.preDispatchKeyEvent(event); return super.dispatchKeyEvent(event); } @Override public String getDescription() { if (!mUsingTabs && mIsSearching) { return getContext().getString(R.string.all_apps_search_results); } else { return super.getDescription(); } } @Override protected boolean showTabs() { return super.showTabs() && !mIsSearching; } @Override protected void rebindAdapters(boolean force) { super.rebindAdapters(force); if (!FeatureFlags.ENABLE_DEVICE_SEARCH.get() || getMainAdapterProvider().getDecorator() == null) { return; } RecyclerView.ItemDecoration decoration = getMainAdapterProvider().getDecorator(); mAH.stream() .map(adapterHolder -> adapterHolder.mRecyclerView) .filter(Objects::nonNull) .forEach(v -> { v.removeItemDecoration(decoration); // Remove in case it is already added. v.addItemDecoration(decoration); }); } @Override protected View replaceRVContainer(boolean showTabs) { View rvContainer = super.replaceRVContainer(showTabs); removeCustomRules(rvContainer); if (FeatureFlags.ENABLE_FLOATING_SEARCH_BAR.get()) { alignParentTop(rvContainer, showTabs); layoutAboveSearchContainer(rvContainer); } else { layoutBelowSearchContainer(rvContainer, showTabs); } return rvContainer; } @Override void setupHeader() { super.setupHeader(); removeCustomRules(mHeader); if (FeatureFlags.ENABLE_FLOATING_SEARCH_BAR.get()) { alignParentTop(mHeader, false /* includeTabsMargin */); } else { layoutBelowSearchContainer(mHeader, false /* includeTabsMargin */); } } @Override protected void updateHeaderScroll(int scrolledOffset) { super.updateHeaderScroll(scrolledOffset); if (mSearchUiManager.getEditText() == null) { return; } float prog = Utilities.boundToRange((float) scrolledOffset / mHeaderThreshold, 0f, 1f); boolean bgVisible = mSearchUiManager.getBackgroundVisibility(); if (scrolledOffset == 0 && !mIsSearching) { bgVisible = true; } else if (scrolledOffset > mHeaderThreshold) { bgVisible = false; } mSearchUiManager.setBackgroundVisibility(bgVisible, 1 - prog); } @Override protected int getHeaderColor(float blendRatio) { return ColorUtils.setAlphaComponent( super.getHeaderColor(blendRatio), (int) (mSearchContainer.getAlpha() * 255)); } @Override protected int getHeaderBottom() { if (FeatureFlags.ENABLE_FLOATING_SEARCH_BAR.get()) { return super.getHeaderBottom(); } return super.getHeaderBottom() + mSearchContainer.getBottom(); } private void layoutBelowSearchContainer(View v, boolean includeTabsMargin) { if (!(v.getLayoutParams() instanceof RelativeLayout.LayoutParams)) { return; } RelativeLayout.LayoutParams layoutParams = (LayoutParams) v.getLayoutParams(); layoutParams.addRule(RelativeLayout.ALIGN_TOP, R.id.search_container_all_apps); int topMargin = getContext().getResources().getDimensionPixelSize( R.dimen.all_apps_header_top_margin); if (includeTabsMargin) { topMargin = topMargin + getContext().getResources().getDimensionPixelSize( R.dimen.all_apps_header_pill_height); } layoutParams.topMargin = topMargin; } private void layoutAboveSearchContainer(View v) { if (!(v.getLayoutParams() instanceof RelativeLayout.LayoutParams)) { return; } RelativeLayout.LayoutParams layoutParams = (LayoutParams) v.getLayoutParams(); layoutParams.addRule(RelativeLayout.ABOVE, R.id.search_container_all_apps); } private void alignParentTop(View v, boolean includeTabsMargin) { if (!(v.getLayoutParams() instanceof RelativeLayout.LayoutParams)) { return; } RelativeLayout.LayoutParams layoutParams = (LayoutParams) v.getLayoutParams(); layoutParams.addRule(RelativeLayout.ALIGN_PARENT_TOP); layoutParams.topMargin = includeTabsMargin ? getContext().getResources().getDimensionPixelSize( R.dimen.all_apps_header_pill_height) : 0; } private void removeCustomRules(View v) { if (!(v.getLayoutParams() instanceof RelativeLayout.LayoutParams)) { return; } RelativeLayout.LayoutParams layoutParams = (LayoutParams) v.getLayoutParams(); layoutParams.removeRule(RelativeLayout.ABOVE); layoutParams.removeRule(RelativeLayout.ALIGN_TOP); layoutParams.removeRule(RelativeLayout.ALIGN_PARENT_TOP); } @Override protected BaseAllAppsAdapter getAdapter(AlphabeticalAppsList mAppsList, BaseAdapterProvider[] adapterProviders) { return new AllAppsGridAdapter<>(mActivityContext, getLayoutInflater(), mAppsList, adapterProviders); } }