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
This commit is contained in:
Winson Chung
2015-06-04 17:18:17 -07:00
parent e89cf793ab
commit ef7f874a88
29 changed files with 1087 additions and 1064 deletions
@@ -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));
2 * res.getDimensionPixelSize(R.dimen.all_apps_prediction_bar_top_bottom_padding));
mLayoutInflater = LayoutInflater.from(context);
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
}
}
}
});
};
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);
// 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);
// 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,97 +332,113 @@ 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
DeviceProfile grid = mLauncher.getDeviceProfile();
int startMargin = grid.isPhone ? mContentMarginStart : 0;
int inset = mFixedBounds.isEmpty() ? mContainerInset : mFixedBoundsContainerInset;
if (isRtl) {
mAppsRecyclerView.setPadding(inset + mAppsRecyclerView.getScrollbarWidth(), 0,
inset + startMargin, 0);
} else {
mAppsRecyclerView.setPadding(inset + startMargin, 0,
inset + mAppsRecyclerView.getScrollbarWidth(), 0);
}
// Update the header bar
if (hasSearchBar) {
FrameLayout.LayoutParams lp =
(FrameLayout.LayoutParams) mHeaderView.getLayoutParams();
lp.leftMargin = lp.rightMargin = inset;
mHeaderView.requestLayout();
}
FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mPredictionBarView.getLayoutParams();
lp.leftMargin = inset + mAppsRecyclerView.getScrollbarWidth();
lp.rightMargin = inset + 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());
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 ? getResources().getDimensionPixelSize(
R.dimen.all_apps_grid_view_start_margin) : mAppsRecyclerView.getScrollbarWidth();
if (isRtl) {
mAppsRecyclerView.setPadding(padding.left + mAppsRecyclerView.getScrollbarWidth(), 0,
padding.right + startMargin, 0);
} else {
mAppsRecyclerView.setPadding(padding.left + startMargin, 0,
padding.right + mAppsRecyclerView.getScrollbarWidth(), 0);
}
// 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 = padding.left + mAppsRecyclerView.getScrollbarWidth();
lp.rightMargin = padding.right + mAppsRecyclerView.getScrollbarWidth();
mPredictionBarView.requestLayout();
}
@Override
public boolean onPreDraw() {
synchronizeToRecyclerViewScrollPosition(mAppsRecyclerView.getScrollPosition());
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 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
public boolean onInterceptTouchEvent(MotionEvent ev) {
return handleTouchEvent(ev);
@@ -569,15 +462,6 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
return false;
}
@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
@@ -660,70 +544,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
@@ -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);
@@ -874,6 +694,29 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
return false;
}
@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
@@ -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;
}
@@ -914,95 +757,13 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
return null;
}
/**
* 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);
}
}