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