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
+75
View File
@@ -0,0 +1,75 @@
<?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.SearchResultPlayItem xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="8dp"
android:orientation="horizontal">
<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" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="start|center_vertical"
android:layout_weight="1"
android:orientation="vertical"
android:padding="8dp">
<TextView
android:id="@+id/title_view"
style="@style/TextHeadline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAlignment="viewStart"
android:textColor="?android:attr/textColorPrimary"
android:textSize="16sp" />
<TextView
android:id="@+id/detail_0"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="?android:attr/textColorPrimary" />
<TextView
android:id="@+id/detail_1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="?android:attr/textColorPrimary"
android:visibility="gone" />
<TextView
android:id="@+id/detail_2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="?android:attr/textColorPrimary"
android:visibility="gone" />
</LinearLayout>
<Button
android:id="@+id/try_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="start|center_vertical"
android:background="?android:attr/selectableItemBackground"
android:text="@string/search_action_try_now">
</Button>
</com.android.launcher3.views.SearchResultPlayItem>
+8 -8
View File
@@ -13,11 +13,11 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/section_title"
android:textSize="14sp"
android:fontFamily="@style/TextHeadline"
android:layout_width="wrap_content"
android:textColor="?android:attr/textColorPrimary"
android:padding="4dp"
android:layout_height="wrap_content"/>
<com.android.launcher3.views.SearchSectionHeaderView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/section_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@style/TextHeadline"
android:padding="4dp"
android:textColor="?android:attr/textColorPrimary"
android:textSize="14sp" />
+2
View File
@@ -70,6 +70,8 @@
<!--All apps Search-->
<!-- Section title for apps [CHAR_LIMIT=50] -->
<string name="search_corpus_apps">Apps</string>
<!-- try instant app action for play search result [CHAR_LIMIT=50 -->
<string name="search_action_try_now">Try Now</string>
<!-- Popup items -->
<!-- Text to display as the header above notifications. [CHAR_LIMIT=30] -->
@@ -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);
}
}
}