• Home
  • History
  • Annotate
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
continueMerging(AlphabeticalAppsList.SectionInfo section, AlphabeticalAppsList.SectionInfo withSection, int sectionAppCount, int numAppsPerRow, int mergeCount)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 
SimpleSectionMergeAlgorithm(int minAppsPerRow, int minRowsInMergedSection, int maxNumMerges)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
continueMerging(AlphabeticalAppsList.SectionInfo section, AlphabeticalAppsList.SectionInfo withSection, int sectionAppCount, int numAppsPerRow, int mergeCount)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 
AllAppsContainerView(Context context)162     public AllAppsContainerView(Context context) {
163         this(context, null);
164     }
165 
AllAppsContainerView(Context context, AttributeSet attrs)166     public AllAppsContainerView(Context context, AttributeSet attrs) {
167         this(context, attrs, 0);
168     }
169 
AllAppsContainerView(Context context, AttributeSet attrs, int defStyleAttr)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      */
setPredictedApps(List<ComponentKey> apps)191     public void setPredictedApps(List<ComponentKey> apps) {
192         mApps.setPredictedApps(apps);
193     }
194 
195     /**
196      * Sets the current set of apps.
197      */
setApps(List<AppInfo> apps)198     public void setApps(List<AppInfo> apps) {
199         mApps.setApps(apps);
200     }
201 
202     /**
203      * Adds new apps to the list.
204      */
addApps(List<AppInfo> apps)205     public void addApps(List<AppInfo> apps) {
206         mApps.addApps(apps);
207     }
208 
209     /**
210      * Updates existing apps in the list
211      */
updateApps(List<AppInfo> apps)212     public void updateApps(List<AppInfo> apps) {
213         mApps.updateApps(apps);
214     }
215 
216     /**
217      * Removes some apps from the list.
218      */
removeApps(List<AppInfo> apps)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      */
setSearchBarController(AllAppsSearchBarController searchController)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      */
scrollToTop()240     public void scrollToTop() {
241         mAppsRecyclerView.scrollToTop();
242     }
243 
244     /**
245      * Focuses the search field and begins an app search.
246      */
startAppsSearch()247     public void startAppsSearch() {
248         if (mSearchBarController != null) {
249             mSearchBarController.focusSearchField();
250         }
251     }
252 
253     /**
254      * Resets the state of AllApps.
255      */
reset()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
onFinishInflate()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
onBoundsChanged(Rect newBounds)318     public void onBoundsChanged(Rect newBounds) {
319         mLauncher.updateOverlayBounds(newBounds);
320     }
321 
322     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)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
onUpdateBgPadding(Rect padding, Rect bgPadding)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
dispatchKeyEvent(KeyEvent event)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
onInterceptTouchEvent(MotionEvent ev)415     public boolean onInterceptTouchEvent(MotionEvent ev) {
416         return handleTouchEvent(ev);
417     }
418 
419     @SuppressLint("ClickableViewAccessibility")
420     @Override
onTouchEvent(MotionEvent ev)421     public boolean onTouchEvent(MotionEvent ev) {
422         return handleTouchEvent(ev);
423     }
424 
425     @SuppressLint("ClickableViewAccessibility")
426     @Override
onTouch(View v, MotionEvent ev)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
onLongClick(View v)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
supportsFlingToDelete()456     public boolean supportsFlingToDelete() {
457         return true;
458     }
459 
460     @Override
supportsAppInfoDropTarget()461     public boolean supportsAppInfoDropTarget() {
462         return true;
463     }
464 
465     @Override
supportsDeleteDropTarget()466     public boolean supportsDeleteDropTarget() {
467         return false;
468     }
469 
470     @Override
getIntrinsicIconScaleFactor()471     public float getIntrinsicIconScaleFactor() {
472         DeviceProfile grid = mLauncher.getDeviceProfile();
473         return (float) grid.allAppsIconSizePx / grid.iconSizePx;
474     }
475 
476     @Override
onFlingToDeleteCompleted()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
onDropCompleted(View target, DropTarget.DragObject d, boolean isFlingToDelete, boolean success)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
onLauncherTransitionPrepare(Launcher l, boolean animated, boolean toWorkspace)519     public void onLauncherTransitionPrepare(Launcher l, boolean animated, boolean toWorkspace) {
520         // Do nothing
521     }
522 
523     @Override
onLauncherTransitionStart(Launcher l, boolean animated, boolean toWorkspace)524     public void onLauncherTransitionStart(Launcher l, boolean animated, boolean toWorkspace) {
525         // Do nothing
526     }
527 
528     @Override
onLauncherTransitionStep(Launcher l, float t)529     public void onLauncherTransitionStep(Launcher l, float t) {
530         // Do nothing
531     }
532 
533     @Override
onLauncherTransitionEnd(Launcher l, boolean animated, boolean toWorkspace)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      */
handleTouchEvent(MotionEvent ev)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
onSearchResult(String query, ArrayList<ComponentKey> apps)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
clearSearchResult()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