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