Merge "Introduce support for play results in launcher" into ub-launcher3-master

This commit is contained in:
Samuel Fufa
2020-08-19 16:44:29 +00:00
committed by Android (Google) Code Review
16 changed files with 493 additions and 215 deletions
@@ -15,6 +15,8 @@
*/
package com.android.launcher3.allapps;
import static com.android.launcher3.allapps.AllAppsGridAdapter.AdapterItem;
import static com.android.launcher3.allapps.AllAppsGridAdapter.AdapterItemWithPayload;
import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_HAS_SHORTCUT_PERMISSION;
import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_QUIET_MODE_CHANGE_PERMISSION;
@@ -527,6 +529,25 @@ public class AllAppsContainerView extends SpringRelativeLayout implements DragSo
return mViewPager == null ? getActiveRecyclerView() : mViewPager;
}
/**
* Handles selection on focused view and returns success
*/
public boolean selectFocusedView(View v) {
ItemInfo itemInfo = getHighlightedItemInfo();
if (itemInfo != null) {
return mLauncher.startActivitySafely(v, itemInfo.getIntent(), itemInfo);
}
AdapterItem focusedItem = getActiveRecyclerView().getApps().getFocusedChild();
if (focusedItem instanceof AdapterItemWithPayload) {
Runnable onSelection = ((AdapterItemWithPayload) focusedItem).getSelectionHandler();
if (onSelection != null) {
onSelection.run();
return true;
}
}
return false;
}
/**
* Returns the ItemInfo of a view that is in focus, ready to be launched by an IME.
*/
@@ -40,10 +40,10 @@ import androidx.recyclerview.widget.RecyclerView;
import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.R;
import com.android.launcher3.allapps.AlphabeticalAppsList.AdapterItem;
import com.android.launcher3.allapps.search.AllAppsSearchBarController.PayloadResultHandler;
import com.android.launcher3.allapps.search.SearchSectionInfo;
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.util.PackageManagerHelper;
import com.android.launcher3.views.HeroSearchResultView;
import java.util.List;
@@ -71,6 +71,8 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.
public static final int VIEW_TYPE_SEARCH_HERO_APP = 1 << 6;
public static final int DETAIL_ROW_WITH_BUTTON = 1 << 7;
// Common view type masks
public static final int VIEW_TYPE_MASK_DIVIDER = VIEW_TYPE_ALL_APPS_DIVIDER;
public static final int VIEW_TYPE_MASK_ICON = VIEW_TYPE_ICON;
@@ -85,6 +87,108 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.
}
}
/**
* Info about a particular adapter item (can be either section or app)
*/
public static class AdapterItem {
/** Common properties */
// The index of this adapter item in the list
public int position;
// The type of this item
public int viewType;
/** App-only properties */
// The section name of this app. Note that there can be multiple items with different
// sectionNames in the same section
public String sectionName = null;
// The row that this item shows up on
public int rowIndex;
// The index of this app in the row
public int rowAppIndex;
// The associated AppInfo for the app
public AppInfo appInfo = null;
// The index of this app not including sections
public int appIndex = -1;
// Search section associated to result
public SearchSectionInfo searchSectionInfo = null;
/**
* Factory method for AppIcon AdapterItem
*/
public static AdapterItem asApp(int pos, String sectionName, AppInfo appInfo,
int appIndex) {
AdapterItem item = new AdapterItem();
item.viewType = VIEW_TYPE_ICON;
item.position = pos;
item.sectionName = sectionName;
item.appInfo = appInfo;
item.appIndex = appIndex;
return item;
}
/**
* Factory method for empty search results view
*/
public static AdapterItem asEmptySearch(int pos) {
AdapterItem item = new AdapterItem();
item.viewType = VIEW_TYPE_EMPTY_SEARCH;
item.position = pos;
return item;
}
/**
* Factory method for a dividerView in AllAppsSearch
*/
public static AdapterItem asAllAppsDivider(int pos) {
AdapterItem item = new AdapterItem();
item.viewType = VIEW_TYPE_ALL_APPS_DIVIDER;
item.position = pos;
return item;
}
/**
* Factory method for a market search button
*/
public static AdapterItem asMarketSearch(int pos) {
AdapterItem item = new AdapterItem();
item.viewType = VIEW_TYPE_SEARCH_MARKET;
item.position = pos;
return item;
}
boolean isCountedForAccessibility() {
return viewType == VIEW_TYPE_ICON
|| viewType == VIEW_TYPE_SEARCH_HERO_APP
|| viewType == DETAIL_ROW_WITH_BUTTON;
}
}
/**
* Extension of AdapterItem that contains an extra payload specific to item
* @param <T> Play load Type
*/
public static class AdapterItemWithPayload<T> extends AdapterItem {
private T mPayload;
private Runnable mSelectionHandler;
public AdapterItemWithPayload(T payload, int type) {
mPayload = payload;
viewType = type;
}
public void setSelectionHandler(Runnable runnable) {
mSelectionHandler = runnable;
}
public Runnable getSelectionHandler() {
return mSelectionHandler;
}
public T getPayload() {
return mPayload;
}
}
/**
* A subclass of GridLayoutManager that overrides accessibility values during app search.
*/
@@ -286,6 +390,9 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.
case VIEW_TYPE_SEARCH_HERO_APP:
return new ViewHolder(mLayoutInflater.inflate(
R.layout.search_result_hero_app, parent, false));
case DETAIL_ROW_WITH_BUTTON:
return new ViewHolder(mLayoutInflater.inflate(
R.layout.search_result_play_item, parent, false));
default:
throw new RuntimeException("Unexpected view type");
}
@@ -315,15 +422,11 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.
}
break;
case VIEW_TYPE_SEARCH_CORPUS_TITLE:
TextView titleView = (TextView) holder.itemView;
titleView.setText(mApps.getAdapterItems().get(position).searchSectionInfo.getTitle(
titleView.getContext()));
break;
case DETAIL_ROW_WITH_BUTTON:
case VIEW_TYPE_SEARCH_HERO_APP:
HeroSearchResultView heroView = (HeroSearchResultView) holder.itemView;
heroView.prepareUsingAdapterItem(
(AlphabeticalAppsList.HeroAppAdapterItem) mApps.getAdapterItems().get(
position));
PayloadResultHandler payloadResultView = (PayloadResultHandler) holder.itemView;
payloadResultView.applyAdapterInfo(
(AdapterItemWithPayload) mApps.getAdapterItems().get(position));
break;
case VIEW_TYPE_ALL_APPS_DIVIDER:
// nothing to do
@@ -344,7 +447,7 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.
@Override
public int getItemViewType(int position) {
AlphabeticalAppsList.AdapterItem item = mApps.getAdapterItems().get(position);
AdapterItem item = mApps.getAdapterItems().get(position);
return item.viewType;
}
@@ -278,7 +278,7 @@ public class AllAppsRecyclerView extends BaseRecyclerView implements LogContaine
if (mApps == null) {
return;
}
List<AlphabeticalAppsList.AdapterItem> items = mApps.getAdapterItems();
List<AllAppsGridAdapter.AdapterItem> items = mApps.getAdapterItems();
// Skip early if there are no items or we haven't been measured
if (items.isEmpty() || mNumAppsPerRow == 0) {
@@ -352,7 +352,7 @@ public class AllAppsRecyclerView extends BaseRecyclerView implements LogContaine
@Override
public int getCurrentScrollY() {
// Return early if there are no items or we haven't been measured
List<AlphabeticalAppsList.AdapterItem> items = mApps.getAdapterItems();
List<AllAppsGridAdapter.AdapterItem> items = mApps.getAdapterItems();
if (items.isEmpty() || mNumAppsPerRow == 0 || getChildCount() == 0) {
return -1;
}
@@ -368,14 +368,14 @@ public class AllAppsRecyclerView extends BaseRecyclerView implements LogContaine
}
public int getCurrentScrollY(int position, int offset) {
List<AlphabeticalAppsList.AdapterItem> items = mApps.getAdapterItems();
AlphabeticalAppsList.AdapterItem posItem = position < items.size() ?
items.get(position) : null;
List<AllAppsGridAdapter.AdapterItem> items = mApps.getAdapterItems();
AllAppsGridAdapter.AdapterItem posItem = position < items.size()
? items.get(position) : null;
int y = mCachedScrollPositions.get(position, -1);
if (y < 0) {
y = 0;
for (int i = 0; i < position; i++) {
AlphabeticalAppsList.AdapterItem item = items.get(i);
AllAppsGridAdapter.AdapterItem item = items.get(i);
if (AllAppsGridAdapter.isIconViewType(item.viewType)) {
// Break once we reach the desired row
if (posItem != null && posItem.viewType == item.viewType &&
@@ -47,13 +47,13 @@ public class AllAppsSectionDecorator extends RecyclerView.ItemDecoration {
// Since views in the same section will follow each other, we can skip to a last view in
// a section to get the bounds of the section without having to iterate on every item.
int itemCount = parent.getChildCount();
List<AlphabeticalAppsList.AdapterItem> adapterItems = mAppsView.getApps().getAdapterItems();
List<AllAppsGridAdapter.AdapterItem> adapterItems = mAppsView.getApps().getAdapterItems();
SectionDecorationHandler lastDecorationHandler = null;
int i = 0;
while (i < itemCount) {
View view = parent.getChildAt(i);
int position = parent.getChildAdapterPosition(view);
AlphabeticalAppsList.AdapterItem adapterItem = adapterItems.get(position);
AllAppsGridAdapter.AdapterItem adapterItem = adapterItems.get(position);
if (adapterItem.searchSectionInfo != null) {
SearchSectionInfo sectionInfo = adapterItem.searchSectionInfo;
int endIndex = Math.min(i + sectionInfo.getPosEnd() - position, itemCount - 1);
@@ -19,10 +19,10 @@ package com.android.launcher3.allapps;
import android.content.Context;
import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.allapps.AllAppsGridAdapter.AdapterItem;
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;
@@ -62,101 +62,6 @@ public class AlphabeticalAppsList implements AllAppsStore.OnUpdateListener {
}
}
/**
* Info about a particular adapter item (can be either section or app)
*/
public static class AdapterItem {
/** Common properties */
// The index of this adapter item in the list
public int position;
// The type of this item
public int viewType;
/** App-only properties */
// The section name of this app. Note that there can be multiple items with different
// sectionNames in the same section
public String sectionName = null;
// The row that this item shows up on
public int rowIndex;
// The index of this app in the row
public int rowAppIndex;
// The associated AppInfo for the app
public AppInfo appInfo = null;
// The index of this app not including sections
public int appIndex = -1;
// Search section associated to result
public SearchSectionInfo searchSectionInfo = null;
public static AdapterItem asApp(int pos, String sectionName, AppInfo appInfo,
int appIndex) {
AdapterItem item = new AdapterItem();
item.viewType = AllAppsGridAdapter.VIEW_TYPE_ICON;
item.position = pos;
item.sectionName = sectionName;
item.appInfo = appInfo;
item.appIndex = appIndex;
return item;
}
public static AdapterItem asEmptySearch(int pos) {
AdapterItem item = new AdapterItem();
item.viewType = AllAppsGridAdapter.VIEW_TYPE_EMPTY_SEARCH;
item.position = pos;
return item;
}
public static AdapterItem asAllAppsDivider(int pos) {
AdapterItem item = new AdapterItem();
item.viewType = AllAppsGridAdapter.VIEW_TYPE_ALL_APPS_DIVIDER;
item.position = pos;
return item;
}
public static AdapterItem asMarketSearch(int pos) {
AdapterItem item = new AdapterItem();
item.viewType = AllAppsGridAdapter.VIEW_TYPE_SEARCH_MARKET;
item.position = pos;
return item;
}
/**
* Factory method for search section title AdapterItem
*/
public static AdapterItem asSearchTitle(SearchSectionInfo sectionInfo, int pos) {
AdapterItem item = new AdapterItem();
item.viewType = AllAppsGridAdapter.VIEW_TYPE_SEARCH_CORPUS_TITLE;
item.position = pos;
item.searchSectionInfo = sectionInfo;
return item;
}
boolean isCountedForAccessibility() {
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;
@@ -396,7 +301,9 @@ public class AlphabeticalAppsList implements AllAppsStore.OnUpdateListener {
adapterItem.position = i;
mAdapterItems.add(adapterItem);
if (adapterItem.searchSectionInfo != lastSection) {
adapterItem.searchSectionInfo.setPosStart(i);
if (adapterItem.searchSectionInfo != null) {
adapterItem.searchSectionInfo.setPosStart(i);
}
if (lastSection != null) {
lastSection.setPosEnd(i - 1);
}
@@ -15,35 +15,26 @@
*/
package com.android.launcher3.allapps.search;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.view.View.OnFocusChangeListener;
import android.view.inputmethod.EditorInfo;
import android.widget.TextView;
import android.widget.TextView.OnEditorActionListener;
import android.widget.Toast;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.ExtendedEditText;
import com.android.launcher3.Launcher;
import com.android.launcher3.Utilities;
import com.android.launcher3.allapps.AlphabeticalAppsList;
import com.android.launcher3.allapps.AllAppsGridAdapter;
import com.android.launcher3.allapps.AllAppsGridAdapter.AdapterItemWithPayload;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
import com.android.launcher3.util.PackageManagerHelper;
import com.android.systemui.plugins.AllAppsSearchPlugin;
import com.android.systemui.plugins.PluginListener;
import java.util.ArrayList;
import java.util.List;
@@ -54,17 +45,14 @@ import java.util.function.Consumer;
*/
public class AllAppsSearchBarController
implements TextWatcher, OnEditorActionListener, ExtendedEditText.OnBackKeyListener,
OnFocusChangeListener, PluginListener<AllAppsSearchPlugin> {
OnFocusChangeListener {
private static final String TAG = "AllAppsSearchBarContoller";
protected BaseDraggingActivity mLauncher;
protected Callbacks mCb;
protected ExtendedEditText mInput;
protected String mQuery;
protected SearchAlgorithm mSearchAlgorithm;
private AllAppsSearchPlugin mPlugin;
private Consumer mPlubinCb;
public void setVisibility(int visibility) {
mInput.setVisibility(visibility);
@@ -85,18 +73,13 @@ public class AllAppsSearchBarController
mInput.setOnBackKeyListener(this);
mInput.setOnFocusChangeListener(this);
mSearchAlgorithm = searchAlgorithm;
PluginManagerWrapper.INSTANCE.get(launcher).addPluginListener(this,
AllAppsSearchPlugin.class);
mPlubinCb = secondaryCb;
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
if (mPlugin != null) {
if (s.length() == 0) {
mPlugin.startedTyping();
}
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
if (mSearchAlgorithm instanceof PluginWrapper) {
((PluginWrapper) mSearchAlgorithm).runOnPluginIfConnected(
AllAppsSearchPlugin::startedTyping);
}
}
@@ -114,9 +97,6 @@ public class AllAppsSearchBarController
} else {
mSearchAlgorithm.cancel(false);
mSearchAlgorithm.doSearch(mQuery, mCb);
if (mPlugin != null) {
mPlugin.performSearch(mQuery, mPlubinCb);
}
}
}
@@ -133,10 +113,8 @@ public class AllAppsSearchBarController
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if (FeatureFlags.ENABLE_DEVICE_SEARCH.get()) {
if (actionId == EditorInfo.IME_ACTION_SEARCH) {
ItemInfo info = Launcher.getLauncher(mLauncher).getAppsView()
.getHighlightedItemInfo();
if (info != null) {
return mLauncher.startActivitySafely(v, info.getIntent(), info);
if (Launcher.getLauncher(mLauncher).getAppsView().selectFocusedView(v)) {
return true;
}
}
}
@@ -197,43 +175,14 @@ public class AllAppsSearchBarController
return mInput.isFocused();
}
@Override
public void onPluginConnected(AllAppsSearchPlugin allAppsSearchPlugin, Context context) {
if (FeatureFlags.ENABLE_DEVICE_SEARCH.get()) {
mPlugin = allAppsSearchPlugin;
checkCallPermission();
}
}
/**
* Check call permissions.
* A wrapper setup for running essential calls to plugin from search controller
*/
public void checkCallPermission() {
final String[] permission = {"android.permission.CALL_PHONE",
"android.permission.READ_CONTACTS"};
boolean request = false;
for (String p : permission) {
int permissionCheck = ContextCompat.checkSelfPermission(mLauncher, p);
if (permissionCheck != PackageManager.PERMISSION_GRANTED) {
request = true;
}
}
if (!request) return;
boolean rationale = false;
for (String p : permission) {
if (mLauncher.shouldShowRequestPermissionRationale(p)) {
rationale = true;
}
if (rationale) {
Log.e(TAG, p + " Show rationale");
Toast.makeText(mLauncher, "Requesting Permissions", Toast.LENGTH_SHORT).show();
} else {
ActivityCompat.requestPermissions(mLauncher, permission, 123);
Log.e(TAG, p + " request permission");
}
}
public interface PluginWrapper {
/**
* executes call if plugin is connected
*/
void runOnPluginIfConnected(Consumer<AllAppsSearchPlugin> plugin);
}
/**
@@ -246,11 +195,23 @@ public class AllAppsSearchBarController
*
* @param items sorted list of search result adapter items.
*/
void onSearchResult(String query, ArrayList<AlphabeticalAppsList.AdapterItem> items);
void onSearchResult(String query, ArrayList<AllAppsGridAdapter.AdapterItem> items);
/**
* Called when the search results should be cleared.
*/
void clearSearchResult();
}
/**
* An interface for supporting dynamic search results
*
* @param <T> Type of payload
*/
public interface PayloadResultHandler<T> {
/**
* Updates View using Adapter's payload
*/
void applyAdapterInfo(AdapterItemWithPayload<T> adapterItemWithPayload);
}
}
@@ -42,6 +42,7 @@ import com.android.launcher3.Insettable;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.R;
import com.android.launcher3.allapps.AllAppsContainerView;
import com.android.launcher3.allapps.AllAppsGridAdapter;
import com.android.launcher3.allapps.AllAppsStore;
import com.android.launcher3.allapps.AlphabeticalAppsList;
import com.android.launcher3.allapps.SearchUiManager;
@@ -172,7 +173,7 @@ public class AppsSearchContainerLayout extends ExtendedEditText
}
@Override
public void onSearchResult(String query, ArrayList<AlphabeticalAppsList.AdapterItem> items) {
public void onSearchResult(String query, ArrayList<AllAppsGridAdapter.AdapterItem> items) {
if (items != null) {
mApps.setSearchResults(items);
notifyResultChanged();
@@ -16,6 +16,7 @@
package com.android.launcher3.allapps.search;
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_SHORTCUTS;
import static com.android.launcher3.allapps.AllAppsGridAdapter.VIEW_TYPE_SEARCH_HERO_APP;
import android.content.Context;
import android.content.pm.ShortcutInfo;
@@ -23,9 +24,9 @@ import android.content.pm.ShortcutInfo;
import androidx.annotation.WorkerThread;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.allapps.AllAppsGridAdapter;
import com.android.launcher3.allapps.AllAppsGridAdapter.AdapterItem;
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;
@@ -82,7 +83,7 @@ public class AppsSearchPipeline implements SearchPipeline {
/**
* Returns MAX_SHORTCUTS_COUNT shortcuts from local cache
* TODO: Shortcuts should be ranked based on relevancy
* TODO: Shortcuts should be ranked based on relevancy
*/
private ArrayList<WorkspaceItemInfo> getShortcutInfos(Context context, AppInfo appInfo) {
List<ShortcutInfo> shortcuts = new ShortcutRequest(context, appInfo.user)
@@ -126,7 +127,9 @@ public class AppsSearchPipeline implements SearchPipeline {
//hero app
AppInfo appInfo = apps.get(i);
ArrayList<WorkspaceItemInfo> shortcuts = getShortcutInfos(context, appInfo);
AdapterItem adapterItem = new HeroAppAdapterItem(appInfo, shortcuts);
AdapterItem adapterItem = new AllAppsGridAdapter.AdapterItemWithPayload(shortcuts,
VIEW_TYPE_SEARCH_HERO_APP);
adapterItem.appInfo = appInfo;
adapterItem.searchSectionInfo = mSearchSectionInfo;
adapterItems.add(adapterItem);
}
@@ -15,7 +15,7 @@
*/
package com.android.launcher3.allapps.search;
import com.android.launcher3.allapps.AlphabeticalAppsList;
import com.android.launcher3.allapps.AllAppsGridAdapter;
import java.util.ArrayList;
import java.util.function.Consumer;
@@ -28,5 +28,5 @@ public interface SearchPipeline {
/**
* Perform query
*/
void performSearch(String query, Consumer<ArrayList<AlphabeticalAppsList.AdapterItem>> cb);
void performSearch(String query, Consumer<ArrayList<AllAppsGridAdapter.AdapterItem>> cb);
}
@@ -15,8 +15,6 @@
*/
package com.android.launcher3.allapps.search;
import android.content.Context;
import com.android.launcher3.allapps.AllAppsSectionDecorator.SectionDecorationHandler;
/**
@@ -24,7 +22,7 @@ import com.android.launcher3.allapps.AllAppsSectionDecorator.SectionDecorationHa
*/
public class SearchSectionInfo {
private final int mTitleResId;
private String mTitle;
private SectionDecorationHandler mDecorationHandler;
public int getPosStart() {
@@ -47,11 +45,11 @@ public class SearchSectionInfo {
private int mPosEnd;
public SearchSectionInfo() {
this(-1);
this(null);
}
public SearchSectionInfo(int titleResId) {
mTitleResId = titleResId;
public SearchSectionInfo(String title) {
mTitle = title;
}
public void setDecorationHandler(SectionDecorationHandler sectionDecorationHandler) {
@@ -66,10 +64,7 @@ public class SearchSectionInfo {
/**
* Returns the section's title
*/
public String getTitle(Context context) {
if (mTitleResId == -1) {
return "";
}
return context.getString(mTitleResId);
public String getTitle() {
return mTitle == null ? "" : mTitle;
}
}
@@ -30,7 +30,8 @@ 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.allapps.AllAppsGridAdapter.AdapterItemWithPayload;
import com.android.launcher3.allapps.search.AllAppsSearchBarController;
import com.android.launcher3.dragndrop.DragOptions;
import com.android.launcher3.dragndrop.DraggableView;
import com.android.launcher3.graphics.DragPreviewProvider;
@@ -47,7 +48,8 @@ import java.util.List;
/**
* A view representing a high confidence app search result that includes shortcuts
*/
public class HeroSearchResultView extends LinearLayout implements DragSource {
public class HeroSearchResultView extends LinearLayout implements DragSource,
AllAppsSearchBarController.PayloadResultHandler<List<WorkspaceItemInfo>> {
BubbleTextView mBubbleTextView;
View mIconView;
@@ -96,18 +98,18 @@ public class HeroSearchResultView extends LinearLayout implements DragSource {
/**
* Apply {@link ItemInfo} for appIcon and shortcut Icons
*/
public void prepareUsingAdapterItem(AlphabeticalAppsList.HeroAppAdapterItem adapterItem) {
@Override
public void applyAdapterInfo(AdapterItemWithPayload<List<WorkspaceItemInfo>> adapterItem) {
mBubbleTextView.applyFromApplicationInfo(adapterItem.appInfo);
mIconView.setBackground(mBubbleTextView.getIcon());
mIconView.setTag(adapterItem.appInfo);
List<WorkspaceItemInfo> shorcutInfos = adapterItem.getShortcutInfos();
List<WorkspaceItemInfo> shorcutInfos = adapterItem.getPayload();
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
@@ -126,7 +128,6 @@ public class HeroSearchResultView extends LinearLayout implements DragSource {
mIconView.setVisibility(willDraw ? View.VISIBLE : View.INVISIBLE);
}
/**
* Drag and drop handler for popup items in Launcher activity
*/
@@ -0,0 +1,154 @@
/*
* Copyright (C) 2015 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.util.Executors.UI_HELPER_EXECUTOR;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.drawable.BitmapDrawable;
import android.net.Uri;
import android.os.Bundle;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.Nullable;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
import com.android.launcher3.R;
import com.android.launcher3.allapps.AllAppsGridAdapter.AdapterItemWithPayload;
import com.android.launcher3.allapps.search.AllAppsSearchBarController;
import java.io.IOException;
import java.net.URL;
/**
* A View representing a PlayStore item.
*/
public class SearchResultPlayItem extends LinearLayout implements
AllAppsSearchBarController.PayloadResultHandler<Bundle> {
private final DeviceProfile mDeviceProfile;
private View mIconView;
private TextView mTitleView;
private TextView[] mDetailViews = new TextView[3];
private Button mPreviewButton;
private String mPackageName;
private boolean mIsInstantGame;
public SearchResultPlayItem(Context context) {
this(context, null, 0);
}
public SearchResultPlayItem(Context context,
@Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public SearchResultPlayItem(Context context, @Nullable AttributeSet attrs,
int defStyleAttr) {
super(context, attrs, defStyleAttr);
mDeviceProfile = Launcher.getLauncher(getContext()).getDeviceProfile();
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mIconView = findViewById(R.id.icon);
mTitleView = findViewById(R.id.title_view);
mPreviewButton = findViewById(R.id.try_button);
mPreviewButton.setOnClickListener(view -> launchInstantGame());
mDetailViews[0] = findViewById(R.id.detail_0);
mDetailViews[1] = findViewById(R.id.detail_1);
mDetailViews[2] = findViewById(R.id.detail_2);
ViewGroup.LayoutParams iconParams = mIconView.getLayoutParams();
iconParams.height = mDeviceProfile.allAppsIconSizePx;
iconParams.width = mDeviceProfile.allAppsIconSizePx;
setOnClickListener(view -> handleSelection());
}
@Override
public void applyAdapterInfo(AdapterItemWithPayload<Bundle> adapterItemWithPayload) {
Bundle bundle = adapterItemWithPayload.getPayload();
adapterItemWithPayload.setSelectionHandler(this::handleSelection);
if (bundle.getString("package", "").equals(mPackageName)) {
return;
}
mIsInstantGame = bundle.getBoolean("instant_game", false);
mPackageName = bundle.getString("package");
mPreviewButton.setVisibility(mIsInstantGame ? VISIBLE : GONE);
mTitleView.setText(bundle.getString("title"));
// TODO: Should use a generic type to get values b/165320033
showIfNecessary(mDetailViews[0], bundle.getString("price"));
showIfNecessary(mDetailViews[1], bundle.getString("rating"));
showIfNecessary(mDetailViews[2], bundle.getString("category"));
mIconView.setBackgroundResource(R.drawable.ic_deepshortcut_placeholder);
UI_HELPER_EXECUTOR.execute(() -> {
try {
// TODO: Handle caching
URL url = new URL(bundle.getString("icon_url"));
Bitmap bitmap = BitmapFactory.decodeStream(url.openStream());
BitmapDrawable bitmapDrawable = new BitmapDrawable(getResources(),
Bitmap.createScaledBitmap(bitmap, mDeviceProfile.allAppsIconSizePx,
mDeviceProfile.allAppsIconSizePx, false));
mIconView.post(() -> mIconView.setBackground(bitmapDrawable));
} catch (IOException e) {
e.printStackTrace();
}
});
}
private void showIfNecessary(TextView textView, @Nullable String string) {
if (string == null || string.isEmpty()) {
textView.setVisibility(GONE);
} else {
textView.setText(string);
textView.setVisibility(VISIBLE);
}
}
private void handleSelection() {
if (mPackageName == null) return;
Intent i = new Intent(Intent.ACTION_VIEW, Uri.parse(
"https://play.google.com/store/apps/details?id="
+ mPackageName));
i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
getContext().startActivity(i);
}
private void launchInstantGame() {
if (!mIsInstantGame) return;
Intent intent = new Intent(Intent.ACTION_VIEW);
String referrer = "Pixel_Launcher";
String id = mPackageName;
String deepLinkUrl = "market://details?id=" + id + "&launch=true&referrer=" + referrer;
intent.setPackage("com.android.vending");
intent.setData(Uri.parse(deepLinkUrl));
intent.putExtra("overlay", true);
intent.putExtra("callerId", getContext().getPackageName());
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
getContext().startActivity(intent);
}
}
@@ -0,0 +1,55 @@
/*
* 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.util.AttributeSet;
import android.widget.TextView;
import androidx.annotation.Nullable;
import com.android.launcher3.allapps.AllAppsGridAdapter;
import com.android.launcher3.allapps.search.AllAppsSearchBarController;
/**
* Header text view that shows a title for a given section in All apps search
*/
public class SearchSectionHeaderView extends TextView implements
AllAppsSearchBarController.PayloadResultHandler<String> {
public SearchSectionHeaderView(Context context) {
super(context);
}
public SearchSectionHeaderView(Context context,
@Nullable AttributeSet attrs) {
super(context, attrs);
}
public SearchSectionHeaderView(Context context, @Nullable AttributeSet attrs, int styleAttr) {
super(context, attrs, styleAttr);
}
@Override
public void applyAdapterInfo(AllAppsGridAdapter.AdapterItemWithPayload<String> adapterItem) {
String title = adapterItem.getPayload();
if (title == null || !title.isEmpty()) {
setText(title);
setVisibility(VISIBLE);
} else {
setVisibility(INVISIBLE);
}
}
}