diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/AppsDividerView.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/AppsDividerView.java index 914d9e9774..7dc7bfcc36 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/AppsDividerView.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/AppsDividerView.java @@ -1,4 +1,4 @@ -/** +/* * Copyright (C) 2019 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -42,6 +42,7 @@ import com.android.launcher3.R; import com.android.launcher3.allapps.FloatingHeaderRow; import com.android.launcher3.allapps.FloatingHeaderView; import com.android.launcher3.anim.PropertySetter; +import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.statemanager.StateManager.StateListener; import com.android.launcher3.util.Themes; @@ -90,7 +91,8 @@ public class AppsDividerView extends View implements StateListener \ No newline at end of file diff --git a/res/values/colors.xml b/res/values/colors.xml index f56fbaaace..ad607a3cb6 100644 --- a/res/values/colors.xml +++ b/res/values/colors.xml @@ -35,6 +35,10 @@ #E0E0E0 + #E5E5E5 + #9AA0A6 + #327d7d7d + #A0C2F9 #6DA1FF #FFFFFFFF diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java index 77b8a32c7a..2d711e6251 100644 --- a/src/com/android/launcher3/allapps/AllAppsContainerView.java +++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java @@ -55,6 +55,7 @@ import com.android.launcher3.Insettable; import com.android.launcher3.InsettableFrameLayout; import com.android.launcher3.R; import com.android.launcher3.Utilities; +import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.keyboard.FocusedItemDecorator; import com.android.launcher3.model.data.AppInfo; import com.android.launcher3.model.data.ItemInfo; @@ -486,7 +487,7 @@ public class AllAppsContainerView extends SpringRelativeLayout implements DragSo if (mWorkModeSwitch != null) { mWorkModeSwitch.setWorkTabVisible(pos == AdapterHolder.WORK && mAllAppsStore.hasModelFlag( - FLAG_HAS_SHORTCUT_PERMISSION | FLAG_QUIET_MODE_CHANGE_PERMISSION)); + FLAG_HAS_SHORTCUT_PERMISSION | FLAG_QUIET_MODE_CHANGE_PERMISSION)); } } @@ -538,6 +539,10 @@ public class AllAppsContainerView extends SpringRelativeLayout implements DragSo int padding = mHeader.getMaxTranslation(); for (int i = 0; i < mAH.length; i++) { mAH[i].padding.top = padding; + if (FeatureFlags.ENABLE_DEVICE_SEARCH.get() && mUsingTabs) { + //add extra space between tabs and recycler view + mAH[i].padding.top += mLauncher.getDeviceProfile().edgeMarginPx; + } mAH[i].applyPadding(); } } @@ -652,6 +657,9 @@ public class AllAppsContainerView extends SpringRelativeLayout implements DragSo applyVerticalFadingEdgeEnabled(verticalFadingEdge); applyPadding(); setupOverlay(); + if (FeatureFlags.ENABLE_DEVICE_SEARCH.get()) { + recyclerView.addItemDecoration(new AllAppsSectionDecorator(getApps())); + } } void setupOverlay() { diff --git a/src/com/android/launcher3/allapps/AllAppsSectionDecorator.java b/src/com/android/launcher3/allapps/AllAppsSectionDecorator.java new file mode 100644 index 0000000000..ac55072d58 --- /dev/null +++ b/src/com/android/launcher3/allapps/AllAppsSectionDecorator.java @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2020 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.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.RectF; +import android.view.View; + +import androidx.recyclerview.widget.RecyclerView; + +import com.android.launcher3.R; +import com.android.launcher3.allapps.search.SearchSectionInfo; +import com.android.launcher3.util.Themes; + +import java.util.List; + +/** + * ItemDecoration class that groups items in {@link AllAppsRecyclerView} + */ +public class AllAppsSectionDecorator extends RecyclerView.ItemDecoration { + + private final AlphabeticalAppsList mApps; + + AllAppsSectionDecorator(AlphabeticalAppsList appsList) { + mApps = appsList; + } + + @Override + public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) { + // Iterate through views in recylerview and draw bounds around views in the same section. + // Since views in the same section will follow each other, we can skip to a last view in + // a section to get the bounds of the section without having to iterate on evert item. + int itemCount = parent.getChildCount(); + List adapterItems = mApps.getAdapterItems(); + SectionDecorationHandler lastDecorationHandler = null; + int i = 0; + while (i < itemCount) { + View view = parent.getChildAt(i); + int position = parent.getChildAdapterPosition(view); + AlphabeticalAppsList.AdapterItem adapterItem = adapterItems.get(position); + if (adapterItem.searchSectionInfo != null) { + SearchSectionInfo sectionInfo = adapterItem.searchSectionInfo; + int endIndex = Math.min(i + sectionInfo.getPosEnd() - position, itemCount - 1); + SectionDecorationHandler decorationHandler = sectionInfo.getDecorationHandler(); + if (decorationHandler != lastDecorationHandler && lastDecorationHandler != null) { + drawDecoration(c, lastDecorationHandler, parent); + } + lastDecorationHandler = decorationHandler; + if (decorationHandler != null) { + decorationHandler.extendBounds(view); + } + + if (endIndex > i) { + i = endIndex; + continue; + } + + } + i++; + } + if (lastDecorationHandler != null) { + drawDecoration(c, lastDecorationHandler, parent); + } + } + + private void drawDecoration(Canvas c, SectionDecorationHandler decorationHandler, View parent) { + if (decorationHandler == null) return; + if (decorationHandler.mIsFullWidth) { + decorationHandler.mBounds.left = parent.getPaddingLeft(); + decorationHandler.mBounds.right = parent.getWidth() - parent.getPaddingRight(); + } + decorationHandler.onDraw(c); + decorationHandler.reset(); + } + + /** + * Handles grouping and drawing of items in the same all apps sections. + */ + public static class SectionDecorationHandler { + protected RectF mBounds = new RectF(); + private final boolean mIsFullWidth; + private final float mRadius; + private final int mFillcolor; + Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + + + public SectionDecorationHandler(Context context, boolean isFullWidth) { + mIsFullWidth = isFullWidth; + mFillcolor = context.getColor(R.color.all_apps_section_fill); + mRadius = Themes.getDialogCornerRadius(context); + } + + /** + * Extends current bounds to include view + */ + public void extendBounds(View view) { + if (mBounds.isEmpty()) { + mBounds.set(view.getLeft(), view.getTop(), view.getRight(), view.getBottom()); + } else { + mBounds.set( + Math.min(mBounds.left, view.getLeft()), + Math.min(mBounds.top, view.getTop()), + Math.max(mBounds.right, view.getRight()), + Math.max(mBounds.bottom, view.getBottom()) + ); + } + } + + /** + * Draw bounds onto canvas + */ + public void onDraw(Canvas canvas) { + mPaint.setColor(mFillcolor); + canvas.drawRoundRect(mBounds, mRadius, mRadius, mPaint); + } + + /** + * Reset view bounds to empty + */ + public void reset() { + mBounds.setEmpty(); + } + } + +} diff --git a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java index 266bfa2785..5d9c5549f3 100644 --- a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java +++ b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java @@ -31,7 +31,6 @@ import java.util.List; import java.util.Locale; import java.util.Map; import java.util.TreeMap; -import java.util.stream.Collectors; /** * The alphabetically sorted list of applications. @@ -311,9 +310,15 @@ public class AlphabeticalAppsList implements AllAppsStore.OnUpdateListener { mFastScrollerSections.clear(); mAdapterItems.clear(); + SearchSectionInfo appSection = new SearchSectionInfo(); + appSection.setDecorationHandler( + new AllAppsSectionDecorator.SectionDecorationHandler(mLauncher, true)); + // Recreate the filtered and sectioned apps (for convenience for the grid layout) from the // ordered set of sections + if (!hasFilter()) { + appSection.setPosStart(position); for (AppInfo info : mApps) { String sectionName = info.sectionName; @@ -329,14 +334,31 @@ public class AlphabeticalAppsList implements AllAppsStore.OnUpdateListener { if (lastFastScrollerSectionInfo.fastScrollToItem == null) { lastFastScrollerSectionInfo.fastScrollToItem = appItem; } + if (FeatureFlags.ENABLE_DEVICE_SEARCH.get()) { + appItem.searchSectionInfo = appSection; + } mAdapterItems.add(appItem); mFilteredApps.add(info); } + appSection.setPosEnd(mApps.isEmpty() ? appSection.getPosStart() : position - 1); } else { - mAdapterItems.addAll(mSearchResults); - List appInfos = mSearchResults.stream().filter( - i -> AllAppsGridAdapter.isIconViewType(i.viewType)).map(i -> i.appInfo).collect( - Collectors.toList()); + List appInfos = new ArrayList<>(); + SearchSectionInfo lastSection = null; + for (int i = 0; i < mSearchResults.size(); i++) { + AdapterItem adapterItem = mSearchResults.get(i); + adapterItem.position = i; + mAdapterItems.add(adapterItem); + if (adapterItem.searchSectionInfo != lastSection) { + adapterItem.searchSectionInfo.setPosStart(i); + if (lastSection != null) { + lastSection.setPosEnd(i - 1); + } + lastSection = adapterItem.searchSectionInfo; + } + if (AllAppsGridAdapter.isIconViewType(adapterItem.viewType)) { + appInfos.add(adapterItem.appInfo); + } + } mFilteredApps.addAll(appInfos); if (!FeatureFlags.ENABLE_DEVICE_SEARCH.get()) { // Append the search market item diff --git a/src/com/android/launcher3/allapps/search/AppsSearchPipeline.java b/src/com/android/launcher3/allapps/search/AppsSearchPipeline.java index 5beb956079..fb786513ac 100644 --- a/src/com/android/launcher3/allapps/search/AppsSearchPipeline.java +++ b/src/com/android/launcher3/allapps/search/AppsSearchPipeline.java @@ -19,6 +19,7 @@ import androidx.annotation.WorkerThread; import com.android.launcher3.LauncherAppState; import com.android.launcher3.R; +import com.android.launcher3.allapps.AllAppsSectionDecorator.SectionDecorationHandler; import com.android.launcher3.allapps.AlphabeticalAppsList.AdapterItem; import com.android.launcher3.model.AllAppsList; import com.android.launcher3.model.BaseModelUpdateTask; @@ -36,12 +37,21 @@ public class AppsSearchPipeline implements SearchPipeline { private static final int MAX_RESULTS_COUNT = 5; - private final SearchSectionInfo mSearchSectionInfo; + private static final int SECTION_TYPE_HEADER = 0; + private static final int SECTION_TYPE_APPS = 1; + + private final SearchSectionInfo[] mSearchSectionInfos; private final LauncherAppState mLauncherAppState; + public AppsSearchPipeline(LauncherAppState launcherAppState) { mLauncherAppState = launcherAppState; - mSearchSectionInfo = new SearchSectionInfo(R.string.search_corpus_apps); + mSearchSectionInfos = new SearchSectionInfo[]{ + new SearchSectionInfo(R.string.search_corpus_apps), + new SearchSectionInfo() + }; + mSearchSectionInfos[SECTION_TYPE_APPS].setDecorationHandler( + new SectionDecorationHandler(launcherAppState.getContext(), true)); } @Override @@ -78,12 +88,12 @@ public class AppsSearchPipeline implements SearchPipeline { if (matchingApps.isEmpty()) { return items; } - items.add(AdapterItem.asSearchTitle(mSearchSectionInfo, 0)); + items.add(AdapterItem.asSearchTitle(mSearchSectionInfos[SECTION_TYPE_HEADER], 0)); int existingItems = items.size(); int searchResultsCount = Math.min(matchingApps.size(), MAX_RESULTS_COUNT); for (int i = 0; i < searchResultsCount; i++) { AdapterItem appItem = AdapterItem.asApp(i + existingItems, "", matchingApps.get(i), i); - appItem.searchSectionInfo = mSearchSectionInfo; + appItem.searchSectionInfo = mSearchSectionInfos[SECTION_TYPE_APPS]; items.add(appItem); } diff --git a/src/com/android/launcher3/allapps/search/SearchSectionInfo.java b/src/com/android/launcher3/allapps/search/SearchSectionInfo.java index 880b2466dc..dee0ffd2df 100644 --- a/src/com/android/launcher3/allapps/search/SearchSectionInfo.java +++ b/src/com/android/launcher3/allapps/search/SearchSectionInfo.java @@ -17,20 +17,59 @@ package com.android.launcher3.allapps.search; import android.content.Context; +import com.android.launcher3.allapps.AllAppsSectionDecorator.SectionDecorationHandler; + /** * Info class for a search section */ public class SearchSectionInfo { + private final int mTitleResId; + private SectionDecorationHandler mDecorationHandler; + + public int getPosStart() { + return mPosStart; + } + + public void setPosStart(int posStart) { + mPosStart = posStart; + } + + public int getPosEnd() { + return mPosEnd; + } + + public void setPosEnd(int posEnd) { + mPosEnd = posEnd; + } + + private int mPosStart; + private int mPosEnd; + + public SearchSectionInfo() { + this(-1); + } public SearchSectionInfo(int titleResId) { mTitleResId = titleResId; } + public void setDecorationHandler(SectionDecorationHandler sectionDecorationHandler) { + mDecorationHandler = sectionDecorationHandler; + } + + + public SectionDecorationHandler getDecorationHandler() { + return mDecorationHandler; + } + /** * Returns the section's title */ public String getTitle(Context context) { + if (mTitleResId == -1) { + return ""; + } return context.getString(mTitleResId); } }