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