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