OSDN Git Service

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