Merge "Adding market search." into ub-launcher3-burnaby

This commit is contained in:
Winson Chung
2015-08-10 22:55:40 +00:00
committed by Android (Google) Code Review
27 changed files with 327 additions and 88 deletions
Binary file not shown.

Before

Width:  |  Height:  |  Size: 190 B

After

Width:  |  Height:  |  Size: 152 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 743 B

After

Width:  |  Height:  |  Size: 419 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 151 B

After

Width:  |  Height:  |  Size: 117 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 497 B

After

Width:  |  Height:  |  Size: 263 B

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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.
-->
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="@color/all_apps_search_market_button_focused_bg_color">
<item android:drawable="@color/quantum_panel_bg_color" />
</ripple>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 234 B

After

Width:  |  Height:  |  Size: 151 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 972 B

After

Width:  |  Height:  |  Size: 497 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 308 B

After

Width:  |  Height:  |  Size: 190 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 743 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 359 B

After

Width:  |  Height:  |  Size: 234 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 972 B

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_focused="true" android:drawable="@color/all_apps_search_market_button_focused_bg_color" />
<item android:state_pressed="true" android:drawable="@color/all_apps_search_market_button_focused_bg_color" />
<item android:drawable="@android:color/transparent" />
</selector>
+21
View File
@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
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.
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<size android:height="1dp" />
<solid android:color="#ddd" />
</shape>
+9 -6
View File
@@ -18,11 +18,14 @@
android:id="@+id/empty_text" android:id="@+id/empty_text"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:gravity="center" android:gravity="start"
android:paddingTop="24dp" android:paddingTop="20dp"
android:paddingBottom="24dp" android:paddingBottom="8dp"
android:paddingRight="@dimen/all_apps_grid_view_start_margin" android:paddingLeft="16dp"
android:textSize="16sp" android:paddingRight="16dp"
android:textColor="#4c4c4c" android:fontFamily="sans-serif-medium"
android:textSize="14sp"
android:textColor="#212121"
android:alpha="0.56"
android:focusable="false" /> android:focusable="false" />
+5 -8
View File
@@ -32,11 +32,10 @@
android:id="@+id/dismiss_search_button" android:id="@+id/dismiss_search_button"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginLeft="4dp" android:layout_gravity="center_vertical"
android:layout_marginStart="4dp" android:layout_marginLeft="16dp"
android:layout_marginStart="16dp"
android:contentDescription="@string/all_apps_button_label" android:contentDescription="@string/all_apps_button_label"
android:paddingBottom="13dp"
android:paddingTop="13dp"
android:src="@drawable/ic_arrow_back_grey" /> android:src="@drawable/ic_arrow_back_grey" />
<com.android.launcher3.allapps.AllAppsSearchEditView <com.android.launcher3.allapps.AllAppsSearchEditView
@@ -63,10 +62,8 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="@dimen/all_apps_search_bar_height" android:layout_height="@dimen/all_apps_search_bar_height"
android:layout_gravity="end|center_vertical" android:layout_gravity="end|center_vertical"
android:layout_marginEnd="4dp" android:layout_marginEnd="16dp"
android:layout_marginRight="4dp" android:layout_marginRight="16dp"
android:contentDescription="@string/all_apps_search_bar_hint" android:contentDescription="@string/all_apps_search_bar_hint"
android:paddingBottom="13dp"
android:paddingTop="13dp"
android:src="@drawable/ic_search_grey" /> android:src="@drawable/ic_search_grey" />
</FrameLayout> </FrameLayout>
+29
View File
@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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.
-->
<TextView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/search_market_text"
android:layout_width="wrap_content"
android:layout_height="48dp"
android:gravity="start|center_vertical"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:fontFamily="sans-serif-medium"
android:textSize="14sp"
android:textColor="#009688"
android:textAllCaps="true"
android:focusable="false"
android:background="@drawable/all_apps_search_market_bg" />
@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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.
-->
<ImageView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:paddingTop="16dp"
android:paddingBottom="8dp"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:focusable="false"
android:scaleType="matrix"
android:src="@drawable/horizontal_line" />
+1
View File
@@ -44,6 +44,7 @@
<!-- All Apps --> <!-- All Apps -->
<color name="all_apps_grid_section_text_color">#009688</color> <color name="all_apps_grid_section_text_color">#009688</color>
<color name="all_apps_search_market_button_focused_bg_color">#DDDDDD</color>
<!-- Widgets view --> <!-- Widgets view -->
<color name="widgets_view_fastscroll_thumb_inactive_color">#42FFFFFF</color> <color name="widgets_view_fastscroll_thumb_inactive_color">#42FFFFFF</color>
+5 -2
View File
@@ -24,10 +24,10 @@
<!-- URI used to import old favorites. [DO NOT TRANSLATE] --> <!-- URI used to import old favorites. [DO NOT TRANSLATE] -->
<string name="old_launcher_provider_uri" translatable="false">content://com.android.launcher2.settings/favorites?notify=true</string> <string name="old_launcher_provider_uri" translatable="false">content://com.android.launcher2.settings/favorites?notify=true</string>
<!-- Permission to receive the com.android.launcher3.action.LAUNCH intent --> <!-- Permission to receive the com.android.launcher3.action.LAUNCH intent. [DO NOT TRANSLATE] -->
<string name="receive_launch_broadcasts_permission" translatable="false">com.android.launcher3.permission.RECEIVE_LAUNCH_BROADCASTS</string> <string name="receive_launch_broadcasts_permission" translatable="false">com.android.launcher3.permission.RECEIVE_LAUNCH_BROADCASTS</string>
<!-- Permission to receive the com.android.launcher3.action.FIRST_LOAD_COMPLETE intent --> <!-- Permission to receive the com.android.launcher3.action.FIRST_LOAD_COMPLETE intent. [DO NOT TRANSLATE] -->
<string name="receive_first_load_broadcast_permission" translatable="false">com.android.launcher3.permission.RECEIVE_FIRST_LOAD_BROADCAST</string> <string name="receive_first_load_broadcast_permission" translatable="false">com.android.launcher3.permission.RECEIVE_FIRST_LOAD_BROADCAST</string>
<!-- Application name --> <!-- Application name -->
@@ -61,6 +61,9 @@
<string name="all_apps_loading_message">Loading Apps&#8230;</string> <string name="all_apps_loading_message">Loading Apps&#8230;</string>
<!-- No-search-results text. [CHAR_LIMIT=50] --> <!-- No-search-results text. [CHAR_LIMIT=50] -->
<string name="all_apps_no_search_results">No Apps found matching \"<xliff:g id="query" example="Android">%1$s</xliff:g>\"</string> <string name="all_apps_no_search_results">No Apps found matching \"<xliff:g id="query" example="Android">%1$s</xliff:g>\"</string>
<!-- Search market text. This is a format string where the first argument is the name of the activity
handling the search. The format string does not need to handle both of these arguments. [CHAR_LIMIT=50] -->
<string name="all_apps_search_market_message">Go to <xliff:g id="query" example="Play Store">%1$s</xliff:g></string>
<!-- Drag and drop --> <!-- Drag and drop -->
<skip /> <skip />
+14 -1
View File
@@ -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() { public boolean isOnCustomContent() {
return mWorkspace.isOnOrMovingToCustomContent(); return mWorkspace.isOnOrMovingToCustomContent();
} }
@@ -2538,6 +2547,10 @@ public class Launcher extends Activity
if (!isAppsViewVisible()) { if (!isAppsViewVisible()) {
showAppsView(true /* animated */, false /* resetListToTop */, showAppsView(true /* animated */, false /* resetListToTop */,
true /* updatePredictedApps */, false /* focusSearchBar */); true /* updatePredictedApps */, false /* focusSearchBar */);
if (mLauncherCallbacks != null) {
mLauncherCallbacks.onClickAllAppsButton(v);
}
} }
} }
@@ -2929,7 +2942,7 @@ public class Launcher extends Activity
return false; return false;
} }
@Thunk boolean startActivitySafely(View v, Intent intent, Object tag) { public boolean startActivitySafely(View v, Intent intent, Object tag) {
boolean success = false; boolean success = false;
if (mIsSafeModeEnabled && !Utilities.isSystemApp(this, intent)) { if (mIsSafeModeEnabled && !Utilities.isSystemApp(this, intent)) {
Toast.makeText(this, R.string.safemode_shortcut_error, Toast.LENGTH_SHORT).show(); Toast.makeText(this, R.string.safemode_shortcut_error, Toast.LENGTH_SHORT).show();
@@ -77,6 +77,7 @@ public interface LauncherCallbacks {
public boolean providesSearch(); public boolean providesSearch();
public boolean startSearch(String initialQuery, boolean selectInitialQuery, public boolean startSearch(String initialQuery, boolean selectInitialQuery,
Bundle appSearchData, Rect sourceBounds); Bundle appSearchData, Rect sourceBounds);
public boolean startSearchFromAllApps(String query);
@Deprecated @Deprecated
public void startVoice(); public void startVoice();
public boolean hasCustomContentToLeft(); public boolean hasCustomContentToLeft();
@@ -16,34 +16,26 @@
package com.android.launcher3.allapps; package com.android.launcher3.allapps;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.content.Context; import android.content.Context;
import android.content.Intent;
import android.content.res.Resources; import android.content.res.Resources;
import android.graphics.Point; import android.graphics.Point;
import android.graphics.Rect; import android.graphics.Rect;
import android.graphics.drawable.InsetDrawable; import android.graphics.drawable.InsetDrawable;
import android.os.Build;
import android.os.Bundle;
import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView;
import android.text.Selection; import android.text.Selection;
import android.text.SpannableStringBuilder; import android.text.SpannableStringBuilder;
import android.text.method.TextKeyListener; import android.text.method.TextKeyListener;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.view.KeyEvent; import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.MotionEvent; import android.view.MotionEvent;
import android.view.View; import android.view.View;
import android.view.ViewConfiguration; import android.view.ViewConfiguration;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.widget.FrameLayout;
import android.widget.LinearLayout; import android.widget.LinearLayout;
import com.android.launcher3.AppInfo; import com.android.launcher3.AppInfo;
import com.android.launcher3.BaseContainerView; import com.android.launcher3.BaseContainerView;
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.CellLayout; import com.android.launcher3.CellLayout;
import com.android.launcher3.CheckLongPressHelper;
import com.android.launcher3.DeleteDropTarget; import com.android.launcher3.DeleteDropTarget;
import com.android.launcher3.DeviceProfile; import com.android.launcher3.DeviceProfile;
import com.android.launcher3.DragSource; import com.android.launcher3.DragSource;
@@ -53,7 +45,6 @@ import com.android.launcher3.ItemInfo;
import com.android.launcher3.Launcher; import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherTransitionable; import com.android.launcher3.LauncherTransitionable;
import com.android.launcher3.R; import com.android.launcher3.R;
import com.android.launcher3.Stats;
import com.android.launcher3.Utilities; import com.android.launcher3.Utilities;
import com.android.launcher3.Workspace; import com.android.launcher3.Workspace;
import com.android.launcher3.util.ComponentKey; import com.android.launcher3.util.ComponentKey;
@@ -155,6 +146,7 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
@Thunk AllAppsSearchBarController mSearchBarController; @Thunk AllAppsSearchBarController mSearchBarController;
private ViewGroup mSearchBarContainerView; private ViewGroup mSearchBarContainerView;
private View mSearchBarView; private View mSearchBarView;
private SpannableStringBuilder mSearchQueryBuilder = null;
private int mSectionNamesMargin; private int mSectionNamesMargin;
private int mNumAppsPerRow; private int mNumAppsPerRow;
@@ -165,7 +157,13 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
// This coordinate is relative to its parent // This coordinate is relative to its parent
private final Point mIconLastTouchPos = new Point(); 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) { public AllAppsContainerView(Context context) {
this(context, null); this(context, null);
@@ -182,8 +180,7 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
mLauncher = (Launcher) context; mLauncher = (Launcher) context;
mSectionNamesMargin = res.getDimensionPixelSize(R.dimen.all_apps_grid_view_start_margin); mSectionNamesMargin = res.getDimensionPixelSize(R.dimen.all_apps_grid_view_start_margin);
mApps = new AlphabeticalAppsList(context); mApps = new AlphabeticalAppsList(context);
mAdapter = new AllAppsGridAdapter(context, mApps, this, mLauncher, this); mAdapter = new AllAppsGridAdapter(mLauncher, mApps, this, mLauncher, this);
mAdapter.setEmptySearchText(res.getString(R.string.all_apps_loading_message));
mApps.setAdapter(mAdapter); mApps.setAdapter(mAdapter);
mLayoutManager = mAdapter.getLayoutManager(); mLayoutManager = mAdapter.getLayoutManager();
mItemDecoration = mAdapter.getItemDecoration(); mItemDecoration = mAdapter.getItemDecoration();
@@ -615,13 +612,9 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
@Override @Override
public void onSearchResult(String query, ArrayList<ComponentKey> apps) { public void onSearchResult(String query, ArrayList<ComponentKey> apps) {
if (apps != null) { 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); mApps.setOrderedFilter(apps);
mAdapter.setLastSearchQuery(query);
mAppsRecyclerView.scrollToTop();
} }
} }
@@ -16,14 +16,17 @@
package com.android.launcher3.allapps; package com.android.launcher3.allapps;
import android.content.Context; 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.content.res.Resources;
import android.graphics.Canvas; import android.graphics.Canvas;
import android.graphics.Paint; import android.graphics.Paint;
import android.graphics.PointF; import android.graphics.PointF;
import android.graphics.Rect; import android.graphics.Rect;
import android.os.Handler;
import android.support.v4.view.accessibility.AccessibilityRecordCompat; import android.support.v4.view.accessibility.AccessibilityRecordCompat;
import android.support.v4.view.accessibility.AccessibilityEventCompat; import android.support.v4.view.accessibility.AccessibilityEventCompat;
import android.net.Uri;
import android.support.v7.widget.GridLayoutManager; import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater; import android.view.LayoutInflater;
@@ -34,6 +37,7 @@ import android.view.accessibility.AccessibilityEvent;
import android.widget.TextView; import android.widget.TextView;
import com.android.launcher3.AppInfo; import com.android.launcher3.AppInfo;
import com.android.launcher3.BubbleTextView; import com.android.launcher3.BubbleTextView;
import com.android.launcher3.Launcher;
import com.android.launcher3.R; import com.android.launcher3.R;
import com.android.launcher3.Utilities; import com.android.launcher3.Utilities;
import com.android.launcher3.util.Thunk; 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; public static final int PREDICTION_ICON_VIEW_TYPE = 2;
// The message shown when there are no filtered results // The message shown when there are no filtered results
public static final int EMPTY_SEARCH_VIEW_TYPE = 3; 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. * ViewHolder for each icon.
@@ -83,12 +91,12 @@ class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.ViewHol
@Override @Override
public void onInitializeAccessibilityEvent(AccessibilityEvent event) { public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
super.onInitializeAccessibilityEvent(event); super.onInitializeAccessibilityEvent(event);
if (mApps.hasNoFilteredResults()) {
// Disregard the no-search-results text as a list item for accessibility // Ensure that we only report the number apps for accessibility not including other
final AccessibilityRecordCompat record = AccessibilityEventCompat // adapter views
.asRecord(event); final AccessibilityRecordCompat record = AccessibilityEventCompat
record.setItemCount(0); .asRecord(event);
} record.setItemCount(mApps.getNumFilteredApps());
} }
@Override @Override
@@ -115,11 +123,6 @@ class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.ViewHol
@Override @Override
public int getSpanSize(int position) { public int getSpanSize(int position) {
if (mApps.hasNoFilteredResults()) {
// Empty view spans full width
return mAppsPerRow;
}
switch (mApps.getAdapterItems().get(position).viewType) { switch (mApps.getAdapterItems().get(position).viewType) {
case AllAppsGridAdapter.ICON_VIEW_TYPE: case AllAppsGridAdapter.ICON_VIEW_TYPE:
case AllAppsGridAdapter.PREDICTION_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; private LayoutInflater mLayoutInflater;
@Thunk AlphabeticalAppsList mApps; @Thunk AlphabeticalAppsList mApps;
private GridLayoutManager mGridLayoutMgr; private GridLayoutManager mGridLayoutMgr;
@@ -326,7 +330,19 @@ class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.ViewHol
@Thunk int mPredictionBarDividerOffset; @Thunk int mPredictionBarDividerOffset;
@Thunk int mAppsPerRow; @Thunk int mAppsPerRow;
@Thunk boolean mIsRtl; @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 // Section drawing
@Thunk int mSectionNamesMargin; @Thunk int mSectionNamesMargin;
@@ -334,16 +350,18 @@ class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.ViewHol
@Thunk Paint mSectionTextPaint; @Thunk Paint mSectionTextPaint;
@Thunk Paint mPredictedAppsDividerPaint; @Thunk Paint mPredictedAppsDividerPaint;
public AllAppsGridAdapter(Context context, AlphabeticalAppsList apps, public AllAppsGridAdapter(Launcher launcher, AlphabeticalAppsList apps,
View.OnTouchListener touchListener, View.OnClickListener iconClickListener, View.OnTouchListener touchListener, View.OnClickListener iconClickListener,
View.OnLongClickListener iconLongClickListener) { View.OnLongClickListener iconLongClickListener) {
Resources res = context.getResources(); Resources res = launcher.getResources();
mLauncher = launcher;
mApps = apps; mApps = apps;
mEmptySearchMessage = res.getString(R.string.all_apps_loading_message);
mGridSizer = new GridSpanSizer(); mGridSizer = new GridSpanSizer();
mGridLayoutMgr = new AppsGridLayoutManager(context); mGridLayoutMgr = new AppsGridLayoutManager(launcher);
mGridLayoutMgr.setSpanSizeLookup(mGridSizer); mGridLayoutMgr.setSpanSizeLookup(mGridSizer);
mItemDecoration = new GridItemDecoration(); mItemDecoration = new GridItemDecoration();
mLayoutInflater = LayoutInflater.from(context); mLayoutInflater = LayoutInflater.from(launcher);
mTouchListener = touchListener; mTouchListener = touchListener;
mIconClickListener = iconClickListener; mIconClickListener = iconClickListener;
mIconLongClickListener = iconLongClickListener; mIconLongClickListener = iconLongClickListener;
@@ -363,6 +381,14 @@ class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.ViewHol
mPredictionBarDividerOffset = mPredictionBarDividerOffset =
(-res.getDimensionPixelSize(R.dimen.all_apps_prediction_icon_bottom_padding) + (-res.getDimensionPixelSize(R.dimen.all_apps_prediction_icon_bottom_padding) +
res.getDimensionPixelSize(R.dimen.all_apps_icon_top_bottom_padding)) / 2; 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) { public void setLastSearchQuery(String query) {
mEmptySearchText = 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 @Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
switch (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: case SECTION_BREAK_VIEW_TYPE:
return new ViewHolder(new View(parent.getContext())); return new ViewHolder(new View(parent.getContext()));
case ICON_VIEW_TYPE: { case ICON_VIEW_TYPE: {
@@ -440,6 +472,22 @@ class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.ViewHol
icon.setFocusable(true); icon.setFocusable(true);
return new ViewHolder(icon); 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: default:
throw new RuntimeException("Unexpected view type"); throw new RuntimeException("Unexpected view type");
} }
@@ -461,28 +509,44 @@ class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.ViewHol
break; break;
} }
case EMPTY_SEARCH_VIEW_TYPE: case EMPTY_SEARCH_VIEW_TYPE:
TextView emptyViewText = (TextView) holder.mContent.findViewById(R.id.empty_text); TextView emptyViewText = (TextView) holder.mContent;
emptyViewText.setText(mEmptySearchText); 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; break;
} }
} }
@Override @Override
public int getItemCount() { public int getItemCount() {
if (mApps.hasNoFilteredResults()) {
// For the empty view
return 1;
}
return mApps.getAdapterItems().size(); return mApps.getAdapterItems().size();
} }
@Override @Override
public int getItemViewType(int position) { public int getItemViewType(int position) {
if (mApps.hasNoFilteredResults()) {
return EMPTY_SEARCH_VIEW_TYPE;
}
AlphabeticalAppsList.AdapterItem item = mApps.getAdapterItems().get(position); AlphabeticalAppsList.AdapterItem item = mApps.getAdapterItems().get(position);
return item.viewType; 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;
}
} }
@@ -90,6 +90,8 @@ public class AllAppsRecyclerView extends BaseRecyclerView
RecyclerView.RecycledViewPool pool = getRecycledViewPool(); RecyclerView.RecycledViewPool pool = getRecycledViewPool();
int approxRows = (int) Math.ceil(grid.availableHeightPx / grid.allAppsIconSizePx); int approxRows = (int) Math.ceil(grid.availableHeightPx / grid.allAppsIconSizePx);
pool.setMaxRecycledViews(AllAppsGridAdapter.EMPTY_SEARCH_VIEW_TYPE, 1); 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.ICON_VIEW_TYPE, approxRows * mNumAppsPerRow);
pool.setMaxRecycledViews(AllAppsGridAdapter.PREDICTION_ICON_VIEW_TYPE, mNumAppsPerRow); pool.setMaxRecycledViews(AllAppsGridAdapter.PREDICTION_ICON_VIEW_TYPE, mNumAppsPerRow);
pool.setMaxRecycledViews(AllAppsGridAdapter.SECTION_BREAK_VIEW_TYPE, approxRows); pool.setMaxRecycledViews(AllAppsGridAdapter.SECTION_BREAK_VIEW_TYPE, approxRows);
@@ -81,12 +81,12 @@ public class AlphabeticalAppsList {
public int position; public int position;
// The type of this item // The type of this item
public int viewType; public int viewType;
// The row that this item shows up on
public int rowIndex;
/** Section & App properties */ /** Section & App properties */
// The section for this item // The section for this item
public SectionInfo sectionInfo; public SectionInfo sectionInfo;
// The row that this item shows up on
public int rowIndex;
/** App-only properties */ /** App-only properties */
// The section name of this app. Note that there can be multiple items with different // 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, 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); AdapterItem item = asApp(pos, section, sectionName, sectionAppIndex, appInfo, appIndex);
item.viewType = AllAppsGridAdapter.PREDICTION_ICON_VIEW_TYPE; item.viewType = AllAppsGridAdapter.PREDICTION_ICON_VIEW_TYPE;
return item; return item;
} }
public static AdapterItem asApp(int pos, SectionInfo section, String sectionName, 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(); AdapterItem item = new AdapterItem();
item.viewType = AllAppsGridAdapter.ICON_VIEW_TYPE; item.viewType = AllAppsGridAdapter.ICON_VIEW_TYPE;
item.position = pos; item.position = pos;
@@ -129,6 +129,27 @@ public class AlphabeticalAppsList {
item.appIndex = appIndex; item.appIndex = appIndex;
return item; 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 mNumAppsPerRow;
private int mNumPredictedAppsPerRow; private int mNumPredictedAppsPerRow;
private int mNumAppRowsInAdapter; private int mNumAppRowsInAdapter;
private boolean mDisableEmptyText;
public AlphabeticalAppsList(Context context) { public AlphabeticalAppsList(Context context) {
mLauncher = (Launcher) context; mLauncher = (Launcher) context;
@@ -193,6 +215,13 @@ public class AlphabeticalAppsList {
mAdapter = adapter; mAdapter = adapter;
} }
/**
* Disables the empty text message when there are no search results.
*/
public void disableEmptyText() {
mDisableEmptyText = true;
}
/** /**
* Returns all the apps. * Returns all the apps.
*/ */
@@ -221,13 +250,6 @@ public class AlphabeticalAppsList {
return mAdapterItems; return mAdapterItems;
} }
/**
* Returns the number of applications in this list.
*/
public int getSize() {
return mFilteredApps.size();
}
/** /**
* Returns the number of rows of applications (not including predictions) * Returns the number of rows of applications (not including predictions)
*/ */
@@ -235,6 +257,13 @@ public class AlphabeticalAppsList {
return mNumAppRowsInAdapter; return mNumAppRowsInAdapter;
} }
/**
* Returns the number of applications in this list.
*/
public int getNumFilteredApps() {
return mFilteredApps.size();
}
/** /**
* Returns whether there are is a filter set. * Returns whether there are is a filter set.
*/ */
@@ -457,6 +486,16 @@ public class AlphabeticalAppsList {
mFilteredApps.add(info); 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 // Merge multiple sections together as requested by the merge strategy for this device
mergeSections(); mergeSections();
@@ -169,19 +169,21 @@ final class DefaultAppSearchController extends AllAppsSearchBarController
if (actionId != EditorInfo.IME_ACTION_DONE) { if (actionId != EditorInfo.IME_ACTION_DONE) {
return false; return false;
} }
// Skip if there isn't exactly one item // Skip if there are more than one icon
if (mApps.getSize() != 1) { if (mApps.getNumFilteredApps() > 1) {
return false; 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(); List<AlphabeticalAppsList.AdapterItem> items = mApps.getAdapterItems();
for (int i = 0; i < items.size(); i++) { for (int i = 0; i < items.size(); i++) {
AlphabeticalAppsList.AdapterItem item = items.get(i); AlphabeticalAppsList.AdapterItem item = items.get(i);
if (item.viewType == AllAppsGridAdapter.ICON_VIEW_TYPE) { switch (item.viewType) {
mAppsRecyclerView.getChildAt(i).performClick(); case AllAppsGridAdapter.ICON_VIEW_TYPE:
mInputMethodManager.hideSoftInputFromWindow( case AllAppsGridAdapter.SEARCH_MARKET_VIEW_TYPE:
mContainerView.getWindowToken(), 0); mAppsRecyclerView.getChildAt(i).performClick();
return true; mInputMethodManager.hideSoftInputFromWindow(
mContainerView.getWindowToken(), 0);
return true;
} }
} }
return false; return false;
@@ -201,6 +201,11 @@ public class LauncherExtension extends Launcher {
return false; return false;
} }
@Override
public boolean startSearchFromAllApps(String query) {
return false;
}
@Override @Override
public void startVoice() { public void startVoice() {
} }