From 6e5efb0929fac4f914efa2ceaaa6a7ef2fa4cf71 Mon Sep 17 00:00:00 2001 From: Samuel Fufa Date: Mon, 2 Nov 2020 11:31:59 -0600 Subject: [PATCH] Consolidate Hero search result with SearchResultIconRow With this, we can now show app title and support drag/drop for shortcut results. Bug: 172245107 preview: https://drive.google.com/file/d/1A4eKKTDPht-MDbfA2VFI3OuAO36fc3AS/view?usp=sharing Change-Id: Icf94a2d23b44bfe5527aea71e27178906e5deb3e --- res/layout/search_result_hero_app.xml | 74 ------ res/layout/search_result_icon_row.xml | 67 ++++- res/layout/search_result_suggest.xml | 9 +- res/values/attrs.xml | 14 +- res/values/dimens.xml | 5 + .../launcher3/allapps/AllAppsGridAdapter.java | 7 - .../launcher3/touch/ItemClickHandler.java | 2 + .../launcher3/views/HeroSearchResultView.java | 209 --------------- .../launcher3/views/SearchResultIcon.java | 134 +++++++++- .../launcher3/views/SearchResultIconRow.java | 249 ++++++++---------- .../views/SearchResultSuggestion.java | 62 +++++ .../views/ThumbnailSearchResultView.java | 5 +- 12 files changed, 378 insertions(+), 459 deletions(-) delete mode 100644 res/layout/search_result_hero_app.xml delete mode 100644 src/com/android/launcher3/views/HeroSearchResultView.java create mode 100644 src/com/android/launcher3/views/SearchResultSuggestion.java diff --git a/res/layout/search_result_hero_app.xml b/res/layout/search_result_hero_app.xml deleted file mode 100644 index bd0e42b6a9..0000000000 --- a/res/layout/search_result_hero_app.xml +++ /dev/null @@ -1,74 +0,0 @@ - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/res/layout/search_result_icon_row.xml b/res/layout/search_result_icon_row.xml index ef3c8b2857..b51896e8a7 100644 --- a/res/layout/search_result_icon_row.xml +++ b/res/layout/search_result_icon_row.xml @@ -1,4 +1,4 @@ - + android:padding="@dimen/dynamic_grid_edge_margin"> + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/layout/search_result_suggest.xml b/res/layout/search_result_suggest.xml index 1d8c8039e6..01e25d5893 100644 --- a/res/layout/search_result_suggest.xml +++ b/res/layout/search_result_suggest.xml @@ -12,7 +12,7 @@ See the License for the specific language governing permissions and limitations under the License. --> - + android:drawablePadding="@dimen/dynamic_grid_icon_drawable_padding"> - \ No newline at end of file + \ No newline at end of file diff --git a/res/values/attrs.xml b/res/values/attrs.xml index c3ad1eb823..96c30b5f03 100644 --- a/res/values/attrs.xml +++ b/res/values/attrs.xml @@ -1,5 +1,4 @@ - - 24dp + + 16sp + 15sp + 12sp + diff --git a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java index f7731915e3..7018f20a55 100644 --- a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java +++ b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java @@ -75,8 +75,6 @@ public class AllAppsGridAdapter extends public static final int VIEW_TYPE_SEARCH_CORPUS_TITLE = 1 << 5; - public static final int VIEW_TYPE_SEARCH_HERO_APP = 1 << 6; - public static final int VIEW_TYPE_SEARCH_ROW_WITH_BUTTON = 1 << 7; public static final int VIEW_TYPE_SEARCH_ROW = 1 << 8; @@ -178,7 +176,6 @@ public class AllAppsGridAdapter extends boolean isCountedForAccessibility() { return viewType == VIEW_TYPE_ICON - || viewType == VIEW_TYPE_SEARCH_HERO_APP || viewType == VIEW_TYPE_SEARCH_ROW_WITH_BUTTON || viewType == VIEW_TYPE_SEARCH_SLICE || viewType == VIEW_TYPE_SEARCH_ROW @@ -411,9 +408,6 @@ public class AllAppsGridAdapter extends case VIEW_TYPE_SEARCH_CORPUS_TITLE: return new ViewHolder( mLayoutInflater.inflate(R.layout.search_section_title, parent, false)); - case VIEW_TYPE_SEARCH_HERO_APP: - return new ViewHolder(mLayoutInflater.inflate( - R.layout.search_result_hero_app, parent, false)); case VIEW_TYPE_SEARCH_ROW_WITH_BUTTON: return new ViewHolder(mLayoutInflater.inflate( R.layout.search_result_play_item, parent, false)); @@ -478,7 +472,6 @@ public class AllAppsGridAdapter extends break; case VIEW_TYPE_SEARCH_CORPUS_TITLE: case VIEW_TYPE_SEARCH_ROW_WITH_BUTTON: - case VIEW_TYPE_SEARCH_HERO_APP: case VIEW_TYPE_SEARCH_ROW: case VIEW_TYPE_SEARCH_ICON: case VIEW_TYPE_SEARCH_ICON_ROW: diff --git a/src/com/android/launcher3/touch/ItemClickHandler.java b/src/com/android/launcher3/touch/ItemClickHandler.java index d56391da3d..9b9cb0a61b 100644 --- a/src/com/android/launcher3/touch/ItemClickHandler.java +++ b/src/com/android/launcher3/touch/ItemClickHandler.java @@ -96,6 +96,8 @@ public class ItemClickHandler { if (v instanceof PendingAppWidgetHostView) { onClickPendingWidget((PendingAppWidgetHostView) v, launcher); } + } else if (tag instanceof RemoteActionItemInfo) { + onClickRemoteAction(launcher, (RemoteActionItemInfo) tag); } } diff --git a/src/com/android/launcher3/views/HeroSearchResultView.java b/src/com/android/launcher3/views/HeroSearchResultView.java deleted file mode 100644 index a098df9f31..0000000000 --- a/src/com/android/launcher3/views/HeroSearchResultView.java +++ /dev/null @@ -1,209 +0,0 @@ -/* - * 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 static com.android.launcher3.util.Executors.MAIN_EXECUTOR; -import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; - -import android.content.Context; -import android.content.pm.ShortcutInfo; -import android.graphics.Point; -import android.util.AttributeSet; -import android.util.Pair; -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.LauncherAppState; -import com.android.launcher3.R; -import com.android.launcher3.allapps.AllAppsStore; -import com.android.launcher3.allapps.search.AllAppsSearchBarController.SearchTargetHandler; -import com.android.launcher3.allapps.search.SearchEventTracker; -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.ItemInfoWithIcon; -import com.android.launcher3.model.data.WorkspaceItemInfo; -import com.android.launcher3.shortcuts.ShortcutDragPreviewProvider; -import com.android.launcher3.touch.ItemLongClickListener; -import com.android.launcher3.util.ComponentKey; -import com.android.systemui.plugins.shared.SearchTarget; -import com.android.systemui.plugins.shared.SearchTargetEvent; - -import java.util.ArrayList; -import java.util.List; - -/** - * A view representing a high confidence app search result that includes shortcuts - * TODO (sfufa@) consolidate this with SearchResultIconRow - */ -public class HeroSearchResultView extends LinearLayout implements DragSource, SearchTargetHandler { - - public static final String TARGET_TYPE_HERO_APP = "hero_app"; - - public static final int MAX_SHORTCUTS_COUNT = 2; - - private SearchTarget mSearchTarget; - private BubbleTextView mBubbleTextView; - private View mIconView; - private 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(view -> { - handleSelection(SearchTargetEvent.SELECT); - launcher.getItemOnClickListener().onClick(view); - }); - mBubbleTextView.setOnLongClickListener(new HeroItemDragHandler(getContext(), this)); - - - 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(view -> { - WorkspaceItemInfo itemInfo = (WorkspaceItemInfo) bubbleTextView.getTag(); - SearchTargetEvent event = new SearchTargetEvent.Builder(mSearchTarget, - SearchTargetEvent.CHILD_SELECT).setShortcutPosition(itemInfo.rank).build(); - SearchEventTracker.getInstance(getContext()).notifySearchTargetEvent(event); - launcher.getItemOnClickListener().onClick(view); - }); - } - } - - @Override - public void applySearchTarget(SearchTarget searchTarget) { - mSearchTarget = searchTarget; - AllAppsStore apps = Launcher.getLauncher(getContext()).getAppsView().getAppsStore(); - AppInfo appInfo = apps.getApp(new ComponentKey(searchTarget.getComponentName(), - searchTarget.getUserHandle())); - List infos = mSearchTarget.getShortcutInfos(); - - ArrayList> shortcuts = new ArrayList<>(); - for (int i = 0; infos != null && i < infos.size() && i < MAX_SHORTCUTS_COUNT; i++) { - ShortcutInfo shortcutInfo = infos.get(i); - ItemInfoWithIcon si = new WorkspaceItemInfo(shortcutInfo, getContext()); - si.rank = i; - shortcuts.add(new Pair<>(shortcutInfo, si)); - } - - mBubbleTextView.applyFromApplicationInfo(appInfo); - mIconView.setBackground(mBubbleTextView.getIcon()); - mIconView.setTag(appInfo); - LauncherAppState appState = LauncherAppState.getInstance(getContext()); - for (int i = 0; i < mDeepShortcutTextViews.length; i++) { - BubbleTextView shortcutView = mDeepShortcutTextViews[i]; - mDeepShortcutTextViews[i].setVisibility(shortcuts.size() > i ? VISIBLE : GONE); - if (i < shortcuts.size()) { - Pair p = shortcuts.get(i); - //apply ItemInfo and prepare view - shortcutView.applyFromWorkspaceItem((WorkspaceItemInfo) p.second); - MODEL_EXECUTOR.execute(() -> { - // load unbadged shortcut in background and update view when icon ready - appState.getIconCache().getUnbadgedShortcutIcon(p.second, p.first); - MAIN_EXECUTOR.post(() -> shortcutView.reapplyItemInfo(p.second)); - }); - } - } - SearchEventTracker.INSTANCE.get(getContext()).registerWeakHandler(searchTarget, this); - } - - @Override - public void onDropCompleted(View target, DropTarget.DragObject d, boolean success) { - mBubbleTextView.setVisibility(VISIBLE); - mBubbleTextView.setIconVisible(true); - } - - 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()); - - SearchTargetEvent event = new SearchTargetEvent.Builder(mContainer.mSearchTarget, - SearchTargetEvent.LONG_PRESS).build(); - SearchEventTracker.INSTANCE.get(mLauncher).notifySearchTargetEvent(event); - return false; - } - } - - @Override - public void handleSelection(int eventType) { - ItemInfo itemInfo = (ItemInfo) mBubbleTextView.getTag(); - if (itemInfo == null) return; - Launcher launcher = Launcher.getLauncher(getContext()); - launcher.startActivitySafely(this, itemInfo.getIntent(), itemInfo); - - SearchEventTracker.INSTANCE.get(getContext()).notifySearchTargetEvent( - new SearchTargetEvent.Builder(mSearchTarget, eventType).build()); - } -} diff --git a/src/com/android/launcher3/views/SearchResultIcon.java b/src/com/android/launcher3/views/SearchResultIcon.java index ea06d5b20f..0851b5432f 100644 --- a/src/com/android/launcher3/views/SearchResultIcon.java +++ b/src/com/android/launcher3/views/SearchResultIcon.java @@ -15,16 +15,35 @@ */ package com.android.launcher3.views; +import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; +import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; +import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; + +import android.app.RemoteAction; +import android.content.ComponentName; import android.content.Context; +import android.content.pm.ShortcutInfo; +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.os.UserHandle; import android.util.AttributeSet; import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.UiThread; import com.android.launcher3.BubbleTextView; import com.android.launcher3.Launcher; +import com.android.launcher3.LauncherAppState; import com.android.launcher3.allapps.AllAppsStore; import com.android.launcher3.allapps.search.AllAppsSearchBarController; import com.android.launcher3.allapps.search.SearchEventTracker; +import com.android.launcher3.icons.BitmapInfo; +import com.android.launcher3.icons.LauncherIcons; import com.android.launcher3.model.data.AppInfo; +import com.android.launcher3.model.data.ItemInfoWithIcon; +import com.android.launcher3.model.data.RemoteActionItemInfo; +import com.android.launcher3.model.data.WorkspaceItemInfo; import com.android.launcher3.touch.ItemLongClickListener; import com.android.launcher3.util.ComponentKey; import com.android.systemui.plugins.shared.SearchTarget; @@ -39,6 +58,17 @@ public class SearchResultIcon extends BubbleTextView implements public static final String TARGET_TYPE_APP = "app"; + public static final String TARGET_TYPE_HERO_APP = "hero_app"; + public static final String TARGET_TYPE_SHORTCUT = "shortcut"; + public static final String TARGET_TYPE_REMOTE_ACTION = "remote_action"; + public static final String TARGET_TYPE_SUGGEST = "suggest"; + + public static final String REMOTE_ACTION_SHOULD_START = "should_start_for_result"; + public static final String REMOTE_ACTION_TOKEN = "action_token"; + + + private static final String[] LONG_PRESS_SUPPORTED_TYPES = + new String[]{TARGET_TYPE_APP, TARGET_TYPE_SHORTCUT, TARGET_TYPE_HERO_APP}; private final Launcher mLauncher; @@ -64,26 +94,96 @@ public class SearchResultIcon extends BubbleTextView implements setOnFocusChangeListener(mLauncher.getFocusHandler()); setOnClickListener(this); setOnLongClickListener(this); - getLayoutParams().height = mLauncher.getDeviceProfile().allAppsCellHeightPx; + setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, + mLauncher.getDeviceProfile().allAppsCellHeightPx)); } @Override public void applySearchTarget(SearchTarget searchTarget) { mSearchTarget = searchTarget; - AllAppsStore appsStore = mLauncher.getAppsView().getAppsStore(); SearchEventTracker.getInstance(getContext()).registerWeakHandler(mSearchTarget, this); - if (searchTarget.getItemType().equals(TARGET_TYPE_APP)) { - AppInfo appInfo = appsStore.getApp(new ComponentKey(searchTarget.getComponentName(), - searchTarget.getUserHandle())); - applyFromApplicationInfo(appInfo); + switch (searchTarget.getItemType()) { + case TARGET_TYPE_APP: + case TARGET_TYPE_HERO_APP: + prepareUsingApp(searchTarget.getComponentName(), searchTarget.getUserHandle()); + break; + case TARGET_TYPE_SHORTCUT: + prepareUsingShortcutInfo(searchTarget.getShortcutInfos().get(0)); + break; + case TARGET_TYPE_REMOTE_ACTION: + case TARGET_TYPE_SUGGEST: + prepareUsingRemoteAction(searchTarget.getRemoteAction(), + searchTarget.getExtras().getString(REMOTE_ACTION_TOKEN), + searchTarget.getExtras().getBoolean(REMOTE_ACTION_SHOULD_START), + searchTarget.getItemType().equals(TARGET_TYPE_REMOTE_ACTION)); + break; } } + private void prepareUsingApp(ComponentName componentName, UserHandle userHandle) { + AllAppsStore appsStore = mLauncher.getAppsView().getAppsStore(); + AppInfo appInfo = appsStore.getApp(new ComponentKey(componentName, userHandle)); + applyFromApplicationInfo(appInfo); + } + + + private void prepareUsingShortcutInfo(ShortcutInfo shortcutInfo) { + WorkspaceItemInfo workspaceItemInfo = new WorkspaceItemInfo(shortcutInfo, getContext()); + applyFromWorkspaceItem(workspaceItemInfo); + LauncherAppState launcherAppState = LauncherAppState.getInstance(getContext()); + MODEL_EXECUTOR.execute(() -> { + launcherAppState.getIconCache().getShortcutIcon(workspaceItemInfo, shortcutInfo); + reapplyItemInfoAsync(workspaceItemInfo); + }); + } + + private void prepareUsingRemoteAction(RemoteAction remoteAction, String token, boolean start, + boolean useIconToBadge) { + RemoteActionItemInfo itemInfo = new RemoteActionItemInfo(remoteAction, token, start); + + applyFromRemoteActionInfo(itemInfo); + if (!loadIconFromResource()) { + UI_HELPER_EXECUTOR.post(() -> { + // If the Drawable from the remote action is not AdaptiveBitmap, styling will not + // work. + try (LauncherIcons li = LauncherIcons.obtain(getContext())) { + Drawable d = itemInfo.getRemoteAction().getIcon().loadDrawable(getContext()); + BitmapInfo bitmap = li.createBadgedIconBitmap(d, itemInfo.user, + Build.VERSION.SDK_INT); + + if (useIconToBadge) { + BitmapInfo placeholder = li.createIconBitmap( + itemInfo.getRemoteAction().getTitle().toString().substring(0, 1), + bitmap.color); + itemInfo.bitmap = li.badgeBitmap(placeholder.icon, bitmap); + } else { + itemInfo.bitmap = bitmap; + } + reapplyItemInfoAsync(itemInfo); + } + }); + } + } + + @UiThread + void reapplyItemInfoAsync(ItemInfoWithIcon itemInfoWithIcon) { + MAIN_EXECUTOR.post(() -> reapplyItemInfo(itemInfoWithIcon)); + } + + @Override public void handleSelection(int eventType) { mLauncher.getItemOnClickListener().onClick(this); - SearchEventTracker.INSTANCE.get(mLauncher).notifySearchTargetEvent( - new SearchTargetEvent.Builder(mSearchTarget, eventType).build()); + reportEvent(eventType); + } + + private void reportEvent(int eventType) { + SearchTargetEvent.Builder b = new SearchTargetEvent.Builder(mSearchTarget, eventType); + if (mSearchTarget.getItemType().equals(TARGET_TYPE_SHORTCUT)) { + b.setShortcutPosition(0); + } + SearchEventTracker.INSTANCE.get(mLauncher).notifySearchTargetEvent(b.build()); + } @Override @@ -93,8 +193,22 @@ public class SearchResultIcon extends BubbleTextView implements @Override public boolean onLongClick(View view) { - SearchEventTracker.INSTANCE.get(mLauncher).notifySearchTargetEvent( - new SearchTargetEvent.Builder(mSearchTarget, SearchTargetEvent.LONG_PRESS).build()); + if (!supportsLongPress(mSearchTarget.getItemType())) { + return false; + } + reportEvent(SearchTargetEvent.LONG_PRESS); return ItemLongClickListener.INSTANCE_ALL_APPS.onLongClick(view); + + } + + private boolean supportsLongPress(String type) { + for (String t : LONG_PRESS_SUPPORTED_TYPES) { + if (t.equals(type)) return true; + } + return false; + } + + protected boolean loadIconFromResource() { + return false; } } diff --git a/src/com/android/launcher3/views/SearchResultIconRow.java b/src/com/android/launcher3/views/SearchResultIconRow.java index bdbe890e8d..03332c1501 100644 --- a/src/com/android/launcher3/views/SearchResultIconRow.java +++ b/src/com/android/launcher3/views/SearchResultIconRow.java @@ -17,188 +17,173 @@ package com.android.launcher3.views; import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; -import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; -import android.app.RemoteAction; +import android.content.ComponentName; import android.content.Context; import android.content.pm.ShortcutInfo; -import android.content.res.TypedArray; -import android.graphics.Canvas; -import android.graphics.drawable.Drawable; -import android.os.Build; +import android.os.UserHandle; import android.util.AttributeSet; -import android.widget.EditText; +import android.util.Pair; +import android.view.View; +import android.view.ViewGroup; +import android.widget.LinearLayout; +import android.widget.TextView; -import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import com.android.launcher3.BubbleTextView; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherAppState; import com.android.launcher3.R; import com.android.launcher3.allapps.search.AllAppsSearchBarController; import com.android.launcher3.allapps.search.SearchEventTracker; -import com.android.launcher3.icons.BitmapInfo; -import com.android.launcher3.icons.LauncherIcons; -import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.model.data.ItemInfoWithIcon; -import com.android.launcher3.model.data.RemoteActionItemInfo; +import com.android.launcher3.model.data.PackageItemInfo; import com.android.launcher3.model.data.WorkspaceItemInfo; -import com.android.launcher3.touch.ItemClickHandler; -import com.android.launcher3.util.Themes; import com.android.systemui.plugins.shared.SearchTarget; import com.android.systemui.plugins.shared.SearchTargetEvent; +import java.util.ArrayList; +import java.util.List; + /** - * A view representing a stand alone shortcut search result + * A full width representation of {@link SearchResultIcon} with a secondary label and inline + * shortcuts */ -public class SearchResultIconRow extends DoubleShadowBubbleTextView implements - AllAppsSearchBarController.SearchTargetHandler { +public class SearchResultIconRow extends LinearLayout implements + AllAppsSearchBarController.SearchTargetHandler, View.OnClickListener, + View.OnLongClickListener { + public static final int MAX_SHORTCUTS_COUNT = 2; - public static final String TARGET_TYPE_REMOTE_ACTION = "remote_action"; - public static final String TARGET_TYPE_SUGGEST = "suggest"; - public static final String TARGET_TYPE_SHORTCUT = "shortcut"; - - - public static final String REMOTE_ACTION_SHOULD_START = "should_start_for_result"; - public static final String REMOTE_ACTION_TOKEN = "action_token"; - - private final boolean mMatchesInset; + private final Launcher mLauncher; + private final LauncherAppState mLauncherAppState; + private SearchResultIcon mResultIcon; + private TextView mTitleView; + private TextView mDescriptionView; + private BubbleTextView[] mShortcutViews = new BubbleTextView[2]; private SearchTarget mSearchTarget; + private PackageItemInfo mProviderInfo; - @Nullable private Drawable mCustomIcon; - public SearchResultIconRow(@NonNull Context context) { + public SearchResultIconRow(Context context) { this(context, null, 0); } - public SearchResultIconRow(@NonNull Context context, + public SearchResultIconRow(Context context, @Nullable AttributeSet attrs) { this(context, attrs, 0); } - public SearchResultIconRow(@NonNull Context context, @Nullable AttributeSet attrs, - int defStyleAttr) { + public SearchResultIconRow(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); - TypedArray a = context.obtainStyledAttributes(attrs, - R.styleable.SearchResultIconRow, defStyleAttr, 0); - mMatchesInset = a.getBoolean(R.styleable.SearchResultIconRow_matchTextInsetWithQuery, - false); - - int customIconResId = a.getResourceId(R.styleable.SearchResultIconRow_customIcon, 0); - - if (customIconResId != 0) { - mCustomIcon = Launcher.getLauncher(context).getDrawable(customIconResId); - } - - a.recycle(); - } - - - @Override - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - Launcher launcher = Launcher.getLauncher(getContext()); - if (mMatchesInset && launcher.getAppsView() != null && getParent() != null) { - EditText editText = launcher.getAppsView().getSearchUiManager().getEditText(); - if (editText != null) { - int counterOffset = getIconSize() + getCompoundDrawablePadding() / 2; - setPadding(editText.getLeft() - counterOffset, getPaddingTop(), - getPaddingRight(), getPaddingBottom()); - } - } + mLauncher = Launcher.getLauncher(getContext()); + mLauncherAppState = LauncherAppState.getInstance(getContext()); } @Override - protected void drawFocusHighlight(Canvas canvas) { - mHighlightPaint.setColor(mHighlightColor); - float r = Themes.getDialogCornerRadius(getContext()); - canvas.drawRoundRect(0, 0, getWidth(), getHeight(), r, r, mHighlightPaint); - } + protected void onFinishInflate() { + super.onFinishInflate(); + int iconSize = mLauncher.getDeviceProfile().allAppsIconSizePx; + mResultIcon = findViewById(R.id.icon); + mTitleView = findViewById(R.id.title); + mDescriptionView = findViewById(R.id.desc); + mShortcutViews[0] = findViewById(R.id.shortcut_0); + mShortcutViews[1] = findViewById(R.id.shortcut_1); + mResultIcon.getLayoutParams().height = iconSize; + mResultIcon.getLayoutParams().width = iconSize; + for (BubbleTextView bubbleTextView : mShortcutViews) { + ViewGroup.LayoutParams lp = bubbleTextView.getLayoutParams(); + lp.width = iconSize; + bubbleTextView.setOnClickListener(view -> { + WorkspaceItemInfo itemInfo = (WorkspaceItemInfo) bubbleTextView.getTag(); + SearchTargetEvent event = new SearchTargetEvent.Builder(mSearchTarget, + SearchTargetEvent.CHILD_SELECT).setShortcutPosition(itemInfo.rank).build(); + SearchEventTracker.getInstance(getContext()).notifySearchTargetEvent(event); + mLauncher.getItemOnClickListener().onClick(view); + }); + } + setOnClickListener(this); + setOnLongClickListener(this); + } @Override public void applySearchTarget(SearchTarget searchTarget) { mSearchTarget = searchTarget; - String type = searchTarget.getItemType(); - if (type.equals(TARGET_TYPE_REMOTE_ACTION) || type.equals(TARGET_TYPE_SUGGEST)) { - prepareUsingRemoteAction(searchTarget.getRemoteAction(), - searchTarget.getExtras().getString(REMOTE_ACTION_TOKEN), - searchTarget.getExtras().getBoolean(REMOTE_ACTION_SHOULD_START), - type.equals(TARGET_TYPE_REMOTE_ACTION)); + mResultIcon.applySearchTarget(searchTarget); + mResultIcon.setTextVisibility(false); + mTitleView.setText(mResultIcon.getText()); + String itemType = searchTarget.getItemType(); + boolean showDesc = itemType.equals(SearchResultIcon.TARGET_TYPE_SHORTCUT); + mDescriptionView.setVisibility(showDesc ? VISIBLE : GONE); - } else if (type.equals(TARGET_TYPE_SHORTCUT)) { - prepareUsingShortcutInfo(searchTarget.getShortcutInfos().get(0)); + if (itemType.equals(SearchResultIcon.TARGET_TYPE_SHORTCUT)) { + ShortcutInfo shortcutInfo = searchTarget.getShortcutInfos().get(0); + setProviderDetails(new ComponentName(shortcutInfo.getPackage(), ""), + shortcutInfo.getUserHandle()); + } else if (itemType.equals(SearchResultIcon.TARGET_TYPE_HERO_APP)) { + showInlineShortcuts(mSearchTarget.getShortcutInfos()); + } + if (!itemType.equals(SearchResultIcon.TARGET_TYPE_HERO_APP)) { + showInlineShortcuts(new ArrayList<>()); } - setOnClickListener(v -> handleSelection(SearchTargetEvent.SELECT)); - SearchEventTracker.INSTANCE.get(getContext()).registerWeakHandler(searchTarget, this); } - private void prepareUsingShortcutInfo(ShortcutInfo shortcutInfo) { - WorkspaceItemInfo workspaceItemInfo = new WorkspaceItemInfo(shortcutInfo, getContext()); - applyFromWorkspaceItem(workspaceItemInfo); - LauncherAppState launcherAppState = LauncherAppState.getInstance(getContext()); - if (!loadIconFromResource()) { - MODEL_EXECUTOR.execute(() -> { - launcherAppState.getIconCache().getShortcutIcon(workspaceItemInfo, shortcutInfo); - reapplyItemInfoAsync(workspaceItemInfo); + private void showInlineShortcuts(List infos) { + if (infos == null) return; + ArrayList> shortcuts = new ArrayList<>(); + for (int i = 0; infos != null && i < infos.size() && i < MAX_SHORTCUTS_COUNT; i++) { + ShortcutInfo shortcutInfo = infos.get(i); + ItemInfoWithIcon si = new WorkspaceItemInfo(shortcutInfo, getContext()); + si.rank = i; + shortcuts.add(new Pair<>(shortcutInfo, si)); + } + + for (int i = 0; i < mShortcutViews.length; i++) { + BubbleTextView shortcutView = mShortcutViews[i]; + mShortcutViews[i].setVisibility(shortcuts.size() > i ? VISIBLE : GONE); + if (i < shortcuts.size()) { + Pair p = shortcuts.get(i); + //apply ItemInfo and prepare view + shortcutView.applyFromWorkspaceItem((WorkspaceItemInfo) p.second); + MODEL_EXECUTOR.execute(() -> { + // load unbadged shortcut in background and update view when icon ready + mLauncherAppState.getIconCache().getUnbadgedShortcutIcon(p.second, p.first); + MAIN_EXECUTOR.post(() -> shortcutView.reapplyItemInfo(p.second)); + }); + } + } + } + + + private void setProviderDetails(ComponentName componentName, UserHandle userHandle) { + PackageItemInfo packageItemInfo = new PackageItemInfo(componentName.getPackageName()); + if (mProviderInfo == packageItemInfo) return; + MODEL_EXECUTOR.post(() -> { + packageItemInfo.user = userHandle; + mLauncherAppState.getIconCache().getTitleAndIconForApp(packageItemInfo, true); + MAIN_EXECUTOR.post(() -> { + mDescriptionView.setText(packageItemInfo.title); + mProviderInfo = packageItemInfo; }); - } - } - - private void prepareUsingRemoteAction(RemoteAction remoteAction, String token, boolean start, - boolean useIconToBadge) { - RemoteActionItemInfo itemInfo = new RemoteActionItemInfo(remoteAction, token, start); - - applyFromRemoteActionInfo(itemInfo); - if (itemInfo.isEscapeHatch() || !loadIconFromResource()) { - UI_HELPER_EXECUTOR.post(() -> { - // If the Drawable from the remote action is not AdaptiveBitmap, styling will not - // work. - try (LauncherIcons li = LauncherIcons.obtain(getContext())) { - Drawable d = itemInfo.getRemoteAction().getIcon().loadDrawable(getContext()); - BitmapInfo bitmap = li.createBadgedIconBitmap(d, itemInfo.user, - Build.VERSION.SDK_INT); - - if (useIconToBadge) { - BitmapInfo placeholder = li.createIconBitmap( - itemInfo.getRemoteAction().getTitle().toString().substring(0, 1), - bitmap.color); - itemInfo.bitmap = li.badgeBitmap(placeholder.icon, bitmap); - } else { - itemInfo.bitmap = bitmap; - } - reapplyItemInfoAsync(itemInfo); - } - }); - } - - } - - private boolean loadIconFromResource() { - if (mCustomIcon == null) return false; - setIcon(mCustomIcon); - return true; - } - - void reapplyItemInfoAsync(ItemInfoWithIcon itemInfoWithIcon) { - MAIN_EXECUTOR.post(() -> { - reapplyItemInfo(itemInfoWithIcon); - mCustomIcon = getIcon(); }); } @Override public void handleSelection(int eventType) { - ItemInfo itemInfo = (ItemInfo) getTag(); - Launcher launcher = Launcher.getLauncher(getContext()); - if (itemInfo instanceof WorkspaceItemInfo) { - ItemClickHandler.onClickAppShortcut(this, (WorkspaceItemInfo) itemInfo, launcher); - } else { - ItemClickHandler.onClickRemoteAction(launcher, (RemoteActionItemInfo) itemInfo); - } - SearchEventTracker.INSTANCE.get(getContext()).notifySearchTargetEvent( - new SearchTargetEvent.Builder(mSearchTarget, eventType).build()); + mResultIcon.handleSelection(eventType); + } + + @Override + public void onClick(View view) { + mResultIcon.performClick(); + } + + @Override + public boolean onLongClick(View view) { + mResultIcon.performLongClick(); + return false; } } diff --git a/src/com/android/launcher3/views/SearchResultSuggestion.java b/src/com/android/launcher3/views/SearchResultSuggestion.java new file mode 100644 index 0000000000..ab94bf0342 --- /dev/null +++ b/src/com/android/launcher3/views/SearchResultSuggestion.java @@ -0,0 +1,62 @@ +/* + * 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 android.content.Context; +import android.content.res.TypedArray; +import android.graphics.drawable.Drawable; +import android.util.AttributeSet; +import android.view.ViewGroup; + +import com.android.launcher3.R; + +/** + * {@link SearchResultIconRow} with custom drawable resource + */ +public class SearchResultSuggestion extends SearchResultIcon { + + private final Drawable mCustomIcon; + + public SearchResultSuggestion(Context context) { + this(context, null, 0); + } + + public SearchResultSuggestion(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public SearchResultSuggestion(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + + TypedArray a = context.obtainStyledAttributes(attrs, + R.styleable.SearchResultSuggestion, defStyle, 0); + mCustomIcon = a.getDrawable(R.styleable.SearchResultSuggestion_customIcon); + a.recycle(); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + ViewGroup.LayoutParams lp = getLayoutParams(); + lp.height = BaseDragLayer.LayoutParams.WRAP_CONTENT; + } + + @Override + protected boolean loadIconFromResource() { + setIcon(mCustomIcon); + return true; + } +} diff --git a/src/com/android/launcher3/views/ThumbnailSearchResultView.java b/src/com/android/launcher3/views/ThumbnailSearchResultView.java index e929d7fbd8..f213f229a8 100644 --- a/src/com/android/launcher3/views/ThumbnailSearchResultView.java +++ b/src/com/android/launcher3/views/ThumbnailSearchResultView.java @@ -15,8 +15,9 @@ */ package com.android.launcher3.views; -import static com.android.launcher3.views.SearchResultIconRow.REMOTE_ACTION_SHOULD_START; -import static com.android.launcher3.views.SearchResultIconRow.REMOTE_ACTION_TOKEN; + +import static com.android.launcher3.views.SearchResultIcon.REMOTE_ACTION_SHOULD_START; +import static com.android.launcher3.views.SearchResultIcon.REMOTE_ACTION_TOKEN; import android.content.Context; import android.content.Intent;