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