OSDN Git Service

Merge "Adding TransformingTouchDelegate to allow transforming touch events before...
[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         mSearchBarController.refreshSearchResult();
214     }
215
216     /**
217      * Updates existing apps in the list
218      */
219     public void updateApps(List<AppInfo> apps) {
220         mApps.updateApps(apps);
221         mSearchBarController.refreshSearchResult();
222     }
223
224     /**
225      * Removes some apps from the list.
226      */
227     public void removeApps(List<AppInfo> apps) {
228         mApps.removeApps(apps);
229         mSearchBarController.refreshSearchResult();
230     }
231
232     public void setSearchBarVisible(boolean visible) {
233         if (visible) {
234             mSearchBarController.setVisibility(View.VISIBLE);
235         } else {
236             mSearchBarController.setVisibility(View.INVISIBLE);
237         }
238     }
239
240     /**
241      * Sets the search bar that shows above the a-z list.
242      */
243     public void setSearchBarController(AllAppsSearchBarController searchController) {
244         if (mSearchBarController != null) {
245             throw new RuntimeException("Expected search bar controller to only be set once");
246         }
247         mSearchBarController = searchController;
248         mSearchBarController.initialize(mApps, mSearchInput, mLauncher, this);
249         mAdapter.setSearchController(mSearchBarController);
250     }
251
252     /**
253      * Scrolls this list view to the top.
254      */
255     public void scrollToTop() {
256         mAppsRecyclerView.scrollToTop();
257     }
258
259     /**
260      * Returns whether the view itself will handle the touch event or not.
261      */
262     public boolean shouldContainerScroll(float x, float y) {
263         int[] point = new int[2];
264         point[0] = (int) x;
265         point[1] = (int) y;
266         Utilities.mapCoordInSelfToDescendent(mAppsRecyclerView, this, point);
267
268         // if the MotionEvent is inside the thumb, container should not be pulled down.
269         if (mAppsRecyclerView.getScrollBar().isNearThumb(point[0], point[1])) {
270             return false;
271         }
272         // IF scroller is at the very top OR there is no scroll bar because there is probably not
273         // enough items to scroll, THEN it's okay for the container to be pulled down.
274         if (mAppsRecyclerView.getScrollBar().getThumbOffset().y <= 0) {
275             return true;
276         }
277         return false;
278     }
279
280     /**
281      * Focuses the search field and begins an app search.
282      */
283     public void startAppsSearch() {
284         if (mSearchBarController != null) {
285             mSearchBarController.focusSearchField();
286         }
287     }
288
289     /**
290      * Resets the state of AllApps.
291      */
292     public void reset() {
293         // Reset the search bar and base recycler view after transitioning home
294         scrollToTop();
295         mSearchBarController.reset();
296         mAppsRecyclerView.reset();
297     }
298
299     @Override
300     protected void onFinishInflate() {
301         super.onFinishInflate();
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         getContentView().setOnFocusChangeListener(new View.OnFocusChangeListener() {
306             @Override
307             public void onFocusChange(View v, boolean hasFocus) {
308                 if (hasFocus) {
309                     mAppsRecyclerView.requestFocus();
310                 }
311             }
312         });
313
314         mSearchContainer = findViewById(R.id.search_container);
315         mSearchInput = (ExtendedEditText) findViewById(R.id.search_box_input);
316
317         // Update the hint to contain the icon.
318         // Prefix the original hint with two spaces. The first space gets replaced by the icon
319         // using span. The second space is used for a singe space character between the hint
320         // and the icon.
321         SpannableString spanned = new SpannableString("  " + mSearchInput.getHint());
322         spanned.setSpan(new TintedDrawableSpan(getContext(), R.drawable.ic_allapps_search),
323                 0, 1, Spannable.SPAN_EXCLUSIVE_INCLUSIVE);
324         mSearchInput.setHint(spanned);
325
326         mSearchContainerOffsetTop = getResources().getDimensionPixelSize(
327                 R.dimen.all_apps_search_bar_margin_top);
328
329         mElevationController = Utilities.ATLEAST_LOLLIPOP
330                 ? new HeaderElevationController.ControllerVL(mSearchContainer)
331                 : new HeaderElevationController.ControllerV16(mSearchContainer);
332
333         // Load the all apps recycler view
334         mAppsRecyclerView = (AllAppsRecyclerView) findViewById(R.id.apps_list_view);
335         mAppsRecyclerView.setApps(mApps);
336         mAppsRecyclerView.setLayoutManager(mLayoutManager);
337         mAppsRecyclerView.setAdapter(mAdapter);
338         mAppsRecyclerView.setHasFixedSize(true);
339         mAppsRecyclerView.addOnScrollListener(mElevationController);
340         mAppsRecyclerView.setElevationController(mElevationController);
341
342         if (mItemDecoration != null) {
343             mAppsRecyclerView.addItemDecoration(mItemDecoration);
344         }
345
346         FocusedItemDecorator focusedItemDecorator = new FocusedItemDecorator(mAppsRecyclerView);
347         mAppsRecyclerView.addItemDecoration(focusedItemDecorator);
348         mAppsRecyclerView.preMeasureViews(mAdapter);
349         mAdapter.setIconFocusListener(focusedItemDecorator.getFocusListener());
350
351         // TODO(hyunyoungs): clean up setting the content and the reveal view.
352         if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP) {
353             getRevealView().setVisibility(View.VISIBLE);
354         }
355     }
356
357     @Override
358     public void onBoundsChanged(Rect newBounds) { }
359
360     @Override
361     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
362         int widthPx = MeasureSpec.getSize(widthMeasureSpec);
363         int heightPx = MeasureSpec.getSize(heightMeasureSpec);
364         updatePaddingsAndMargins(widthPx, heightPx);
365         mContentBounds.set(mContainerPaddingLeft, 0, widthPx - mContainerPaddingRight, heightPx);
366
367         DeviceProfile grid = mLauncher.getDeviceProfile();
368         grid.updateAppsViewNumCols();
369         if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP) {
370             if (mNumAppsPerRow != grid.inv.numColumns ||
371                     mNumPredictedAppsPerRow != grid.inv.numColumns) {
372                 mNumAppsPerRow = grid.inv.numColumns;
373                 mNumPredictedAppsPerRow = grid.inv.numColumns;
374
375                 mAppsRecyclerView.setNumAppsPerRow(grid, mNumAppsPerRow);
376                 mAdapter.setNumAppsPerRow(mNumAppsPerRow);
377                 mApps.setNumAppsPerRow(mNumAppsPerRow, mNumPredictedAppsPerRow, new FullMergeAlgorithm());
378                 if (mNumAppsPerRow > 0) {
379                     int rvPadding = mAppsRecyclerView.getPaddingStart(); // Assumes symmetry
380                     final int thumbMaxWidth =
381                             getResources().getDimensionPixelSize(
382                                     R.dimen.container_fastscroll_thumb_max_width);
383                     mSearchContainer.setPadding(
384                             rvPadding - mContainerPaddingLeft + thumbMaxWidth,
385                             mSearchContainer.getPaddingTop(),
386                             rvPadding - mContainerPaddingRight + thumbMaxWidth,
387                             mSearchContainer.getPaddingBottom());
388                 }
389             }
390             super.onMeasure(widthMeasureSpec, heightMeasureSpec);
391             return;
392         }
393
394         // --- remove START when {@code FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP} is enabled. ---
395
396         // Update the number of items in the grid before we measure the view
397         // TODO: mSectionNamesMargin is currently 0, but also account for it,
398         // if it's enabled in the future.
399         grid.updateAppsViewNumCols();
400         if (mNumAppsPerRow != grid.allAppsNumCols ||
401                 mNumPredictedAppsPerRow != grid.allAppsNumPredictiveCols) {
402             mNumAppsPerRow = grid.allAppsNumCols;
403             mNumPredictedAppsPerRow = grid.allAppsNumPredictiveCols;
404
405             // If there is a start margin to draw section names, determine how we are going to merge
406             // app sections
407             boolean mergeSectionsFully = mSectionNamesMargin == 0 || !grid.isPhone;
408             AlphabeticalAppsList.MergeAlgorithm mergeAlgorithm = mergeSectionsFully ?
409                     new FullMergeAlgorithm() :
410                     new SimpleSectionMergeAlgorithm((int) Math.ceil(mNumAppsPerRow / 2f),
411                             MIN_ROWS_IN_MERGED_SECTION_PHONE, MAX_NUM_MERGES_PHONE);
412
413             mAppsRecyclerView.setNumAppsPerRow(grid, mNumAppsPerRow);
414             mAdapter.setNumAppsPerRow(mNumAppsPerRow);
415             mApps.setNumAppsPerRow(mNumAppsPerRow, mNumPredictedAppsPerRow, mergeAlgorithm);
416         }
417
418         // --- remove END when {@code FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP} is enabled. ---
419         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
420     }
421
422     /**
423      * Update the background and padding of the Apps view and children.  Instead of insetting the
424      * container view, we inset the background and padding of the recycler view to allow for the
425      * recycler view to handle touch events (for fast scrolling) all the way to the edge.
426      */
427     private void updatePaddingsAndMargins(int widthPx, int heightPx) {
428         Rect bgPadding = new Rect();
429         getRevealView().getBackground().getPadding(bgPadding);
430
431         mAppsRecyclerView.updateBackgroundPadding(bgPadding);
432         mAdapter.updateBackgroundPadding(bgPadding);
433         mElevationController.updateBackgroundPadding(bgPadding);
434
435         // Pad the recycler view by the background padding plus the start margin (for the section
436         // names)
437         int maxScrollBarWidth = mAppsRecyclerView.getMaxScrollbarWidth();
438         int startInset = Math.max(mSectionNamesMargin, maxScrollBarWidth);
439         if (Utilities.isRtl(getResources())) {
440             mAppsRecyclerView.setPadding(bgPadding.left + maxScrollBarWidth, 0, bgPadding.right
441                     + startInset, mRecyclerViewBottomPadding);
442         } else {
443             mAppsRecyclerView.setPadding(bgPadding.left + startInset, 0, bgPadding.right +
444                     maxScrollBarWidth, mRecyclerViewBottomPadding);
445         }
446
447         MarginLayoutParams lp = (MarginLayoutParams) mSearchContainer.getLayoutParams();
448         lp.leftMargin = bgPadding.left;
449         lp.rightMargin = bgPadding.right;
450
451         // Clip the view to the left and right edge of the background to
452         // to prevent shadows from rendering beyond the edges
453         final Rect newClipBounds = new Rect(
454                 bgPadding.left, 0, widthPx - bgPadding.right, heightPx);
455         setClipBounds(newClipBounds);
456
457         // Allow the overscroll effect to reach the edges of the view
458         mAppsRecyclerView.setClipToPadding(false);
459
460         DeviceProfile grid = mLauncher.getDeviceProfile();
461         if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP) {
462             if (!grid.isVerticalBarLayout()) {
463                 MarginLayoutParams mlp = (MarginLayoutParams) mAppsRecyclerView.getLayoutParams();
464
465                 Rect insets = mLauncher.getDragLayer().getInsets();
466                 getContentView().setPadding(0, 0, 0, insets.bottom);
467                 int height = insets.top + grid.hotseatCellHeightPx;
468
469                 mlp.topMargin = height;
470                 mAppsRecyclerView.setLayoutParams(mlp);
471
472                 mSearchContainer.setPadding(
473                         mSearchContainer.getPaddingLeft(),
474                         insets.top + mSearchContainerOffsetTop,
475                         mSearchContainer.getPaddingRight(),
476                         mSearchContainer.getPaddingBottom());
477                 lp.height = height;
478
479                 View navBarBg = findViewById(R.id.nav_bar_bg);
480                 ViewGroup.LayoutParams params = navBarBg.getLayoutParams();
481                 params.height = insets.bottom;
482                 navBarBg.setLayoutParams(params);
483                 navBarBg.setVisibility(View.VISIBLE);
484             }
485         }
486         mSearchContainer.setLayoutParams(lp);
487     }
488
489     @Override
490     public boolean dispatchKeyEvent(KeyEvent event) {
491         // Determine if the key event was actual text, if so, focus the search bar and then dispatch
492         // the key normally so that it can process this key event
493         if (!mSearchBarController.isSearchFieldFocused() &&
494                 event.getAction() == KeyEvent.ACTION_DOWN) {
495             final int unicodeChar = event.getUnicodeChar();
496             final boolean isKeyNotWhitespace = unicodeChar > 0 &&
497                     !Character.isWhitespace(unicodeChar) && !Character.isSpaceChar(unicodeChar);
498             if (isKeyNotWhitespace) {
499                 boolean gotKey = TextKeyListener.getInstance().onKeyDown(this, mSearchQueryBuilder,
500                         event.getKeyCode(), event);
501                 if (gotKey && mSearchQueryBuilder.length() > 0) {
502                     mSearchBarController.focusSearchField();
503                 }
504             }
505         }
506
507         return super.dispatchKeyEvent(event);
508     }
509
510     @Override
511     public boolean onInterceptTouchEvent(MotionEvent ev) {
512         return handleTouchEvent(ev);
513     }
514
515     @SuppressLint("ClickableViewAccessibility")
516     @Override
517     public boolean onTouchEvent(MotionEvent ev) {
518         return handleTouchEvent(ev);
519     }
520
521     @Override
522     public boolean onLongClick(View v) {
523         // Return early if this is not initiated from a touch
524         if (!v.isInTouchMode()) return false;
525         // When we have exited all apps or are in transition, disregard long clicks
526
527         if (!mLauncher.isAppsViewVisible() ||
528                 mLauncher.getWorkspace().isSwitchingState()) return false;
529         // Return if global dragging is not enabled
530         if (!mLauncher.isDraggingEnabled()) return false;
531
532         // Start the drag
533         mLauncher.getWorkspace().beginDragShared(v, this, false);
534         // Enter spring loaded mode
535         mLauncher.enterSpringLoadedDragMode();
536
537         return false;
538     }
539
540     @Override
541     public boolean supportsFlingToDelete() {
542         return true;
543     }
544
545     @Override
546     public boolean supportsAppInfoDropTarget() {
547         return true;
548     }
549
550     @Override
551     public boolean supportsDeleteDropTarget() {
552         return false;
553     }
554
555     @Override
556     public float getIntrinsicIconScaleFactor() {
557         DeviceProfile grid = mLauncher.getDeviceProfile();
558         return (float) grid.allAppsIconSizePx / grid.iconSizePx;
559     }
560
561     @Override
562     public void onFlingToDeleteCompleted() {
563         // We just dismiss the drag when we fling, so cleanup here
564         mLauncher.exitSpringLoadedDragModeDelayed(true,
565                 Launcher.EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, null);
566         mLauncher.unlockScreenOrientation(false);
567     }
568
569     @Override
570     public void onDropCompleted(View target, DropTarget.DragObject d, boolean isFlingToDelete,
571             boolean success) {
572         if (isFlingToDelete || !success || (target != mLauncher.getWorkspace() &&
573                 !(target instanceof DeleteDropTarget) && !(target instanceof Folder))) {
574             // Exit spring loaded mode if we have not successfully dropped or have not handled the
575             // drop in Workspace
576             mLauncher.exitSpringLoadedDragModeDelayed(true,
577                     Launcher.EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, null);
578         }
579         mLauncher.unlockScreenOrientation(false);
580
581         // Display an error message if the drag failed due to there not being enough space on the
582         // target layout we were dropping on.
583         if (!success) {
584             boolean showOutOfSpaceMessage = false;
585             if (target instanceof Workspace) {
586                 int currentScreen = mLauncher.getCurrentWorkspaceScreen();
587                 Workspace workspace = (Workspace) target;
588                 CellLayout layout = (CellLayout) workspace.getChildAt(currentScreen);
589                 ItemInfo itemInfo = d.dragInfo;
590                 if (layout != null) {
591                     showOutOfSpaceMessage =
592                             !layout.findCellForSpan(null, itemInfo.spanX, itemInfo.spanY);
593                 }
594             }
595             if (showOutOfSpaceMessage) {
596                 mLauncher.showOutOfSpaceMessage(false);
597             }
598
599             d.deferDragViewCleanupPostAnimation = false;
600         }
601     }
602
603     @Override
604     public void onLauncherTransitionPrepare(Launcher l, boolean animated,
605             boolean multiplePagesVisible) {
606         // Do nothing
607     }
608
609     @Override
610     public void onLauncherTransitionStart(Launcher l, boolean animated, boolean toWorkspace) {
611         // Do nothing
612     }
613
614     @Override
615     public void onLauncherTransitionStep(Launcher l, float t) {
616         // Do nothing
617     }
618
619     @Override
620     public void onLauncherTransitionEnd(Launcher l, boolean animated, boolean toWorkspace) {
621         if (toWorkspace) {
622             reset();
623         }
624     }
625
626     /**
627      * Handles the touch events to dismiss all apps when clicking outside the bounds of the
628      * recycler view.
629      */
630     private boolean handleTouchEvent(MotionEvent ev) {
631         DeviceProfile grid = mLauncher.getDeviceProfile();
632         int x = (int) ev.getX();
633         int y = (int) ev.getY();
634
635         switch (ev.getAction()) {
636             case MotionEvent.ACTION_DOWN:
637                 if (!mContentBounds.isEmpty()) {
638                     // Outset the fixed bounds and check if the touch is outside all apps
639                     Rect tmpRect = new Rect(mContentBounds);
640                     tmpRect.inset(-grid.allAppsIconSizePx / 2, 0);
641                     if (ev.getX() < tmpRect.left || ev.getX() > tmpRect.right) {
642                         mBoundsCheckLastTouchDownPos.set(x, y);
643                         return true;
644                     }
645                 } else {
646                     // Check if the touch is outside all apps
647                     if (ev.getX() < getPaddingLeft() ||
648                             ev.getX() > (getWidth() - getPaddingRight())) {
649                         mBoundsCheckLastTouchDownPos.set(x, y);
650                         return true;
651                     }
652                 }
653                 break;
654             case MotionEvent.ACTION_UP:
655                 if (mBoundsCheckLastTouchDownPos.x > -1) {
656                     ViewConfiguration viewConfig = ViewConfiguration.get(getContext());
657                     float dx = ev.getX() - mBoundsCheckLastTouchDownPos.x;
658                     float dy = ev.getY() - mBoundsCheckLastTouchDownPos.y;
659                     float distance = (float) Math.hypot(dx, dy);
660                     if (distance < viewConfig.getScaledTouchSlop()) {
661                         // The background was clicked, so just go home
662                         Launcher launcher = (Launcher) getContext();
663                         launcher.showWorkspace(true);
664                         return true;
665                     }
666                 }
667                 // Fall through
668             case MotionEvent.ACTION_CANCEL:
669                 mBoundsCheckLastTouchDownPos.set(-1, -1);
670                 break;
671         }
672         return false;
673     }
674
675     @Override
676     public void onSearchResult(String query, ArrayList<ComponentKey> apps) {
677         if (apps != null) {
678             if (mApps.setOrderedFilter(apps)) {
679                 mAppsRecyclerView.onSearchResultsChanged();
680             }
681             mAdapter.setLastSearchQuery(query);
682         }
683     }
684
685     @Override
686     public void clearSearchResult() {
687         if (mApps.setOrderedFilter(null)) {
688             mAppsRecyclerView.onSearchResultsChanged();
689         }
690
691         // Clear the search query
692         mSearchQueryBuilder.clear();
693         mSearchQueryBuilder.clearSpans();
694         Selection.setSelection(mSearchQueryBuilder, 0);
695     }
696 }