Allow search results decoration [part 2/3]
[Video attached to bug report]
Bug: 162480567
Test: Manual
Change-Id: Iff285abde5b2a3f3f3a63e7318020cfe7572af49
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 914d9e9..7dc7bfc 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.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 @@
mLauncher = Launcher.getLauncher(context);
boolean isMainColorDark = Themes.getAttrBoolean(context, R.attr.isMainColorDark);
- mPaint.setStrokeWidth(getResources().getDimensionPixelSize(R.dimen.all_apps_divider_height));
+ mPaint.setStrokeWidth(
+ getResources().getDimensionPixelSize(R.dimen.all_apps_divider_height));
mStrokeColor = ContextCompat.getColor(context, isMainColorDark
? R.color.all_apps_prediction_row_separator_dark
@@ -134,7 +136,7 @@
if (row == this) {
break;
} else if (row.shouldDraw()) {
- sectionCount ++;
+ sectionCount++;
}
}
@@ -181,6 +183,11 @@
}
private void updateViewVisibility() {
+ // hide divider since we have item decoration for prediction row
+ if (FeatureFlags.ENABLE_DEVICE_SEARCH.get()) {
+ setVisibility(GONE);
+ return;
+ }
setVisibility(mDividerType == DividerType.NONE
? GONE
: (mIsScrolledOut ? INVISIBLE : VISIBLE));
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionRowView.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionRowView.java
index 55384af..8a810e3 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionRowView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionRowView.java
@@ -45,10 +45,12 @@
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherState;
import com.android.launcher3.R;
+import com.android.launcher3.allapps.AllAppsSectionDecorator;
import com.android.launcher3.allapps.FloatingHeaderRow;
import com.android.launcher3.allapps.FloatingHeaderView;
import com.android.launcher3.anim.AlphaUpdateListener;
import com.android.launcher3.anim.PropertySetter;
+import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.keyboard.FocusIndicatorHelper;
import com.android.launcher3.keyboard.FocusIndicatorHelper.SimpleFocusIndicatorHelper;
import com.android.launcher3.logging.StatsLogUtils.LogContainerProvider;
@@ -110,6 +112,8 @@
private boolean mPredictionsEnabled = false;
+ AllAppsSectionDecorator.SectionDecorationHandler mDecorationHandler;
+
public PredictionRowView(@NonNull Context context) {
this(context, null);
}
@@ -128,6 +132,11 @@
mIconFullTextAlpha = Color.alpha(mIconTextColor);
mIconCurrentTextAlpha = mIconFullTextAlpha;
+ if (FeatureFlags.ENABLE_DEVICE_SEARCH.get()) {
+ mDecorationHandler = new AllAppsSectionDecorator.SectionDecorationHandler(getContext(),
+ false);
+ }
+
updateVisibility();
}
@@ -153,6 +162,14 @@
@Override
protected void dispatchDraw(Canvas canvas) {
+ if (mDecorationHandler != null) {
+ mDecorationHandler.reset();
+ int childrenCount = getChildCount();
+ for (int i = 0; i < childrenCount; i++) {
+ mDecorationHandler.extendBounds(getChildAt(i));
+ }
+ mDecorationHandler.onDraw(canvas);
+ }
mFocusHelper.draw(canvas);
super.dispatchDraw(canvas);
}
diff --git a/res/layout/search_section_title.xml b/res/layout/search_section_title.xml
index 9ab27c1..c39a641 100644
--- a/res/layout/search_section_title.xml
+++ b/res/layout/search_section_title.xml
@@ -19,7 +19,5 @@
android:fontFamily="@style/TextHeadline"
android:layout_width="wrap_content"
android:textColor="?android:attr/textColorPrimary"
- android:layout_marginLeft="16dp"
- android:layout_marginRight="16dp"
- android:paddingTop="8dp"
+ android:padding="4dp"
android:layout_height="wrap_content"/>
\ No newline at end of file
diff --git a/res/values/colors.xml b/res/values/colors.xml
index f56fbaa..ad607a3 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -35,6 +35,10 @@
<color name="icon_background">#E0E0E0</color> <!-- Gray 300 -->
+ <color name="all_apps_bg_hand_fill">#E5E5E5</color>
+ <color name="all_apps_bg_hand_fill_dark">#9AA0A6</color>
+ <color name="all_apps_section_fill">#327d7d7d</color>
+
<color name="gesture_tutorial_ripple_color">#A0C2F9</color> <!-- Light Blue -->
<color name="gesture_tutorial_fake_task_view_color">#6DA1FF</color> <!-- Light Blue -->
<color name="gesture_tutorial_action_button_label_color">#FFFFFFFF</color>
diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java
index 77b8a32..2d711e6 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.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 @@
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 @@
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 @@
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 0000000..ac55072
--- /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<AlphabeticalAppsList.AdapterItem> 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 266bfa2..5d9c554 100644
--- a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
+++ b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
@@ -31,7 +31,6 @@
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 @@
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 @@
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<AppInfo> appInfos = mSearchResults.stream().filter(
- i -> AllAppsGridAdapter.isIconViewType(i.viewType)).map(i -> i.appInfo).collect(
- Collectors.toList());
+ List<AppInfo> 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 5beb956..fb78651 100644
--- a/src/com/android/launcher3/allapps/search/AppsSearchPipeline.java
+++ b/src/com/android/launcher3/allapps/search/AppsSearchPipeline.java
@@ -19,6 +19,7 @@
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 @@
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 @@
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 880b246..dee0ffd 100644
--- a/src/com/android/launcher3/allapps/search/SearchSectionInfo.java
+++ b/src/com/android/launcher3/allapps/search/SearchSectionInfo.java
@@ -17,20 +17,59 @@
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);
}
}