OSDN Git Service

Merge "Change NavBar to transparent black b/30615471" into ub-launcher3-calgary
[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         if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP) {
352             getRevealView().setVisibility(View.VISIBLE);
353             getContentView().setVisibility(View.VISIBLE);
354             getContentView().setBackground(null);
355         }
356     }
357
358     @Override
359     public void onBoundsChanged(Rect newBounds) { }
360
361     @Override
362     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
363         int widthPx = MeasureSpec.getSize(widthMeasureSpec);
364         int heightPx = MeasureSpec.getSize(heightMeasureSpec);
365         updatePaddingsAndMargins(widthPx, heightPx);
366         mContentBounds.set(mContainerPaddingLeft, 0, widthPx - mContainerPaddingRight, heightPx);
367
368         DeviceProfile grid = mLauncher.getDeviceProfile();
369         grid.updateAppsViewNumCols();
370         if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP) {
371             if (mNumAppsPerRow != grid.inv.numColumns ||
372                     mNumPredictedAppsPerRow != grid.inv.numColumns) {
373                 mNumAppsPerRow = grid.inv.numColumns;
374                 mNumPredictedAppsPerRow = grid.inv.numColumns;
375
376                 mAppsRecyclerView.setNumAppsPerRow(grid, mNumAppsPerRow);
377                 mAdapter.setNumAppsPerRow(mNumAppsPerRow);
378                 mApps.setNumAppsPerRow(mNumAppsPerRow, mNumPredictedAppsPerRow, new FullMergeAlgorithm());
379                 if (mNumAppsPerRow > 0) {
380                     int rvPadding = mAppsRecyclerView.getPaddingStart(); // Assumes symmetry
381                     final int thumbMaxWidth =
382                             getResources().getDimensionPixelSize(
383                                     R.dimen.container_fastscroll_thumb_max_width);
384                     mSearchContainer.setPadding(
385                             rvPadding - mContainerPaddingLeft + thumbMaxWidth,
386                             mSearchContainer.getPaddingTop(),
387                             rvPadding - mContainerPaddingRight + thumbMaxWidth,
388                             mSearchContainer.getPaddingBottom());
389                 }
390             }
391             super.onMeasure(widthMeasureSpec, heightMeasureSpec);
392             return;
393         }
394
395         // --- remove START when {@code FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP} is enabled. ---
396
397         // Update the number of items in the grid before we measure the view
398         // TODO: mSectionNamesMargin is currently 0, but also account for it,
399         // if it's enabled in the future.
400         grid.updateAppsViewNumCols();
401         if (mNumAppsPerRow != grid.allAppsNumCols ||
402                 mNumPredictedAppsPerRow != grid.allAppsNumPredictiveCols) {
403             mNumAppsPerRow = grid.allAppsNumCols;
404             mNumPredictedAppsPerRow = grid.allAppsNumPredictiveCols;
405
406             // If there is a start margin to draw section names, determine how we are going to merge
407             // app sections
408             boolean mergeSectionsFully = mSectionNamesMargin == 0 || !grid.isPhone;
409             AlphabeticalAppsList.MergeAlgorithm mergeAlgorithm = mergeSectionsFully ?
410                     new FullMergeAlgorithm() :
411                     new SimpleSectionMergeAlgorithm((int) Math.ceil(mNumAppsPerRow / 2f),
412                             MIN_ROWS_IN_MERGED_SECTION_PHONE, MAX_NUM_MERGES_PHONE);
413
414             mAppsRecyclerView.setNumAppsPerRow(grid, mNumAppsPerRow);
415             mAdapter.setNumAppsPerRow(mNumAppsPerRow);
416             mApps.setNumAppsPerRow(mNumAppsPerRow, mNumPredictedAppsPerRow, mergeAlgorithm);
417         }
418
419         // --- remove END when {@code FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP} is enabled. ---
420         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
421     }
422
423     /**
424      * Update the background and padding of the Apps view and children.  Instead of insetting the
425      * container view, we inset the background and padding of the recycler view to allow for the
426      * recycler view to handle touch events (for fast scrolling) all the way to the edge.
427      */
428     private void updatePaddingsAndMargins(int widthPx, int heightPx) {
429         Rect bgPadding = new Rect();
430         getRevealView().getBackground().getPadding(bgPadding);
431
432         mAppsRecyclerView.updateBackgroundPadding(bgPadding);
433         mAdapter.updateBackgroundPadding(bgPadding);
434         mElevationController.updateBackgroundPadding(bgPadding);
435
436         // Pad the recycler view by the background padding plus the start margin (for the section
437         // names)
438         int maxScrollBarWidth = mAppsRecyclerView.getMaxScrollbarWidth();
439         int startInset = Math.max(mSectionNamesMargin, maxScrollBarWidth);
440         if (Utilities.isRtl(getResources())) {
441             mAppsRecyclerView.setPadding(bgPadding.left + maxScrollBarWidth, 0, bgPadding.right
442                     + startInset, mRecyclerViewBottomPadding);
443         } else {
444             mAppsRecyclerView.setPadding(bgPadding.left + startInset, 0, bgPadding.right +
445                     maxScrollBarWidth, mRecyclerViewBottomPadding);
446         }
447
448         MarginLayoutParams lp = (MarginLayoutParams) mSearchContainer.getLayoutParams();
449         lp.leftMargin = bgPadding.left;
450         lp.rightMargin = bgPadding.right;
451
452         // Clip the view to the left and right edge of the background to
453         // to prevent shadows from rendering beyond the edges
454         final Rect newClipBounds = new Rect(
455                 bgPadding.left, 0, widthPx - bgPadding.right, heightPx);
456         setClipBounds(newClipBounds);
457
458         // Allow the overscroll effect to reach the edges of the view
459         mAppsRecyclerView.setClipToPadding(false);
460
461         DeviceProfile grid = mLauncher.getDeviceProfile();
462         if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP) {
463             if (!grid.isVerticalBarLayout()) {
464                 MarginLayoutParams mlp = (MarginLayoutParams) mAppsRecyclerView.getLayoutParams();
465
466                 Rect insets = mLauncher.getDragLayer().getInsets();
467                 getContentView().setPadding(0, 0, 0, 0);
468                 int height = insets.top + grid.hotseatCellHeightPx;
469
470                 mlp.topMargin = height;
471                 mAppsRecyclerView.setLayoutParams(mlp);
472
473                 mSearchContainer.setPadding(
474                         mSearchContainer.getPaddingLeft(),
475                         insets.top + mSearchContainerOffsetTop,
476                         mSearchContainer.getPaddingRight(),
477                         mSearchContainer.getPaddingBottom());
478                 lp.height = height;
479
480                 View navBarBg = findViewById(R.id.nav_bar_bg);
481                 ViewGroup.LayoutParams params = navBarBg.getLayoutParams();
482                 params.height = insets.bottom;
483                 navBarBg.setLayoutParams(params);
484                 navBarBg.setVisibility(View.VISIBLE);
485             }
486         }
487         mSearchContainer.setLayoutParams(lp);
488     }
489
490     @Override
491     public boolean dispatchKeyEvent(KeyEvent event) {
492         // Determine if the key event was actual text, if so, focus the search bar and then dispatch
493         // the key normally so that it can process this key event
494         if (!mSearchBarController.isSearchFieldFocused() &&
495                 event.getAction() == KeyEvent.ACTION_DOWN) {
496             final int unicodeChar = event.getUnicodeChar();
497             final boolean isKeyNotWhitespace = unicodeChar > 0 &&
498                     !Character.isWhitespace(unicodeChar) && !Character.isSpaceChar(unicodeChar);
499             if (isKeyNotWhitespace) {
500                 boolean gotKey = TextKeyListener.getInstance().onKeyDown(this, mSearchQueryBuilder,
501                         event.getKeyCode(), event);
502                 if (gotKey && mSearchQueryBuilder.length() > 0) {
503                     mSearchBarController.focusSearchField();
504                 }
505             }
506         }
507
508         return super.dispatchKeyEvent(event);
509     }
510
511     @Override
512     public boolean onInterceptTouchEvent(MotionEvent ev) {
513         return handleTouchEvent(ev);
514     }
515
516     @SuppressLint("ClickableViewAccessibility")
517     @Override
518     public boolean onTouchEvent(MotionEvent ev) {
519         return handleTouchEvent(ev);
520     }
521
522     @Override
523     public boolean onLongClick(View v) {
524         // Return early if this is not initiated from a touch
525         if (!v.isInTouchMode()) return false;
526         // When we have exited all apps or are in transition, disregard long clicks
527
528         if (!mLauncher.isAppsViewVisible() ||
529                 mLauncher.getWorkspace().isSwitchingState()) return false;
530         // Return if global dragging is not enabled
531         if (!mLauncher.isDraggingEnabled()) return false;
532
533         // Start the drag
534         mLauncher.getWorkspace().beginDragShared(v, this, false);
535         // Enter spring loaded mode
536         mLauncher.enterSpringLoadedDragMode();
537
538         return false;
539     }
540
541     @Override
542     public boolean supportsFlingToDelete() {
543         return true;
544     }
545
546     @Override
547     public boolean supportsAppInfoDropTarget() {
548         return true;
549     }
550
551     @Override
552     public boolean supportsDeleteDropTarget() {
553         return false;
554     }
555
556     @Override
557     public float getIntrinsicIconScaleFactor() {
558         DeviceProfile grid = mLauncher.getDeviceProfile();
559         return (float) grid.allAppsIconSizePx / grid.iconSizePx;
560     }
561
562     @Override
563     public void onFlingToDeleteCompleted() {
564         // We just dismiss the drag when we fling, so cleanup here
565         mLauncher.exitSpringLoadedDragModeDelayed(true,
566                 Launcher.EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, null);
567         mLauncher.unlockScreenOrientation(false);
568     }
569
570     @Override
571     public void onDropCompleted(View target, DropTarget.DragObject d, boolean isFlingToDelete,
572             boolean success) {
573         if (isFlingToDelete || !success || (target != mLauncher.getWorkspace() &&
574                 !(target instanceof DeleteDropTarget) && !(target instanceof Folder))) {
575             // Exit spring loaded mode if we have not successfully dropped or have not handled the
576             // drop in Workspace
577             mLauncher.exitSpringLoadedDragModeDelayed(true,
578                     Launcher.EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, null);
579         }
580         mLauncher.unlockScreenOrientation(false);
581
582         // Display an error message if the drag failed due to there not being enough space on the
583         // target layout we were dropping on.
584         if (!success) {
585             boolean showOutOfSpaceMessage = false;
586             if (target instanceof Workspace) {
587                 int currentScreen = mLauncher.getCurrentWorkspaceScreen();
588                 Workspace workspace = (Workspace) target;
589                 CellLayout layout = (CellLayout) workspace.getChildAt(currentScreen);
590                 ItemInfo itemInfo = d.dragInfo;
591                 if (layout != null) {
592                     showOutOfSpaceMessage =
593                             !layout.findCellForSpan(null, itemInfo.spanX, itemInfo.spanY);
594                 }
595             }
596             if (showOutOfSpaceMessage) {
597                 mLauncher.showOutOfSpaceMessage(false);
598             }
599
600             d.deferDragViewCleanupPostAnimation = false;
601         }
602     }
603
604     @Override
605     public void onLauncherTransitionPrepare(Launcher l, boolean animated,
606             boolean multiplePagesVisible) {
607         // Do nothing
608     }
609
610     @Override
611     public void onLauncherTransitionStart(Launcher l, boolean animated, boolean toWorkspace) {
612         // Do nothing
613     }
614
615     @Override
616     public void onLauncherTransitionStep(Launcher l, float t) {
617         // Do nothing
618     }
619
620     @Override
621     public void onLauncherTransitionEnd(Launcher l, boolean animated, boolean toWorkspace) {
622         if (toWorkspace) {
623             reset();
624         }
625     }
626
627     /**
628      * Handles the touch events to dismiss all apps when clicking outside the bounds of the
629      * recycler view.
630      */
631     private boolean handleTouchEvent(MotionEvent ev) {
632         DeviceProfile grid = mLauncher.getDeviceProfile();
633         int x = (int) ev.getX();
634         int y = (int) ev.getY();
635
636         switch (ev.getAction()) {
637             case MotionEvent.ACTION_DOWN:
638                 if (!mContentBounds.isEmpty()) {
639                     // Outset the fixed bounds and check if the touch is outside all apps
640                     Rect tmpRect = new Rect(mContentBounds);
641                     tmpRect.inset(-grid.allAppsIconSizePx / 2, 0);
642                     if (ev.getX() < tmpRect.left || ev.getX() > tmpRect.right) {
643                         mBoundsCheckLastTouchDownPos.set(x, y);
644                         return true;
645                     }
646                 } else {
647                     // Check if the touch is outside all apps
648                     if (ev.getX() < getPaddingLeft() ||
649                             ev.getX() > (getWidth() - getPaddingRight())) {
650                         mBoundsCheckLastTouchDownPos.set(x, y);
651                         return true;
652                     }
653                 }
654                 break;
655             case MotionEvent.ACTION_UP:
656                 if (mBoundsCheckLastTouchDownPos.x > -1) {
657                     ViewConfiguration viewConfig = ViewConfiguration.get(getContext());
658                     float dx = ev.getX() - mBoundsCheckLastTouchDownPos.x;
659                     float dy = ev.getY() - mBoundsCheckLastTouchDownPos.y;
660                     float distance = (float) Math.hypot(dx, dy);
661                     if (distance < viewConfig.getScaledTouchSlop()) {
662                         // The background was clicked, so just go home
663                         Launcher launcher = (Launcher) getContext();
664                         launcher.showWorkspace(true);
665                         return true;
666                     }
667                 }
668                 // Fall through
669             case MotionEvent.ACTION_CANCEL:
670                 mBoundsCheckLastTouchDownPos.set(-1, -1);
671                 break;
672         }
673         return false;
674     }
675
676     @Override
677     public void onSearchResult(String query, ArrayList<ComponentKey> apps) {
678         if (apps != null) {
679             if (mApps.setOrderedFilter(apps)) {
680                 mAppsRecyclerView.onSearchResultsChanged();
681             }
682             mAdapter.setLastSearchQuery(query);
683         }
684     }
685
686     @Override
687     public void clearSearchResult() {
688         if (mApps.setOrderedFilter(null)) {
689             mAppsRecyclerView.onSearchResultsChanged();
690         }
691
692         // Clear the search query
693         mSearchQueryBuilder.clear();
694         mSearchQueryBuilder.clearSpans();
695         Selection.setSelection(mSearchQueryBuilder, 0);
696     }
697 }