/* * 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.annotation.SuppressLint; import android.content.Context; import android.content.res.Resources; import android.graphics.Point; import android.graphics.Rect; import android.support.v4.content.ContextCompat; import android.support.v7.widget.RecyclerView; import android.text.Selection; import android.text.Spannable; import android.text.SpannableString; import android.text.SpannableStringBuilder; import android.text.TextUtils; import android.text.method.TextKeyListener; import android.util.AttributeSet; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.View; import android.view.ViewConfiguration; import android.view.ViewGroup; import com.android.launcher3.AppInfo; import com.android.launcher3.BaseContainerView; import com.android.launcher3.BubbleTextView; import com.android.launcher3.CellLayout; import com.android.launcher3.DeleteDropTarget; import com.android.launcher3.DeviceProfile; import com.android.launcher3.DragSource; import com.android.launcher3.DropTarget; import com.android.launcher3.ExtendedEditText; import com.android.launcher3.ItemInfo; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherTransitionable; import com.android.launcher3.R; import com.android.launcher3.Utilities; import com.android.launcher3.Workspace; import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.dragndrop.DragOptions; import com.android.launcher3.folder.Folder; import com.android.launcher3.graphics.TintedDrawableSpan; import com.android.launcher3.keyboard.FocusedItemDecorator; import com.android.launcher3.shortcuts.DeepShortcutsContainer; import com.android.launcher3.userevent.nano.LauncherLogProto.Target; import com.android.launcher3.util.ComponentKey; import java.nio.charset.Charset; import java.nio.charset.CharsetEncoder; import java.util.ArrayList; import java.util.List; /** * A merge algorithm that merges every section indiscriminately. */ final class FullMergeAlgorithm implements AlphabeticalAppsList.MergeAlgorithm { @Override public boolean continueMerging(AlphabeticalAppsList.SectionInfo section, AlphabeticalAppsList.SectionInfo withSection, int sectionAppCount, int numAppsPerRow, int mergeCount) { // Don't merge the predicted apps if (section.firstAppItem.viewType != AllAppsGridAdapter.VIEW_TYPE_ICON) { return false; } // Otherwise, merge every other section return true; } } /** * The logic we use to merge multiple sections. We only merge sections when their final row * contains less than a certain number of icons, and stop at a specified max number of merges. * In addition, we will try and not merge sections that identify apps from different scripts. */ final class SimpleSectionMergeAlgorithm implements AlphabeticalAppsList.MergeAlgorithm { private int mMinAppsPerRow; private int mMinRowsInMergedSection; private int mMaxAllowableMerges; private CharsetEncoder mAsciiEncoder; public SimpleSectionMergeAlgorithm(int minAppsPerRow, int minRowsInMergedSection, int maxNumMerges) { mMinAppsPerRow = minAppsPerRow; mMinRowsInMergedSection = minRowsInMergedSection; mMaxAllowableMerges = maxNumMerges; mAsciiEncoder = Charset.forName("US-ASCII").newEncoder(); } @Override public boolean continueMerging(AlphabeticalAppsList.SectionInfo section, AlphabeticalAppsList.SectionInfo withSection, int sectionAppCount, int numAppsPerRow, int mergeCount) { // Don't merge the predicted apps if (section.firstAppItem.viewType != AllAppsGridAdapter.VIEW_TYPE_ICON) { return false; } // Continue merging if the number of hanging apps on the final row is less than some // fixed number (ragged), the merged rows has yet to exceed some minimum row count, // and while the number of merged sections is less than some fixed number of merges int rows = sectionAppCount / numAppsPerRow; int cols = sectionAppCount % numAppsPerRow; // Ensure that we do not merge across scripts, currently we only allow for english and // native scripts so we can test if both can just be ascii encoded boolean isCrossScript = false; if (section.firstAppItem != null && withSection.firstAppItem != null) { isCrossScript = mAsciiEncoder.canEncode(section.firstAppItem.sectionName) != mAsciiEncoder.canEncode(withSection.firstAppItem.sectionName); } return (0 < cols && cols < mMinAppsPerRow) && rows < mMinRowsInMergedSection && mergeCount < mMaxAllowableMerges && !isCrossScript; } } /** * The all apps view container. */ public class AllAppsContainerView extends BaseContainerView implements DragSource, LauncherTransitionable, View.OnLongClickListener, AllAppsSearchBarController.Callbacks { private static final int MIN_ROWS_IN_MERGED_SECTION_PHONE = 3; private static final int MAX_NUM_MERGES_PHONE = 2; private final Launcher mLauncher; private final AlphabeticalAppsList mApps; private final AllAppsGridAdapter mAdapter; private final RecyclerView.LayoutManager mLayoutManager; private final RecyclerView.ItemDecoration mItemDecoration; // The computed bounds of the container private final Rect mContentBounds = new Rect(); private AllAppsRecyclerView mAppsRecyclerView; private AllAppsSearchBarController mSearchBarController; private View mSearchContainer; private ExtendedEditText mSearchInput; private HeaderElevationController mElevationController; private int mSearchContainerOffsetTop; private SpannableStringBuilder mSearchQueryBuilder = null; private int mSectionNamesMargin; private int mNumAppsPerRow; private int mNumPredictedAppsPerRow; private int mRecyclerViewBottomPadding; // This coordinate is relative to this container view private final Point mBoundsCheckLastTouchDownPos = new Point(-1, -1); public AllAppsContainerView(Context context) { this(context, null); } public AllAppsContainerView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public AllAppsContainerView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); Resources res = context.getResources(); mLauncher = Launcher.getLauncher(context); mSectionNamesMargin = res.getDimensionPixelSize(R.dimen.all_apps_grid_view_start_margin); mApps = new AlphabeticalAppsList(context); mAdapter = new AllAppsGridAdapter(mLauncher, mApps, mLauncher, this); mApps.setAdapter(mAdapter); mLayoutManager = mAdapter.getLayoutManager(); mItemDecoration = mAdapter.getItemDecoration(); DeviceProfile grid = mLauncher.getDeviceProfile(); if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP && !grid.isVerticalBarLayout()) { mRecyclerViewBottomPadding = 0; setPadding(0, 0, 0, 0); } else { mRecyclerViewBottomPadding = res.getDimensionPixelSize(R.dimen.all_apps_list_bottom_padding); } mSearchQueryBuilder = new SpannableStringBuilder(); Selection.setSelection(mSearchQueryBuilder, 0); } /** * Sets the current set of predicted apps. */ public void setPredictedApps(List<ComponentKey> apps) { mApps.setPredictedApps(apps); } /** * Sets the current set of apps. */ public void setApps(List<AppInfo> apps) { mApps.setApps(apps); } /** * Adds new apps to the list. */ public void addApps(List<AppInfo> apps) { mApps.addApps(apps); mSearchBarController.refreshSearchResult(); } /** * Updates existing apps in the list */ public void updateApps(List<AppInfo> apps) { mApps.updateApps(apps); mSearchBarController.refreshSearchResult(); } /** * Removes some apps from the list. */ public void removeApps(List<AppInfo> apps) { mApps.removeApps(apps); mSearchBarController.refreshSearchResult(); } public void setSearchBarVisible(boolean visible) { if (visible) { mSearchBarController.setVisibility(View.VISIBLE); } else { mSearchBarController.setVisibility(View.INVISIBLE); } } /** * Sets the search bar that shows above the a-z list. */ 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, mSearchInput, mLauncher, this); mAdapter.setSearchController(mSearchBarController); } /** * Scrolls this list view to the top. */ public void scrollToTop() { mAppsRecyclerView.scrollToTop(); } /** * Returns whether the view itself will handle the touch event or not. */ public boolean shouldContainerScroll(MotionEvent ev) { int[] point = new int[2]; point[0] = (int) ev.getX(); point[1] = (int) ev.getY(); Utilities.mapCoordInSelfToDescendent(mAppsRecyclerView, this, point); // IF the MotionEvent is inside the search box, and the container keeps on receiving // touch input, container should move down. if (mLauncher.getDragLayer().isEventOverView(mSearchContainer, ev)) { return true; } // IF the MotionEvent is inside the thumb, container should not be pulled down. if (mAppsRecyclerView.getScrollBar().isNearThumb(point[0], point[1])) { return false; } // IF a shortcuts container is open, container should not be pulled down. if (mLauncher.getOpenShortcutsContainer() != null) { return false; } // IF scroller is at the very top OR there is no scroll bar because there is probably not // enough items to scroll, THEN it's okay for the container to be pulled down. if (mAppsRecyclerView.getScrollBar().getThumbOffset().y <= 0) { return true; } return false; } /** * Focuses the search field and begins an app search. */ public void startAppsSearch() { if (mSearchBarController != null) { mSearchBarController.focusSearchField(); } } /** * Resets the state of AllApps. */ public void reset() { // Reset the search bar and base recycler view after transitioning home scrollToTop(); mSearchBarController.reset(); mAppsRecyclerView.reset(); } @Override protected void onFinishInflate() { super.onFinishInflate(); // 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. getContentView().setOnFocusChangeListener(new View.OnFocusChangeListener() { @Override public void onFocusChange(View v, boolean hasFocus) { if (hasFocus) { mAppsRecyclerView.requestFocus(); } } }); mSearchContainer = findViewById(R.id.search_container); if(Utilities.isAllowNightModePrefEnabled(getContext())) { mSearchContainer.setBackgroundColor(ContextCompat.getColor(getContext(), R.color.night_color)); }else{ if(Utilities.getDrawerBackgroundPrefEnabled(getContext()) != -1){ mSearchContainer.setBackgroundColor(Utilities.getDrawerBackgroundPrefEnabled(getContext())); }else { mSearchContainer.setBackgroundColor(ContextCompat.getColor(getContext(), android.R.color.white)); } } mSearchInput = (ExtendedEditText) findViewById(R.id.search_box_input); // Update the hint to contain the icon. // Prefix the original hint with two spaces. The first space gets replaced by the icon // using span. The second space is used for a singe space character between the hint // and the icon. SpannableString spanned = new SpannableString(" " + mSearchInput.getHint()); spanned.setSpan(new TintedDrawableSpan(getContext(), R.drawable.ic_allapps_search), 0, 1, Spannable.SPAN_EXCLUSIVE_INCLUSIVE); mSearchInput.setHint(spanned); mSearchContainerOffsetTop = getResources().getDimensionPixelSize( R.dimen.all_apps_search_bar_margin_top); mElevationController = Utilities.ATLEAST_LOLLIPOP ? new HeaderElevationController.ControllerVL(mSearchContainer) : new HeaderElevationController.ControllerV16(mSearchContainer); // Load the all apps recycler view mAppsRecyclerView = (AllAppsRecyclerView) findViewById(R.id.apps_list_view); mAppsRecyclerView.setApps(mApps); mAppsRecyclerView.setLayoutManager(mLayoutManager); mAppsRecyclerView.setAdapter(mAdapter); mAppsRecyclerView.setHasFixedSize(true); mAppsRecyclerView.addOnScrollListener(mElevationController); mAppsRecyclerView.setElevationController(mElevationController); if (mItemDecoration != null) { mAppsRecyclerView.addItemDecoration(mItemDecoration); } FocusedItemDecorator focusedItemDecorator = new FocusedItemDecorator(mAppsRecyclerView); mAppsRecyclerView.addItemDecoration(focusedItemDecorator); mAppsRecyclerView.preMeasureViews(mAdapter); mAdapter.setIconFocusListener(focusedItemDecorator.getFocusListener()); if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP) { getRevealView().setVisibility(View.VISIBLE); getContentView().setVisibility(View.VISIBLE); getContentView().setBackground(null); } } @Override public void onBoundsChanged(Rect newBounds) { } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int widthPx = MeasureSpec.getSize(widthMeasureSpec); int heightPx = MeasureSpec.getSize(heightMeasureSpec); updatePaddingsAndMargins(widthPx, heightPx); mContentBounds.set(mContainerPaddingLeft, 0, widthPx - mContainerPaddingRight, heightPx); DeviceProfile grid = mLauncher.getDeviceProfile(); grid.updateAppsViewNumCols(); if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP) { if (mNumAppsPerRow != grid.inv.numColumns || mNumPredictedAppsPerRow != grid.inv.numColumns) { mNumAppsPerRow = grid.inv.numColumns; mNumPredictedAppsPerRow = grid.inv.numColumns; mAppsRecyclerView.setNumAppsPerRow(grid, mNumAppsPerRow); mAdapter.setNumAppsPerRow(mNumAppsPerRow); mApps.setNumAppsPerRow(mNumAppsPerRow, mNumPredictedAppsPerRow, new FullMergeAlgorithm()); if (mNumAppsPerRow > 0) { int rvPadding = mAppsRecyclerView.getPaddingStart(); // Assumes symmetry final int thumbMaxWidth = getResources().getDimensionPixelSize( R.dimen.container_fastscroll_thumb_max_width); mSearchContainer.setPadding( rvPadding - mContainerPaddingLeft + thumbMaxWidth, mSearchContainer.getPaddingTop(), rvPadding - mContainerPaddingRight + thumbMaxWidth, mSearchContainer.getPaddingBottom()); } } super.onMeasure(widthMeasureSpec, heightMeasureSpec); return; } // --- remove START when {@code FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP} is enabled. --- // Update the number of items in the grid before we measure the view // TODO: mSectionNamesMargin is currently 0, but also account for it, // if it's enabled in the future. grid.updateAppsViewNumCols(); if (mNumAppsPerRow != grid.allAppsNumCols || mNumPredictedAppsPerRow != grid.allAppsNumPredictiveCols) { mNumAppsPerRow = grid.allAppsNumCols; mNumPredictedAppsPerRow = grid.allAppsNumPredictiveCols; // If there is a start margin to draw section names, determine how we are going to merge // app sections boolean mergeSectionsFully = mSectionNamesMargin == 0 || !grid.isPhone; AlphabeticalAppsList.MergeAlgorithm mergeAlgorithm = mergeSectionsFully ? new FullMergeAlgorithm() : new SimpleSectionMergeAlgorithm((int) Math.ceil(mNumAppsPerRow / 2f), MIN_ROWS_IN_MERGED_SECTION_PHONE, MAX_NUM_MERGES_PHONE); mAppsRecyclerView.setNumAppsPerRow(grid, mNumAppsPerRow); mAdapter.setNumAppsPerRow(mNumAppsPerRow); mApps.setNumAppsPerRow(mNumAppsPerRow, mNumPredictedAppsPerRow, mergeAlgorithm); } // --- remove END when {@code FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP} is enabled. --- super.onMeasure(widthMeasureSpec, heightMeasureSpec); } /** * 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. */ private void updatePaddingsAndMargins(int widthPx, int heightPx) { Rect bgPadding = new Rect(); getRevealView().getBackground().getPadding(bgPadding); mAppsRecyclerView.updateBackgroundPadding(bgPadding); mAdapter.updateBackgroundPadding(bgPadding); mElevationController.updateBackgroundPadding(bgPadding); // Pad the recycler view by the background padding plus the start margin (for the section // names) int maxScrollBarWidth = mAppsRecyclerView.getMaxScrollbarWidth(); int startInset = Math.max(mSectionNamesMargin, maxScrollBarWidth); if (Utilities.isRtl(getResources())) { mAppsRecyclerView.setPadding(bgPadding.left + maxScrollBarWidth, 0, bgPadding.right + startInset, mRecyclerViewBottomPadding); } else { mAppsRecyclerView.setPadding(bgPadding.left + startInset, 0, bgPadding.right + maxScrollBarWidth, mRecyclerViewBottomPadding); } MarginLayoutParams lp = (MarginLayoutParams) mSearchContainer.getLayoutParams(); lp.leftMargin = bgPadding.left; lp.rightMargin = bgPadding.right; // Clip the view to the left and right edge of the background to // to prevent shadows from rendering beyond the edges final Rect newClipBounds = new Rect( bgPadding.left, 0, widthPx - bgPadding.right, heightPx); setClipBounds(newClipBounds); // Allow the overscroll effect to reach the edges of the view mAppsRecyclerView.setClipToPadding(false); DeviceProfile grid = mLauncher.getDeviceProfile(); if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP) { if (!grid.isVerticalBarLayout()) { MarginLayoutParams mlp = (MarginLayoutParams) mAppsRecyclerView.getLayoutParams(); Rect insets = mLauncher.getDragLayer().getInsets(); getContentView().setPadding(0, 0, 0, 0); int height = insets.top + grid.hotseatCellHeightPx; mlp.topMargin = height; mAppsRecyclerView.setLayoutParams(mlp); mSearchContainer.setPadding( mSearchContainer.getPaddingLeft(), insets.top + mSearchContainerOffsetTop, mSearchContainer.getPaddingRight(), mSearchContainer.getPaddingBottom()); lp.height = height; View navBarBg = findViewById(R.id.nav_bar_bg); ViewGroup.LayoutParams params = navBarBg.getLayoutParams(); params.height = insets.bottom; navBarBg.setLayoutParams(params); navBarBg.setVisibility(View.VISIBLE); } } mSearchContainer.setLayoutParams(lp); } @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); } @SuppressLint("ClickableViewAccessibility") @Override public boolean onTouchEvent(MotionEvent ev) { return handleTouchEvent(ev); } @Override public boolean onLongClick(View v) { // Return early if this is not initiated from a touch if (!v.isInTouchMode()) return false; // When we have exited all apps or are in transition, disregard long clicks if (!mLauncher.isAppsViewVisible() || mLauncher.getWorkspace().isSwitchingState()) return false; // Return if global dragging is not enabled or we are already dragging if (!mLauncher.isDraggingEnabled()) return false; if (mLauncher.getDragController().isDragging()) return false; // Start the drag DragOptions dragOptions = new DragOptions(); if (v instanceof BubbleTextView) { final BubbleTextView icon = (BubbleTextView) v; if (icon.hasDeepShortcuts()) { DeepShortcutsContainer dsc = DeepShortcutsContainer.showForIcon(icon); if (dsc != null) { dragOptions.deferDragCondition = dsc.createDeferDragCondition(new Runnable() { @Override public void run() { icon.setVisibility(VISIBLE); } }); } } } mLauncher.getWorkspace().beginDragShared(v, this, dragOptions); if (FeatureFlags.LAUNCHER3_LEGACY_WORKSPACE_DND) { // Enter spring loaded mode (the new workspace does this in // onDragStart(), so we don't want to do it here) mLauncher.enterSpringLoadedDragMode(); } return false; } @Override public boolean supportsFlingToDelete() { return true; } @Override public boolean supportsAppInfoDropTarget() { return true; } @Override public boolean supportsDeleteDropTarget() { return false; } @Override public float getIntrinsicIconScaleFactor() { DeviceProfile grid = mLauncher.getDeviceProfile(); return (float) grid.allAppsIconSizePx / grid.iconSizePx; } @Override public void onFlingToDeleteCompleted() { // We just dismiss the drag when we fling, so cleanup here mLauncher.exitSpringLoadedDragModeDelayed(true, Launcher.EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, null); mLauncher.unlockScreenOrientation(false); } @Override public void onDropCompleted(View target, DropTarget.DragObject d, boolean isFlingToDelete, boolean success) { if (isFlingToDelete || !success || (target != mLauncher.getWorkspace() && !(target instanceof DeleteDropTarget) && !(target instanceof Folder))) { // Exit spring loaded mode if we have not successfully dropped or have not handled the // drop in Workspace mLauncher.exitSpringLoadedDragModeDelayed(true, Launcher.EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, null); } mLauncher.unlockScreenOrientation(false); // Display an error message if the drag failed due to there not being enough space on the // target layout we were dropping on. if (!success) { boolean showOutOfSpaceMessage = false; if (target instanceof Workspace && !mLauncher.getDragController().isDeferringDrag()) { int currentScreen = mLauncher.getCurrentWorkspaceScreen(); Workspace workspace = (Workspace) target; CellLayout layout = (CellLayout) workspace.getChildAt(currentScreen); ItemInfo itemInfo = d.dragInfo; if (layout != null) { showOutOfSpaceMessage = !layout.findCellForSpan(null, itemInfo.spanX, itemInfo.spanY); } } if (showOutOfSpaceMessage) { mLauncher.showOutOfSpaceMessage(false); } d.deferDragViewCleanupPostAnimation = false; } } @Override public void onLauncherTransitionPrepare(Launcher l, boolean animated, boolean multiplePagesVisible) { // Do nothing } @Override public void onLauncherTransitionStart(Launcher l, boolean animated, boolean toWorkspace) { // Do nothing } @Override public void onLauncherTransitionStep(Launcher l, float t) { // Do nothing } @Override public void onLauncherTransitionEnd(Launcher l, boolean animated, boolean toWorkspace) { if (toWorkspace) { reset(); } } /** * Handles the touch events to dismiss all apps when clicking outside the bounds of the * recycler view. */ private boolean handleTouchEvent(MotionEvent ev) { DeviceProfile grid = mLauncher.getDeviceProfile(); int x = (int) ev.getX(); int y = (int) ev.getY(); switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: if (!mContentBounds.isEmpty()) { // Outset the fixed bounds and check if the touch is outside all apps Rect tmpRect = new Rect(mContentBounds); tmpRect.inset(-grid.allAppsIconSizePx / 2, 0); if (ev.getX() < tmpRect.left || ev.getX() > tmpRect.right) { mBoundsCheckLastTouchDownPos.set(x, y); return true; } } else { // Check if the touch is outside all apps if (ev.getX() < getPaddingLeft() || ev.getX() > (getWidth() - getPaddingRight())) { mBoundsCheckLastTouchDownPos.set(x, y); return true; } } break; case MotionEvent.ACTION_UP: if (mBoundsCheckLastTouchDownPos.x > -1) { ViewConfiguration viewConfig = ViewConfiguration.get(getContext()); float dx = ev.getX() - mBoundsCheckLastTouchDownPos.x; float dy = ev.getY() - mBoundsCheckLastTouchDownPos.y; float distance = (float) Math.hypot(dx, dy); if (distance < viewConfig.getScaledTouchSlop()) { // The background was clicked, so just go home Launcher launcher = Launcher.getLauncher(getContext()); launcher.showWorkspace(true); return true; } } // Fall through case MotionEvent.ACTION_CANCEL: mBoundsCheckLastTouchDownPos.set(-1, -1); break; } return false; } @Override public void onSearchResult(String query, ArrayList<ComponentKey> apps) { if (apps != null) { if (mApps.setOrderedFilter(apps)) { mAppsRecyclerView.onSearchResultsChanged(); } mAdapter.setLastSearchQuery(query); } } @Override public void clearSearchResult() { if (mApps.setOrderedFilter(null)) { mAppsRecyclerView.onSearchResultsChanged(); } // Clear the search query mSearchQueryBuilder.clear(); mSearchQueryBuilder.clearSpans(); Selection.setSelection(mSearchQueryBuilder, 0); } @Override public void fillInLaunchSourceData(View v, ItemInfo info, Target target, Target targetParent) { targetParent.containerType = mAppsRecyclerView.getContainerType(v); } public boolean shouldRestoreImeState() { return !TextUtils.isEmpty(mSearchInput.getText()); } }