Home | History | Annotate | Download | only in allapps
      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
     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 
     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
    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 
    168     public AllAppsContainerView(Context context) {
    169         this(context, null);
    170     }
    171 
    172     public AllAppsContainerView(Context context, AttributeSet attrs) {
    173         this(context, attrs, 0);
    174     }
    175 
    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      */
    202     public void setPredictedApps(List<ComponentKey> apps) {
    203         mApps.setPredictedApps(apps);
    204     }
    205 
    206     /**
    207      * Sets the current set of apps.
    208      */
    209     public void setApps(List<AppInfo> apps) {
    210         mApps.setApps(apps);
    211     }
    212 
    213     /**
    214      * Adds new apps to the list.
    215      */
    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      */
    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      */
    232     public void removeApps(List<AppInfo> apps) {
    233         mApps.removeApps(apps);
    234         mSearchBarController.refreshSearchResult();
    235     }
    236 
    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      */
    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      */
    260     public void scrollToTop() {
    261         mAppsRecyclerView.scrollToTop();
    262     }
    263 
    264     /**
    265      * Returns whether the view itself will handle the touch event or not.
    266      */
    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      */
    300     public void startAppsSearch() {
    301         if (mSearchBarController != null) {
    302             mSearchBarController.focusSearchField();
    303         }
    304     }
    305 
    306     /**
    307      * Resets the state of AllApps.
    308      */
    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
    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
    376     public void onBoundsChanged(Rect newBounds) { }
    377 
    378     @Override
    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      */
    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
    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
    529     public boolean onInterceptTouchEvent(MotionEvent ev) {
    530         return handleTouchEvent(ev);
    531     }
    532 
    533     @SuppressLint("ClickableViewAccessibility")
    534     @Override
    535     public boolean onTouchEvent(MotionEvent ev) {
    536         return handleTouchEvent(ev);
    537     }
    538 
    539     @Override
    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
    578     public boolean supportsFlingToDelete() {
    579         return true;
    580     }
    581 
    582     @Override
    583     public boolean supportsAppInfoDropTarget() {
    584         return true;
    585     }
    586 
    587     @Override
    588     public boolean supportsDeleteDropTarget() {
    589         return false;
    590     }
    591 
    592     @Override
    593     public float getIntrinsicIconScaleFactor() {
    594         DeviceProfile grid = mLauncher.getDeviceProfile();
    595         return (float) grid.allAppsIconSizePx / grid.iconSizePx;
    596     }
    597 
    598     @Override
    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
    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
    641     public void onLauncherTransitionPrepare(Launcher l, boolean animated,
    642             boolean multiplePagesVisible) {
    643         // Do nothing
    644     }
    645 
    646     @Override
    647     public void onLauncherTransitionStart(Launcher l, boolean animated, boolean toWorkspace) {
    648         // Do nothing
    649     }
    650 
    651     @Override
    652     public void onLauncherTransitionStep(Launcher l, float t) {
    653         // Do nothing
    654     }
    655 
    656     @Override
    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      */
    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
    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
    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
    735     public void fillInLaunchSourceData(View v, ItemInfo info, Target target, Target targetParent) {
    736         targetParent.containerType = mAppsRecyclerView.getContainerType(v);
    737     }
    738 
    739     public boolean shouldRestoreImeState() {
    740         return !TextUtils.isEmpty(mSearchInput.getText());
    741     }
    742 }
    743