OSDN Git Service

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