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
This commit is contained in:
Samuel Fufa
2020-11-02 11:31:59 -06:00
parent a1733bceb7
commit 6e5efb0929
12 changed files with 378 additions and 459 deletions

View File

@@ -1,74 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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.
-->
<com.android.launcher3.views.HeroSearchResultView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:launcher="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:padding="@dimen/dynamic_grid_edge_margin">
<FrameLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_gravity="center_vertical"
android:layout_weight="1">
<com.android.launcher3.BubbleTextView
android:id="@+id/bubble_text"
style="@style/BaseIcon"
android:drawablePadding="@dimen/dynamic_grid_icon_drawable_padding"
android:gravity="start|center_vertical"
android:textAlignment="viewStart"
android:textColor="?android:attr/textColorPrimary"
android:textSize="16sp"
android:layout_height="wrap_content"
launcher:iconDisplay="hero_app"
launcher:layoutHorizontal="true"/>
<View
android:id="@+id/icon"
android:layout_width="@dimen/deep_shortcut_icon_size"
android:layout_height="@dimen/deep_shortcut_icon_size"
android:layout_gravity="start|center_vertical"
android:background="@drawable/ic_deepshortcut_placeholder"/>
</FrameLayout>
<com.android.launcher3.BubbleTextView
android:id="@+id/shortcut_0"
style="@style/BaseIcon"
android:layout_width="@dimen/deep_shortcut_icon_size"
android:layout_height="match_parent"
android:gravity="start|center_vertical"
android:textAlignment="center"
launcher:iconDisplay="shortcut_popup"
android:textSize="12sp"
launcher:iconSizeOverride="@dimen/deep_shortcut_icon_size"
launcher:layoutHorizontal="false"/>
<com.android.launcher3.BubbleTextView
android:id="@+id/shortcut_1"
style="@style/BaseIcon"
android:layout_width="@dimen/deep_shortcut_icon_size"
android:layout_height="match_parent"
android:gravity="start|center_vertical"
android:textAlignment="center"
launcher:iconDisplay="shortcut_popup"
android:textSize="12sp"
launcher:iconSizeOverride="@dimen/deep_shortcut_icon_size"
launcher:layoutHorizontal="false"/>
</com.android.launcher3.views.HeroSearchResultView>

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?><!-- Copyright (C) 2020 The Android Open Source Projectza
<?xml version="1.0" encoding="utf-8"?><!-- 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.
@@ -14,18 +14,61 @@
-->
<com.android.launcher3.views.SearchResultIconRow xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:launcher="http://schemas.android.com/apk/res-auto"
style="@style/BaseIcon"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:drawablePadding="@dimen/dynamic_grid_icon_drawable_padding"
android:gravity="start|center_vertical"
android:textAlignment="viewStart"
android:textColor="?android:attr/textColorPrimary"
android:textSize="16sp"
android:padding="@dimen/dynamic_grid_edge_margin"
launcher:iconDisplay="hero_app"
launcher:layoutHorizontal="true"
>
android:padding="@dimen/dynamic_grid_edge_margin">
<com.android.launcher3.views.SearchResultIcon
android:layout_width="wrap_content"
android:id="@+id/icon"
launcher:iconDisplay="hero_app"
android:layout_height="wrap_content" />
<LinearLayout
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
android:padding="@dimen/dynamic_grid_edge_margin"
android:orientation="vertical"
android:layout_gravity="center_vertical">
<TextView
android:layout_width="wrap_content"
android:id="@id/title"
android:layout_height="wrap_content"
android:gravity="start|center_vertical"
android:textAlignment="viewStart"
android:textColor="?android:attr/textColorPrimary"
android:textSize="@dimen/settings_hero_title_size" />
<TextView
android:layout_width="wrap_content"
android:id="@+id/desc"
android:textColor="?android:attr/textColorTertiary"
android:textSize="@dimen/settings_hero_subtitle_size"
android:layout_height="wrap_content" />
</LinearLayout>
<com.android.launcher3.BubbleTextView
android:id="@+id/shortcut_0"
style="@style/BaseIcon"
android:layout_width="@dimen/deep_shortcut_icon_size"
android:layout_height="match_parent"
android:gravity="start|center_vertical"
launcher:iconDisplay="shortcut_popup"
android:textSize="@dimen/settings_hero_subtitle_size"
launcher:iconSizeOverride="@dimen/deep_shortcut_icon_size"
launcher:layoutHorizontal="false" />
<com.android.launcher3.BubbleTextView
android:id="@+id/shortcut_1"
style="@style/BaseIcon"
android:layout_width="@dimen/deep_shortcut_icon_size"
android:layout_height="match_parent"
launcher:iconDisplay="shortcut_popup"
android:textSize="@dimen/settings_hero_inline_button_size"
launcher:iconSizeOverride="@dimen/deep_shortcut_icon_size"
launcher:layoutHorizontal="false" />
</com.android.launcher3.views.SearchResultIconRow>

View File

@@ -12,7 +12,7 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
<com.android.launcher3.views.SearchResultIconRow xmlns:android="http://schemas.android.com/apk/res/android"
<com.android.launcher3.views.SearchResultSuggestion xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:launcher="http://schemas.android.com/apk/res-auto"
style="@style/BaseIcon"
android:layout_width="match_parent"
@@ -26,10 +26,9 @@
launcher:iconDisplay="hero_app"
android:drawableTint="?android:attr/textColorPrimary"
launcher:customIcon="@drawable/ic_allapps_search"
launcher:iconSizeOverride="48dp"
launcher:iconSizeOverride="24dp"
launcher:matchTextInsetWithQuery="true"
launcher:layoutHorizontal="true"
android:drawablePadding="@dimen/dynamic_grid_icon_drawable_padding"
>
android:drawablePadding="@dimen/dynamic_grid_icon_drawable_padding">
</com.android.launcher3.views.SearchResultIconRow>
</com.android.launcher3.views.SearchResultSuggestion>

View File

@@ -1,5 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
<?xml version="1.0" encoding="utf-8"?><!--
/* Copyright 2008, The Android Open Source Project
**
** Licensed under the Apache License, Version 2.0 (the "License");
@@ -30,13 +29,13 @@
<attr name="isWorkspaceDarkText" format="boolean" />
<attr name="workspaceTextColor" format="color" />
<attr name="workspaceShadowColor" format="color" />
<attr name="workspaceAmbientShadowColor" format="color"/>
<attr name="workspaceAmbientShadowColor" format="color" />
<attr name="workspaceKeyShadowColor" format="color" />
<attr name="workspaceStatusBarScrim" format="reference" />
<attr name="widgetsTheme" format="reference" />
<attr name="loadingIconColor" format="color" />
<attr name="iconOnlyShortcutColor" format="color"/>
<attr name="eduHalfSheetBGColor" format="color"/>
<attr name="iconOnlyShortcutColor" format="color" />
<attr name="eduHalfSheetBGColor" format="color" />
<attr name="folderDotColor" format="color" />
<attr name="folderFillColor" format="color" />
@@ -69,13 +68,12 @@
<attr name="folderDotColor" />
</declare-styleable>
<declare-styleable name="SearchResultIconRow">
<declare-styleable name="SearchResultSuggestion">
<attr name="customIcon" format="reference" />
<attr name="matchTextInsetWithQuery" format="boolean" />
</declare-styleable>
<declare-styleable name="ShadowInfo">
<attr name="ambientShadowColor" format="color" />
<attr name="ambientShadowBlur" format="dimension" />
@@ -167,7 +165,7 @@
<attr name="android:src" />
<attr name="android:shadowColor" />
<attr name="android:elevation" />
<attr name="darkTintColor" format="color"/>
<attr name="darkTintColor" format="color" />
</declare-styleable>
<declare-styleable name="RecyclerViewFastScroller">

View File

@@ -248,4 +248,9 @@
<!-- Onboarding bottomsheet related -->
<dimen name="bottom_sheet_edu_padding">24dp</dimen>
<!-- Search related -->
<dimen name="settings_hero_title_size">16sp</dimen>
<dimen name="settings_hero_subtitle_size">15sp</dimen>
<dimen name="settings_hero_inline_button_size">12sp</dimen>
</resources>

View File

@@ -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:

View File

@@ -96,6 +96,8 @@ public class ItemClickHandler {
if (v instanceof PendingAppWidgetHostView) {
onClickPendingWidget((PendingAppWidgetHostView) v, launcher);
}
} else if (tag instanceof RemoteActionItemInfo) {
onClickRemoteAction(launcher, (RemoteActionItemInfo) tag);
}
}

View File

@@ -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<ShortcutInfo> infos = mSearchTarget.getShortcutInfos();
ArrayList<Pair<ShortcutInfo, ItemInfoWithIcon>> 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<ShortcutInfo, ItemInfoWithIcon> 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());
}
}

View File

@@ -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;
}
}

View File

@@ -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<ShortcutInfo> infos) {
if (infos == null) return;
ArrayList<Pair<ShortcutInfo, ItemInfoWithIcon>> 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<ShortcutInfo, ItemInfoWithIcon> 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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;