Merge "Introduce support for Hero app Section" into ub-launcher3-master

This commit is contained in:
Samuel Fufa
2020-08-07 18:11:59 +00:00
committed by Android (Google) Code Review
9 changed files with 342 additions and 26 deletions
+74
View File
@@ -0,0 +1,74 @@
<?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>
+1
View File
@@ -57,6 +57,7 @@
<enum name="folder" value="2" />
<enum name="widget_section" value="3" />
<enum name="shortcut_popup" value="4" />
<enum name="hero_app" value="5" />
</attr>
<attr name="centerVertically" format="boolean" />
</declare-styleable>
-2
View File
@@ -35,8 +35,6 @@
<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 -->
@@ -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;
@@ -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<AllAppsGridAdapter.
// A divider that separates the apps list and the search market button
public static final int VIEW_TYPE_ALL_APPS_DIVIDER = 1 << 4;
public static final int VIEW_TYPE_SEARCH_CORPUS_TITLE = 1 << 5;
public static final int VIEW_TYPE_SEARCH_CORPUS_TITLE = 1 << 5;
public static final int VIEW_TYPE_SEARCH_HERO_APP = 1 << 6;
// Common view type masks
public static final int VIEW_TYPE_MASK_DIVIDER = VIEW_TYPE_ALL_APPS_DIVIDER;
@@ -279,6 +282,10 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.
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));
default:
throw new RuntimeException("Unexpected view type");
}
@@ -311,6 +318,13 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.
TextView titleView = (TextView) holder.itemView;
titleView.setText(mApps.getAdapterItems().get(position).searchSectionInfo.getTitle(
titleView.getContext()));
break;
case VIEW_TYPE_SEARCH_HERO_APP:
HeroSearchResultView heroView = (HeroSearchResultView) holder.itemView;
heroView.prepareUsingAdapterItem(
(AlphabeticalAppsList.HeroAppAdapterItem) mApps.getAdapterItems().get(
position));
break;
case VIEW_TYPE_ALL_APPS_DIVIDER:
// nothing to do
break;
@@ -22,6 +22,7 @@ import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.allapps.search.SearchSectionInfo;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.util.ItemInfoMatcher;
import com.android.launcher3.util.LabelComparator;
@@ -130,10 +131,33 @@ public class AlphabeticalAppsList implements AllAppsStore.OnUpdateListener {
}
boolean isCountedForAccessibility() {
return viewType == AllAppsGridAdapter.VIEW_TYPE_ICON;
return viewType == AllAppsGridAdapter.VIEW_TYPE_ICON
|| viewType == AllAppsGridAdapter.VIEW_TYPE_SEARCH_HERO_APP;
}
}
/**
* Extension of AdapterItem that contains shortcut workspace items
*/
public static class HeroAppAdapterItem extends AdapterItem {
private ArrayList<WorkspaceItemInfo> mShortcutInfos;
public HeroAppAdapterItem(AppInfo info, ArrayList<WorkspaceItemInfo> shortcutInfos) {
viewType = AllAppsGridAdapter.VIEW_TYPE_SEARCH_HERO_APP;
mShortcutInfos = shortcutInfos;
appInfo = info;
}
/**
* Returns list of shortcuts for appInfo
*/
public ArrayList<WorkspaceItemInfo> getShortcutInfos() {
return mShortcutInfos;
}
}
private final BaseDraggingActivity mLauncher;
// The set of apps from the system
@@ -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<AppInfo> 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<WorkspaceItemInfo> getShortcutInfos(Context context, AppInfo appInfo) {
List<ShortcutInfo> shortcuts = new ShortcutRequest(context, appInfo.user)
.withContainer(appInfo.getTargetComponent())
.query(ShortcutRequest.PUBLISHED);
shortcuts = PopupPopulator.sortAndFilterShortcuts(shortcuts, null);
IconCache cache = LauncherAppState.getInstance(context).getIconCache();
ArrayList<WorkspaceItemInfo> 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<AdapterItem> getHeroAdapterItems(Context context, List<AppInfo> apps) {
ArrayList<AdapterItem> adapterItems = new ArrayList<>();
for (int i = 0; i < apps.size(); i++) {
//hero app
AppInfo appInfo = apps.get(i);
ArrayList<WorkspaceItemInfo> shortcuts = getShortcutInfos(context, appInfo);
AdapterItem adapterItem = new HeroAppAdapterItem(appInfo, shortcuts);
adapterItem.searchSectionInfo = mSearchSectionInfo;
adapterItems.add(adapterItem);
}
return adapterItems;
}
private ArrayList<AdapterItem> getAdapterItems(List<AppInfo> matchingApps) {
ArrayList<AdapterItem> 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);
}
@@ -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
@@ -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<WorkspaceItemInfo> 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<LauncherLogProto.Target> 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;
}
}
}