OSDN Git Service

Merge "Get rid of CATCH state in verticalpulldetector" 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.inputmethod.InputMethodManager;
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
156     private SpannableStringBuilder mSearchQueryBuilder = null;
157
158     private int mSectionNamesMargin;
159     private int mNumAppsPerRow;
160     private int mNumPredictedAppsPerRow;
161     private int mRecyclerViewTopBottomPadding;
162     // This coordinate is relative to this container view
163     private final Point mBoundsCheckLastTouchDownPos = new Point(-1, -1);
164
165     public AllAppsContainerView(Context context) {
166         this(context, null);
167     }
168
169     public AllAppsContainerView(Context context, AttributeSet attrs) {
170         this(context, attrs, 0);
171     }
172
173     public AllAppsContainerView(Context context, AttributeSet attrs, int defStyleAttr) {
174         super(context, attrs, defStyleAttr);
175         Resources res = context.getResources();
176
177         mLauncher = Launcher.getLauncher(context);
178         mSectionNamesMargin = res.getDimensionPixelSize(R.dimen.all_apps_grid_view_start_margin);
179         mApps = new AlphabeticalAppsList(context);
180         mAdapter = new AllAppsGridAdapter(mLauncher, mApps, mLauncher, this);
181         mApps.setAdapter(mAdapter);
182         mLayoutManager = mAdapter.getLayoutManager();
183         mItemDecoration = mAdapter.getItemDecoration();
184         DeviceProfile grid = mLauncher.getDeviceProfile();
185         if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP && !grid.isVerticalBarLayout()) {
186             mRecyclerViewTopBottomPadding = 0;
187             setPadding(0, 0, 0, 0);
188         } else {
189             mRecyclerViewTopBottomPadding =
190                     res.getDimensionPixelSize(R.dimen.all_apps_list_top_bottom_padding);
191         }
192         mSearchQueryBuilder = new SpannableStringBuilder();
193         Selection.setSelection(mSearchQueryBuilder, 0);
194     }
195
196     /**
197      * Sets the current set of predicted apps.
198      */
199     public void setPredictedApps(List<ComponentKey> apps) {
200         mApps.setPredictedApps(apps);
201     }
202
203     /**
204      * Sets the current set of apps.
205      */
206     public void setApps(List<AppInfo> apps) {
207         mApps.setApps(apps);
208     }
209
210     /**
211      * Adds new apps to the list.
212      */
213     public void addApps(List<AppInfo> apps) {
214         mApps.addApps(apps);
215     }
216
217     /**
218      * Updates existing apps in the list
219      */
220     public void updateApps(List<AppInfo> apps) {
221         mApps.updateApps(apps);
222     }
223
224     /**
225      * Removes some apps from the list.
226      */
227     public void removeApps(List<AppInfo> apps) {
228         mApps.removeApps(apps);
229     }
230
231     public void setSearchBarVisible(boolean visible) {
232         if (visible) {
233             mSearchBarController.setVisibility(View.VISIBLE);
234         } else {
235             mSearchBarController.setVisibility(View.INVISIBLE);
236         }
237     }
238
239     /**
240      * Sets the search bar that shows above the a-z list.
241      */
242     public void setSearchBarController(AllAppsSearchBarController searchController) {
243         if (mSearchBarController != null) {
244             throw new RuntimeException("Expected search bar controller to only be set once");
245         }
246         mSearchBarController = searchController;
247         mSearchBarController.initialize(mApps, mSearchInput, mLauncher, this);
248         mAdapter.setSearchController(mSearchBarController);
249     }
250
251     /**
252      * Scrolls this list view to the top.
253      */
254     public void scrollToTop() {
255         mAppsRecyclerView.scrollToTop();
256     }
257
258     /**
259      * Returns whether the view itself will handle the touch event or not.
260      */
261     public boolean shouldContainerScroll(float x, float y) {
262         int[] point = new int[2];
263         point[0] = (int) x;
264         point[1] = (int) y;
265         Utilities.mapCoordInSelfToDescendent(mAppsRecyclerView, this, point);
266
267         // if the MotionEvent is inside the thumb, container should not be pulled down.
268         if (mAppsRecyclerView.getScrollBar().isNearThumb(point[0], point[1])) {
269             return false;
270         }
271         // If scroller is at the very top, then it's okay for the container to be pulled down.
272         if (Float.compare(0f, mAppsRecyclerView.getScrollBar().getThumbOffset().y) == 0) {
273             return true;
274         }
275         return false;
276     }
277
278     /**
279      * Focuses the search field and begins an app search.
280      */
281     public void startAppsSearch() {
282         if (mSearchBarController != null) {
283             mSearchBarController.focusSearchField();
284         }
285     }
286
287     /**
288      * Resets the state of AllApps.
289      */
290     public void reset() {
291         // Reset the search bar and base recycler view after transitioning home
292         mSearchBarController.reset();
293         mAppsRecyclerView.reset();
294     }
295
296     @Override
297     protected void onFinishInflate() {
298         super.onFinishInflate();
299
300         // This is a focus listener that proxies focus from a view into the list view.  This is to
301         // work around the search box from getting first focus and showing the cursor.
302         getContentView().setOnFocusChangeListener(new View.OnFocusChangeListener() {
303             @Override
304             public void onFocusChange(View v, boolean hasFocus) {
305                 if (hasFocus) {
306                     mAppsRecyclerView.requestFocus();
307                 }
308             }
309         });
310
311         mSearchContainer = findViewById(R.id.search_container);
312         mSearchInput = (ExtendedEditText) findViewById(R.id.search_box_input);
313         mSearchIcon = (ImageView) findViewById(R.id.search_icon);
314
315         final LinearLayout.LayoutParams searchParams =
316                 (LinearLayout.LayoutParams) mSearchInput.getLayoutParams();
317         mSearchInput.setOnFocusChangeListener(new OnFocusChangeListener() {
318             @Override
319             public void onFocusChange(View view, boolean focused) {
320                 if (focused) {
321                     searchParams.width = LayoutParams.MATCH_PARENT;
322                     mSearchInput.setLayoutParams(searchParams);
323                     mSearchInput.setGravity(Gravity.FILL_HORIZONTAL | Gravity.CENTER_VERTICAL);
324                     mSearchIcon.setVisibility(View.GONE);
325                 } else {
326                     searchParams.width = LayoutParams.WRAP_CONTENT;
327                     mSearchInput.setLayoutParams(searchParams);
328                     mSearchInput.setGravity(Gravity.START | Gravity.CENTER_VERTICAL);
329                     mSearchIcon.setVisibility(View.VISIBLE);
330                 }
331             }
332         });
333
334         final OnClickListener searchFocusListener = new OnClickListener() {
335             @Override
336             public void onClick(View view) {
337                 if (!mSearchInput.isFocused()) {
338                     mSearchInput.requestFocus();
339                     final InputMethodManager imm =
340                             (InputMethodManager)getContext().getSystemService(
341                                     Context.INPUT_METHOD_SERVICE);
342                     imm.showSoftInput(mSearchInput, 0);
343                 }
344             }
345         };
346         mSearchInput.setOnClickListener(searchFocusListener);
347         mSearchContainer.setOnClickListener(searchFocusListener);
348
349         mElevationController = Utilities.ATLEAST_LOLLIPOP
350                 ? new HeaderElevationController.ControllerVL(mSearchContainer)
351                 : new HeaderElevationController.ControllerV16(mSearchContainer);
352
353         // Load the all apps recycler view
354         mAppsRecyclerView = (AllAppsRecyclerView) findViewById(R.id.apps_list_view);
355         mAppsRecyclerView.setApps(mApps);
356         mAppsRecyclerView.setLayoutManager(mLayoutManager);
357         mAppsRecyclerView.setAdapter(mAdapter);
358         mAppsRecyclerView.setHasFixedSize(true);
359         mAppsRecyclerView.addOnScrollListener(mElevationController);
360         mAppsRecyclerView.setElevationController(mElevationController);
361
362         if (mItemDecoration != null) {
363             mAppsRecyclerView.addItemDecoration(mItemDecoration);
364         }
365
366         FocusedItemDecorator focusedItemDecorator = new FocusedItemDecorator(mAppsRecyclerView);
367         mAppsRecyclerView.addItemDecoration(focusedItemDecorator);
368         mAdapter.setIconFocusListener(focusedItemDecorator.getFocusListener());
369
370         // Precalculate the prediction icon and normal icon sizes
371         LayoutInflater layoutInflater = LayoutInflater.from(getContext());
372         final int widthMeasureSpec = MeasureSpec.makeMeasureSpec(
373                 getResources().getDisplayMetrics().widthPixels, MeasureSpec.AT_MOST);
374         final int heightMeasureSpec = MeasureSpec.makeMeasureSpec(
375                 getResources().getDisplayMetrics().heightPixels, MeasureSpec.AT_MOST);
376
377         BubbleTextView icon = (BubbleTextView) layoutInflater.inflate(
378                 R.layout.all_apps_icon, this, false);
379         icon.applyDummyInfo();
380         icon.measure(widthMeasureSpec, heightMeasureSpec);
381         BubbleTextView predIcon = (BubbleTextView) layoutInflater.inflate(
382                 R.layout.all_apps_prediction_bar_icon, this, false);
383         predIcon.applyDummyInfo();
384         predIcon.measure(widthMeasureSpec, heightMeasureSpec);
385         mAppsRecyclerView.setPremeasuredIconHeights(predIcon.getMeasuredHeight(),
386                 icon.getMeasuredHeight());
387
388         // TODO(hyunyoungs): clean up setting the content and the reveal view.
389         if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP) {
390             getContentView().setBackground(null);
391             getRevealView().setVisibility(View.VISIBLE);
392             getRevealView().setAlpha(AllAppsTransitionController.ALL_APPS_FINAL_ALPHA);
393         }
394     }
395
396     @Override
397     public void onBoundsChanged(Rect newBounds) { }
398
399     @Override
400     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
401         updatePaddingsAndMargins();
402         mContentBounds.set(mHorizontalPadding, 0,
403                 MeasureSpec.getSize(widthMeasureSpec) - mHorizontalPadding,
404                 MeasureSpec.getSize(heightMeasureSpec));
405
406         DeviceProfile grid = mLauncher.getDeviceProfile();
407         int availableWidth = (!mContentBounds.isEmpty() ? mContentBounds.width() :
408                 MeasureSpec.getSize(widthMeasureSpec))
409                 - 2 * mAppsRecyclerView.getMaxScrollbarWidth();
410         grid.updateAppsViewNumCols(getResources(), availableWidth);
411         if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP) {
412             if (mNumAppsPerRow != grid.inv.numColumns ||
413                     mNumPredictedAppsPerRow != grid.inv.numColumns) {
414                 mNumAppsPerRow = grid.inv.numColumns;
415                 mNumPredictedAppsPerRow = grid.inv.numColumns;
416
417                 mAppsRecyclerView.setNumAppsPerRow(grid, mNumAppsPerRow);
418                 mAdapter.setNumAppsPerRow(mNumAppsPerRow);
419                 mApps.setNumAppsPerRow(mNumAppsPerRow, mNumPredictedAppsPerRow, new FullMergeAlgorithm());
420                 if (mNumAppsPerRow > 0) {
421                     int iconSize = availableWidth / mNumAppsPerRow;
422                     int iconSpacing = (iconSize - grid.allAppsIconSizePx) / 2;
423                     final int thumbMaxWidth =
424                             getResources().getDimensionPixelSize(
425                                     R.dimen.container_fastscroll_thumb_max_width);
426                     mSearchContainer.setPaddingRelative(
427                             iconSpacing + thumbMaxWidth, 0, iconSpacing + thumbMaxWidth, 0);
428                 }
429             }
430             super.onMeasure(widthMeasureSpec, heightMeasureSpec);
431             return;
432         }
433
434         // --- remove START when {@code FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP} is enabled. ---
435
436         // Update the number of items in the grid before we measure the view
437         // TODO: mSectionNamesMargin is currently 0, but also account for it,
438         // if it's enabled in the future.
439         grid.updateAppsViewNumCols(getResources(), availableWidth);
440         if (mNumAppsPerRow != grid.allAppsNumCols ||
441                 mNumPredictedAppsPerRow != grid.allAppsNumPredictiveCols) {
442             mNumAppsPerRow = grid.allAppsNumCols;
443             mNumPredictedAppsPerRow = grid.allAppsNumPredictiveCols;
444
445             // If there is a start margin to draw section names, determine how we are going to merge
446             // app sections
447             boolean mergeSectionsFully = mSectionNamesMargin == 0 || !grid.isPhone;
448             AlphabeticalAppsList.MergeAlgorithm mergeAlgorithm = mergeSectionsFully ?
449                     new FullMergeAlgorithm() :
450                     new SimpleSectionMergeAlgorithm((int) Math.ceil(mNumAppsPerRow / 2f),
451                             MIN_ROWS_IN_MERGED_SECTION_PHONE, MAX_NUM_MERGES_PHONE);
452
453             mAppsRecyclerView.setNumAppsPerRow(grid, mNumAppsPerRow);
454             mAdapter.setNumAppsPerRow(mNumAppsPerRow);
455             mApps.setNumAppsPerRow(mNumAppsPerRow, mNumPredictedAppsPerRow, mergeAlgorithm);
456
457             // TODO: should we not do all this complicated computation but just match the
458             // numAppsPerRow with the workspace?
459             if (mNumAppsPerRow > 0) {
460                 int iconSize = availableWidth / mNumAppsPerRow;
461                 int iconSpacing = (iconSize - grid.allAppsIconSizePx) / 2;
462                 mSearchInput.setPaddingRelative(iconSpacing, 0, iconSpacing, 0);
463             }
464         }
465
466         // --- remove END when {@code FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP} is enabled. ---
467         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
468     }
469
470     /**
471      * Update the background and padding of the Apps view and children.  Instead of insetting the
472      * container view, we inset the background and padding of the recycler view to allow for the
473      * recycler view to handle touch events (for fast scrolling) all the way to the edge.
474      */
475     private void updatePaddingsAndMargins() {
476         Rect bgPadding = new Rect();
477         getRevealView().getBackground().getPadding(bgPadding);
478
479         mAppsRecyclerView.updateBackgroundPadding(bgPadding);
480         mAdapter.updateBackgroundPadding(bgPadding);
481         mElevationController.updateBackgroundPadding(bgPadding);
482
483         // Pad the recycler view by the background padding plus the start margin (for the section
484         // names)
485         int maxScrollBarWidth = mAppsRecyclerView.getMaxScrollbarWidth();
486         int startInset = Math.max(mSectionNamesMargin, maxScrollBarWidth);
487         int topBottomPadding = mRecyclerViewTopBottomPadding;
488         if (Utilities.isRtl(getResources())) {
489             mAppsRecyclerView.setPadding(bgPadding.left + maxScrollBarWidth,
490                     topBottomPadding, bgPadding.right + startInset, topBottomPadding);
491         } else {
492             mAppsRecyclerView.setPadding(bgPadding.left + startInset, topBottomPadding,
493                     bgPadding.right + maxScrollBarWidth, topBottomPadding);
494         }
495
496         MarginLayoutParams lp = (MarginLayoutParams) mSearchContainer.getLayoutParams();
497         lp.leftMargin = bgPadding.left;
498         lp.rightMargin = bgPadding.right;
499
500         // Clip the view to the left and right edge of the background to
501         // to prevent shadows from rendering beyond the edges
502         final Rect newClipBounds = new Rect(
503                 bgPadding.left,
504                 0,
505                 getWidth() - bgPadding.right,
506                 getHeight()
507         );
508         setClipBounds(newClipBounds);
509
510         // Allow the overscroll effect to reach the edges of the view
511         mAppsRecyclerView.setClipToPadding(false);
512
513         DeviceProfile grid = mLauncher.getDeviceProfile();
514         if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP) {
515             if (!grid.isVerticalBarLayout()) {
516                 MarginLayoutParams mlp = (MarginLayoutParams) mAppsRecyclerView.getLayoutParams();
517
518                 Rect insets = mLauncher.getDragLayer().getInsets();
519                 getContentView().setPadding(0, 0, 0, insets.bottom);
520                 int height = insets.top + grid.hotseatCellHeightPx;
521
522                 mlp.topMargin = height;
523                 mAppsRecyclerView.setLayoutParams(mlp);
524
525                 LinearLayout.LayoutParams llp =
526                         (LinearLayout.LayoutParams) mSearchInput.getLayoutParams();
527                 llp.topMargin = insets.top;
528                 mSearchInput.setLayoutParams(llp);
529                 mSearchIcon.setLayoutParams(llp);
530
531                 lp.height = height;
532             }
533         }
534         mSearchContainer.setLayoutParams(lp);
535     }
536
537     @Override
538     public boolean dispatchKeyEvent(KeyEvent event) {
539         // Determine if the key event was actual text, if so, focus the search bar and then dispatch
540         // the key normally so that it can process this key event
541         if (!mSearchBarController.isSearchFieldFocused() &&
542                 event.getAction() == KeyEvent.ACTION_DOWN) {
543             final int unicodeChar = event.getUnicodeChar();
544             final boolean isKeyNotWhitespace = unicodeChar > 0 &&
545                     !Character.isWhitespace(unicodeChar) && !Character.isSpaceChar(unicodeChar);
546             if (isKeyNotWhitespace) {
547                 boolean gotKey = TextKeyListener.getInstance().onKeyDown(this, mSearchQueryBuilder,
548                         event.getKeyCode(), event);
549                 if (gotKey && mSearchQueryBuilder.length() > 0) {
550                     mSearchBarController.focusSearchField();
551                 }
552             }
553         }
554
555         return super.dispatchKeyEvent(event);
556     }
557
558     @Override
559     public boolean onInterceptTouchEvent(MotionEvent ev) {
560         return handleTouchEvent(ev);
561     }
562
563     @SuppressLint("ClickableViewAccessibility")
564     @Override
565     public boolean onTouchEvent(MotionEvent ev) {
566         return handleTouchEvent(ev);
567     }
568
569     @Override
570     public boolean onLongClick(View v) {
571         // Return early if this is not initiated from a touch
572         if (!v.isInTouchMode()) return false;
573         // When we have exited all apps or are in transition, disregard long clicks
574
575         if (!mLauncher.isAppsViewVisible() ||
576                 mLauncher.getWorkspace().isSwitchingState()) return false;
577         // Return if global dragging is not enabled
578         if (!mLauncher.isDraggingEnabled()) return false;
579
580         // Start the drag
581         mLauncher.getWorkspace().beginDragShared(v, this, false);
582         // Enter spring loaded mode
583         mLauncher.enterSpringLoadedDragMode();
584
585         return false;
586     }
587
588     @Override
589     public boolean supportsFlingToDelete() {
590         return true;
591     }
592
593     @Override
594     public boolean supportsAppInfoDropTarget() {
595         return true;
596     }
597
598     @Override
599     public boolean supportsDeleteDropTarget() {
600         return false;
601     }
602
603     @Override
604     public float getIntrinsicIconScaleFactor() {
605         DeviceProfile grid = mLauncher.getDeviceProfile();
606         return (float) grid.allAppsIconSizePx / grid.iconSizePx;
607     }
608
609     @Override
610     public void onFlingToDeleteCompleted() {
611         // We just dismiss the drag when we fling, so cleanup here
612         mLauncher.exitSpringLoadedDragModeDelayed(true,
613                 Launcher.EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, null);
614         mLauncher.unlockScreenOrientation(false);
615     }
616
617     @Override
618     public void onDropCompleted(View target, DropTarget.DragObject d, boolean isFlingToDelete,
619             boolean success) {
620         if (isFlingToDelete || !success || (target != mLauncher.getWorkspace() &&
621                 !(target instanceof DeleteDropTarget) && !(target instanceof Folder))) {
622             // Exit spring loaded mode if we have not successfully dropped or have not handled the
623             // drop in Workspace
624             mLauncher.exitSpringLoadedDragModeDelayed(true,
625                     Launcher.EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, null);
626         }
627         mLauncher.unlockScreenOrientation(false);
628
629         // Display an error message if the drag failed due to there not being enough space on the
630         // target layout we were dropping on.
631         if (!success) {
632             boolean showOutOfSpaceMessage = false;
633             if (target instanceof Workspace) {
634                 int currentScreen = mLauncher.getCurrentWorkspaceScreen();
635                 Workspace workspace = (Workspace) target;
636                 CellLayout layout = (CellLayout) workspace.getChildAt(currentScreen);
637                 ItemInfo itemInfo = d.dragInfo;
638                 if (layout != null) {
639                     showOutOfSpaceMessage =
640                             !layout.findCellForSpan(null, itemInfo.spanX, itemInfo.spanY);
641                 }
642             }
643             if (showOutOfSpaceMessage) {
644                 mLauncher.showOutOfSpaceMessage(false);
645             }
646
647             d.deferDragViewCleanupPostAnimation = false;
648         }
649     }
650
651     @Override
652     public void onLauncherTransitionPrepare(Launcher l, boolean animated,
653             boolean multiplePagesVisible) {
654         // Do nothing
655     }
656
657     @Override
658     public void onLauncherTransitionStart(Launcher l, boolean animated, boolean toWorkspace) {
659         // Do nothing
660     }
661
662     @Override
663     public void onLauncherTransitionStep(Launcher l, float t) {
664         // Do nothing
665     }
666
667     @Override
668     public void onLauncherTransitionEnd(Launcher l, boolean animated, boolean toWorkspace) {
669         if (toWorkspace) {
670             reset();
671         }
672     }
673
674     /**
675      * Handles the touch events to dismiss all apps when clicking outside the bounds of the
676      * recycler view.
677      */
678     private boolean handleTouchEvent(MotionEvent ev) {
679         DeviceProfile grid = mLauncher.getDeviceProfile();
680         int x = (int) ev.getX();
681         int y = (int) ev.getY();
682
683         switch (ev.getAction()) {
684             case MotionEvent.ACTION_DOWN:
685                 if (!mContentBounds.isEmpty()) {
686                     // Outset the fixed bounds and check if the touch is outside all apps
687                     Rect tmpRect = new Rect(mContentBounds);
688                     tmpRect.inset(-grid.allAppsIconSizePx / 2, 0);
689                     if (ev.getX() < tmpRect.left || ev.getX() > tmpRect.right) {
690                         mBoundsCheckLastTouchDownPos.set(x, y);
691                         return true;
692                     }
693                 } else {
694                     // Check if the touch is outside all apps
695                     if (ev.getX() < getPaddingLeft() ||
696                             ev.getX() > (getWidth() - getPaddingRight())) {
697                         mBoundsCheckLastTouchDownPos.set(x, y);
698                         return true;
699                     }
700                 }
701                 break;
702             case MotionEvent.ACTION_UP:
703                 if (mBoundsCheckLastTouchDownPos.x > -1) {
704                     ViewConfiguration viewConfig = ViewConfiguration.get(getContext());
705                     float dx = ev.getX() - mBoundsCheckLastTouchDownPos.x;
706                     float dy = ev.getY() - mBoundsCheckLastTouchDownPos.y;
707                     float distance = (float) Math.hypot(dx, dy);
708                     if (distance < viewConfig.getScaledTouchSlop()) {
709                         // The background was clicked, so just go home
710                         Launcher launcher = (Launcher) getContext();
711                         launcher.showWorkspace(true);
712                         return true;
713                     }
714                 }
715                 // Fall through
716             case MotionEvent.ACTION_CANCEL:
717                 mBoundsCheckLastTouchDownPos.set(-1, -1);
718                 break;
719         }
720         return false;
721     }
722
723     @Override
724     public void onSearchResult(String query, ArrayList<ComponentKey> apps) {
725         if (apps != null) {
726             if (mApps.setOrderedFilter(apps)) {
727                 mAppsRecyclerView.onSearchResultsChanged();
728             }
729             mAdapter.setLastSearchQuery(query);
730         }
731     }
732
733     @Override
734     public void clearSearchResult() {
735         if (mApps.setOrderedFilter(null)) {
736             mAppsRecyclerView.onSearchResultsChanged();
737         }
738
739         // Clear the search query
740         mSearchQueryBuilder.clear();
741         mSearchQueryBuilder.clearSpans();
742         Selection.setSelection(mSearchQueryBuilder, 0);
743     }
744 }