From 4a53c70c5734f65d210c1b425549224acbe7c642 Mon Sep 17 00:00:00 2001 From: Samuel Fufa Date: Tue, 4 Aug 2020 14:06:55 -0700 Subject: [PATCH] Introduce support for Hero app Section [Video attached to bug report] Bug: 162871508 Test: Manual Change-Id: Ia6f5621d6220f55e6fd5e56530853c267838442c --- res/layout/search_result_hero_app.xml | 74 ++++++++ res/values/attrs.xml | 1 + res/values/colors.xml | 2 - src/com/android/launcher3/BubbleTextView.java | 3 + .../launcher3/allapps/AllAppsGridAdapter.java | 16 +- .../allapps/AlphabeticalAppsList.java | 26 ++- .../allapps/search/AppsSearchPipeline.java | 86 +++++++--- .../search/DefaultAppSearchAlgorithm.java | 2 +- .../launcher3/views/HeroSearchResultView.java | 158 ++++++++++++++++++ 9 files changed, 342 insertions(+), 26 deletions(-) create mode 100644 res/layout/search_result_hero_app.xml create mode 100644 src/com/android/launcher3/views/HeroSearchResultView.java diff --git a/res/layout/search_result_hero_app.xml b/res/layout/search_result_hero_app.xml new file mode 100644 index 0000000000..bd0e42b6a9 --- /dev/null +++ b/res/layout/search_result_hero_app.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/values/attrs.xml b/res/values/attrs.xml index 26e6cbad0c..acb8221d7c 100644 --- a/res/values/attrs.xml +++ b/res/values/attrs.xml @@ -57,6 +57,7 @@ + diff --git a/res/values/colors.xml b/res/values/colors.xml index ad607a3cb6..9a89dee956 100644 --- a/res/values/colors.xml +++ b/res/values/colors.xml @@ -35,8 +35,6 @@ #E0E0E0 - #E5E5E5 - #9AA0A6 #327d7d7d #A0C2F9 diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java index 198f13d02a..55d5de758a 100644 --- a/src/com/android/launcher3/BubbleTextView.java +++ b/src/com/android/launcher3/BubbleTextView.java @@ -77,6 +77,7 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, private static final int DISPLAY_WORKSPACE = 0; private static final int DISPLAY_ALL_APPS = 1; private static final int DISPLAY_FOLDER = 2; + private static final int DISPLAY_HERO_APP = 5; private static final int[] STATE_PRESSED = new int[] {android.R.attr.state_pressed}; @@ -178,6 +179,8 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, setTextSize(TypedValue.COMPLEX_UNIT_PX, grid.folderChildTextSizePx); setCompoundDrawablePadding(grid.folderChildDrawablePaddingPx); defaultIconSize = grid.folderChildIconSizePx; + } else if (mDisplay == DISPLAY_HERO_APP) { + defaultIconSize = grid.allAppsIconSizePx; } else { // widget_selection or shortcut_popup defaultIconSize = grid.iconSizePx; diff --git a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java index dec92df2d3..2cec797fd0 100644 --- a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java +++ b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java @@ -43,6 +43,7 @@ import com.android.launcher3.R; import com.android.launcher3.allapps.AlphabeticalAppsList.AdapterItem; import com.android.launcher3.model.data.AppInfo; import com.android.launcher3.util.PackageManagerHelper; +import com.android.launcher3.views.HeroSearchResultView; import java.util.List; @@ -66,7 +67,9 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter mShortcutInfos; + + public HeroAppAdapterItem(AppInfo info, ArrayList shortcutInfos) { + viewType = AllAppsGridAdapter.VIEW_TYPE_SEARCH_HERO_APP; + mShortcutInfos = shortcutInfos; + appInfo = info; + } + + /** + * Returns list of shortcuts for appInfo + */ + public ArrayList getShortcutInfos() { + return mShortcutInfos; + } + + } + + private final BaseDraggingActivity mLauncher; // The set of apps from the system diff --git a/src/com/android/launcher3/allapps/search/AppsSearchPipeline.java b/src/com/android/launcher3/allapps/search/AppsSearchPipeline.java index fb786513ac..e67e8972e7 100644 --- a/src/com/android/launcher3/allapps/search/AppsSearchPipeline.java +++ b/src/com/android/launcher3/allapps/search/AppsSearchPipeline.java @@ -15,16 +15,25 @@ */ package com.android.launcher3.allapps.search; +import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_SHORTCUTS; + +import android.content.Context; +import android.content.pm.ShortcutInfo; + 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.allapps.AlphabeticalAppsList.HeroAppAdapterItem; +import com.android.launcher3.icons.IconCache; import com.android.launcher3.model.AllAppsList; import com.android.launcher3.model.BaseModelUpdateTask; import com.android.launcher3.model.BgDataModel; import com.android.launcher3.model.data.AppInfo; +import com.android.launcher3.model.data.WorkspaceItemInfo; +import com.android.launcher3.popup.PopupPopulator; +import com.android.launcher3.shortcuts.ShortcutRequest; import java.util.ArrayList; import java.util.List; @@ -36,22 +45,23 @@ import java.util.function.Consumer; public class AppsSearchPipeline implements SearchPipeline { private static final int MAX_RESULTS_COUNT = 5; + private static final int MAX_HERO_SECTION_COUNT = 2; + private static final int MAX_SHORTCUTS_COUNT = 2; - private static final int SECTION_TYPE_HEADER = 0; - private static final int SECTION_TYPE_APPS = 1; - - private final SearchSectionInfo[] mSearchSectionInfos; + private final SearchSectionInfo mSearchSectionInfo; private final LauncherAppState mLauncherAppState; - + private final boolean mHeroSectionSupported; public AppsSearchPipeline(LauncherAppState launcherAppState) { + this(launcherAppState, true); + } + + public AppsSearchPipeline(LauncherAppState launcherAppState, boolean supportsHeroView) { mLauncherAppState = launcherAppState; - mSearchSectionInfos = new SearchSectionInfo[]{ - new SearchSectionInfo(R.string.search_corpus_apps), - new SearchSectionInfo() - }; - mSearchSectionInfos[SECTION_TYPE_APPS].setDecorationHandler( + mSearchSectionInfo = new SearchSectionInfo(); + mSearchSectionInfo.setDecorationHandler( new SectionDecorationHandler(launcherAppState.getContext(), true)); + mHeroSectionSupported = supportsHeroView; } @Override @@ -60,11 +70,38 @@ public class AppsSearchPipeline implements SearchPipeline { mLauncherAppState.getModel().enqueueModelUpdateTask(new BaseModelUpdateTask() { @Override public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) { - callback.accept(getAdapterItems(getTitleMatchResult(apps.data, query))); + List matchingResults = getTitleMatchResult(apps.data, query); + if (mHeroSectionSupported && matchingResults.size() <= MAX_HERO_SECTION_COUNT) { + callback.accept(getHeroAdapterItems(app.getContext(), matchingResults)); + } else { + callback.accept(getAdapterItems(matchingResults)); + } } }); } + /** + * Returns MAX_SHORTCUTS_COUNT shortcuts from local cache + * TODO: Shortcuts should be ranked based on relevancy + */ + private ArrayList getShortcutInfos(Context context, AppInfo appInfo) { + List shortcuts = new ShortcutRequest(context, appInfo.user) + .withContainer(appInfo.getTargetComponent()) + .query(ShortcutRequest.PUBLISHED); + shortcuts = PopupPopulator.sortAndFilterShortcuts(shortcuts, null); + IconCache cache = LauncherAppState.getInstance(context).getIconCache(); + ArrayList shortcutItems = new ArrayList<>(); + for (int i = 0; i < shortcuts.size() && i < MAX_SHORTCUTS_COUNT; i++) { + final ShortcutInfo shortcut = shortcuts.get(i); + final WorkspaceItemInfo si = new WorkspaceItemInfo(shortcut, context); + cache.getUnbadgedShortcutIcon(si, shortcut); + si.rank = i; + si.container = CONTAINER_SHORTCUTS; + shortcutItems.add(si); + } + return shortcutItems; + } + /** * Filters {@link AppInfo}s matching specified query */ @@ -83,17 +120,24 @@ public class AppsSearchPipeline implements SearchPipeline { return result; } + private ArrayList getHeroAdapterItems(Context context, List apps) { + ArrayList adapterItems = new ArrayList<>(); + for (int i = 0; i < apps.size(); i++) { + //hero app + AppInfo appInfo = apps.get(i); + ArrayList shortcuts = getShortcutInfos(context, appInfo); + AdapterItem adapterItem = new HeroAppAdapterItem(appInfo, shortcuts); + adapterItem.searchSectionInfo = mSearchSectionInfo; + adapterItems.add(adapterItem); + } + return adapterItems; + } + private ArrayList getAdapterItems(List matchingApps) { ArrayList items = new ArrayList<>(); - if (matchingApps.isEmpty()) { - return items; - } - 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 = mSearchSectionInfos[SECTION_TYPE_APPS]; + for (int i = 0; i < matchingApps.size() && i < MAX_RESULTS_COUNT; i++) { + AdapterItem appItem = AdapterItem.asApp(i, "", matchingApps.get(i), i); + appItem.searchSectionInfo = mSearchSectionInfo; items.add(appItem); } diff --git a/src/com/android/launcher3/allapps/search/DefaultAppSearchAlgorithm.java b/src/com/android/launcher3/allapps/search/DefaultAppSearchAlgorithm.java index db10311675..470191c3fc 100644 --- a/src/com/android/launcher3/allapps/search/DefaultAppSearchAlgorithm.java +++ b/src/com/android/launcher3/allapps/search/DefaultAppSearchAlgorithm.java @@ -32,7 +32,7 @@ public class DefaultAppSearchAlgorithm implements SearchAlgorithm { public DefaultAppSearchAlgorithm(LauncherAppState launcherAppState) { mResultHandler = new Handler(); - mAppsSearchPipeline = new AppsSearchPipeline(launcherAppState); + mAppsSearchPipeline = new AppsSearchPipeline(launcherAppState, false); } @Override diff --git a/src/com/android/launcher3/views/HeroSearchResultView.java b/src/com/android/launcher3/views/HeroSearchResultView.java new file mode 100644 index 0000000000..c2a02bcdab --- /dev/null +++ b/src/com/android/launcher3/views/HeroSearchResultView.java @@ -0,0 +1,158 @@ +/* + * 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.views; + +import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_ALL_APPS; + +import android.content.Context; +import android.graphics.Point; +import android.util.AttributeSet; +import android.view.View; +import android.view.ViewGroup; +import android.widget.LinearLayout; + +import com.android.launcher3.BubbleTextView; +import com.android.launcher3.DeviceProfile; +import com.android.launcher3.DragSource; +import com.android.launcher3.DropTarget; +import com.android.launcher3.Launcher; +import com.android.launcher3.R; +import com.android.launcher3.allapps.AlphabeticalAppsList; +import com.android.launcher3.dragndrop.DragOptions; +import com.android.launcher3.dragndrop.DraggableView; +import com.android.launcher3.graphics.DragPreviewProvider; +import com.android.launcher3.model.data.AppInfo; +import com.android.launcher3.model.data.ItemInfo; +import com.android.launcher3.model.data.WorkspaceItemInfo; +import com.android.launcher3.shortcuts.ShortcutDragPreviewProvider; +import com.android.launcher3.touch.ItemLongClickListener; +import com.android.launcher3.userevent.nano.LauncherLogProto; + +import java.util.ArrayList; +import java.util.List; + +/** + * A view representing a high confidence app search result that includes shortcuts + */ +public class HeroSearchResultView extends LinearLayout implements DragSource { + + BubbleTextView mBubbleTextView; + View mIconView; + BubbleTextView[] mDeepShortcutTextViews = new BubbleTextView[2]; + + public HeroSearchResultView(Context context) { + super(context); + } + + public HeroSearchResultView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public HeroSearchResultView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + Launcher launcher = Launcher.getLauncher(getContext()); + DeviceProfile grid = launcher.getDeviceProfile(); + mIconView = findViewById(R.id.icon); + ViewGroup.LayoutParams iconParams = mIconView.getLayoutParams(); + iconParams.height = grid.allAppsIconSizePx; + iconParams.width = grid.allAppsIconSizePx; + + + mBubbleTextView = findViewById(R.id.bubble_text); + mBubbleTextView.setOnClickListener(launcher.getItemOnClickListener()); + mBubbleTextView.setOnLongClickListener(new HeroItemDragHandler(getContext(), this)); + setLayoutParams( + new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, grid.allAppsCellHeightPx)); + + + mDeepShortcutTextViews[0] = findViewById(R.id.shortcut_0); + mDeepShortcutTextViews[1] = findViewById(R.id.shortcut_1); + for (BubbleTextView bubbleTextView : mDeepShortcutTextViews) { + bubbleTextView.setLayoutParams( + new LinearLayout.LayoutParams(grid.allAppsIconSizePx, + grid.allAppsIconSizePx)); + bubbleTextView.setOnClickListener(launcher.getItemOnClickListener()); + } + } + + /** + * Apply {@link ItemInfo} for appIcon and shortcut Icons + */ + public void prepareUsingAdapterItem(AlphabeticalAppsList.HeroAppAdapterItem adapterItem) { + mBubbleTextView.applyFromApplicationInfo(adapterItem.appInfo); + mIconView.setBackground(mBubbleTextView.getIcon()); + mIconView.setTag(adapterItem.appInfo); + List shorcutInfos = adapterItem.getShortcutInfos(); + for (int i = 0; i < mDeepShortcutTextViews.length; i++) { + mDeepShortcutTextViews[i].setVisibility(shorcutInfos.size() > i ? VISIBLE : GONE); + if (i < shorcutInfos.size()) { + mDeepShortcutTextViews[i].applyFromWorkspaceItem(shorcutInfos.get(i)); + } + } + + } + + @Override + public void onDropCompleted(View target, DropTarget.DragObject d, boolean success) { + mBubbleTextView.setVisibility(VISIBLE); + mBubbleTextView.setIconVisible(true); + } + + @Override + public void fillInLogContainerData(ItemInfo childInfo, LauncherLogProto.Target child, + ArrayList parents) { + + } + + private void setWillDrawIcon(boolean willDraw) { + mIconView.setVisibility(willDraw ? View.VISIBLE : View.INVISIBLE); + } + + + /** + * Drag and drop handler for popup items in Launcher activity + */ + public static class HeroItemDragHandler implements OnLongClickListener { + private final Launcher mLauncher; + private final HeroSearchResultView mContainer; + + HeroItemDragHandler(Context context, HeroSearchResultView container) { + mLauncher = Launcher.getLauncher(context); + mContainer = container; + } + + @Override + public boolean onLongClick(View v) { + if (!ItemLongClickListener.canStartDrag(mLauncher)) return false; + mContainer.setWillDrawIcon(false); + + DraggableView draggableView = DraggableView.ofType(DraggableView.DRAGGABLE_ICON); + WorkspaceItemInfo itemInfo = new WorkspaceItemInfo((AppInfo) v.getTag()); + itemInfo.container = CONTAINER_ALL_APPS; + DragPreviewProvider previewProvider = new ShortcutDragPreviewProvider( + mContainer.mIconView, new Point()); + mLauncher.getWorkspace().beginDragShared(mContainer.mBubbleTextView, + draggableView, mContainer, itemInfo, previewProvider, new DragOptions()); + + return false; + } + } +}