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 */
16package com.android.launcher3.allapps;
17
18import android.annotation.SuppressLint;
19import android.annotation.TargetApi;
20import android.content.Context;
21import android.content.res.Resources;
22import android.graphics.Point;
23import android.graphics.Rect;
24import android.graphics.drawable.InsetDrawable;
25import android.os.Build;
26import android.os.Bundle;
27import android.support.v7.widget.RecyclerView;
28import android.text.Selection;
29import android.text.SpannableStringBuilder;
30import android.text.method.TextKeyListener;
31import android.util.AttributeSet;
32import android.view.KeyEvent;
33import android.view.LayoutInflater;
34import android.view.MotionEvent;
35import android.view.View;
36import android.view.ViewConfiguration;
37import android.view.ViewGroup;
38import android.view.ViewTreeObserver;
39import android.widget.FrameLayout;
40import android.widget.LinearLayout;
41
42import com.android.launcher3.AppInfo;
43import com.android.launcher3.BaseContainerView;
44import com.android.launcher3.BubbleTextView;
45import com.android.launcher3.CellLayout;
46import com.android.launcher3.CheckLongPressHelper;
47import com.android.launcher3.DeleteDropTarget;
48import com.android.launcher3.DeviceProfile;
49import com.android.launcher3.DragSource;
50import com.android.launcher3.DropTarget;
51import com.android.launcher3.Folder;
52import com.android.launcher3.ItemInfo;
53import com.android.launcher3.Launcher;
54import com.android.launcher3.LauncherTransitionable;
55import com.android.launcher3.R;
56import com.android.launcher3.Stats;
57import com.android.launcher3.Utilities;
58import com.android.launcher3.Workspace;
59import com.android.launcher3.util.ComponentKey;
60import com.android.launcher3.util.Thunk;
61
62import java.nio.charset.Charset;
63import java.nio.charset.CharsetEncoder;
64import java.util.ArrayList;
65import java.util.List;
66
67
68
69/**
70 * A merge algorithm that merges every section indiscriminately.
71 */
72final class FullMergeAlgorithm implements AlphabeticalAppsList.MergeAlgorithm {
73
74    @Override
75    public boolean continueMerging(AlphabeticalAppsList.SectionInfo section,
76           AlphabeticalAppsList.SectionInfo withSection,
77           int sectionAppCount, int numAppsPerRow, int mergeCount) {
78        // Don't merge the predicted apps
79        if (section.firstAppItem.viewType != AllAppsGridAdapter.ICON_VIEW_TYPE) {
80            return false;
81        }
82        // Otherwise, merge every other section
83        return true;
84    }
85}
86
87/**
88 * The logic we use to merge multiple sections.  We only merge sections when their final row
89 * contains less than a certain number of icons, and stop at a specified max number of merges.
90 * In addition, we will try and not merge sections that identify apps from different scripts.
91 */
92final class SimpleSectionMergeAlgorithm implements AlphabeticalAppsList.MergeAlgorithm {
93
94    private int mMinAppsPerRow;
95    private int mMinRowsInMergedSection;
96    private int mMaxAllowableMerges;
97    private CharsetEncoder mAsciiEncoder;
98
99    public SimpleSectionMergeAlgorithm(int minAppsPerRow, int minRowsInMergedSection, int maxNumMerges) {
100        mMinAppsPerRow = minAppsPerRow;
101        mMinRowsInMergedSection = minRowsInMergedSection;
102        mMaxAllowableMerges = maxNumMerges;
103        mAsciiEncoder = Charset.forName("US-ASCII").newEncoder();
104    }
105
106    @Override
107    public boolean continueMerging(AlphabeticalAppsList.SectionInfo section,
108           AlphabeticalAppsList.SectionInfo withSection,
109           int sectionAppCount, int numAppsPerRow, int mergeCount) {
110        // Don't merge the predicted apps
111        if (section.firstAppItem.viewType != AllAppsGridAdapter.ICON_VIEW_TYPE) {
112            return false;
113        }
114
115        // Continue merging if the number of hanging apps on the final row is less than some
116        // fixed number (ragged), the merged rows has yet to exceed some minimum row count,
117        // and while the number of merged sections is less than some fixed number of merges
118        int rows = sectionAppCount / numAppsPerRow;
119        int cols = sectionAppCount % numAppsPerRow;
120
121        // Ensure that we do not merge across scripts, currently we only allow for english and
122        // native scripts so we can test if both can just be ascii encoded
123        boolean isCrossScript = false;
124        if (section.firstAppItem != null && withSection.firstAppItem != null) {
125            isCrossScript = mAsciiEncoder.canEncode(section.firstAppItem.sectionName) !=
126                    mAsciiEncoder.canEncode(withSection.firstAppItem.sectionName);
127        }
128        return (0 < cols && cols < mMinAppsPerRow) &&
129                rows < mMinRowsInMergedSection &&
130                mergeCount < mMaxAllowableMerges &&
131                !isCrossScript;
132    }
133}
134
135/**
136 * The all apps view container.
137 */
138public class AllAppsContainerView extends BaseContainerView implements DragSource,
139        LauncherTransitionable, View.OnTouchListener, View.OnLongClickListener,
140        AllAppsSearchBarController.Callbacks {
141
142    private static final int MIN_ROWS_IN_MERGED_SECTION_PHONE = 3;
143    private static final int MAX_NUM_MERGES_PHONE = 2;
144
145    @Thunk Launcher mLauncher;
146    @Thunk AlphabeticalAppsList mApps;
147    private AllAppsGridAdapter mAdapter;
148    private RecyclerView.LayoutManager mLayoutManager;
149    private RecyclerView.ItemDecoration mItemDecoration;
150
151    @Thunk View mContent;
152    @Thunk View mContainerView;
153    @Thunk View mRevealView;
154    @Thunk AllAppsRecyclerView mAppsRecyclerView;
155    @Thunk AllAppsSearchBarController mSearchBarController;
156    private ViewGroup mSearchBarContainerView;
157    private View mSearchBarView;
158
159    private int mSectionNamesMargin;
160    private int mNumAppsPerRow;
161    private int mNumPredictedAppsPerRow;
162    private int mRecyclerViewTopBottomPadding;
163    // This coordinate is relative to this container view
164    private final Point mBoundsCheckLastTouchDownPos = new Point(-1, -1);
165    // This coordinate is relative to its parent
166    private final Point mIconLastTouchPos = new Point();
167
168    private SpannableStringBuilder mSearchQueryBuilder = null;
169
170    public AllAppsContainerView(Context context) {
171        this(context, null);
172    }
173
174    public AllAppsContainerView(Context context, AttributeSet attrs) {
175        this(context, attrs, 0);
176    }
177
178    public AllAppsContainerView(Context context, AttributeSet attrs, int defStyleAttr) {
179        super(context, attrs, defStyleAttr);
180        Resources res = context.getResources();
181
182        mLauncher = (Launcher) context;
183        mSectionNamesMargin = res.getDimensionPixelSize(R.dimen.all_apps_grid_view_start_margin);
184        mApps = new AlphabeticalAppsList(context);
185        mAdapter = new AllAppsGridAdapter(context, mApps, this, mLauncher, this);
186        mAdapter.setEmptySearchText(res.getString(R.string.all_apps_loading_message));
187        mApps.setAdapter(mAdapter);
188        mLayoutManager = mAdapter.getLayoutManager();
189        mItemDecoration = mAdapter.getItemDecoration();
190        mRecyclerViewTopBottomPadding =
191                res.getDimensionPixelSize(R.dimen.all_apps_list_top_bottom_padding);
192
193        mSearchQueryBuilder = new SpannableStringBuilder();
194        Selection.setSelection(mSearchQueryBuilder, 0);
195    }
196
197    /**
198     * Sets the current set of predicted apps.
199     */
200    public void setPredictedApps(List<ComponentKey> apps) {
201        mApps.setPredictedApps(apps);
202    }
203
204    /**
205     * Sets the current set of apps.
206     */
207    public void setApps(List<AppInfo> apps) {
208        mApps.setApps(apps);
209    }
210
211    /**
212     * Adds new apps to the list.
213     */
214    public void addApps(List<AppInfo> apps) {
215        mApps.addApps(apps);
216    }
217
218    /**
219     * Updates existing apps in the list
220     */
221    public void updateApps(List<AppInfo> apps) {
222        mApps.updateApps(apps);
223    }
224
225    /**
226     * Removes some apps from the list.
227     */
228    public void removeApps(List<AppInfo> apps) {
229        mApps.removeApps(apps);
230    }
231
232    /**
233     * Sets the search bar that shows above the a-z list.
234     */
235    public void setSearchBarController(AllAppsSearchBarController searchController) {
236        if (mSearchBarController != null) {
237            throw new RuntimeException("Expected search bar controller to only be set once");
238        }
239        mSearchBarController = searchController;
240        mSearchBarController.initialize(mApps, this);
241
242        // Add the new search view to the layout
243        View searchBarView = searchController.getView(mSearchBarContainerView);
244        mSearchBarContainerView.addView(searchBarView);
245        mSearchBarContainerView.setVisibility(View.VISIBLE);
246        mSearchBarView = searchBarView;
247        setHasSearchBar();
248
249        updateBackgroundAndPaddings();
250    }
251
252    /**
253     * Scrolls this list view to the top.
254     */
255    public void scrollToTop() {
256        mAppsRecyclerView.scrollToTop();
257    }
258
259    /**
260     * Returns the content view used for the launcher transitions.
261     */
262    public View getContentView() {
263        return mContainerView;
264    }
265
266    /**
267     * Returns the all apps search view.
268     */
269    public View getSearchBarView() {
270        return mSearchBarView;
271    }
272
273    /**
274     * Returns the reveal view used for the launcher transitions.
275     */
276    public View getRevealView() {
277        return mRevealView;
278    }
279
280    /**
281     * Returns an new instance of the default app search controller.
282     */
283    public AllAppsSearchBarController newDefaultAppSearchController() {
284        return new DefaultAppSearchController(getContext(), this, mAppsRecyclerView);
285    }
286
287    /**
288     * Focuses the search field and begins an app search.
289     */
290    public void startAppsSearch() {
291        if (mSearchBarController != null) {
292            mSearchBarController.focusSearchField();
293        }
294    }
295
296    @Override
297    protected void onFinishInflate() {
298        super.onFinishInflate();
299        boolean isRtl = Utilities.isRtl(getResources());
300        mAdapter.setRtl(isRtl);
301        mContent = findViewById(R.id.content);
302
303        // This is a focus listener that proxies focus from a view into the list view.  This is to
304        // work around the search box from getting first focus and showing the cursor.
305        View.OnFocusChangeListener focusProxyListener = new View.OnFocusChangeListener() {
306            @Override
307            public void onFocusChange(View v, boolean hasFocus) {
308                if (hasFocus) {
309                    mAppsRecyclerView.requestFocus();
310                }
311            }
312        };
313        mSearchBarContainerView = (ViewGroup) findViewById(R.id.search_box_container);
314        mSearchBarContainerView.setOnFocusChangeListener(focusProxyListener);
315        mContainerView = findViewById(R.id.all_apps_container);
316        mContainerView.setOnFocusChangeListener(focusProxyListener);
317        mRevealView = findViewById(R.id.all_apps_reveal);
318
319        // Load the all apps recycler view
320        mAppsRecyclerView = (AllAppsRecyclerView) findViewById(R.id.apps_list_view);
321        mAppsRecyclerView.setApps(mApps);
322        mAppsRecyclerView.setLayoutManager(mLayoutManager);
323        mAppsRecyclerView.setAdapter(mAdapter);
324        mAppsRecyclerView.setHasFixedSize(true);
325        if (mItemDecoration != null) {
326            mAppsRecyclerView.addItemDecoration(mItemDecoration);
327        }
328
329        updateBackgroundAndPaddings();
330    }
331
332    @Override
333    public void onBoundsChanged(Rect newBounds) {
334        mLauncher.updateOverlayBounds(newBounds);
335    }
336
337    @Override
338    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
339        // Update the number of items in the grid before we measure the view
340        int availableWidth = !mContentBounds.isEmpty() ? mContentBounds.width() :
341                MeasureSpec.getSize(widthMeasureSpec);
342        DeviceProfile grid = mLauncher.getDeviceProfile();
343        grid.updateAppsViewNumCols(getResources(), availableWidth);
344        if (mNumAppsPerRow != grid.allAppsNumCols ||
345                mNumPredictedAppsPerRow != grid.allAppsNumPredictiveCols) {
346            mNumAppsPerRow = grid.allAppsNumCols;
347            mNumPredictedAppsPerRow = grid.allAppsNumPredictiveCols;
348
349            // If there is a start margin to draw section names, determine how we are going to merge
350            // app sections
351            boolean mergeSectionsFully = mSectionNamesMargin == 0 || !grid.isPhone;
352            AlphabeticalAppsList.MergeAlgorithm mergeAlgorithm = mergeSectionsFully ?
353                    new FullMergeAlgorithm() :
354                    new SimpleSectionMergeAlgorithm((int) Math.ceil(mNumAppsPerRow / 2f),
355                            MIN_ROWS_IN_MERGED_SECTION_PHONE, MAX_NUM_MERGES_PHONE);
356
357            mAppsRecyclerView.setNumAppsPerRow(grid, mNumAppsPerRow);
358            mAdapter.setNumAppsPerRow(mNumAppsPerRow);
359            mApps.setNumAppsPerRow(mNumAppsPerRow, mNumPredictedAppsPerRow, mergeAlgorithm);
360        }
361
362        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
363    }
364
365    /**
366     * Update the background and padding of the Apps view and children.  Instead of insetting the
367     * container view, we inset the background and padding of the recycler view to allow for the
368     * recycler view to handle touch events (for fast scrolling) all the way to the edge.
369     */
370    @Override
371    protected void onUpdateBackgroundAndPaddings(Rect searchBarBounds, Rect padding) {
372        boolean isRtl = Utilities.isRtl(getResources());
373
374        // TODO: Use quantum_panel instead of quantum_panel_shape
375        InsetDrawable background = new InsetDrawable(
376                getResources().getDrawable(R.drawable.quantum_panel_shape), padding.left, 0,
377                padding.right, 0);
378        Rect bgPadding = new Rect();
379        background.getPadding(bgPadding);
380        mContainerView.setBackground(background);
381        mRevealView.setBackground(background.getConstantState().newDrawable());
382        mAppsRecyclerView.updateBackgroundPadding(bgPadding);
383        mAdapter.updateBackgroundPadding(bgPadding);
384
385        // Hack: We are going to let the recycler view take the full width, so reset the padding on
386        // the container to zero after setting the background and apply the top-bottom padding to
387        // the content view instead so that the launcher transition clips correctly.
388        mContent.setPadding(0, padding.top, 0, padding.bottom);
389        mContainerView.setPadding(0, 0, 0, 0);
390
391        // Pad the recycler view by the background padding plus the start margin (for the section
392        // names)
393        int startInset = Math.max(mSectionNamesMargin, mAppsRecyclerView.getMaxScrollbarWidth());
394        int topBottomPadding = mRecyclerViewTopBottomPadding;
395        if (isRtl) {
396            mAppsRecyclerView.setPadding(padding.left + mAppsRecyclerView.getMaxScrollbarWidth(),
397                    topBottomPadding, padding.right + startInset, topBottomPadding);
398        } else {
399            mAppsRecyclerView.setPadding(padding.left + startInset, topBottomPadding,
400                    padding.right + mAppsRecyclerView.getMaxScrollbarWidth(), topBottomPadding);
401        }
402
403        // Inset the search bar to fit its bounds above the container
404        if (mSearchBarView != null) {
405            Rect backgroundPadding = new Rect();
406            if (mSearchBarView.getBackground() != null) {
407                mSearchBarView.getBackground().getPadding(backgroundPadding);
408            }
409            LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams)
410                    mSearchBarContainerView.getLayoutParams();
411            lp.leftMargin = searchBarBounds.left - backgroundPadding.left;
412            lp.topMargin = searchBarBounds.top - backgroundPadding.top;
413            lp.rightMargin = (getMeasuredWidth() - searchBarBounds.right) - backgroundPadding.right;
414            mSearchBarContainerView.requestLayout();
415        }
416    }
417
418    @Override
419    public boolean dispatchKeyEvent(KeyEvent event) {
420        // Determine if the key event was actual text, if so, focus the search bar and then dispatch
421        // the key normally so that it can process this key event
422        if (!mSearchBarController.isSearchFieldFocused() &&
423                event.getAction() == KeyEvent.ACTION_DOWN) {
424            final int unicodeChar = event.getUnicodeChar();
425            final boolean isKeyNotWhitespace = unicodeChar > 0 &&
426                    !Character.isWhitespace(unicodeChar) && !Character.isSpaceChar(unicodeChar);
427            if (isKeyNotWhitespace) {
428                boolean gotKey = TextKeyListener.getInstance().onKeyDown(this, mSearchQueryBuilder,
429                        event.getKeyCode(), event);
430                if (gotKey && mSearchQueryBuilder.length() > 0) {
431                    mSearchBarController.focusSearchField();
432                }
433            }
434        }
435
436        return super.dispatchKeyEvent(event);
437    }
438
439    @Override
440    public boolean onInterceptTouchEvent(MotionEvent ev) {
441        return handleTouchEvent(ev);
442    }
443
444    @SuppressLint("ClickableViewAccessibility")
445    @Override
446    public boolean onTouchEvent(MotionEvent ev) {
447        return handleTouchEvent(ev);
448    }
449
450    @SuppressLint("ClickableViewAccessibility")
451    @Override
452    public boolean onTouch(View v, MotionEvent ev) {
453        switch (ev.getAction()) {
454            case MotionEvent.ACTION_DOWN:
455            case MotionEvent.ACTION_MOVE:
456                mIconLastTouchPos.set((int) ev.getX(), (int) ev.getY());
457                break;
458        }
459        return false;
460    }
461
462    @Override
463    public boolean onLongClick(View v) {
464        // Return early if this is not initiated from a touch
465        if (!v.isInTouchMode()) return false;
466        // When we have exited all apps or are in transition, disregard long clicks
467        if (!mLauncher.isAppsViewVisible() ||
468                mLauncher.getWorkspace().isSwitchingState()) return false;
469        // Return if global dragging is not enabled
470        if (!mLauncher.isDraggingEnabled()) return false;
471
472        // Start the drag
473        mLauncher.getWorkspace().beginDragShared(v, mIconLastTouchPos, this, false);
474        // Enter spring loaded mode
475        mLauncher.enterSpringLoadedDragMode();
476
477        return false;
478    }
479
480    @Override
481    public boolean supportsFlingToDelete() {
482        return true;
483    }
484
485    @Override
486    public boolean supportsAppInfoDropTarget() {
487        return true;
488    }
489
490    @Override
491    public boolean supportsDeleteDropTarget() {
492        return false;
493    }
494
495    @Override
496    public float getIntrinsicIconScaleFactor() {
497        DeviceProfile grid = mLauncher.getDeviceProfile();
498        return (float) grid.allAppsIconSizePx / grid.iconSizePx;
499    }
500
501    @Override
502    public void onFlingToDeleteCompleted() {
503        // We just dismiss the drag when we fling, so cleanup here
504        mLauncher.exitSpringLoadedDragModeDelayed(true,
505                Launcher.EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, null);
506        mLauncher.unlockScreenOrientation(false);
507    }
508
509    @Override
510    public void onDropCompleted(View target, DropTarget.DragObject d, boolean isFlingToDelete,
511            boolean success) {
512        if (isFlingToDelete || !success || (target != mLauncher.getWorkspace() &&
513                !(target instanceof DeleteDropTarget) && !(target instanceof Folder))) {
514            // Exit spring loaded mode if we have not successfully dropped or have not handled the
515            // drop in Workspace
516            mLauncher.exitSpringLoadedDragModeDelayed(true,
517                    Launcher.EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, null);
518        }
519        mLauncher.unlockScreenOrientation(false);
520
521        // Display an error message if the drag failed due to there not being enough space on the
522        // target layout we were dropping on.
523        if (!success) {
524            boolean showOutOfSpaceMessage = false;
525            if (target instanceof Workspace) {
526                int currentScreen = mLauncher.getCurrentWorkspaceScreen();
527                Workspace workspace = (Workspace) target;
528                CellLayout layout = (CellLayout) workspace.getChildAt(currentScreen);
529                ItemInfo itemInfo = (ItemInfo) d.dragInfo;
530                if (layout != null) {
531                    layout.calculateSpans(itemInfo);
532                    showOutOfSpaceMessage =
533                            !layout.findCellForSpan(null, itemInfo.spanX, itemInfo.spanY);
534                }
535            }
536            if (showOutOfSpaceMessage) {
537                mLauncher.showOutOfSpaceMessage(false);
538            }
539
540            d.deferDragViewCleanupPostAnimation = false;
541        }
542    }
543
544    @Override
545    public void onLauncherTransitionPrepare(Launcher l, boolean animated, boolean toWorkspace) {
546        // Do nothing
547    }
548
549    @Override
550    public void onLauncherTransitionStart(Launcher l, boolean animated, boolean toWorkspace) {
551        // Do nothing
552    }
553
554    @Override
555    public void onLauncherTransitionStep(Launcher l, float t) {
556        // Do nothing
557    }
558
559    @Override
560    public void onLauncherTransitionEnd(Launcher l, boolean animated, boolean toWorkspace) {
561        if (toWorkspace) {
562            // Reset the search bar after transitioning home
563            mSearchBarController.reset();
564        }
565    }
566
567    /**
568     * Handles the touch events to dismiss all apps when clicking outside the bounds of the
569     * recycler view.
570     */
571    private boolean handleTouchEvent(MotionEvent ev) {
572        DeviceProfile grid = mLauncher.getDeviceProfile();
573        int x = (int) ev.getX();
574        int y = (int) ev.getY();
575
576        switch (ev.getAction()) {
577            case MotionEvent.ACTION_DOWN:
578                if (!mContentBounds.isEmpty()) {
579                    // Outset the fixed bounds and check if the touch is outside all apps
580                    Rect tmpRect = new Rect(mContentBounds);
581                    tmpRect.inset(-grid.allAppsIconSizePx / 2, 0);
582                    if (ev.getX() < tmpRect.left || ev.getX() > tmpRect.right) {
583                        mBoundsCheckLastTouchDownPos.set(x, y);
584                        return true;
585                    }
586                } else {
587                    // Check if the touch is outside all apps
588                    if (ev.getX() < getPaddingLeft() ||
589                            ev.getX() > (getWidth() - getPaddingRight())) {
590                        mBoundsCheckLastTouchDownPos.set(x, y);
591                        return true;
592                    }
593                }
594                break;
595            case MotionEvent.ACTION_UP:
596                if (mBoundsCheckLastTouchDownPos.x > -1) {
597                    ViewConfiguration viewConfig = ViewConfiguration.get(getContext());
598                    float dx = ev.getX() - mBoundsCheckLastTouchDownPos.x;
599                    float dy = ev.getY() - mBoundsCheckLastTouchDownPos.y;
600                    float distance = (float) Math.hypot(dx, dy);
601                    if (distance < viewConfig.getScaledTouchSlop()) {
602                        // The background was clicked, so just go home
603                        Launcher launcher = (Launcher) getContext();
604                        launcher.showWorkspace(true);
605                        return true;
606                    }
607                }
608                // Fall through
609            case MotionEvent.ACTION_CANCEL:
610                mBoundsCheckLastTouchDownPos.set(-1, -1);
611                break;
612        }
613        return false;
614    }
615
616    @Override
617    public void onSearchResult(String query, ArrayList<ComponentKey> apps) {
618        if (apps != null) {
619            if (apps.isEmpty()) {
620                String formatStr = getResources().getString(R.string.all_apps_no_search_results);
621                mAdapter.setEmptySearchText(String.format(formatStr, query));
622            } else {
623                mAppsRecyclerView.scrollToTop();
624            }
625            mApps.setOrderedFilter(apps);
626        }
627    }
628
629    @Override
630    public void clearSearchResult() {
631        mApps.setOrderedFilter(null);
632
633        // Clear the search query
634        mSearchQueryBuilder.clear();
635        mSearchQueryBuilder.clearSpans();
636        Selection.setSelection(mSearchQueryBuilder, 0);
637    }
638}
639