OSDN Git Service

Updating the search hint to contain the icon as a span object.
[android-x86/packages-apps-Launcher3.git] / src / com / android / launcher3 / allapps / AllAppsContainerView.java
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.Spannable;
26 import android.text.SpannableString;
27 import android.text.SpannableStringBuilder;
28 import android.text.method.TextKeyListener;
29 import android.util.AttributeSet;
30 import android.view.KeyEvent;
31 import android.view.MotionEvent;
32 import android.view.View;
33 import android.view.ViewConfiguration;
34 import android.view.ViewGroup;
35
36 import com.android.launcher3.AppInfo;
37 import com.android.launcher3.BaseContainerView;
38 import com.android.launcher3.CellLayout;
39 import com.android.launcher3.DeleteDropTarget;
40 import com.android.launcher3.DeviceProfile;
41 import com.android.launcher3.DragSource;
42 import com.android.launcher3.DropTarget;
43 import com.android.launcher3.ExtendedEditText;
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.config.FeatureFlags;
51 import com.android.launcher3.folder.Folder;
52 import com.android.launcher3.graphics.TintedDrawableSpan;
53 import com.android.launcher3.keyboard.FocusedItemDecorator;
54 import com.android.launcher3.util.ComponentKey;
55
56 import java.nio.charset.Charset;
57 import java.nio.charset.CharsetEncoder;
58 import java.util.ArrayList;
59 import java.util.List;
60
61
62 /**
63  * A merge algorithm that merges every section indiscriminately.
64  */
65 final class FullMergeAlgorithm implements AlphabeticalAppsList.MergeAlgorithm {
66
67     @Override
68     public boolean continueMerging(AlphabeticalAppsList.SectionInfo section,
69             AlphabeticalAppsList.SectionInfo withSection,
70             int sectionAppCount, int numAppsPerRow, int mergeCount) {
71         // Don't merge the predicted apps
72         if (section.firstAppItem.viewType != AllAppsGridAdapter.VIEW_TYPE_ICON) {
73             return false;
74         }
75         // Otherwise, merge every other section
76         return true;
77     }
78 }
79
80 /**
81  * The logic we use to merge multiple sections.  We only merge sections when their final row
82  * contains less than a certain number of icons, and stop at a specified max number of merges.
83  * In addition, we will try and not merge sections that identify apps from different scripts.
84  */
85 final class SimpleSectionMergeAlgorithm implements AlphabeticalAppsList.MergeAlgorithm {
86
87     private int mMinAppsPerRow;
88     private int mMinRowsInMergedSection;
89     private int mMaxAllowableMerges;
90     private CharsetEncoder mAsciiEncoder;
91
92     public SimpleSectionMergeAlgorithm(int minAppsPerRow, int minRowsInMergedSection, int maxNumMerges) {
93         mMinAppsPerRow = minAppsPerRow;
94         mMinRowsInMergedSection = minRowsInMergedSection;
95         mMaxAllowableMerges = maxNumMerges;
96         mAsciiEncoder = Charset.forName("US-ASCII").newEncoder();
97     }
98
99     @Override
100     public boolean continueMerging(AlphabeticalAppsList.SectionInfo section,
101             AlphabeticalAppsList.SectionInfo withSection,
102             int sectionAppCount, int numAppsPerRow, int mergeCount) {
103         // Don't merge the predicted apps
104         if (section.firstAppItem.viewType != AllAppsGridAdapter.VIEW_TYPE_ICON) {
105             return false;
106         }
107
108         // Continue merging if the number of hanging apps on the final row is less than some
109         // fixed number (ragged), the merged rows has yet to exceed some minimum row count,
110         // and while the number of merged sections is less than some fixed number of merges
111         int rows = sectionAppCount / numAppsPerRow;
112         int cols = sectionAppCount % numAppsPerRow;
113
114         // Ensure that we do not merge across scripts, currently we only allow for english and
115         // native scripts so we can test if both can just be ascii encoded
116         boolean isCrossScript = false;
117         if (section.firstAppItem != null && withSection.firstAppItem != null) {
118             isCrossScript = mAsciiEncoder.canEncode(section.firstAppItem.sectionName) !=
119                     mAsciiEncoder.canEncode(withSection.firstAppItem.sectionName);
120         }
121         return (0 < cols && cols < mMinAppsPerRow) &&
122                 rows < mMinRowsInMergedSection &&
123                 mergeCount < mMaxAllowableMerges &&
124                 !isCrossScript;
125     }
126 }
127
128 /**
129  * The all apps view container.
130  */
131 public class AllAppsContainerView extends BaseContainerView implements DragSource,
132         LauncherTransitionable, View.OnLongClickListener, AllAppsSearchBarController.Callbacks {
133
134     private static final int MIN_ROWS_IN_MERGED_SECTION_PHONE = 3;
135     private static final int MAX_NUM_MERGES_PHONE = 2;
136
137     private final Launcher mLauncher;
138     private final AlphabeticalAppsList mApps;
139     private final AllAppsGridAdapter mAdapter;
140     private final RecyclerView.LayoutManager mLayoutManager;
141     private final RecyclerView.ItemDecoration mItemDecoration;
142
143     // The computed bounds of the container
144     private final Rect mContentBounds = new Rect();
145
146     private AllAppsRecyclerView mAppsRecyclerView;
147     private AllAppsSearchBarController mSearchBarController;
148
149     private View mSearchContainer;
150     private ExtendedEditText mSearchInput;
151     private HeaderElevationController mElevationController;
152     private int mSearchContainerOffsetTop;
153
154     private SpannableStringBuilder mSearchQueryBuilder = null;
155
156     private int mSectionNamesMargin;
157     private int mNumAppsPerRow;
158     private int mNumPredictedAppsPerRow;
159     private int mRecyclerViewBottomPadding;
160     // This coordinate is relative to this container view
161     private final Point mBoundsCheckLastTouchDownPos = new Point(-1, -1);
162
163     public AllAppsContainerView(Context context) {
164         this(context, null);
165     }
166
167     public AllAppsContainerView(Context context, AttributeSet attrs) {
168         this(context, attrs, 0);
169     }
170
171     public AllAppsContainerView(Context context, AttributeSet attrs, int defStyleAttr) {
172         super(context, attrs, defStyleAttr);
173         Resources res = context.getResources();
174
175         mLauncher = Launcher.getLauncher(context);
176         mSectionNamesMargin = res.getDimensionPixelSize(R.dimen.all_apps_grid_view_start_margin);
177         mApps = new AlphabeticalAppsList(context);
178         mAdapter = new AllAppsGridAdapter(mLauncher, mApps, mLauncher, this);
179         mApps.setAdapter(mAdapter);
180         mLayoutManager = mAdapter.getLayoutManager();
181         mItemDecoration = mAdapter.getItemDecoration();
182         DeviceProfile grid = mLauncher.getDeviceProfile();
183         if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP && !grid.isVerticalBarLayout()) {
184             mRecyclerViewBottomPadding = 0;
185             setPadding(0, 0, 0, 0);
186         } else {
187             mRecyclerViewBottomPadding =
188                     res.getDimensionPixelSize(R.dimen.all_apps_list_bottom_padding);
189         }
190         mSearchQueryBuilder = new SpannableStringBuilder();
191         Selection.setSelection(mSearchQueryBuilder, 0);
192     }
193
194     /**
195      * Sets the current set of predicted apps.
196      */
197     public void setPredictedApps(List<ComponentKey> apps) {
198         mApps.setPredictedApps(apps);
199     }
200
201     /**
202      * Sets the current set of apps.
203      */
204     public void setApps(List<AppInfo> apps) {
205         mApps.setApps(apps);
206     }
207
208     /**
209      * Adds new apps to the list.
210      */
211     public void addApps(List<AppInfo> apps) {
212         mApps.addApps(apps);
213     }
214
215     /**
216      * Updates existing apps in the list
217      */
218     public void updateApps(List<AppInfo> apps) {
219         mApps.updateApps(apps);
220     }
221
222     /**
223      * Removes some apps from the list.
224      */
225     public void removeApps(List<AppInfo> apps) {
226         mApps.removeApps(apps);
227     }
228
229     public void setSearchBarVisible(boolean visible) {
230         if (visible) {
231             mSearchBarController.setVisibility(View.VISIBLE);
232         } else {
233             mSearchBarController.setVisibility(View.INVISIBLE);
234         }
235     }
236
237     /**
238      * Sets the search bar that shows above the a-z list.
239      */
240     public void setSearchBarController(AllAppsSearchBarController searchController) {
241         if (mSearchBarController != null) {
242             throw new RuntimeException("Expected search bar controller to only be set once");
243         }
244         mSearchBarController = searchController;
245         mSearchBarController.initialize(mApps, mSearchInput, mLauncher, this);
246         mAdapter.setSearchController(mSearchBarController);
247     }
248
249     /**
250      * Scrolls this list view to the top.
251      */
252     public void scrollToTop() {
253         mAppsRecyclerView.scrollToTop();
254     }
255
256     /**
257      * Returns whether the view itself will handle the touch event or not.
258      */
259     public boolean shouldContainerScroll(float x, float y) {
260         int[] point = new int[2];
261         point[0] = (int) x;
262         point[1] = (int) y;
263         Utilities.mapCoordInSelfToDescendent(mAppsRecyclerView, this, point);
264
265         // if the MotionEvent is inside the thumb, container should not be pulled down.
266         if (mAppsRecyclerView.getScrollBar().isNearThumb(point[0], point[1])) {
267             return false;
268         }
269         // IF scroller is at the very top OR there is no scroll bar because there is probably not
270         // enough items to scroll, THEN it's okay for the container to be pulled down.
271         if (mAppsRecyclerView.getScrollBar().getThumbOffset().y <= 0) {
272             return true;
273         }
274         return false;
275     }
276
277     /**
278      * Focuses the search field and begins an app search.
279      */
280     public void startAppsSearch() {
281         if (mSearchBarController != null) {
282             mSearchBarController.focusSearchField();
283         }
284     }
285
286     /**
287      * Resets the state of AllApps.
288      */
289     public void reset() {
290         // Reset the search bar and base recycler view after transitioning home
291         scrollToTop();
292         mSearchBarController.reset();
293         mAppsRecyclerView.reset();
294     }
295
296     @Override
297     protected void onFinishInflate() {
298         super.onFinishInflate();
299
300         // This is a focus listener that proxies focus from a view into the list view.  This is to
301         // work around the search box from getting first focus and showing the cursor.
302         getContentView().setOnFocusChangeListener(new View.OnFocusChangeListener() {
303             @Override
304             public void onFocusChange(View v, boolean hasFocus) {
305                 if (hasFocus) {
306                     mAppsRecyclerView.requestFocus();
307                 }
308             }
309         });
310
311         mSearchContainer = findViewById(R.id.search_container);
312         mSearchInput = (ExtendedEditText) findViewById(R.id.search_box_input);
313
314         // Update the hint to contain the icon.
315         // Prefix the original hint with two spaces. The first space gets replaced by the icon
316         // using span. The second space is used for a singe space character between the hint
317         // and the icon.
318         SpannableString spanned = new SpannableString("  " + mSearchInput.getHint());
319         spanned.setSpan(new TintedDrawableSpan(getContext(), R.drawable.ic_allapps_search),
320                 0, 1, Spannable.SPAN_EXCLUSIVE_INCLUSIVE);
321         mSearchInput.setHint(spanned);
322
323         mSearchContainerOffsetTop = getResources().getDimensionPixelSize(
324                 R.dimen.all_apps_search_bar_margin_top);
325
326         mElevationController = Utilities.ATLEAST_LOLLIPOP
327                 ? new HeaderElevationController.ControllerVL(mSearchContainer)
328                 : new HeaderElevationController.ControllerV16(mSearchContainer);
329
330         // Load the all apps recycler view
331         mAppsRecyclerView = (AllAppsRecyclerView) findViewById(R.id.apps_list_view);
332         mAppsRecyclerView.setApps(mApps);
333         mAppsRecyclerView.setLayoutManager(mLayoutManager);
334         mAppsRecyclerView.setAdapter(mAdapter);
335         mAppsRecyclerView.setHasFixedSize(true);
336         mAppsRecyclerView.addOnScrollListener(mElevationController);
337         mAppsRecyclerView.setElevationController(mElevationController);
338
339         if (mItemDecoration != null) {
340             mAppsRecyclerView.addItemDecoration(mItemDecoration);
341         }
342
343         FocusedItemDecorator focusedItemDecorator = new FocusedItemDecorator(mAppsRecyclerView);
344         mAppsRecyclerView.addItemDecoration(focusedItemDecorator);
345         mAppsRecyclerView.preMeasureViews(mAdapter);
346         mAdapter.setIconFocusListener(focusedItemDecorator.getFocusListener());
347
348         // TODO(hyunyoungs): clean up setting the content and the reveal view.
349         if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP) {
350             getRevealView().setVisibility(View.VISIBLE);
351         }
352     }
353
354     @Override
355     public void onBoundsChanged(Rect newBounds) { }
356
357     @Override
358     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
359         int widthPx = MeasureSpec.getSize(widthMeasureSpec);
360         int heightPx = MeasureSpec.getSize(heightMeasureSpec);
361         updatePaddingsAndMargins(widthPx, heightPx);
362         mContentBounds.set(mContainerPaddingLeft, 0, widthPx - mContainerPaddingRight, heightPx);
363
364         DeviceProfile grid = mLauncher.getDeviceProfile();
365         grid.updateAppsViewNumCols();
366         if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP) {
367             if (mNumAppsPerRow != grid.inv.numColumns ||
368                     mNumPredictedAppsPerRow != grid.inv.numColumns) {
369                 mNumAppsPerRow = grid.inv.numColumns;
370                 mNumPredictedAppsPerRow = grid.inv.numColumns;
371
372                 mAppsRecyclerView.setNumAppsPerRow(grid, mNumAppsPerRow);
373                 mAdapter.setNumAppsPerRow(mNumAppsPerRow);
374                 mApps.setNumAppsPerRow(mNumAppsPerRow, mNumPredictedAppsPerRow, new FullMergeAlgorithm());
375                 if (mNumAppsPerRow > 0) {
376                     int rvPadding = mAppsRecyclerView.getPaddingStart(); // Assumes symmetry
377                     final int thumbMaxWidth =
378                             getResources().getDimensionPixelSize(
379                                     R.dimen.container_fastscroll_thumb_max_width);
380                     mSearchContainer.setPadding(
381                             rvPadding - mContainerPaddingLeft + thumbMaxWidth,
382                             mSearchContainer.getPaddingTop(),
383                             rvPadding - mContainerPaddingRight + thumbMaxWidth,
384                             mSearchContainer.getPaddingBottom());
385                 }
386             }
387             super.onMeasure(widthMeasureSpec, heightMeasureSpec);
388             return;
389         }
390
391         // --- remove START when {@code FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP} is enabled. ---
392
393         // Update the number of items in the grid before we measure the view
394         // TODO: mSectionNamesMargin is currently 0, but also account for it,
395         // if it's enabled in the future.
396         grid.updateAppsViewNumCols();
397         if (mNumAppsPerRow != grid.allAppsNumCols ||
398                 mNumPredictedAppsPerRow != grid.allAppsNumPredictiveCols) {
399             mNumAppsPerRow = grid.allAppsNumCols;
400             mNumPredictedAppsPerRow = grid.allAppsNumPredictiveCols;
401
402             // If there is a start margin to draw section names, determine how we are going to merge
403             // app sections
404             boolean mergeSectionsFully = mSectionNamesMargin == 0 || !grid.isPhone;
405             AlphabeticalAppsList.MergeAlgorithm mergeAlgorithm = mergeSectionsFully ?
406                     new FullMergeAlgorithm() :
407                     new SimpleSectionMergeAlgorithm((int) Math.ceil(mNumAppsPerRow / 2f),
408                             MIN_ROWS_IN_MERGED_SECTION_PHONE, MAX_NUM_MERGES_PHONE);
409
410             mAppsRecyclerView.setNumAppsPerRow(grid, mNumAppsPerRow);
411             mAdapter.setNumAppsPerRow(mNumAppsPerRow);
412             mApps.setNumAppsPerRow(mNumAppsPerRow, mNumPredictedAppsPerRow, mergeAlgorithm);
413         }
414
415         // --- remove END when {@code FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP} is enabled. ---
416         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
417     }
418
419     /**
420      * Update the background and padding of the Apps view and children.  Instead of insetting the
421      * container view, we inset the background and padding of the recycler view to allow for the
422      * recycler view to handle touch events (for fast scrolling) all the way to the edge.
423      */
424     private void updatePaddingsAndMargins(int widthPx, int heightPx) {
425         Rect bgPadding = new Rect();
426         getRevealView().getBackground().getPadding(bgPadding);
427
428         mAppsRecyclerView.updateBackgroundPadding(bgPadding);
429         mAdapter.updateBackgroundPadding(bgPadding);
430         mElevationController.updateBackgroundPadding(bgPadding);
431
432         // Pad the recycler view by the background padding plus the start margin (for the section
433         // names)
434         int maxScrollBarWidth = mAppsRecyclerView.getMaxScrollbarWidth();
435         int startInset = Math.max(mSectionNamesMargin, maxScrollBarWidth);
436         if (Utilities.isRtl(getResources())) {
437             mAppsRecyclerView.setPadding(bgPadding.left + maxScrollBarWidth, 0, bgPadding.right
438                     + startInset, mRecyclerViewBottomPadding);
439         } else {
440             mAppsRecyclerView.setPadding(bgPadding.left + startInset, 0, bgPadding.right +
441                     maxScrollBarWidth, mRecyclerViewBottomPadding);
442         }
443
444         MarginLayoutParams lp = (MarginLayoutParams) mSearchContainer.getLayoutParams();
445         lp.leftMargin = bgPadding.left;
446         lp.rightMargin = bgPadding.right;
447
448         // Clip the view to the left and right edge of the background to
449         // to prevent shadows from rendering beyond the edges
450         final Rect newClipBounds = new Rect(
451                 bgPadding.left, 0, widthPx - bgPadding.right, heightPx);
452         setClipBounds(newClipBounds);
453
454         // Allow the overscroll effect to reach the edges of the view
455         mAppsRecyclerView.setClipToPadding(false);
456
457         DeviceProfile grid = mLauncher.getDeviceProfile();
458         if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP) {
459             if (!grid.isVerticalBarLayout()) {
460                 MarginLayoutParams mlp = (MarginLayoutParams) mAppsRecyclerView.getLayoutParams();
461
462                 Rect insets = mLauncher.getDragLayer().getInsets();
463                 getContentView().setPadding(0, 0, 0, insets.bottom);
464                 int height = insets.top + grid.hotseatCellHeightPx;
465
466                 mlp.topMargin = height;
467                 mAppsRecyclerView.setLayoutParams(mlp);
468
469                 mSearchContainer.setPadding(
470                         mSearchContainer.getPaddingLeft(),
471                         insets.top + mSearchContainerOffsetTop,
472                         mSearchContainer.getPaddingRight(),
473                         mSearchContainer.getPaddingBottom());
474                 lp.height = height;
475
476                 View navBarBg = findViewById(R.id.nav_bar_bg);
477                 ViewGroup.LayoutParams params = navBarBg.getLayoutParams();
478                 params.height = insets.bottom;
479                 navBarBg.setLayoutParams(params);
480                 navBarBg.setVisibility(View.VISIBLE);
481             }
482         }
483         mSearchContainer.setLayoutParams(lp);
484     }
485
486     @Override
487     public boolean dispatchKeyEvent(KeyEvent event) {
488         // Determine if the key event was actual text, if so, focus the search bar and then dispatch
489         // the key normally so that it can process this key event
490         if (!mSearchBarController.isSearchFieldFocused() &&
491                 event.getAction() == KeyEvent.ACTION_DOWN) {
492             final int unicodeChar = event.getUnicodeChar();
493             final boolean isKeyNotWhitespace = unicodeChar > 0 &&
494                     !Character.isWhitespace(unicodeChar) && !Character.isSpaceChar(unicodeChar);
495             if (isKeyNotWhitespace) {
496                 boolean gotKey = TextKeyListener.getInstance().onKeyDown(this, mSearchQueryBuilder,
497                         event.getKeyCode(), event);
498                 if (gotKey && mSearchQueryBuilder.length() > 0) {
499                     mSearchBarController.focusSearchField();
500                 }
501             }
502         }
503
504         return super.dispatchKeyEvent(event);
505     }
506
507     @Override
508     public boolean onInterceptTouchEvent(MotionEvent ev) {
509         return handleTouchEvent(ev);
510     }
511
512     @SuppressLint("ClickableViewAccessibility")
513     @Override
514     public boolean onTouchEvent(MotionEvent ev) {
515         return handleTouchEvent(ev);
516     }
517
518     @Override
519     public boolean onLongClick(View v) {
520         // Return early if this is not initiated from a touch
521         if (!v.isInTouchMode()) return false;
522         // When we have exited all apps or are in transition, disregard long clicks
523
524         if (!mLauncher.isAppsViewVisible() ||
525                 mLauncher.getWorkspace().isSwitchingState()) return false;
526         // Return if global dragging is not enabled
527         if (!mLauncher.isDraggingEnabled()) return false;
528
529         // Start the drag
530         mLauncher.getWorkspace().beginDragShared(v, this, false);
531         // Enter spring loaded mode
532         mLauncher.enterSpringLoadedDragMode();
533
534         return false;
535     }
536
537     @Override
538     public boolean supportsFlingToDelete() {
539         return true;
540     }
541
542     @Override
543     public boolean supportsAppInfoDropTarget() {
544         return true;
545     }
546
547     @Override
548     public boolean supportsDeleteDropTarget() {
549         return false;
550     }
551
552     @Override
553     public float getIntrinsicIconScaleFactor() {
554         DeviceProfile grid = mLauncher.getDeviceProfile();
555         return (float) grid.allAppsIconSizePx / grid.iconSizePx;
556     }
557
558     @Override
559     public void onFlingToDeleteCompleted() {
560         // We just dismiss the drag when we fling, so cleanup here
561         mLauncher.exitSpringLoadedDragModeDelayed(true,
562                 Launcher.EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, null);
563         mLauncher.unlockScreenOrientation(false);
564     }
565
566     @Override
567     public void onDropCompleted(View target, DropTarget.DragObject d, boolean isFlingToDelete,
568             boolean success) {
569         if (isFlingToDelete || !success || (target != mLauncher.getWorkspace() &&
570                 !(target instanceof DeleteDropTarget) && !(target instanceof Folder))) {
571             // Exit spring loaded mode if we have not successfully dropped or have not handled the
572             // drop in Workspace
573             mLauncher.exitSpringLoadedDragModeDelayed(true,
574                     Launcher.EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, null);
575         }
576         mLauncher.unlockScreenOrientation(false);
577
578         // Display an error message if the drag failed due to there not being enough space on the
579         // target layout we were dropping on.
580         if (!success) {
581             boolean showOutOfSpaceMessage = false;
582             if (target instanceof Workspace) {
583                 int currentScreen = mLauncher.getCurrentWorkspaceScreen();
584                 Workspace workspace = (Workspace) target;
585                 CellLayout layout = (CellLayout) workspace.getChildAt(currentScreen);
586                 ItemInfo itemInfo = d.dragInfo;
587                 if (layout != null) {
588                     showOutOfSpaceMessage =
589                             !layout.findCellForSpan(null, itemInfo.spanX, itemInfo.spanY);
590                 }
591             }
592             if (showOutOfSpaceMessage) {
593                 mLauncher.showOutOfSpaceMessage(false);
594             }
595
596             d.deferDragViewCleanupPostAnimation = false;
597         }
598     }
599
600     @Override
601     public void onLauncherTransitionPrepare(Launcher l, boolean animated,
602             boolean multiplePagesVisible) {
603         // Do nothing
604     }
605
606     @Override
607     public void onLauncherTransitionStart(Launcher l, boolean animated, boolean toWorkspace) {
608         // Do nothing
609     }
610
611     @Override
612     public void onLauncherTransitionStep(Launcher l, float t) {
613         // Do nothing
614     }
615
616     @Override
617     public void onLauncherTransitionEnd(Launcher l, boolean animated, boolean toWorkspace) {
618         if (toWorkspace) {
619             reset();
620         }
621     }
622
623     /**
624      * Handles the touch events to dismiss all apps when clicking outside the bounds of the
625      * recycler view.
626      */
627     private boolean handleTouchEvent(MotionEvent ev) {
628         DeviceProfile grid = mLauncher.getDeviceProfile();
629         int x = (int) ev.getX();
630         int y = (int) ev.getY();
631
632         switch (ev.getAction()) {
633             case MotionEvent.ACTION_DOWN:
634                 if (!mContentBounds.isEmpty()) {
635                     // Outset the fixed bounds and check if the touch is outside all apps
636                     Rect tmpRect = new Rect(mContentBounds);
637                     tmpRect.inset(-grid.allAppsIconSizePx / 2, 0);
638                     if (ev.getX() < tmpRect.left || ev.getX() > tmpRect.right) {
639                         mBoundsCheckLastTouchDownPos.set(x, y);
640                         return true;
641                     }
642                 } else {
643                     // Check if the touch is outside all apps
644                     if (ev.getX() < getPaddingLeft() ||
645                             ev.getX() > (getWidth() - getPaddingRight())) {
646                         mBoundsCheckLastTouchDownPos.set(x, y);
647                         return true;
648                     }
649                 }
650                 break;
651             case MotionEvent.ACTION_UP:
652                 if (mBoundsCheckLastTouchDownPos.x > -1) {
653                     ViewConfiguration viewConfig = ViewConfiguration.get(getContext());
654                     float dx = ev.getX() - mBoundsCheckLastTouchDownPos.x;
655                     float dy = ev.getY() - mBoundsCheckLastTouchDownPos.y;
656                     float distance = (float) Math.hypot(dx, dy);
657                     if (distance < viewConfig.getScaledTouchSlop()) {
658                         // The background was clicked, so just go home
659                         Launcher launcher = (Launcher) getContext();
660                         launcher.showWorkspace(true);
661                         return true;
662                     }
663                 }
664                 // Fall through
665             case MotionEvent.ACTION_CANCEL:
666                 mBoundsCheckLastTouchDownPos.set(-1, -1);
667                 break;
668         }
669         return false;
670     }
671
672     @Override
673     public void onSearchResult(String query, ArrayList<ComponentKey> apps) {
674         if (apps != null) {
675             if (mApps.setOrderedFilter(apps)) {
676                 mAppsRecyclerView.onSearchResultsChanged();
677             }
678             mAdapter.setLastSearchQuery(query);
679         }
680     }
681
682     @Override
683     public void clearSearchResult() {
684         if (mApps.setOrderedFilter(null)) {
685             mAppsRecyclerView.onSearchResultsChanged();
686         }
687
688         // Clear the search query
689         mSearchQueryBuilder.clear();
690         mSearchQueryBuilder.clearSpans();
691         Selection.setSelection(mSearchQueryBuilder, 0);
692     }
693 }