diff options
Diffstat (limited to 'src/com')
8 files changed, 190 insertions, 71 deletions
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index 32e7ee681..298b2c469 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -2137,6 +2137,15 @@ public class Launcher extends Activity } } + public void startSearchFromAllApps(View v, Intent searchIntent, String searchQuery) { + if (mLauncherCallbacks != null && mLauncherCallbacks.startSearchFromAllApps(searchQuery)) { + return; + } + + // If not handled, then just start the provided search intent + startActivitySafely(v, searchIntent, null); + } + public boolean isOnCustomContent() { return mWorkspace.isOnOrMovingToCustomContent(); } @@ -2538,6 +2547,10 @@ public class Launcher extends Activity if (!isAppsViewVisible()) { showAppsView(true /* animated */, false /* resetListToTop */, true /* updatePredictedApps */, false /* focusSearchBar */); + + if (mLauncherCallbacks != null) { + mLauncherCallbacks.onClickAllAppsButton(v); + } } } @@ -2929,7 +2942,7 @@ public class Launcher extends Activity return false; } - @Thunk boolean startActivitySafely(View v, Intent intent, Object tag) { + public boolean startActivitySafely(View v, Intent intent, Object tag) { boolean success = false; if (mIsSafeModeEnabled && !Utilities.isSystemApp(this, intent)) { Toast.makeText(this, R.string.safemode_shortcut_error, Toast.LENGTH_SHORT).show(); diff --git a/src/com/android/launcher3/LauncherCallbacks.java b/src/com/android/launcher3/LauncherCallbacks.java index 6618cca78..e34bd57fd 100644 --- a/src/com/android/launcher3/LauncherCallbacks.java +++ b/src/com/android/launcher3/LauncherCallbacks.java @@ -77,6 +77,7 @@ public interface LauncherCallbacks { public boolean providesSearch(); public boolean startSearch(String initialQuery, boolean selectInitialQuery, Bundle appSearchData, Rect sourceBounds); + public boolean startSearchFromAllApps(String query); @Deprecated public void startVoice(); public boolean hasCustomContentToLeft(); diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java index 010b2cb48..e129dc6d3 100644 --- a/src/com/android/launcher3/allapps/AllAppsContainerView.java +++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java @@ -16,34 +16,26 @@ package com.android.launcher3.allapps; import android.annotation.SuppressLint; -import android.annotation.TargetApi; import android.content.Context; +import android.content.Intent; import android.content.res.Resources; import android.graphics.Point; import android.graphics.Rect; import android.graphics.drawable.InsetDrawable; -import android.os.Build; -import android.os.Bundle; import android.support.v7.widget.RecyclerView; import android.text.Selection; import android.text.SpannableStringBuilder; import android.text.method.TextKeyListener; import android.util.AttributeSet; import android.view.KeyEvent; -import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.ViewConfiguration; import android.view.ViewGroup; -import android.view.ViewTreeObserver; -import android.widget.FrameLayout; import android.widget.LinearLayout; - import com.android.launcher3.AppInfo; import com.android.launcher3.BaseContainerView; -import com.android.launcher3.BubbleTextView; import com.android.launcher3.CellLayout; -import com.android.launcher3.CheckLongPressHelper; import com.android.launcher3.DeleteDropTarget; import com.android.launcher3.DeviceProfile; import com.android.launcher3.DragSource; @@ -53,7 +45,6 @@ import com.android.launcher3.ItemInfo; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherTransitionable; import com.android.launcher3.R; -import com.android.launcher3.Stats; import com.android.launcher3.Utilities; import com.android.launcher3.Workspace; import com.android.launcher3.util.ComponentKey; @@ -155,6 +146,7 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc @Thunk AllAppsSearchBarController mSearchBarController; private ViewGroup mSearchBarContainerView; private View mSearchBarView; + private SpannableStringBuilder mSearchQueryBuilder = null; private int mSectionNamesMargin; private int mNumAppsPerRow; @@ -165,7 +157,13 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc // This coordinate is relative to its parent private final Point mIconLastTouchPos = new Point(); - private SpannableStringBuilder mSearchQueryBuilder = null; + private View.OnClickListener mSearchClickListener = new View.OnClickListener() { + @Override + public void onClick(View v) { + Intent searchIntent = (Intent) v.getTag(); + mLauncher.startActivitySafely(v, searchIntent, null); + } + }; public AllAppsContainerView(Context context) { this(context, null); @@ -182,8 +180,7 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc mLauncher = (Launcher) context; mSectionNamesMargin = res.getDimensionPixelSize(R.dimen.all_apps_grid_view_start_margin); mApps = new AlphabeticalAppsList(context); - mAdapter = new AllAppsGridAdapter(context, mApps, this, mLauncher, this); - mAdapter.setEmptySearchText(res.getString(R.string.all_apps_loading_message)); + mAdapter = new AllAppsGridAdapter(mLauncher, mApps, this, mLauncher, this); mApps.setAdapter(mAdapter); mLayoutManager = mAdapter.getLayoutManager(); mItemDecoration = mAdapter.getItemDecoration(); @@ -615,13 +612,9 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc @Override public void onSearchResult(String query, ArrayList<ComponentKey> apps) { if (apps != null) { - if (apps.isEmpty()) { - String formatStr = getResources().getString(R.string.all_apps_no_search_results); - mAdapter.setEmptySearchText(String.format(formatStr, query)); - } else { - mAppsRecyclerView.scrollToTop(); - } mApps.setOrderedFilter(apps); + mAdapter.setLastSearchQuery(query); + mAppsRecyclerView.scrollToTop(); } } diff --git a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java index e96567c41..4acfc5ca6 100644 --- a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java +++ b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java @@ -16,14 +16,17 @@ package com.android.launcher3.allapps; import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; import android.content.res.Resources; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.PointF; import android.graphics.Rect; -import android.os.Handler; import android.support.v4.view.accessibility.AccessibilityRecordCompat; import android.support.v4.view.accessibility.AccessibilityEventCompat; +import android.net.Uri; import android.support.v7.widget.GridLayoutManager; import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; @@ -34,6 +37,7 @@ import android.view.accessibility.AccessibilityEvent; import android.widget.TextView; import com.android.launcher3.AppInfo; import com.android.launcher3.BubbleTextView; +import com.android.launcher3.Launcher; import com.android.launcher3.R; import com.android.launcher3.Utilities; import com.android.launcher3.util.Thunk; @@ -58,6 +62,10 @@ class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.ViewHol public static final int PREDICTION_ICON_VIEW_TYPE = 2; // The message shown when there are no filtered results public static final int EMPTY_SEARCH_VIEW_TYPE = 3; + // A divider that separates the apps list and the search market button + public static final int SEARCH_MARKET_DIVIDER_VIEW_TYPE = 4; + // The message to continue to a market search when there are no filtered results + public static final int SEARCH_MARKET_VIEW_TYPE = 5; /** * ViewHolder for each icon. @@ -83,12 +91,12 @@ class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.ViewHol @Override public void onInitializeAccessibilityEvent(AccessibilityEvent event) { super.onInitializeAccessibilityEvent(event); - if (mApps.hasNoFilteredResults()) { - // Disregard the no-search-results text as a list item for accessibility - final AccessibilityRecordCompat record = AccessibilityEventCompat - .asRecord(event); - record.setItemCount(0); - } + + // Ensure that we only report the number apps for accessibility not including other + // adapter views + final AccessibilityRecordCompat record = AccessibilityEventCompat + .asRecord(event); + record.setItemCount(mApps.getNumFilteredApps()); } @Override @@ -115,11 +123,6 @@ class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.ViewHol @Override public int getSpanSize(int position) { - if (mApps.hasNoFilteredResults()) { - // Empty view spans full width - return mAppsPerRow; - } - switch (mApps.getAdapterItems().get(position).viewType) { case AllAppsGridAdapter.ICON_VIEW_TYPE: case AllAppsGridAdapter.PREDICTION_ICON_VIEW_TYPE: @@ -314,6 +317,7 @@ class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.ViewHol } } + private Launcher mLauncher; private LayoutInflater mLayoutInflater; @Thunk AlphabeticalAppsList mApps; private GridLayoutManager mGridLayoutMgr; @@ -326,7 +330,19 @@ class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.ViewHol @Thunk int mPredictionBarDividerOffset; @Thunk int mAppsPerRow; @Thunk boolean mIsRtl; - private String mEmptySearchText; + + // The text to show when there are no search results and no market search handler. + private String mEmptySearchMessage; + // The name of the market app which handles searches, to be used in the format str + // below when updating the search-market view. Only needs to be loaded once. + private String mMarketAppName; + // The text to show when there is a market app which can handle a specific query, updated + // each time the search query changes. + private String mMarketSearchMessage; + // The intent to send off to the market app, updated each time the search query changes. + private Intent mMarketSearchIntent; + // The last query that the user entered into the search field + private String mLastSearchQuery; // Section drawing @Thunk int mSectionNamesMargin; @@ -334,16 +350,18 @@ class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.ViewHol @Thunk Paint mSectionTextPaint; @Thunk Paint mPredictedAppsDividerPaint; - public AllAppsGridAdapter(Context context, AlphabeticalAppsList apps, + public AllAppsGridAdapter(Launcher launcher, AlphabeticalAppsList apps, View.OnTouchListener touchListener, View.OnClickListener iconClickListener, View.OnLongClickListener iconLongClickListener) { - Resources res = context.getResources(); + Resources res = launcher.getResources(); + mLauncher = launcher; mApps = apps; + mEmptySearchMessage = res.getString(R.string.all_apps_loading_message); mGridSizer = new GridSpanSizer(); - mGridLayoutMgr = new AppsGridLayoutManager(context); + mGridLayoutMgr = new AppsGridLayoutManager(launcher); mGridLayoutMgr.setSpanSizeLookup(mGridSizer); mItemDecoration = new GridItemDecoration(); - mLayoutInflater = LayoutInflater.from(context); + mLayoutInflater = LayoutInflater.from(launcher); mTouchListener = touchListener; mIconClickListener = iconClickListener; mIconLongClickListener = iconLongClickListener; @@ -363,6 +381,14 @@ class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.ViewHol mPredictionBarDividerOffset = (-res.getDimensionPixelSize(R.dimen.all_apps_prediction_icon_bottom_padding) + res.getDimensionPixelSize(R.dimen.all_apps_icon_top_bottom_padding)) / 2; + + // Resolve the market app handling additional searches + PackageManager pm = launcher.getPackageManager(); + ResolveInfo marketInfo = pm.resolveActivity(createMarketSearchIntent(""), + PackageManager.MATCH_DEFAULT_ONLY); + if (marketInfo != null) { + mMarketAppName = marketInfo.loadLabel(pm).toString(); + } } /** @@ -381,10 +407,19 @@ class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.ViewHol } /** - * Sets the text to show when there are no apps. + * Sets the last search query that was made, used to show when there are no results and to also + * seed the intent for searching the market. */ - public void setEmptySearchText(String query) { - mEmptySearchText = query; + public void setLastSearchQuery(String query) { + Resources res = mLauncher.getResources(); + String formatStr = res.getString(R.string.all_apps_no_search_results); + mLastSearchQuery = query; + mEmptySearchMessage = String.format(formatStr, query); + if (mMarketAppName != null) { + mMarketSearchMessage = String.format(res.getString(R.string.all_apps_search_market_message), + mMarketAppName); + mMarketSearchIntent = createMarketSearchIntent(query); + } } /** @@ -413,9 +448,6 @@ class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.ViewHol @Override public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { switch (viewType) { - case EMPTY_SEARCH_VIEW_TYPE: - return new ViewHolder(mLayoutInflater.inflate(R.layout.all_apps_empty_search, parent, - false)); case SECTION_BREAK_VIEW_TYPE: return new ViewHolder(new View(parent.getContext())); case ICON_VIEW_TYPE: { @@ -440,6 +472,22 @@ class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.ViewHol icon.setFocusable(true); return new ViewHolder(icon); } + case EMPTY_SEARCH_VIEW_TYPE: + return new ViewHolder(mLayoutInflater.inflate(R.layout.all_apps_empty_search, + parent, false)); + case SEARCH_MARKET_DIVIDER_VIEW_TYPE: + return new ViewHolder(mLayoutInflater.inflate(R.layout.all_apps_search_market_divider, + parent, false)); + case SEARCH_MARKET_VIEW_TYPE: + View searchMarketView = mLayoutInflater.inflate(R.layout.all_apps_search_market, + parent, false); + searchMarketView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + mLauncher.startSearchFromAllApps(v, mMarketSearchIntent, mLastSearchQuery); + } + }); + return new ViewHolder(searchMarketView); default: throw new RuntimeException("Unexpected view type"); } @@ -461,28 +509,44 @@ class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.ViewHol break; } case EMPTY_SEARCH_VIEW_TYPE: - TextView emptyViewText = (TextView) holder.mContent.findViewById(R.id.empty_text); - emptyViewText.setText(mEmptySearchText); + TextView emptyViewText = (TextView) holder.mContent; + emptyViewText.setText(mEmptySearchMessage); + break; + case SEARCH_MARKET_VIEW_TYPE: + View searchView = holder.mContent; + if (mMarketSearchIntent != null) { + searchView.setVisibility(View.VISIBLE); + searchView.setContentDescription(mMarketSearchMessage); + ((TextView) searchView.findViewById(R.id.search_market_text)) + .setText(mMarketSearchMessage); + } else { + searchView.setVisibility(View.GONE); + } break; } } @Override public int getItemCount() { - if (mApps.hasNoFilteredResults()) { - // For the empty view - return 1; - } return mApps.getAdapterItems().size(); } @Override public int getItemViewType(int position) { - if (mApps.hasNoFilteredResults()) { - return EMPTY_SEARCH_VIEW_TYPE; - } - AlphabeticalAppsList.AdapterItem item = mApps.getAdapterItems().get(position); return item.viewType; } + + /** + * Creates a new market search intent. + */ + private Intent createMarketSearchIntent(String query) { + Uri marketSearchUri = Uri.parse("market://search") + .buildUpon() + .appendQueryParameter("q", query) + .build(); + Intent marketSearchIntent = new Intent(Intent.ACTION_VIEW); + marketSearchIntent.setData(marketSearchUri); + return marketSearchIntent; + } } diff --git a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java index 730c8d15a..5aa973a15 100644 --- a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java +++ b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java @@ -90,6 +90,8 @@ public class AllAppsRecyclerView extends BaseRecyclerView RecyclerView.RecycledViewPool pool = getRecycledViewPool(); int approxRows = (int) Math.ceil(grid.availableHeightPx / grid.allAppsIconSizePx); pool.setMaxRecycledViews(AllAppsGridAdapter.EMPTY_SEARCH_VIEW_TYPE, 1); + pool.setMaxRecycledViews(AllAppsGridAdapter.SEARCH_MARKET_DIVIDER_VIEW_TYPE, 1); + pool.setMaxRecycledViews(AllAppsGridAdapter.SEARCH_MARKET_VIEW_TYPE, 1); pool.setMaxRecycledViews(AllAppsGridAdapter.ICON_VIEW_TYPE, approxRows * mNumAppsPerRow); pool.setMaxRecycledViews(AllAppsGridAdapter.PREDICTION_ICON_VIEW_TYPE, mNumAppsPerRow); pool.setMaxRecycledViews(AllAppsGridAdapter.SECTION_BREAK_VIEW_TYPE, approxRows); diff --git a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java index 47241ce5d..396f75790 100644 --- a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java +++ b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java @@ -81,12 +81,12 @@ public class AlphabeticalAppsList { public int position; // The type of this item public int viewType; - // The row that this item shows up on - public int rowIndex; /** Section & App properties */ // The section for this item public SectionInfo sectionInfo; + // The row that this item shows up on + public int rowIndex; /** App-only properties */ // The section name of this app. Note that there can be multiple items with different @@ -111,14 +111,14 @@ public class AlphabeticalAppsList { } public static AdapterItem asPredictedApp(int pos, SectionInfo section, String sectionName, - int sectionAppIndex, AppInfo appInfo, int appIndex) { + int sectionAppIndex, AppInfo appInfo, int appIndex) { AdapterItem item = asApp(pos, section, sectionName, sectionAppIndex, appInfo, appIndex); item.viewType = AllAppsGridAdapter.PREDICTION_ICON_VIEW_TYPE; return item; } public static AdapterItem asApp(int pos, SectionInfo section, String sectionName, - int sectionAppIndex, AppInfo appInfo, int appIndex) { + int sectionAppIndex, AppInfo appInfo, int appIndex) { AdapterItem item = new AdapterItem(); item.viewType = AllAppsGridAdapter.ICON_VIEW_TYPE; item.position = pos; @@ -129,6 +129,27 @@ public class AlphabeticalAppsList { item.appIndex = appIndex; return item; } + + public static AdapterItem asEmptySearch(int pos) { + AdapterItem item = new AdapterItem(); + item.viewType = AllAppsGridAdapter.EMPTY_SEARCH_VIEW_TYPE; + item.position = pos; + return item; + } + + public static AdapterItem asDivider(int pos) { + AdapterItem item = new AdapterItem(); + item.viewType = AllAppsGridAdapter.SEARCH_MARKET_DIVIDER_VIEW_TYPE; + item.position = pos; + return item; + } + + public static AdapterItem asMarketSearch(int pos) { + AdapterItem item = new AdapterItem(); + item.viewType = AllAppsGridAdapter.SEARCH_MARKET_VIEW_TYPE; + item.position = pos; + return item; + } } /** @@ -167,6 +188,7 @@ public class AlphabeticalAppsList { private int mNumAppsPerRow; private int mNumPredictedAppsPerRow; private int mNumAppRowsInAdapter; + private boolean mDisableEmptyText; public AlphabeticalAppsList(Context context) { mLauncher = (Launcher) context; @@ -194,6 +216,13 @@ public class AlphabeticalAppsList { } /** + * Disables the empty text message when there are no search results. + */ + public void disableEmptyText() { + mDisableEmptyText = true; + } + + /** * Returns all the apps. */ public List<AppInfo> getApps() { @@ -222,17 +251,17 @@ public class AlphabeticalAppsList { } /** - * Returns the number of applications in this list. + * Returns the number of rows of applications (not including predictions) */ - public int getSize() { - return mFilteredApps.size(); + public int getNumAppRows() { + return mNumAppRowsInAdapter; } /** - * Returns the number of rows of applications (not including predictions) + * Returns the number of applications in this list. */ - public int getNumAppRows() { - return mNumAppRowsInAdapter; + public int getNumFilteredApps() { + return mFilteredApps.size(); } /** @@ -457,6 +486,16 @@ public class AlphabeticalAppsList { mFilteredApps.add(info); } + // Append the search market item if we are currently searching + if (hasFilter()) { + if (hasNoFilteredResults()) { + mAdapterItems.add(AdapterItem.asEmptySearch(position++)); + } else { + mAdapterItems.add(AdapterItem.asDivider(position++)); + } + mAdapterItems.add(AdapterItem.asMarketSearch(position++)); + } + // Merge multiple sections together as requested by the merge strategy for this device mergeSections(); diff --git a/src/com/android/launcher3/allapps/DefaultAppSearchController.java b/src/com/android/launcher3/allapps/DefaultAppSearchController.java index 83b920589..14cbb9534 100644 --- a/src/com/android/launcher3/allapps/DefaultAppSearchController.java +++ b/src/com/android/launcher3/allapps/DefaultAppSearchController.java @@ -169,19 +169,21 @@ final class DefaultAppSearchController extends AllAppsSearchBarController if (actionId != EditorInfo.IME_ACTION_DONE) { return false; } - // Skip if there isn't exactly one item - if (mApps.getSize() != 1) { + // Skip if there are more than one icon + if (mApps.getNumFilteredApps() > 1) { return false; } - // If there is exactly one icon, then quick-launch it + // Otherwise, find the first icon, or fallback to the search-market-view and launch it List<AlphabeticalAppsList.AdapterItem> items = mApps.getAdapterItems(); for (int i = 0; i < items.size(); i++) { AlphabeticalAppsList.AdapterItem item = items.get(i); - if (item.viewType == AllAppsGridAdapter.ICON_VIEW_TYPE) { - mAppsRecyclerView.getChildAt(i).performClick(); - mInputMethodManager.hideSoftInputFromWindow( - mContainerView.getWindowToken(), 0); - return true; + switch (item.viewType) { + case AllAppsGridAdapter.ICON_VIEW_TYPE: + case AllAppsGridAdapter.SEARCH_MARKET_VIEW_TYPE: + mAppsRecyclerView.getChildAt(i).performClick(); + mInputMethodManager.hideSoftInputFromWindow( + mContainerView.getWindowToken(), 0); + return true; } } return false; diff --git a/src/com/android/launcher3/testing/LauncherExtension.java b/src/com/android/launcher3/testing/LauncherExtension.java index 34492e4ca..8702877bf 100644 --- a/src/com/android/launcher3/testing/LauncherExtension.java +++ b/src/com/android/launcher3/testing/LauncherExtension.java @@ -202,6 +202,11 @@ public class LauncherExtension extends Launcher { } @Override + public boolean startSearchFromAllApps(String query) { + return false; + } + + @Override public void startVoice() { } |