Home | History | Annotate | Download | only in allapps
      1 /*
      2  * Copyright (C) 2015 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 package com.android.launcher3.allapps;
     17 
     18 import android.annotation.SuppressLint;
     19 import android.content.Context;
     20 import android.content.res.Resources;
     21 import android.graphics.Point;
     22 import android.graphics.Rect;
     23 import android.support.v7.widget.RecyclerView;
     24 import android.text.Selection;
     25 import android.text.SpannableStringBuilder;
     26 import android.text.method.TextKeyListener;
     27 import android.util.AttributeSet;
     28 import android.view.KeyEvent;
     29 import android.view.LayoutInflater;
     30 import android.view.MotionEvent;
     31 import android.view.View;
     32 import android.view.ViewConfiguration;
     33 
     34 import com.android.launcher3.AppInfo;
     35 import com.android.launcher3.BaseContainerView;
     36 import com.android.launcher3.BubbleTextView;
     37 import com.android.launcher3.CellLayout;
     38 import com.android.launcher3.DeleteDropTarget;
     39 import com.android.launcher3.DeviceProfile;
     40 import com.android.launcher3.DragSource;
     41 import com.android.launcher3.DropTarget;
     42 import com.android.launcher3.ExtendedEditText;
     43 import com.android.launcher3.Folder;
     44 import com.android.launcher3.ItemInfo;
     45 import com.android.launcher3.Launcher;
     46 import com.android.launcher3.LauncherTransitionable;
     47 import com.android.launcher3.R;
     48 import com.android.launcher3.Utilities;
     49 import com.android.launcher3.Workspace;
     50 import com.android.launcher3.util.ComponentKey;
     51 
     52 import java.nio.charset.Charset;
     53 import java.nio.charset.CharsetEncoder;
     54 import java.util.ArrayList;
     55 import java.util.List;
     56 
     57 
     58 
     59 /**
     60  * A merge algorithm that merges every section indiscriminately.
     61  */
     62 final class FullMergeAlgorithm implements AlphabeticalAppsList.MergeAlgorithm {
     63 
     64     @Override
     65     public boolean continueMerging(AlphabeticalAppsList.SectionInfo section,
     66            AlphabeticalAppsList.SectionInfo withSection,
     67            int sectionAppCount, int numAppsPerRow, int mergeCount) {
     68         // Don't merge the predicted apps
     69         if (section.firstAppItem.viewType != AllAppsGridAdapter.ICON_VIEW_TYPE) {
     70             return false;
     71         }
     72         // Otherwise, merge every other section
     73         return true;
     74     }
     75 }
     76 
     77 /**
     78  * The logic we use to merge multiple sections.  We only merge sections when their final row
     79  * contains less than a certain number of icons, and stop at a specified max number of merges.
     80  * In addition, we will try and not merge sections that identify apps from different scripts.
     81  */
     82 final class SimpleSectionMergeAlgorithm implements AlphabeticalAppsList.MergeAlgorithm {
     83 
     84     private int mMinAppsPerRow;
     85     private int mMinRowsInMergedSection;
     86     private int mMaxAllowableMerges;
     87     private CharsetEncoder mAsciiEncoder;
     88 
     89     public SimpleSectionMergeAlgorithm(int minAppsPerRow, int minRowsInMergedSection, int maxNumMerges) {
     90         mMinAppsPerRow = minAppsPerRow;
     91         mMinRowsInMergedSection = minRowsInMergedSection;
     92         mMaxAllowableMerges = maxNumMerges;
     93         mAsciiEncoder = Charset.forName("US-ASCII").newEncoder();
     94     }
     95 
     96     @Override
     97     public boolean continueMerging(AlphabeticalAppsList.SectionInfo section,
     98            AlphabeticalAppsList.SectionInfo withSection,
     99            int sectionAppCount, int numAppsPerRow, int mergeCount) {
    100         // Don't merge the predicted apps
    101         if (section.firstAppItem.viewType != AllAppsGridAdapter.ICON_VIEW_TYPE) {
    102             return false;
    103         }
    104 
    105         // Continue merging if the number of hanging apps on the final row is less than some
    106         // fixed number (ragged), the merged rows has yet to exceed some minimum row count,
    107         // and while the number of merged sections is less than some fixed number of merges
    108         int rows = sectionAppCount / numAppsPerRow;
    109         int cols = sectionAppCount % numAppsPerRow;
    110 
    111         // Ensure that we do not merge across scripts, currently we only allow for english and
    112         // native scripts so we can test if both can just be ascii encoded
    113         boolean isCrossScript = false;
    114         if (section.firstAppItem != null && withSection.firstAppItem != null) {
    115             isCrossScript = mAsciiEncoder.canEncode(section.firstAppItem.sectionName) !=
    116                     mAsciiEncoder.canEncode(withSection.firstAppItem.sectionName);
    117         }
    118         return (0 < cols && cols < mMinAppsPerRow) &&
    119                 rows < mMinRowsInMergedSection &&
    120                 mergeCount < mMaxAllowableMerges &&
    121                 !isCrossScript;
    122     }
    123 }
    124 
    125 /**
    126  * The all apps view container.
    127  */
    128 public class AllAppsContainerView extends BaseContainerView implements DragSource,
    129         LauncherTransitionable, View.OnTouchListener, View.OnLongClickListener,
    130         AllAppsSearchBarController.Callbacks {
    131 
    132     private static final int MIN_ROWS_IN_MERGED_SECTION_PHONE = 3;
    133     private static final int MAX_NUM_MERGES_PHONE = 2;
    134 
    135     private final Launcher mLauncher;
    136     private final AlphabeticalAppsList mApps;
    137     private final AllAppsGridAdapter mAdapter;
    138     private final RecyclerView.LayoutManager mLayoutManager;
    139     private final RecyclerView.ItemDecoration mItemDecoration;
    140 
    141     // The computed bounds of the container
    142     private final Rect mContentBounds = new Rect();
    143 
    144     private AllAppsRecyclerView mAppsRecyclerView;
    145     private AllAppsSearchBarController mSearchBarController;
    146 
    147     private View mSearchContainer;
    148     private ExtendedEditText mSearchInput;
    149     private HeaderElevationController mElevationController;
    150 
    151     private SpannableStringBuilder mSearchQueryBuilder = null;
    152 
    153     private int mSectionNamesMargin;
    154     private int mNumAppsPerRow;
    155     private int mNumPredictedAppsPerRow;
    156     private int mRecyclerViewTopBottomPadding;
    157     // This coordinate is relative to this container view
    158     private final Point mBoundsCheckLastTouchDownPos = new Point(-1, -1);
    159     // This coordinate is relative to its parent
    160     private final Point mIconLastTouchPos = new Point();
    161 
    162     public AllAppsContainerView(Context context) {
    163         this(context, null);
    164     }
    165 
    166     public AllAppsContainerView(Context context, AttributeSet attrs) {
    167         this(context, attrs, 0);
    168     }
    169 
    170     public AllAppsContainerView(Context context, AttributeSet attrs, int defStyleAttr) {
    171         super(context, attrs, defStyleAttr);
    172         Resources res = context.getResources();
    173 
    174         mLauncher = (Launcher) context;
    175         mSectionNamesMargin = res.getDimensionPixelSize(R.dimen.all_apps_grid_view_start_margin);
    176         mApps = new AlphabeticalAppsList(context);
    177         mAdapter = new AllAppsGridAdapter(mLauncher, mApps, this, mLauncher, this);
    178         mApps.setAdapter(mAdapter);
    179         mLayoutManager = mAdapter.getLayoutManager();
    180         mItemDecoration = mAdapter.getItemDecoration();
    181         mRecyclerViewTopBottomPadding =
    182                 res.getDimensionPixelSize(R.dimen.all_apps_list_top_bottom_padding);
    183 
    184         mSearchQueryBuilder = new SpannableStringBuilder();
    185         Selection.setSelection(mSearchQueryBuilder, 0);
    186     }
    187 
    188     /**
    189      * Sets the current set of predicted apps.
    190      */
    191     public void setPredictedApps(List<ComponentKey> apps) {
    192         mApps.setPredictedApps(apps);
    193     }
    194 
    195     /**
    196      * Sets the current set of apps.
    197      */
    198     public void setApps(List<AppInfo> apps) {
    199         mApps.setApps(apps);
    200     }
    201 
    202     /**
    203      * Adds new apps to the list.
    204      */
    205     public void addApps(List<AppInfo> apps) {
    206         mApps.addApps(apps);
    207     }
    208 
    209     /**
    210      * Updates existing apps in the list
    211      */
    212     public void updateApps(List<AppInfo> apps) {
    213         mApps.updateApps(apps);
    214     }
    215 
    216     /**
    217      * Removes some apps from the list.
    218      */
    219     public void removeApps(List<AppInfo> apps) {
    220         mApps.removeApps(apps);
    221     }
    222 
    223     /**
    224      * Sets the search bar that shows above the a-z list.
    225      */
    226     public void setSearchBarController(AllAppsSearchBarController searchController) {
    227         if (mSearchBarController != null) {
    228             throw new RuntimeException("Expected search bar controller to only be set once");
    229         }
    230         mSearchBarController = searchController;
    231         mSearchBarController.initialize(mApps, mSearchInput, mLauncher, this);
    232         mAdapter.setSearchController(mSearchBarController);
    233 
    234         updateBackgroundAndPaddings();
    235     }
    236 
    237     /**
    238      * Scrolls this list view to the top.
    239      */
    240     public void scrollToTop() {
    241         mAppsRecyclerView.scrollToTop();
    242     }
    243 
    244     /**
    245      * Focuses the search field and begins an app search.
    246      */
    247     public void startAppsSearch() {
    248         if (mSearchBarController != null) {
    249             mSearchBarController.focusSearchField();
    250         }
    251     }
    252 
    253     /**
    254      * Resets the state of AllApps.
    255      */
    256     public void reset() {
    257         // Reset the search bar and base recycler view after transitioning home
    258         mSearchBarController.reset();
    259         mAppsRecyclerView.reset();
    260     }
    261 
    262     @Override
    263     protected void onFinishInflate() {
    264         super.onFinishInflate();
    265 
    266         // This is a focus listener that proxies focus from a view into the list view.  This is to
    267         // work around the search box from getting first focus and showing the cursor.
    268         getContentView().setOnFocusChangeListener(new View.OnFocusChangeListener() {
    269             @Override
    270             public void onFocusChange(View v, boolean hasFocus) {
    271                 if (hasFocus) {
    272                     mAppsRecyclerView.requestFocus();
    273                 }
    274             }
    275         });
    276 
    277         mSearchContainer = findViewById(R.id.search_container);
    278         mSearchInput = (ExtendedEditText) findViewById(R.id.search_box_input);
    279         mElevationController = Utilities.ATLEAST_LOLLIPOP
    280                 ? new HeaderElevationController.ControllerVL(mSearchContainer)
    281                 : new HeaderElevationController.ControllerV16(mSearchContainer);
    282 
    283         // Load the all apps recycler view
    284         mAppsRecyclerView = (AllAppsRecyclerView) findViewById(R.id.apps_list_view);
    285         mAppsRecyclerView.setApps(mApps);
    286         mAppsRecyclerView.setLayoutManager(mLayoutManager);
    287         mAppsRecyclerView.setAdapter(mAdapter);
    288         mAppsRecyclerView.setHasFixedSize(true);
    289         mAppsRecyclerView.addOnScrollListener(mElevationController);
    290         mAppsRecyclerView.setElevationController(mElevationController);
    291 
    292         if (mItemDecoration != null) {
    293             mAppsRecyclerView.addItemDecoration(mItemDecoration);
    294         }
    295 
    296         // Precalculate the prediction icon and normal icon sizes
    297         LayoutInflater layoutInflater = LayoutInflater.from(getContext());
    298         final int widthMeasureSpec = MeasureSpec.makeMeasureSpec(
    299                 getResources().getDisplayMetrics().widthPixels, MeasureSpec.AT_MOST);
    300         final int heightMeasureSpec = MeasureSpec.makeMeasureSpec(
    301                 getResources().getDisplayMetrics().heightPixels, MeasureSpec.AT_MOST);
    302 
    303         BubbleTextView icon = (BubbleTextView) layoutInflater.inflate(
    304                 R.layout.all_apps_icon, this, false);
    305         icon.applyDummyInfo();
    306         icon.measure(widthMeasureSpec, heightMeasureSpec);
    307         BubbleTextView predIcon = (BubbleTextView) layoutInflater.inflate(
    308                 R.layout.all_apps_prediction_bar_icon, this, false);
    309         predIcon.applyDummyInfo();
    310         predIcon.measure(widthMeasureSpec, heightMeasureSpec);
    311         mAppsRecyclerView.setPremeasuredIconHeights(predIcon.getMeasuredHeight(),
    312                 icon.getMeasuredHeight());
    313 
    314         updateBackgroundAndPaddings();
    315     }
    316 
    317     @Override
    318     public void onBoundsChanged(Rect newBounds) {
    319         mLauncher.updateOverlayBounds(newBounds);
    320     }
    321 
    322     @Override
    323     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    324         mContentBounds.set(mContentPadding.left, mContentPadding.top,
    325                 MeasureSpec.getSize(widthMeasureSpec) - mContentPadding.right,
    326                 MeasureSpec.getSize(heightMeasureSpec) - mContentPadding.bottom);
    327 
    328         // Update the number of items in the grid before we measure the view
    329         // TODO: mSectionNamesMargin is currently 0, but also account for it,
    330         // if it's enabled in the future.
    331         int availableWidth = (!mContentBounds.isEmpty() ? mContentBounds.width() :
    332                 MeasureSpec.getSize(widthMeasureSpec))
    333                     - 2 * mAppsRecyclerView.getMaxScrollbarWidth();
    334         DeviceProfile grid = mLauncher.getDeviceProfile();
    335         grid.updateAppsViewNumCols(getResources(), availableWidth);
    336         if (mNumAppsPerRow != grid.allAppsNumCols ||
    337                 mNumPredictedAppsPerRow != grid.allAppsNumPredictiveCols) {
    338             mNumAppsPerRow = grid.allAppsNumCols;
    339             mNumPredictedAppsPerRow = grid.allAppsNumPredictiveCols;
    340 
    341             // If there is a start margin to draw section names, determine how we are going to merge
    342             // app sections
    343             boolean mergeSectionsFully = mSectionNamesMargin == 0 || !grid.isPhone;
    344             AlphabeticalAppsList.MergeAlgorithm mergeAlgorithm = mergeSectionsFully ?
    345                     new FullMergeAlgorithm() :
    346                     new SimpleSectionMergeAlgorithm((int) Math.ceil(mNumAppsPerRow / 2f),
    347                             MIN_ROWS_IN_MERGED_SECTION_PHONE, MAX_NUM_MERGES_PHONE);
    348 
    349             mAppsRecyclerView.setNumAppsPerRow(grid, mNumAppsPerRow);
    350             mAdapter.setNumAppsPerRow(mNumAppsPerRow);
    351             mApps.setNumAppsPerRow(mNumAppsPerRow, mNumPredictedAppsPerRow, mergeAlgorithm);
    352 
    353             if (mNumAppsPerRow > 0) {
    354                 int iconSize = availableWidth / mNumAppsPerRow;
    355                 int iconSpacing = (iconSize - grid.allAppsIconSizePx) / 2;
    356                 mSearchInput.setPaddingRelative(iconSpacing, 0, iconSpacing, 0);
    357             }
    358         }
    359 
    360         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    361     }
    362 
    363     /**
    364      * Update the background and padding of the Apps view and children.  Instead of insetting the
    365      * container view, we inset the background and padding of the recycler view to allow for the
    366      * recycler view to handle touch events (for fast scrolling) all the way to the edge.
    367      */
    368     @Override
    369     protected void onUpdateBgPadding(Rect padding, Rect bgPadding) {
    370         mAppsRecyclerView.updateBackgroundPadding(bgPadding);
    371         mAdapter.updateBackgroundPadding(bgPadding);
    372         mElevationController.updateBackgroundPadding(bgPadding);
    373 
    374         // Pad the recycler view by the background padding plus the start margin (for the section
    375         // names)
    376         int maxScrollBarWidth = mAppsRecyclerView.getMaxScrollbarWidth();
    377         int startInset = Math.max(mSectionNamesMargin, maxScrollBarWidth);
    378         int topBottomPadding = mRecyclerViewTopBottomPadding;
    379         if (Utilities.isRtl(getResources())) {
    380             mAppsRecyclerView.setPadding(padding.left + maxScrollBarWidth,
    381                     topBottomPadding, padding.right + startInset, topBottomPadding);
    382         } else {
    383             mAppsRecyclerView.setPadding(padding.left + startInset, topBottomPadding,
    384                     padding.right + maxScrollBarWidth, topBottomPadding);
    385         }
    386 
    387         MarginLayoutParams lp = (MarginLayoutParams) mSearchContainer.getLayoutParams();
    388         lp.leftMargin = padding.left;
    389         lp.rightMargin = padding.right;
    390         mSearchContainer.setLayoutParams(lp);
    391     }
    392 
    393     @Override
    394     public boolean dispatchKeyEvent(KeyEvent event) {
    395         // Determine if the key event was actual text, if so, focus the search bar and then dispatch
    396         // the key normally so that it can process this key event
    397         if (!mSearchBarController.isSearchFieldFocused() &&
    398                 event.getAction() == KeyEvent.ACTION_DOWN) {
    399             final int unicodeChar = event.getUnicodeChar();
    400             final boolean isKeyNotWhitespace = unicodeChar > 0 &&
    401                     !Character.isWhitespace(unicodeChar) && !Character.isSpaceChar(unicodeChar);
    402             if (isKeyNotWhitespace) {
    403                 boolean gotKey = TextKeyListener.getInstance().onKeyDown(this, mSearchQueryBuilder,
    404                         event.getKeyCode(), event);
    405                 if (gotKey && mSearchQueryBuilder.length() > 0) {
    406                     mSearchBarController.focusSearchField();
    407                 }
    408             }
    409         }
    410 
    411         return super.dispatchKeyEvent(event);
    412     }
    413 
    414     @Override
    415     public boolean onInterceptTouchEvent(MotionEvent ev) {
    416         return handleTouchEvent(ev);
    417     }
    418 
    419     @SuppressLint("ClickableViewAccessibility")
    420     @Override
    421     public boolean onTouchEvent(MotionEvent ev) {
    422         return handleTouchEvent(ev);
    423     }
    424 
    425     @SuppressLint("ClickableViewAccessibility")
    426     @Override
    427     public boolean onTouch(View v, MotionEvent ev) {
    428         switch (ev.getAction()) {
    429             case MotionEvent.ACTION_DOWN:
    430             case MotionEvent.ACTION_MOVE:
    431                 mIconLastTouchPos.set((int) ev.getX(), (int) ev.getY());
    432                 break;
    433         }
    434         return false;
    435     }
    436 
    437     @Override
    438     public boolean onLongClick(View v) {
    439         // Return early if this is not initiated from a touch
    440         if (!v.isInTouchMode()) return false;
    441         // When we have exited all apps or are in transition, disregard long clicks
    442         if (!mLauncher.isAppsViewVisible() ||
    443                 mLauncher.getWorkspace().isSwitchingState()) return false;
    444         // Return if global dragging is not enabled
    445         if (!mLauncher.isDraggingEnabled()) return false;
    446 
    447         // Start the drag
    448         mLauncher.getWorkspace().beginDragShared(v, mIconLastTouchPos, this, false);
    449         // Enter spring loaded mode
    450         mLauncher.enterSpringLoadedDragMode();
    451 
    452         return false;
    453     }
    454 
    455     @Override
    456     public boolean supportsFlingToDelete() {
    457         return true;
    458     }
    459 
    460     @Override
    461     public boolean supportsAppInfoDropTarget() {
    462         return true;
    463     }
    464 
    465     @Override
    466     public boolean supportsDeleteDropTarget() {
    467         return false;
    468     }
    469 
    470     @Override
    471     public float getIntrinsicIconScaleFactor() {
    472         DeviceProfile grid = mLauncher.getDeviceProfile();
    473         return (float) grid.allAppsIconSizePx / grid.iconSizePx;
    474     }
    475 
    476     @Override
    477     public void onFlingToDeleteCompleted() {
    478         // We just dismiss the drag when we fling, so cleanup here
    479         mLauncher.exitSpringLoadedDragModeDelayed(true,
    480                 Launcher.EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, null);
    481         mLauncher.unlockScreenOrientation(false);
    482     }
    483 
    484     @Override
    485     public void onDropCompleted(View target, DropTarget.DragObject d, boolean isFlingToDelete,
    486             boolean success) {
    487         if (isFlingToDelete || !success || (target != mLauncher.getWorkspace() &&
    488                 !(target instanceof DeleteDropTarget) && !(target instanceof Folder))) {
    489             // Exit spring loaded mode if we have not successfully dropped or have not handled the
    490             // drop in Workspace
    491             mLauncher.exitSpringLoadedDragModeDelayed(true,
    492                     Launcher.EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, null);
    493         }
    494         mLauncher.unlockScreenOrientation(false);
    495 
    496         // Display an error message if the drag failed due to there not being enough space on the
    497         // target layout we were dropping on.
    498         if (!success) {
    499             boolean showOutOfSpaceMessage = false;
    500             if (target instanceof Workspace) {
    501                 int currentScreen = mLauncher.getCurrentWorkspaceScreen();
    502                 Workspace workspace = (Workspace) target;
    503                 CellLayout layout = (CellLayout) workspace.getChildAt(currentScreen);
    504                 ItemInfo itemInfo = (ItemInfo) d.dragInfo;
    505                 if (layout != null) {
    506                     showOutOfSpaceMessage =
    507                             !layout.findCellForSpan(null, itemInfo.spanX, itemInfo.spanY);
    508                 }
    509             }
    510             if (showOutOfSpaceMessage) {
    511                 mLauncher.showOutOfSpaceMessage(false);
    512             }
    513 
    514             d.deferDragViewCleanupPostAnimation = false;
    515         }
    516     }
    517 
    518     @Override
    519     public void onLauncherTransitionPrepare(Launcher l, boolean animated, boolean toWorkspace) {
    520         // Do nothing
    521     }
    522 
    523     @Override
    524     public void onLauncherTransitionStart(Launcher l, boolean animated, boolean toWorkspace) {
    525         // Do nothing
    526     }
    527 
    528     @Override
    529     public void onLauncherTransitionStep(Launcher l, float t) {
    530         // Do nothing
    531     }
    532 
    533     @Override
    534     public void onLauncherTransitionEnd(Launcher l, boolean animated, boolean toWorkspace) {
    535         if (toWorkspace) {
    536             reset();
    537         }
    538     }
    539 
    540     /**
    541      * Handles the touch events to dismiss all apps when clicking outside the bounds of the
    542      * recycler view.
    543      */
    544     private boolean handleTouchEvent(MotionEvent ev) {
    545         DeviceProfile grid = mLauncher.getDeviceProfile();
    546         int x = (int) ev.getX();
    547         int y = (int) ev.getY();
    548 
    549         switch (ev.getAction()) {
    550             case MotionEvent.ACTION_DOWN:
    551                 if (!mContentBounds.isEmpty()) {
    552                     // Outset the fixed bounds and check if the touch is outside all apps
    553                     Rect tmpRect = new Rect(mContentBounds);
    554                     tmpRect.inset(-grid.allAppsIconSizePx / 2, 0);
    555                     if (ev.getX() < tmpRect.left || ev.getX() > tmpRect.right) {
    556                         mBoundsCheckLastTouchDownPos.set(x, y);
    557                         return true;
    558                     }
    559                 } else {
    560                     // Check if the touch is outside all apps
    561                     if (ev.getX() < getPaddingLeft() ||
    562                             ev.getX() > (getWidth() - getPaddingRight())) {
    563                         mBoundsCheckLastTouchDownPos.set(x, y);
    564                         return true;
    565                     }
    566                 }
    567                 break;
    568             case MotionEvent.ACTION_UP:
    569                 if (mBoundsCheckLastTouchDownPos.x > -1) {
    570                     ViewConfiguration viewConfig = ViewConfiguration.get(getContext());
    571                     float dx = ev.getX() - mBoundsCheckLastTouchDownPos.x;
    572                     float dy = ev.getY() - mBoundsCheckLastTouchDownPos.y;
    573                     float distance = (float) Math.hypot(dx, dy);
    574                     if (distance < viewConfig.getScaledTouchSlop()) {
    575                         // The background was clicked, so just go home
    576                         Launcher launcher = (Launcher) getContext();
    577                         launcher.showWorkspace(true);
    578                         return true;
    579                     }
    580                 }
    581                 // Fall through
    582             case MotionEvent.ACTION_CANCEL:
    583                 mBoundsCheckLastTouchDownPos.set(-1, -1);
    584                 break;
    585         }
    586         return false;
    587     }
    588 
    589     @Override
    590     public void onSearchResult(String query, ArrayList<ComponentKey> apps) {
    591         if (apps != null) {
    592             if (mApps.setOrderedFilter(apps)) {
    593                 mAppsRecyclerView.onSearchResultsChanged();
    594             }
    595             mAdapter.setLastSearchQuery(query);
    596         }
    597     }
    598 
    599     @Override
    600     public void clearSearchResult() {
    601         if (mApps.setOrderedFilter(null)) {
    602             mAppsRecyclerView.onSearchResultsChanged();
    603         }
    604 
    605         // Clear the search query
    606         mSearchQueryBuilder.clear();
    607         mSearchQueryBuilder.clearSpans();
    608         Selection.setSelection(mSearchQueryBuilder, 0);
    609     }
    610 }
    611