summaryrefslogtreecommitdiffstats
path: root/src/com/android/launcher3/allapps
diff options
context:
space:
mode:
authorWinson Chung <winsonc@google.com>2015-06-04 17:18:17 -0700
committerWinson Chung <winsonc@google.com>2015-06-15 14:22:47 -0700
commitef7f874a889b609bd34e692b9c9a1f8cefd1ea95 (patch)
tree23d364f99ae51ea31a9d4e9e9c5bcf7048db7184 /src/com/android/launcher3/allapps
parente89cf793ab22fcb8cea3ff94ca981659208446db (diff)
downloadandroid_packages_apps_Trebuchet-ef7f874a889b609bd34e692b9c9a1f8cefd1ea95.tar.gz
android_packages_apps_Trebuchet-ef7f874a889b609bd34e692b9c9a1f8cefd1ea95.tar.bz2
android_packages_apps_Trebuchet-ef7f874a889b609bd34e692b9c9a1f8cefd1ea95.zip
Refactoring all apps search to support external search bar.
- Adding support for an external search bar that can be used to search a container view. This adds a new interface AllAppsSearchController which manages the external search bar. Each controller will have its own search implementation which means that we no longer need a common AppSearchManager interface. - Removing elevation controller as we no longer have a builtin search bar in all apps - Refactoring container view insets so that they behave the same in all containers. - Refactoring apps view to ensure that we only update the number of columns with the available width - Cleaning up LauncherCallbacks interface Bug: 20127840 Bug: 21494973 Change-Id: I710b8e18196961d77d8a29f0c345531d480936fe
Diffstat (limited to 'src/com/android/launcher3/allapps')
-rw-r--r--src/com/android/launcher3/allapps/AllAppsContainerView.java644
-rw-r--r--src/com/android/launcher3/allapps/AllAppsGridAdapter.java35
-rw-r--r--src/com/android/launcher3/allapps/AllAppsRecyclerView.java23
-rw-r--r--src/com/android/launcher3/allapps/AllAppsSearchBarController.java97
-rw-r--r--src/com/android/launcher3/allapps/AlphabeticalAppsList.java21
-rw-r--r--src/com/android/launcher3/allapps/AppSearchManager.java59
-rw-r--r--src/com/android/launcher3/allapps/DefaultAppSearchAlgorithm.java (renamed from src/com/android/launcher3/allapps/SimpleAppSearchManagerImpl.java)21
-rw-r--r--src/com/android/launcher3/allapps/DefaultAppSearchController.java270
8 files changed, 610 insertions, 560 deletions
diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java
index 855a4430d..b300cae62 100644
--- a/src/com/android/launcher3/allapps/AllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java
@@ -20,17 +20,15 @@ import android.annotation.TargetApi;
import android.content.ComponentName;
import android.content.Context;
import android.content.res.Resources;
-import android.graphics.Paint;
import android.graphics.Point;
import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.InsetDrawable;
import android.os.Build;
import android.os.Bundle;
import android.support.v7.widget.RecyclerView;
-import android.text.Editable;
-import android.text.TextWatcher;
+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;
@@ -39,11 +37,8 @@ import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
-import android.view.inputmethod.EditorInfo;
-import android.view.inputmethod.InputMethodManager;
import android.widget.FrameLayout;
-import android.widget.TextView;
-
+import android.widget.LinearLayout;
import com.android.launcher3.AppInfo;
import com.android.launcher3.BaseContainerView;
import com.android.launcher3.BubbleTextView;
@@ -54,7 +49,6 @@ import com.android.launcher3.DeviceProfile;
import com.android.launcher3.DragSource;
import com.android.launcher3.DropTarget;
import com.android.launcher3.Folder;
-import com.android.launcher3.Insettable;
import com.android.launcher3.ItemInfo;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherTransitionable;
@@ -62,131 +56,24 @@ import com.android.launcher3.R;
import com.android.launcher3.Stats;
import com.android.launcher3.Utilities;
import com.android.launcher3.Workspace;
-import com.android.launcher3.allapps.AppSearchManager.AppSearchResultCallback;
import com.android.launcher3.util.Thunk;
import java.util.ArrayList;
import java.util.List;
-/**
- * Interface for controlling the header elevation in response to RecyclerView scroll.
- */
-interface HeaderElevationController {
- void onScroll(int scrollY);
- void updateBackgroundPadding(Drawable bg);
- void disable();
-}
-
-/**
- * Implementation of the header elevation mechanism for pre-L devices. It simulates elevation
- * by drawing a gradient under the header bar.
- */
-final class HeaderElevationControllerV16 implements HeaderElevationController {
-
- private final View mShadow;
- private final float mScrollToElevation;
- private final Rect mTmpRect = new Rect();
-
- public HeaderElevationControllerV16(View header) {
- Resources res = header.getContext().getResources();
- mScrollToElevation = res.getDimension(R.dimen.all_apps_header_scroll_to_elevation);
-
- mShadow = new View(header.getContext());
- mShadow.setBackground(new GradientDrawable(
- GradientDrawable.Orientation.TOP_BOTTOM, new int[] {0x44000000, 0x00000000}));
- mShadow.setAlpha(0);
-
- FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(
- FrameLayout.LayoutParams.MATCH_PARENT,
- res.getDimensionPixelSize(R.dimen.all_apps_header_shadow_height));
- lp.topMargin = ((FrameLayout.LayoutParams) header.getLayoutParams()).height;
-
- ((ViewGroup) header.getParent()).addView(mShadow, lp);
- }
-
- @Override
- public void onScroll(int scrollY) {
- float elevationPct = (float) Math.min(scrollY, mScrollToElevation) /
- mScrollToElevation;
- mShadow.setAlpha(elevationPct);
- }
-
- @Override
- public void updateBackgroundPadding(Drawable bg) {
- bg.getPadding(mTmpRect);
- FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mShadow.getLayoutParams();
- lp.leftMargin = mTmpRect.left;
- lp.rightMargin = mTmpRect.right;
- mShadow.requestLayout();
- }
-
- @Override
- public void disable() {
- ViewGroup parent = (ViewGroup) mShadow.getParent();
- if (parent != null) {
- parent.removeView(mShadow);
- }
- }
-}
-
-/**
- * Implementation of the header elevation mechanism for L+ devices, which makes use of the native
- * view elevation.
- */
-@TargetApi(Build.VERSION_CODES.LOLLIPOP)
-final class HeaderElevationControllerVL implements HeaderElevationController {
-
- private final View mHeader;
- private final float mMaxElevation;
- private final float mScrollToElevation;
-
- public HeaderElevationControllerVL(View header) {
- mHeader = header;
-
- Resources res = header.getContext().getResources();
- mMaxElevation = res.getDimension(R.dimen.all_apps_header_max_elevation);
- mScrollToElevation = res.getDimension(R.dimen.all_apps_header_scroll_to_elevation);
- }
-
- @Override
- public void onScroll(int scrollY) {
- float elevationPct = (float) Math.min(scrollY, mScrollToElevation) /
- mScrollToElevation;
- float newElevation = mMaxElevation * elevationPct;
- if (Float.compare(mHeader.getElevation(), newElevation) != 0) {
- mHeader.setElevation(newElevation);
- }
- }
-
- @Override
- public void updateBackgroundPadding(Drawable bg) {
- // Do nothing, the background padding on the header view is already applied
- }
-
- @Override
- public void disable() { }
-}
/**
* The all apps view container.
*/
-public class AllAppsContainerView extends BaseContainerView implements DragSource, Insettable,
- TextWatcher, TextView.OnEditorActionListener, LauncherTransitionable,
- AlphabeticalAppsList.AdapterChangedCallback, AllAppsGridAdapter.PredictionBarSpacerCallbacks,
- View.OnTouchListener, View.OnClickListener, View.OnLongClickListener,
- ViewTreeObserver.OnPreDrawListener, AppSearchResultCallback, Stats.LaunchSourceProvider {
+public class AllAppsContainerView extends BaseContainerView implements DragSource,
+ LauncherTransitionable, AlphabeticalAppsList.AdapterChangedCallback,
+ AllAppsGridAdapter.PredictionBarSpacerCallbacks, View.OnTouchListener,
+ View.OnLongClickListener, ViewTreeObserver.OnPreDrawListener,
+ AllAppsSearchBarController.Callbacks, Stats.LaunchSourceProvider {
public static final boolean GRID_MERGE_SECTIONS = true;
- private static final boolean ALLOW_SINGLE_APP_LAUNCH = true;
- private static final boolean DYNAMIC_HEADER_ELEVATION = true;
- private static final boolean DISMISS_SEARCH_ON_BACK = true;
-
- private static final int FADE_IN_DURATION = 175;
- private static final int FADE_OUT_DURATION = 100;
- private static final int SEARCH_TRANSLATION_X_DP = 18;
-
@Thunk Launcher mLauncher;
@Thunk AlphabeticalAppsList mApps;
private LayoutInflater mLayoutInflater;
@@ -194,16 +81,14 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
private RecyclerView.LayoutManager mLayoutManager;
private RecyclerView.ItemDecoration mItemDecoration;
- @Thunk FrameLayout mContentView;
+ @Thunk View mContent;
+ @Thunk View mContainerView;
+ @Thunk View mRevealView;
@Thunk AllAppsRecyclerView mAppsRecyclerView;
@Thunk ViewGroup mPredictionBarView;
- private View mHeaderView;
- @Thunk View mSearchBarContainerView;
- private View mSearchButtonView;
- private View mDismissSearchButtonView;
- @Thunk AllAppsSearchEditView mSearchBarEditView;
-
- private HeaderElevationController mElevationController;
+ @Thunk AllAppsSearchBarController mSearchBarController;
+ private ViewGroup mSearchBarContainerView;
+ private View mSearchBarView;
private int mNumAppsPerRow;
private int mNumPredictedAppsPerRow;
@@ -213,18 +98,16 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
private final Point mIconLastTouchPos = new Point();
// This coordinate is used to proxy click and long-click events to the prediction bar icons
private final Point mPredictionIconTouchDownPos = new Point();
- private int mContentMarginStart;
// Normal container insets
- private int mContainerInset;
private int mPredictionBarHeight;
private int mLastRecyclerViewScrollPos = -1;
@Thunk boolean mFocusPredictionBarOnFirstBind;
+ private SpannableStringBuilder mSearchQueryBuilder = null;
+
private CheckLongPressHelper mPredictionIconCheckForLongPress;
private View mPredictionIconUnderTouch;
- private AppSearchManager mSearchManager;
-
public AllAppsContainerView(Context context) {
this(context, null);
}
@@ -238,30 +121,24 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
Resources res = context.getResources();
mLauncher = (Launcher) context;
+ mLayoutInflater = LayoutInflater.from(context);
DeviceProfile grid = mLauncher.getDeviceProfile();
-
- mContainerInset = res.getDimensionPixelSize(R.dimen.all_apps_container_inset);
mPredictionBarHeight = (int) (grid.allAppsIconSizePx + grid.iconDrawablePaddingOriginalPx +
Utilities.calculateTextHeight(grid.allAppsIconTextSizePx) +
2 * res.getDimensionPixelSize(R.dimen.all_apps_icon_top_bottom_padding) +
- res.getDimensionPixelSize(R.dimen.all_apps_prediction_bar_bottom_padding));
-
- mLayoutInflater = LayoutInflater.from(context);
+ 2 * res.getDimensionPixelSize(R.dimen.all_apps_prediction_bar_top_bottom_padding));
- mNumAppsPerRow = grid.allAppsNumCols;
- mNumPredictedAppsPerRow = grid.allAppsNumPredictiveCols;
- mApps = new AlphabeticalAppsList(context, mNumAppsPerRow, mNumPredictedAppsPerRow);
+ mApps = new AlphabeticalAppsList(context);
mApps.setAdapterChangedCallback(this);
- mAdapter = new AllAppsGridAdapter(context, mApps, mNumAppsPerRow, this, this, mLauncher, this);
+ mAdapter = new AllAppsGridAdapter(context, mApps, this, this, mLauncher, this);
mAdapter.setEmptySearchText(res.getString(R.string.all_apps_loading_message));
- mAdapter.setNumAppsPerRow(mNumAppsPerRow);
mAdapter.setPredictionRowHeight(mPredictionBarHeight);
+ mApps.setAdapter(mAdapter);
mLayoutManager = mAdapter.getLayoutManager();
mItemDecoration = mAdapter.getItemDecoration();
- mContentMarginStart = mAdapter.getContentMarginStart();
- mApps.setAdapter(mAdapter);
- mSearchManager = mApps.newSimpleAppSearchManager();
+ mSearchQueryBuilder = new SpannableStringBuilder();
+ Selection.setSelection(mSearchQueryBuilder, 0);
}
/**
@@ -285,11 +162,6 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
mApps.addApps(apps);
}
- public void setSearchManager(AppSearchManager searchManager) {
- mSearchManager.cancel(true);
- mSearchManager = searchManager;
- }
-
/**
* Updates existing apps in the list
*/
@@ -305,13 +177,23 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
}
/**
- * Hides the header bar
+ * Sets the search bar that shows above the a-z list.
*/
- public void hideHeaderBar() {
- mHeaderView.setVisibility(View.GONE);
- mElevationController.disable();
- onUpdateBackgrounds();
- onUpdatePaddings();
+ public void setSearchBarController(AllAppsSearchBarController searchController) {
+ if (mSearchBarController != null) {
+ throw new RuntimeException("Expected search bar controller to only be set once");
+ }
+ mSearchBarController = searchController;
+ mSearchBarController.initialize(mApps, this);
+
+ // Add the new search view to the layout
+ View searchBarView = searchController.getView(mSearchBarContainerView);
+ mSearchBarContainerView.addView(searchBarView);
+ mSearchBarContainerView.setVisibility(View.VISIBLE);
+ mSearchBarView = searchBarView;
+ setHasSearchBar();
+
+ updateBackgroundAndPaddings();
}
/**
@@ -325,28 +207,43 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
* Returns the content view used for the launcher transitions.
*/
public View getContentView() {
- return mContentView;
+ return mContainerView;
+ }
+
+ /**
+ * Returns the all apps search view.
+ */
+ public View getSearchBarView() {
+ return mSearchBarView;
}
/**
* Returns the reveal view used for the launcher transitions.
*/
public View getRevealView() {
- return findViewById(R.id.apps_view_transition_overlay);
+ return mRevealView;
+ }
+
+ /**
+ * Returns an new instance of the default app search controller.
+ */
+ public AllAppsSearchBarController newDefaultAppSearchController() {
+ return new DefaultAppSearchController(getContext(), this, mAppsRecyclerView);
}
@Override
protected void onFinishInflate() {
+ super.onFinishInflate();
boolean isRtl = Utilities.isRtl(getResources());
mAdapter.setRtl(isRtl);
+ mContent = findViewById(R.id.content);
- // Work around the search box getting first focus and showing the cursor by
- // proxying the focus from the content view to the recycler view directly
- mContentView = (FrameLayout) findViewById(R.id.apps_list);
- mContentView.setOnFocusChangeListener(new View.OnFocusChangeListener() {
+ // This is a focus listener that proxies focus from a view into the list view. This is to
+ // work around the search box from getting first focus and showing the cursor.
+ View.OnFocusChangeListener focusProxyListener = new View.OnFocusChangeListener() {
@Override
public void onFocusChange(View v, boolean hasFocus) {
- if (v == mContentView && hasFocus) {
+ if (hasFocus) {
if (!mApps.getPredictedApps().isEmpty()) {
// If the prediction bar is going to be bound, then defer focusing until
// it is first bound
@@ -360,52 +257,16 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
}
}
}
- });
-
- // Fix the header view elevation if not dynamically calculating it
- mHeaderView = findViewById(R.id.header);
- mHeaderView.setOnClickListener(this);
-
- mElevationController = Utilities.isLmpOrAbove() ?
- new HeaderElevationControllerVL(mHeaderView) :
- new HeaderElevationControllerV16(mHeaderView);
- if (!DYNAMIC_HEADER_ELEVATION) {
- mElevationController.onScroll(getResources()
- .getDimensionPixelSize(R.dimen.all_apps_header_scroll_to_elevation));
- }
-
- // Fix the prediction bar size
- mPredictionBarView = (ViewGroup) findViewById(R.id.prediction_bar);
- FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mPredictionBarView.getLayoutParams();
- lp.height = mPredictionBarHeight;
-
- mSearchButtonView = mHeaderView.findViewById(R.id.search_button);
- mSearchBarContainerView = findViewById(R.id.app_search_container);
- mDismissSearchButtonView = mSearchBarContainerView.findViewById(R.id.dismiss_search_button);
- mDismissSearchButtonView.setOnClickListener(this);
- mSearchBarEditView = (AllAppsSearchEditView) findViewById(R.id.apps_search_box);
- if (mSearchBarEditView != null) {
- mSearchBarEditView.addTextChangedListener(this);
- mSearchBarEditView.setOnEditorActionListener(this);
- if (DISMISS_SEARCH_ON_BACK) {
- mSearchBarEditView.setOnBackKeyListener(
- new AllAppsSearchEditView.OnBackKeyListener() {
- @Override
- public void onBackKey() {
- // Only hide the search field if there is no query, or if there
- // are no filtered results
- String query = Utilities.trim(
- mSearchBarEditView.getEditableText().toString());
- if (query.isEmpty() || mApps.hasNoFilteredResults()) {
- hideSearchField(true, true);
- }
- }
- });
- }
- }
- mAppsRecyclerView = (AllAppsRecyclerView) findViewById(R.id.apps_list_view);
+ };
+ mSearchBarContainerView = (ViewGroup) findViewById(R.id.search_box_container);
+ mSearchBarContainerView.setOnFocusChangeListener(focusProxyListener);
+ mContainerView = findViewById(R.id.all_apps_container);
+ mContainerView.setOnFocusChangeListener(focusProxyListener);
+ mRevealView = findViewById(R.id.all_apps_reveal);
+
+ // Load the all apps recycler view
+ mAppsRecyclerView = (AllAppsRecyclerView) findViewById(R.id.collection);
mAppsRecyclerView.setApps(mApps);
- mAppsRecyclerView.setNumAppsPerRow(mNumAppsPerRow, mNumPredictedAppsPerRow);
mAppsRecyclerView.setPredictionBarHeight(mPredictionBarHeight);
mAppsRecyclerView.setLayoutManager(mLayoutManager);
mAppsRecyclerView.setAdapter(mAdapter);
@@ -413,8 +274,18 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
if (mItemDecoration != null) {
mAppsRecyclerView.addItemDecoration(mItemDecoration);
}
- onUpdateBackgrounds();
- onUpdatePaddings();
+
+ // Fix the prediction bar height
+ mPredictionBarView = (ViewGroup) findViewById(R.id.prediction_bar);
+ FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mPredictionBarView.getLayoutParams();
+ lp.height = mPredictionBarHeight;
+
+ updateBackgroundAndPaddings();
+ }
+
+ @Override
+ public void onBoundsChanged(Rect newBounds) {
+ mLauncher.updateOverlayBounds(newBounds);
}
@Override
@@ -422,6 +293,12 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
updatePredictionBarVisibility();
List<AppInfo> predictedApps = mApps.getPredictedApps();
+
+ // Remove extra prediction icons
+ while (mPredictionBarView.getChildCount() > mNumPredictedAppsPerRow) {
+ mPredictionBarView.removeViewAt(mPredictionBarView.getChildCount() - 1);
+ }
+
int childCount = mPredictionBarView.getChildCount();
for (int i = 0; i < mNumPredictedAppsPerRow; i++) {
BubbleTextView icon;
@@ -455,95 +332,111 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
}
@Override
- protected void onFixedBoundsUpdated() {
- // Update the number of items in the grid
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ // Update the number of items in the grid before we measure the view
+ int availableWidth = !mContentBounds.isEmpty() ? mContentBounds.width() :
+ MeasureSpec.getSize(widthMeasureSpec);
DeviceProfile grid = mLauncher.getDeviceProfile();
- if (grid.updateAppsViewNumCols(getContext().getResources(), mFixedBounds.width())) {
+ grid.updateAppsViewNumCols(getResources(), availableWidth);
+ if (mNumAppsPerRow != grid.allAppsNumCols ||
+ mNumPredictedAppsPerRow != grid.allAppsNumPredictiveCols) {
mNumAppsPerRow = grid.allAppsNumCols;
mNumPredictedAppsPerRow = grid.allAppsNumPredictiveCols;
mAppsRecyclerView.setNumAppsPerRow(mNumAppsPerRow, mNumPredictedAppsPerRow);
mAdapter.setNumAppsPerRow(mNumAppsPerRow);
mApps.setNumAppsPerRow(mNumAppsPerRow, mNumPredictedAppsPerRow);
}
+
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
/**
- * Update the padding of the Apps view and children. To ensure that the RecyclerView has the
- * full width to handle touches right to the edge of the screen, we only apply the top and
- * bottom padding to the AppsContainerView and then the left/right padding on the RecyclerView
- * itself. In particular, the left/right padding is applied to the background of the view,
- * and then additionally inset by the start margin.
+ * Update the background and padding of the Apps view and children. Instead of insetting the
+ * container view, we inset the background and padding of the recycler view to allow for the
+ * recycler view to handle touch events (for fast scrolling) all the way to the edge.
*/
@Override
- protected void onUpdatePaddings() {
+ protected void onUpdateBackgroundAndPaddings(Rect searchBarBounds, Rect padding) {
boolean isRtl = Utilities.isRtl(getResources());
- boolean hasSearchBar = (mSearchBarEditView != null) &&
- (mSearchBarEditView.getVisibility() == View.VISIBLE);
- // Set the background on the container, but let the recyclerView extend the full screen,
- // so that the fast-scroller works on the edge as well.
- mContentView.setPadding(0, 0, 0, 0);
-
- if (mFixedBounds.isEmpty()) {
- // If there are no fixed bounds, then use the default padding and insets
- setPadding(mInsets.left, mContainerInset + mInsets.top, mInsets.right,
- mContainerInset + mInsets.bottom);
- } else {
- // If there are fixed bounds, then we update the padding to reflect the fixed bounds.
- setPadding(mFixedBounds.left, mFixedBounds.top, getMeasuredWidth() - mFixedBounds.right,
- mFixedBounds.bottom);
- }
-
- // Update the apps recycler view, inset it by the container inset as well
+ // TODO: Use quantum_panel instead of quantum_panel_shape.
+ InsetDrawable background = new InsetDrawable(
+ getResources().getDrawable(R.drawable.quantum_panel_shape), padding.left, 0,
+ padding.right, 0);
+ mContainerView.setBackground(background);
+ mRevealView.setBackground(background.getConstantState().newDrawable());
+ mAppsRecyclerView.updateBackgroundPadding(padding);
+ mAdapter.updateBackgroundPadding(padding);
+
+ // Hack: We are going to let the recycler view take the full width, so reset the padding on
+ // the container to zero after setting the background and apply the top-bottom padding to
+ // the content view instead so that the launcher transition clips correctly.
+ mContent.setPadding(0, padding.top, 0, padding.bottom);
+ mContainerView.setPadding(0, 0, 0, 0);
+
+ // Pad the recycler view by the background padding plus the start margin (for the section
+ // names)
DeviceProfile grid = mLauncher.getDeviceProfile();
- int startMargin = grid.isPhone ? mContentMarginStart : 0;
- int inset = mFixedBounds.isEmpty() ? mContainerInset : mFixedBoundsContainerInset;
+ int startMargin = grid.isPhone ? getResources().getDimensionPixelSize(
+ R.dimen.all_apps_grid_view_start_margin) : mAppsRecyclerView.getScrollbarWidth();
if (isRtl) {
- mAppsRecyclerView.setPadding(inset + mAppsRecyclerView.getScrollbarWidth(), 0,
- inset + startMargin, 0);
+ mAppsRecyclerView.setPadding(padding.left + mAppsRecyclerView.getScrollbarWidth(), 0,
+ padding.right + startMargin, 0);
} else {
- mAppsRecyclerView.setPadding(inset + startMargin, 0,
- inset + mAppsRecyclerView.getScrollbarWidth(), 0);
+ mAppsRecyclerView.setPadding(padding.left + startMargin, 0,
+ padding.right + mAppsRecyclerView.getScrollbarWidth(), 0);
}
- // Update the header bar
- if (hasSearchBar) {
- FrameLayout.LayoutParams lp =
- (FrameLayout.LayoutParams) mHeaderView.getLayoutParams();
- lp.leftMargin = lp.rightMargin = inset;
- mHeaderView.requestLayout();
+ // Inset the search bar to fit its bounds above the container
+ if (mSearchBarView != null) {
+ Rect backgroundPadding = new Rect();
+ if (mSearchBarView.getBackground() != null) {
+ mSearchBarView.getBackground().getPadding(backgroundPadding);
+ }
+ LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams)
+ mSearchBarContainerView.getLayoutParams();
+ lp.leftMargin = searchBarBounds.left - backgroundPadding.left;
+ lp.topMargin = searchBarBounds.top - backgroundPadding.top;
+ lp.rightMargin = (getMeasuredWidth() - searchBarBounds.right) - backgroundPadding.right;
+ mSearchBarContainerView.requestLayout();
}
+ // Update the prediction bar insets as well
+ mPredictionBarView = (ViewGroup) findViewById(R.id.prediction_bar);
FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mPredictionBarView.getLayoutParams();
- lp.leftMargin = inset + mAppsRecyclerView.getScrollbarWidth();
- lp.rightMargin = inset + mAppsRecyclerView.getScrollbarWidth();
+ lp.leftMargin = padding.left + mAppsRecyclerView.getScrollbarWidth();
+ lp.rightMargin = padding.right + mAppsRecyclerView.getScrollbarWidth();
mPredictionBarView.requestLayout();
}
- /**
- * Update the background of the Apps view and children.
- */
@Override
- protected void onUpdateBackgrounds() {
- int inset = mFixedBounds.isEmpty() ? mContainerInset : mFixedBoundsContainerInset;
-
- // Update the background of the reveal view and list to be inset with the fixed bound
- // insets instead of the default insets
- // TODO: Use quantum_panel instead of quantum_panel_shape.
- InsetDrawable background = new InsetDrawable(
- getContext().getResources().getDrawable(R.drawable.quantum_panel_shape),
- inset, 0, inset, 0);
- mContentView.setBackground(background);
- mAppsRecyclerView.updateBackgroundPadding(background);
- mAdapter.updateBackgroundPadding(background);
- mElevationController.updateBackgroundPadding(background);
- getRevealView().setBackground(background.getConstantState().newDrawable());
+ public boolean onPreDraw() {
+ if (mNumAppsPerRow > 0) {
+ // Update the position of the prediction bar to match the scroll of the all apps list
+ synchronizeToRecyclerViewScrollPosition(mAppsRecyclerView.getScrollPosition());
+ }
+ return true;
}
@Override
- public boolean onPreDraw() {
- synchronizeToRecyclerViewScrollPosition(mAppsRecyclerView.getScrollPosition());
- return true;
+ public boolean dispatchKeyEvent(KeyEvent event) {
+ // Determine if the key event was actual text, if so, focus the search bar and then dispatch
+ // the key normally so that it can process this key event
+ if (!mSearchBarController.isSearchFieldFocused() &&
+ event.getAction() == KeyEvent.ACTION_DOWN) {
+ final int unicodeChar = event.getUnicodeChar();
+ final boolean isKeyNotWhitespace = unicodeChar > 0 &&
+ !Character.isWhitespace(unicodeChar) && !Character.isSpaceChar(unicodeChar);
+ if (isKeyNotWhitespace) {
+ boolean gotKey = TextKeyListener.getInstance().onKeyDown(this, mSearchQueryBuilder,
+ event.getKeyCode(), event);
+ if (gotKey && mSearchQueryBuilder.length() > 0) {
+ mSearchBarController.focusSearchField();
+ }
+ }
+ }
+
+ return super.dispatchKeyEvent(event);
}
@Override
@@ -570,15 +463,6 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
}
@Override
- public void onClick(View v) {
- if (v == mHeaderView) {
- showSearchField();
- } else if (v == mDismissSearchButtonView) {
- hideSearchField(true, true);
- }
- }
-
- @Override
public boolean onLongClick(View v) {
// Return early if this is not initiated from a touch
if (!v.isInTouchMode()) return false;
@@ -661,70 +545,11 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
}
@Override
- public void beforeTextChanged(CharSequence s, int start, int count, int after) {
- // Do nothing
- }
-
- @Override
- public void onTextChanged(CharSequence s, int start, int before, int count) {
- // Do nothing
- }
-
- @Override
- public void afterTextChanged(final Editable s) {
- String queryText = s.toString();
- if (queryText.isEmpty()) {
- mSearchManager.cancel(true);
- mApps.setOrderedFilter(null);
- } else {
- String formatStr = getResources().getString(R.string.all_apps_no_search_results);
- mAdapter.setEmptySearchText(String.format(formatStr, queryText));
-
- mSearchManager.cancel(false);
- mSearchManager.doSearch(queryText, this);
- }
- scrollToTop();
- }
-
- @Override
- public void onSearchResult(ArrayList<ComponentName> apps) {
- if (apps != null) {
- mApps.setOrderedFilter(apps);
- }
- }
-
- @Override
- public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
- if (ALLOW_SINGLE_APP_LAUNCH && actionId == EditorInfo.IME_ACTION_DONE) {
- // Skip the quick-launch if there isn't exactly one item
- if (mApps.getSize() != 1) {
- return false;
- }
-
- 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();
- getInputMethodManager().hideSoftInputFromWindow(getWindowToken(), 0);
- return true;
- }
- }
- }
- return false;
- }
-
- @Override
public void onAdapterItemsChanged() {
updatePredictionBarVisibility();
}
@Override
- public View getContent() {
- return null;
- }
-
- @Override
public void onLauncherTransitionPrepare(Launcher l, boolean animated, boolean toWorkspace) {
// Register for a pre-draw listener to synchronize the recycler view scroll to other views
// in this container
@@ -745,14 +570,12 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
@Override
public void onLauncherTransitionEnd(Launcher l, boolean animated, boolean toWorkspace) {
- if (mSearchBarEditView != null) {
- if (toWorkspace) {
- hideSearchField(false, false);
- }
- }
if (toWorkspace) {
getViewTreeObserver().removeOnPreDrawListener(this);
mLastRecyclerViewScrollPos = -1;
+
+ // Reset the search bar after transitioning home
+ mSearchBarController.reset();
}
}
@@ -763,9 +586,6 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
private void synchronizeToRecyclerViewScrollPosition(int scrollY) {
if (mLastRecyclerViewScrollPos != scrollY) {
mLastRecyclerViewScrollPos = scrollY;
- if (DYNAMIC_HEADER_ELEVATION) {
- mElevationController.onScroll(scrollY);
- }
// Scroll the prediction bar with the contents of the recycler view
mPredictionBarView.setTranslationY(-scrollY + mAppsRecyclerView.getPaddingTop());
@@ -806,9 +626,9 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
}
}
- if (!mFixedBounds.isEmpty()) {
+ if (!mContentBounds.isEmpty()) {
// Outset the fixed bounds and check if the touch is outside all apps
- Rect tmpRect = new Rect(mFixedBounds);
+ Rect tmpRect = new Rect(mContentBounds);
tmpRect.inset(-grid.allAppsIconSizePx / 2, 0);
if (ev.getX() < tmpRect.left || ev.getX() > tmpRect.right) {
mBoundsCheckLastTouchDownPos.set(x, y);
@@ -875,6 +695,29 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
}
@Override
+ public void onSearchResult(String query, ArrayList<ComponentName> 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);
+ }
+ }
+
+ @Override
+ public void clearSearchResult() {
+ mApps.setOrderedFilter(null);
+
+ // Clear the search query
+ mSearchQueryBuilder.clear();
+ mSearchQueryBuilder.clearSpans();
+ Selection.setSelection(mSearchQueryBuilder, 0);
+ }
+
+ @Override
public void fillInLaunchSourceData(Bundle sourceData) {
// Since the other cases are caught by the AllAppsRecyclerView LaunchSourceProvider, we just
// handle the prediction bar icons here
@@ -889,11 +732,11 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
private View findPredictedAppAtCoordinate(int x, int y) {
Rect hitRect = new Rect();
- // Ensure we aren't hitting the search bar
+ // Ensure that are touching in the recycler view
int[] coord = {x, y};
- Utilities.mapCoordInSelfToDescendent(mHeaderView, this, coord);
- mHeaderView.getHitRect(hitRect);
- if (hitRect.contains(coord[0], coord[1])) {
+ Utilities.mapCoordInSelfToDescendent(mAppsRecyclerView, this, coord);
+ mAppsRecyclerView.getHitRect(hitRect);
+ if (!hitRect.contains(coord[0], coord[1])) {
return null;
}
@@ -915,94 +758,12 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
}
/**
- * Shows the search field.
- */
- private void showSearchField() {
- mSearchManager.connect();
-
- // Show the search bar and focus the search
- final int translationX = Utilities.pxFromDp(SEARCH_TRANSLATION_X_DP,
- getContext().getResources().getDisplayMetrics());
- mSearchBarContainerView.setVisibility(View.VISIBLE);
- mSearchBarContainerView.setAlpha(0f);
- mSearchBarContainerView.setTranslationX(translationX);
- mSearchBarContainerView.animate()
- .alpha(1f)
- .translationX(0)
- .setDuration(FADE_IN_DURATION)
- .withLayer()
- .withEndAction(new Runnable() {
- @Override
- public void run() {
- mSearchBarEditView.requestFocus();
- getInputMethodManager().showSoftInput(mSearchBarEditView,
- InputMethodManager.SHOW_IMPLICIT);
- }
- });
- mSearchButtonView.animate()
- .alpha(0f)
- .translationX(-translationX)
- .setDuration(FADE_OUT_DURATION)
- .withLayer();
- }
-
- /**
- * Hides the search field.
- */
- @Thunk void hideSearchField(boolean animated, final boolean returnFocusToRecyclerView) {
- mSearchManager.cancel(true);
-
- final boolean resetTextField = mSearchBarEditView.getText().toString().length() > 0;
- final int translationX = Utilities.pxFromDp(SEARCH_TRANSLATION_X_DP,
- getContext().getResources().getDisplayMetrics());
- if (animated) {
- // Hide the search bar and focus the recycler view
- mSearchBarContainerView.animate()
- .alpha(0f)
- .translationX(0)
- .setDuration(FADE_IN_DURATION)
- .withLayer()
- .withEndAction(new Runnable() {
- @Override
- public void run() {
- mSearchBarContainerView.setVisibility(View.INVISIBLE);
- if (resetTextField) {
- mSearchBarEditView.setText("");
- }
- mApps.setOrderedFilter(null);
- if (returnFocusToRecyclerView) {
- mAppsRecyclerView.requestFocus();
- }
- }
- });
- mSearchButtonView.setTranslationX(-translationX);
- mSearchButtonView.animate()
- .alpha(1f)
- .translationX(0)
- .setDuration(FADE_OUT_DURATION)
- .withLayer();
- } else {
- mSearchBarContainerView.setVisibility(View.INVISIBLE);
- if (resetTextField) {
- mSearchBarEditView.setText("");
- }
- mApps.setOrderedFilter(null);
- mSearchButtonView.setAlpha(1f);
- mSearchButtonView.setTranslationX(0f);
- if (returnFocusToRecyclerView) {
- mAppsRecyclerView.requestFocus();
- }
- }
- getInputMethodManager().hideSoftInputFromWindow(getWindowToken(), 0);
- }
-
- /**
* Updates the visibility of the prediction bar.
* @return whether the prediction bar is visible
*/
private boolean updatePredictionBarVisibility() {
- boolean showPredictionBar = !mApps.getPredictedApps().isEmpty() && (!mApps.hasFilter() ||
- mSearchBarEditView.getEditableText().toString().isEmpty());
+ boolean showPredictionBar = !mApps.getPredictedApps().isEmpty() &&
+ (!mApps.hasFilter() || mSearchBarController.shouldShowPredictionBar());
if (showPredictionBar) {
mPredictionBarView.setVisibility(View.VISIBLE);
} else if (!showPredictionBar) {
@@ -1010,11 +771,4 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
}
return showPredictionBar;
}
-
- /**
- * Returns an input method manager.
- */
- @Thunk InputMethodManager getInputMethodManager() {
- return (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
- }
}
diff --git a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
index 4b8b2dfc8..dc0d27cd2 100644
--- a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
+++ b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
@@ -109,6 +109,7 @@ class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.ViewHol
*/
public class GridItemDecoration extends RecyclerView.ItemDecoration {
+ private static final boolean DEBUG_SECTION_MARGIN = false;
private static final boolean FADE_OUT_SECTIONS = false;
private HashMap<String, PointF> mCachedSectionBounds = new HashMap<>();
@@ -121,10 +122,17 @@ class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.ViewHol
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
- if (mApps.hasFilter()) {
+ if (mApps.hasFilter() || mAppsPerRow == 0) {
return;
}
+ if (DEBUG_SECTION_MARGIN) {
+ Paint p = new Paint();
+ p.setColor(0x33ff0000);
+ c.drawRect(mBackgroundPadding.left, 0, mBackgroundPadding.left + mStartMargin,
+ parent.getMeasuredHeight(), p);
+ }
+
DeviceProfile grid = mLauncher.getDeviceProfile();
List<AlphabeticalAppsList.AdapterItem> items = mApps.getAdapterItems();
boolean hasDrawnPredictedAppsDivider = false;
@@ -171,8 +179,8 @@ class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.ViewHol
// Calculate where to draw the section
int sectionBaseline = (int) (viewTopOffset + sectionBounds.y);
- int x = mIsRtl ? parent.getWidth() - mPaddingStart - mStartMargin :
- mPaddingStart;
+ int x = mIsRtl ? parent.getWidth() - mBackgroundPadding.left - mStartMargin :
+ mBackgroundPadding.left;
x += (int) ((mStartMargin - sectionBounds.x) / 2f);
int y = child.getTop() + sectionBaseline;
@@ -301,23 +309,20 @@ class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.ViewHol
private String mEmptySearchText;
// Section drawing
- @Thunk int mPaddingStart;
@Thunk int mStartMargin;
@Thunk int mSectionHeaderOffset;
@Thunk Paint mSectionTextPaint;
@Thunk Paint mPredictedAppsDividerPaint;
- public AllAppsGridAdapter(Context context, AlphabeticalAppsList apps, int appsPerRow,
+ public AllAppsGridAdapter(Context context, AlphabeticalAppsList apps,
PredictionBarSpacerCallbacks pbCb, View.OnTouchListener touchListener,
View.OnClickListener iconClickListener, View.OnLongClickListener iconLongClickListener) {
Resources res = context.getResources();
mHandler = new Handler();
mApps = apps;
- mAppsPerRow = appsPerRow;
mPredictionBarCb = pbCb;
mGridSizer = new GridSpanSizer();
- mGridLayoutMgr = new GridLayoutManager(context, appsPerRow, GridLayoutManager.VERTICAL,
- false);
+ mGridLayoutMgr = new GridLayoutManager(context, 1, GridLayoutManager.VERTICAL, false);
mGridLayoutMgr.setSpanSizeLookup(mGridSizer);
mItemDecoration = new GridItemDecoration(context);
mLayoutInflater = LayoutInflater.from(context);
@@ -326,7 +331,6 @@ class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.ViewHol
mIconLongClickListener = iconLongClickListener;
mStartMargin = res.getDimensionPixelSize(R.dimen.all_apps_grid_view_start_margin);
mSectionHeaderOffset = res.getDimensionPixelSize(R.dimen.all_apps_grid_section_y_offset);
- mPaddingStart = res.getDimensionPixelSize(R.dimen.all_apps_container_inset);
mSectionTextPaint = new Paint();
mSectionTextPaint.setTextSize(res.getDimensionPixelSize(
@@ -339,7 +343,7 @@ class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.ViewHol
mPredictedAppsDividerPaint.setColor(0x1E000000);
mPredictedAppsDividerPaint.setAntiAlias(true);
mPredictionBarBottomPadding =
- res.getDimensionPixelSize(R.dimen.all_apps_prediction_bar_bottom_padding);
+ res.getDimensionPixelSize(R.dimen.all_apps_prediction_bar_top_bottom_padding);
}
/**
@@ -375,8 +379,8 @@ class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.ViewHol
* Notifies the adapter of the background padding so that it can draw things correctly in the
* item decorator.
*/
- public void updateBackgroundPadding(Drawable background) {
- background.getPadding(mBackgroundPadding);
+ public void updateBackgroundPadding(Rect padding) {
+ mBackgroundPadding.set(padding);
}
/**
@@ -394,13 +398,6 @@ class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.ViewHol
return mItemDecoration;
}
- /**
- * Returns the left padding for the recycler view.
- */
- public int getContentMarginStart() {
- return mStartMargin;
- }
-
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
switch (viewType) {
diff --git a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
index e1b5d918e..25918ce31 100644
--- a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
@@ -16,8 +16,6 @@
package com.android.launcher3.allapps;
import android.content.Context;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
@@ -40,11 +38,7 @@ public class AllAppsRecyclerView extends BaseRecyclerView
private AlphabeticalAppsList mApps;
private int mNumAppsPerRow;
private int mNumPredictedAppsPerRow;
-
private int mPredictionBarHeight;
- private int mScrollbarMinHeight;
-
- private Rect mBackgroundPadding = new Rect();
private Launcher mLauncher;
@@ -89,10 +83,6 @@ public class AllAppsRecyclerView extends BaseRecyclerView
pool.setMaxRecycledViews(AllAppsGridAdapter.SECTION_BREAK_VIEW_TYPE, approxRows);
}
- public void updateBackgroundPadding(Drawable background) {
- background.getPadding(mBackgroundPadding);
- }
-
/**
* Sets the prediction bar height.
*/
@@ -124,6 +114,8 @@ public class AllAppsRecyclerView extends BaseRecyclerView
@Override
protected void onFinishInflate() {
super.onFinishInflate();
+
+ // Bind event handlers
addOnItemTouchListener(this);
}
@@ -227,8 +219,8 @@ public class AllAppsRecyclerView extends BaseRecyclerView
public void updateVerticalScrollbarBounds() {
List<AlphabeticalAppsList.AdapterItem> items = mApps.getAdapterItems();
- // Skip early if there are no items.
- if (items.isEmpty()) {
+ // Skip early if there are no items or we haven't been measured
+ if (items.isEmpty() || mNumAppsPerRow == 0) {
verticalScrollbarBounds.setEmpty();
return;
}
@@ -242,8 +234,7 @@ public class AllAppsRecyclerView extends BaseRecyclerView
int height = getHeight() - getPaddingTop() - getPaddingBottom();
int totalScrollHeight = rowCount * scrollPosState.rowHeight + predictionBarHeight;
if (totalScrollHeight > height) {
- int scrollbarHeight = Math.max(mScrollbarMinHeight,
- (int) (height / ((float) totalScrollHeight / height)));
+ int scrollbarHeight = (int) (height / ((float) totalScrollHeight / height));
// Calculate the position and size of the scroll bar
if (Utilities.isRtl(getResources())) {
@@ -277,8 +268,8 @@ public class AllAppsRecyclerView extends BaseRecyclerView
stateOut.rowTopOffset = -1;
stateOut.rowHeight = -1;
- // Return early if there are no items
- if (items.isEmpty()) {
+ // Return early if there are no items or we haven't been measured
+ if (items.isEmpty() || mNumAppsPerRow == 0) {
return;
}
diff --git a/src/com/android/launcher3/allapps/AllAppsSearchBarController.java b/src/com/android/launcher3/allapps/AllAppsSearchBarController.java
new file mode 100644
index 000000000..3cacd9d69
--- /dev/null
+++ b/src/com/android/launcher3/allapps/AllAppsSearchBarController.java
@@ -0,0 +1,97 @@
+/*
+ * 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.allapps;
+
+import android.content.ComponentName;
+import android.graphics.Rect;
+import android.view.View;
+import android.view.ViewGroup;
+
+import java.util.ArrayList;
+
+/**
+ * An interface to a search box that AllApps can command.
+ */
+public abstract class AllAppsSearchBarController {
+
+ protected AlphabeticalAppsList mApps;
+ protected Callbacks mCb;
+
+ /**
+ * Sets the references to the apps model and the search result callback.
+ */
+ public final void initialize(AlphabeticalAppsList apps, Callbacks cb) {
+ mApps = apps;
+ mCb = cb;
+ onInitialize();
+ }
+
+ /**
+ * To be overridden by subclasses. This method will get called when the controller is set,
+ * before getView().
+ */
+ protected abstract void onInitialize();
+
+ /**
+ * Returns the search bar view.
+ * @param parent the parent to attach the search bar view to.
+ */
+ public abstract View getView(ViewGroup parent);
+
+ /**
+ * Focuses the search field to handle key events.
+ */
+ public abstract void focusSearchField();
+
+ /**
+ * Returns whether the search field is focused.
+ */
+ public abstract boolean isSearchFieldFocused();
+
+ /**
+ * Resets the search bar state.
+ */
+ public abstract void reset();
+
+ /**
+ * Returns whether the prediction bar should currently be visible depending on the state of
+ * the search bar.
+ */
+ public abstract boolean shouldShowPredictionBar();
+
+ /**
+ * Callback for getting search results.
+ */
+ public interface Callbacks {
+
+ /**
+ * Called when the bounds of the search bar has changed.
+ */
+ void onBoundsChanged(Rect newBounds);
+
+ /**
+ * Called when the search is complete.
+ *
+ * @param apps sorted list of matching components or null if in case of failure.
+ */
+ void onSearchResult(String query, ArrayList<ComponentName> apps);
+
+ /**
+ * Called when the search results should be cleared.
+ */
+ void clearSearchResult();
+ }
+} \ No newline at end of file
diff --git a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
index e284f77c4..a0cf5b6dc 100644
--- a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
+++ b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
@@ -235,11 +235,10 @@ public class AlphabeticalAppsList {
private int mNumAppsPerRow;
private int mNumPredictedAppsPerRow;
- public AlphabeticalAppsList(Context context, int numAppsPerRow, int numPredictedAppsPerRow) {
+ public AlphabeticalAppsList(Context context) {
mLauncher = (Launcher) context;
mIndexer = new AlphabeticIndexCompat(context);
mAppNameComparator = new AppNameComparator(context);
- setNumAppsPerRow(numAppsPerRow, numPredictedAppsPerRow);
}
/**
@@ -249,10 +248,6 @@ public class AlphabeticalAppsList {
mAdapterChangedCallback = cb;
}
- public SimpleAppSearchManagerImpl newSimpleAppSearchManager() {
- return new SimpleAppSearchManagerImpl(mApps);
- }
-
/**
* Sets the number of apps per row. Used only for AppsContainerView.SECTIONED_GRID_COALESCED.
*/
@@ -269,7 +264,7 @@ public class AlphabeticalAppsList {
mNumAppsPerRow = numAppsPerRow;
mNumPredictedAppsPerRow = numPredictedAppsPerRow;
- onAppsUpdated();
+ updateAdapterItems();
}
/**
@@ -280,6 +275,13 @@ public class AlphabeticalAppsList {
}
/**
+ * Returns all the apps.
+ */
+ public List<AppInfo> getApps() {
+ return mApps;
+ }
+
+ /**
* Returns sections of all the current filtered applications.
*/
public List<SectionInfo> getSections() {
@@ -597,6 +599,11 @@ public class AlphabeticalAppsList {
* Merges multiple sections to reduce visual raggedness.
*/
private void mergeSections() {
+ // Ignore merging until we have a valid row size
+ if (mNumAppsPerRow == 0) {
+ return;
+ }
+
// Go through each section and try and merge some of the sections
if (AllAppsContainerView.GRID_MERGE_SECTIONS && !hasFilter()) {
int sectionAppCount = 0;
diff --git a/src/com/android/launcher3/allapps/AppSearchManager.java b/src/com/android/launcher3/allapps/AppSearchManager.java
deleted file mode 100644
index b6aa22341..000000000
--- a/src/com/android/launcher3/allapps/AppSearchManager.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * 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.allapps;
-
-import android.content.ComponentName;
-
-import java.util.ArrayList;
-
-/**
- * Interface for handling app search.
- */
-public interface AppSearchManager {
-
- /**
- * Called when the search is about to be used. This method is optional for making a query but
- * calling this appropriately can improve the initial response time.
- */
- void connect();
-
- /**
- * Cancels all pending search requests.
- *
- * @param interruptActiveRequests if true, any active requests which are already executing will
- * be invalidated, and the corresponding results will not be sent. The client should usually
- * set this to true, before beginning a new search session.
- */
- void cancel(boolean interruptActiveRequests);
-
- /**
- * Performs a search
- */
- void doSearch(String query, AppSearchResultCallback callback);
-
- /**
- * Callback for getting search results.
- */
- public interface AppSearchResultCallback {
-
- /**
- * Called when the search is complete.
- *
- * @param apps sorted list of matching components or null if in case of failure.
- */
- void onSearchResult(ArrayList<ComponentName> apps);
- }
-}
diff --git a/src/com/android/launcher3/allapps/SimpleAppSearchManagerImpl.java b/src/com/android/launcher3/allapps/DefaultAppSearchAlgorithm.java
index e8a31b546..28854be0e 100644
--- a/src/com/android/launcher3/allapps/SimpleAppSearchManagerImpl.java
+++ b/src/com/android/launcher3/allapps/DefaultAppSearchAlgorithm.java
@@ -17,7 +17,6 @@ package com.android.launcher3.allapps;
import android.content.ComponentName;
import android.os.Handler;
-
import com.android.launcher3.AppInfo;
import java.util.ArrayList;
@@ -25,39 +24,33 @@ import java.util.List;
import java.util.regex.Pattern;
/**
- * An {@link AppSearchManager} which does label matching on the UI thread.
+ * The default search implementation.
*/
-public class SimpleAppSearchManagerImpl implements AppSearchManager {
+public class DefaultAppSearchAlgorithm {
private static final Pattern SPLIT_PATTERN = Pattern.compile("[\\s|\\p{javaSpaceChar}]+");
private final List<AppInfo> mApps;
private final Handler mResultHandler;
- public SimpleAppSearchManagerImpl(List<AppInfo> apps) {
+ public DefaultAppSearchAlgorithm(List<AppInfo> apps) {
mApps = apps;
mResultHandler = new Handler();
}
- @Override
- public void connect() {
- // No op
- }
-
- @Override
public void cancel(boolean interruptActiveRequests) {
if (interruptActiveRequests) {
mResultHandler.removeCallbacksAndMessages(null);
}
}
- @Override
- public void doSearch(String query, final AppSearchResultCallback callback) {
+ public void doSearch(final String query,
+ final AllAppsSearchBarController.Callbacks callback) {
// Do an intersection of the words in the query and each title, and filter out all the
// apps that don't match all of the words in the query.
final String queryTextLower = query.toLowerCase();
final String[] queryWords = SPLIT_PATTERN.split(queryTextLower);
- final ArrayList<ComponentName> result = new ArrayList<ComponentName>();
+ final ArrayList<ComponentName> result = new ArrayList<>();
int total = mApps.size();
for (int i = 0; i < total; i++) {
@@ -70,7 +63,7 @@ public class SimpleAppSearchManagerImpl implements AppSearchManager {
@Override
public void run() {
- callback.onSearchResult(result);
+ callback.onSearchResult(query, result);
}
});
}
diff --git a/src/com/android/launcher3/allapps/DefaultAppSearchController.java b/src/com/android/launcher3/allapps/DefaultAppSearchController.java
new file mode 100644
index 000000000..1601c62df
--- /dev/null
+++ b/src/com/android/launcher3/allapps/DefaultAppSearchController.java
@@ -0,0 +1,270 @@
+/*
+ * 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.allapps;
+
+import android.content.Context;
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.TextView;
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.util.Thunk;
+
+import java.util.List;
+
+
+/**
+ * The default search controller.
+ */
+final class DefaultAppSearchController extends AllAppsSearchBarController
+ implements TextWatcher, TextView.OnEditorActionListener, View.OnClickListener {
+
+ private static final boolean ALLOW_SINGLE_APP_LAUNCH = true;
+
+ private static final int FADE_IN_DURATION = 175;
+ private static final int FADE_OUT_DURATION = 100;
+ private static final int SEARCH_TRANSLATION_X_DP = 18;
+
+ private final Context mContext;
+ @Thunk final InputMethodManager mInputMethodManager;
+
+ private DefaultAppSearchAlgorithm mSearchManager;
+
+ private ViewGroup mContainerView;
+ private View mSearchView;
+ @Thunk View mSearchBarContainerView;
+ private View mSearchButtonView;
+ private View mDismissSearchButtonView;
+ @Thunk AllAppsSearchEditView mSearchBarEditView;
+ @Thunk AllAppsRecyclerView mAppsRecyclerView;
+ private Runnable mFocusRecyclerViewRunnable = new Runnable() {
+ @Override
+ public void run() {
+ mAppsRecyclerView.requestFocus();
+ }
+ };
+
+ public DefaultAppSearchController(Context context, ViewGroup containerView,
+ AllAppsRecyclerView appsRecyclerView) {
+ mContext = context;
+ mInputMethodManager = (InputMethodManager)
+ mContext.getSystemService(Context.INPUT_METHOD_SERVICE);
+ mContainerView = containerView;
+ mAppsRecyclerView = appsRecyclerView;
+ }
+
+ @Override
+ public View getView(ViewGroup parent) {
+ LayoutInflater inflater = LayoutInflater.from(parent.getContext());
+ mSearchView = inflater.inflate(R.layout.all_apps_search_bar, parent, false);
+ mSearchView.setOnClickListener(this);
+
+ mSearchButtonView = mSearchView.findViewById(R.id.search_button);
+ mSearchBarContainerView = mSearchView.findViewById(R.id.search_container);
+ mDismissSearchButtonView = mSearchBarContainerView.findViewById(R.id.dismiss_search_button);
+ mDismissSearchButtonView.setOnClickListener(this);
+ mSearchBarEditView = (AllAppsSearchEditView)
+ mSearchBarContainerView.findViewById(R.id.search_box);
+ mSearchBarEditView.addTextChangedListener(this);
+ mSearchBarEditView.setOnEditorActionListener(this);
+ mSearchBarEditView.setOnBackKeyListener(
+ new AllAppsSearchEditView.OnBackKeyListener() {
+ @Override
+ public void onBackKey() {
+ // Only hide the search field if there is no query, or if there
+ // are no filtered results
+ String query = Utilities.trim(
+ mSearchBarEditView.getEditableText().toString());
+ if (query.isEmpty() || mApps.hasNoFilteredResults()) {
+ hideSearchField(true, mFocusRecyclerViewRunnable);
+ }
+ }
+ });
+ return mSearchView;
+ }
+
+ @Override
+ public void focusSearchField() {
+ mSearchBarEditView.requestFocus();
+ showSearchField();
+ }
+
+ @Override
+ public boolean isSearchFieldFocused() {
+ return mSearchBarEditView.isFocused();
+ }
+
+ @Override
+ protected void onInitialize() {
+ mSearchManager = new DefaultAppSearchAlgorithm(mApps.getApps());
+ }
+
+ @Override
+ public void reset() {
+ hideSearchField(false, null);
+ }
+
+ @Override
+ public boolean shouldShowPredictionBar() {
+ // Keep showing the prediction bar if the input query is empty
+ return mSearchBarEditView.getEditableText().toString().isEmpty();
+ }
+
+ @Override
+ public void onClick(View v) {
+ if (v == mSearchView) {
+ showSearchField();
+ } else if (v == mDismissSearchButtonView) {
+ hideSearchField(true, mFocusRecyclerViewRunnable);
+ }
+ }
+
+ @Override
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+ // Do nothing
+ }
+
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
+ // Do nothing
+ }
+
+ @Override
+ public void afterTextChanged(final Editable s) {
+ String query = s.toString();
+ if (query.isEmpty()) {
+ mSearchManager.cancel(true);
+ mCb.clearSearchResult();
+ } else {
+ mSearchManager.cancel(false);
+ mSearchManager.doSearch(query, mCb);
+ }
+ }
+
+ @Override
+ public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
+ // Skip if we disallow app-launch-on-enter
+ if (!ALLOW_SINGLE_APP_LAUNCH) {
+ return false;
+ }
+ // Skip if it's not the right action
+ if (actionId != EditorInfo.IME_ACTION_DONE) {
+ return false;
+ }
+ // Skip if there isn't exactly one item
+ if (mApps.getSize() != 1) {
+ return false;
+ }
+ // If there is exactly one icon, then quick-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;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Focuses the search field.
+ */
+ private void showSearchField() {
+ // Show the search bar and focus the search
+ final int translationX = Utilities.pxFromDp(SEARCH_TRANSLATION_X_DP,
+ mContext.getResources().getDisplayMetrics());
+ mSearchBarContainerView.setVisibility(View.VISIBLE);
+ mSearchBarContainerView.setAlpha(0f);
+ mSearchBarContainerView.setTranslationX(translationX);
+ mSearchBarContainerView.animate()
+ .alpha(1f)
+ .translationX(0)
+ .setDuration(FADE_IN_DURATION)
+ .withLayer()
+ .withEndAction(new Runnable() {
+ @Override
+ public void run() {
+ mSearchBarEditView.requestFocus();
+ mInputMethodManager.showSoftInput(mSearchBarEditView,
+ InputMethodManager.SHOW_IMPLICIT);
+ }
+ });
+ mSearchButtonView.animate()
+ .alpha(0f)
+ .translationX(-translationX)
+ .setDuration(FADE_OUT_DURATION)
+ .withLayer();
+ }
+
+ /**
+ * Unfocuses the search field.
+ */
+ @Thunk void hideSearchField(boolean animated, final Runnable postAnimationRunnable) {
+ mSearchManager.cancel(true);
+
+ final boolean resetTextField = mSearchBarEditView.getText().toString().length() > 0;
+ final int translationX = Utilities.pxFromDp(SEARCH_TRANSLATION_X_DP,
+ mContext.getResources().getDisplayMetrics());
+ if (animated) {
+ // Hide the search bar and focus the recycler view
+ mSearchBarContainerView.animate()
+ .alpha(0f)
+ .translationX(0)
+ .setDuration(FADE_IN_DURATION)
+ .withLayer()
+ .withEndAction(new Runnable() {
+ @Override
+ public void run() {
+ mSearchBarContainerView.setVisibility(View.INVISIBLE);
+ if (resetTextField) {
+ mSearchBarEditView.setText("");
+ }
+ mCb.clearSearchResult();
+ if (postAnimationRunnable != null) {
+ postAnimationRunnable.run();
+ }
+ }
+ });
+ mSearchButtonView.setTranslationX(-translationX);
+ mSearchButtonView.animate()
+ .alpha(1f)
+ .translationX(0)
+ .setDuration(FADE_OUT_DURATION)
+ .withLayer();
+ } else {
+ mSearchBarContainerView.setVisibility(View.INVISIBLE);
+ if (resetTextField) {
+ mSearchBarEditView.setText("");
+ }
+ mCb.clearSearchResult();
+ mSearchButtonView.setAlpha(1f);
+ mSearchButtonView.setTranslationX(0f);
+ if (postAnimationRunnable != null) {
+ postAnimationRunnable.run();
+ }
+ }
+ mInputMethodManager.hideSoftInputFromWindow(mContainerView.getWindowToken(), 0);
+ }
+}