1 /* 2 * Copyright (C) 2008 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 17 package com.android.launcher3; 18 19 import android.animation.Animator; 20 import android.animation.AnimatorListenerAdapter; 21 import android.animation.AnimatorSet; 22 import android.animation.ObjectAnimator; 23 import android.animation.PropertyValuesHolder; 24 import android.content.Context; 25 import android.content.res.Resources; 26 import android.graphics.PointF; 27 import android.graphics.Rect; 28 import android.os.SystemClock; 29 import android.support.v4.widget.AutoScrollHelper; 30 import android.text.InputType; 31 import android.text.Selection; 32 import android.text.Spannable; 33 import android.util.AttributeSet; 34 import android.util.Log; 35 import android.view.ActionMode; 36 import android.view.KeyEvent; 37 import android.view.LayoutInflater; 38 import android.view.Menu; 39 import android.view.MenuItem; 40 import android.view.MotionEvent; 41 import android.view.View; 42 import android.view.accessibility.AccessibilityEvent; 43 import android.view.accessibility.AccessibilityManager; 44 import android.view.animation.AccelerateInterpolator; 45 import android.view.inputmethod.EditorInfo; 46 import android.view.inputmethod.InputMethodManager; 47 import android.widget.LinearLayout; 48 import android.widget.ScrollView; 49 import android.widget.TextView; 50 51 import com.android.launcher3.FolderInfo.FolderListener; 52 53 import java.util.ArrayList; 54 import java.util.Collections; 55 import java.util.Comparator; 56 57 /** 58 * Represents a set of icons chosen by the user or generated by the system. 59 */ 60 public class Folder extends LinearLayout implements DragSource, View.OnClickListener, 61 View.OnLongClickListener, DropTarget, FolderListener, TextView.OnEditorActionListener, 62 View.OnFocusChangeListener { 63 private static final String TAG = "Launcher.Folder"; 64 65 protected DragController mDragController; 66 protected Launcher mLauncher; 67 protected FolderInfo mInfo; 68 69 static final int STATE_NONE = -1; 70 static final int STATE_SMALL = 0; 71 static final int STATE_ANIMATING = 1; 72 static final int STATE_OPEN = 2; 73 74 private static final int CLOSE_FOLDER_DELAY_MS = 150; 75 76 private int mExpandDuration; 77 private int mMaterialExpandDuration; 78 private int mMaterialExpandStagger; 79 protected CellLayout mContent; 80 private ScrollView mScrollView; 81 private final LayoutInflater mInflater; 82 private final IconCache mIconCache; 83 private int mState = STATE_NONE; 84 private static final int REORDER_ANIMATION_DURATION = 230; 85 private static final int REORDER_DELAY = 250; 86 private static final int ON_EXIT_CLOSE_DELAY = 400; 87 private boolean mRearrangeOnClose = false; 88 private FolderIcon mFolderIcon; 89 private int mMaxCountX; 90 private int mMaxCountY; 91 private int mMaxNumItems; 92 private ArrayList<View> mItemsInReadingOrder = new ArrayList<View>(); 93 boolean mItemsInvalidated = false; 94 private ShortcutInfo mCurrentDragInfo; 95 private View mCurrentDragView; 96 private boolean mIsExternalDrag; 97 boolean mSuppressOnAdd = false; 98 private int[] mTargetCell = new int[2]; 99 private int[] mPreviousTargetCell = new int[2]; 100 private int[] mEmptyCell = new int[2]; 101 private Alarm mReorderAlarm = new Alarm(); 102 private Alarm mOnExitAlarm = new Alarm(); 103 private int mFolderNameHeight; 104 private Rect mTempRect = new Rect(); 105 private boolean mDragInProgress = false; 106 private boolean mDeleteFolderOnDropCompleted = false; 107 private boolean mSuppressFolderDeletion = false; 108 private boolean mItemAddedBackToSelfViaIcon = false; 109 FolderEditText mFolderName; 110 private float mFolderIconPivotX; 111 private float mFolderIconPivotY; 112 113 private boolean mIsEditingName = false; 114 private InputMethodManager mInputMethodManager; 115 116 private static String sDefaultFolderName; 117 private static String sHintText; 118 119 private FocusIndicatorView mFocusIndicatorHandler; 120 121 // We avoid measuring the scroll view with a 0 width or height, as this 122 // results in CellLayout being measured as UNSPECIFIED, which it does 123 // not support. 124 private static final int MIN_CONTENT_DIMEN = 5; 125 126 private boolean mDestroyed; 127 128 private AutoScrollHelper mAutoScrollHelper; 129 130 private Runnable mDeferredAction; 131 private boolean mDeferDropAfterUninstall; 132 private boolean mUninstallSuccessful; 133 134 /** 135 * Used to inflate the Workspace from XML. 136 * 137 * @param context The application's context. 138 * @param attrs The attribtues set containing the Workspace's customization values. 139 */ 140 public Folder(Context context, AttributeSet attrs) { 141 super(context, attrs); 142 143 LauncherAppState app = LauncherAppState.getInstance(); 144 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile(); 145 setAlwaysDrawnWithCacheEnabled(false); 146 mInflater = LayoutInflater.from(context); 147 mIconCache = app.getIconCache(); 148 149 Resources res = getResources(); 150 mMaxCountX = (int) grid.numColumns; 151 // Allow scrolling folders when DISABLE_ALL_APPS is true. 152 if (LauncherAppState.isDisableAllApps()) { 153 mMaxCountY = mMaxNumItems = Integer.MAX_VALUE; 154 } else { 155 mMaxCountY = (int) grid.numRows; 156 mMaxNumItems = mMaxCountX * mMaxCountY; 157 } 158 159 mInputMethodManager = (InputMethodManager) 160 getContext().getSystemService(Context.INPUT_METHOD_SERVICE); 161 162 mExpandDuration = res.getInteger(R.integer.config_folderExpandDuration); 163 mMaterialExpandDuration = res.getInteger(R.integer.config_materialFolderExpandDuration); 164 mMaterialExpandStagger = res.getInteger(R.integer.config_materialFolderExpandStagger); 165 166 if (sDefaultFolderName == null) { 167 sDefaultFolderName = res.getString(R.string.folder_name); 168 } 169 if (sHintText == null) { 170 sHintText = res.getString(R.string.folder_hint_text); 171 } 172 mLauncher = (Launcher) context; 173 // We need this view to be focusable in touch mode so that when text editing of the folder 174 // name is complete, we have something to focus on, thus hiding the cursor and giving 175 // reliable behvior when clicking the text field (since it will always gain focus on click). 176 setFocusableInTouchMode(true); 177 } 178 179 @Override 180 protected void onFinishInflate() { 181 super.onFinishInflate(); 182 mScrollView = (ScrollView) findViewById(R.id.scroll_view); 183 mContent = (CellLayout) findViewById(R.id.folder_content); 184 185 mFocusIndicatorHandler = new FocusIndicatorView(getContext()); 186 mContent.addView(mFocusIndicatorHandler, 0); 187 mFocusIndicatorHandler.getLayoutParams().height = FocusIndicatorView.DEFAULT_LAYOUT_SIZE; 188 mFocusIndicatorHandler.getLayoutParams().width = FocusIndicatorView.DEFAULT_LAYOUT_SIZE; 189 190 LauncherAppState app = LauncherAppState.getInstance(); 191 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile(); 192 193 mContent.setCellDimensions(grid.folderCellWidthPx, grid.folderCellHeightPx); 194 mContent.setGridSize(0, 0); 195 mContent.getShortcutsAndWidgets().setMotionEventSplittingEnabled(false); 196 mContent.setInvertIfRtl(true); 197 mFolderName = (FolderEditText) findViewById(R.id.folder_name); 198 mFolderName.setFolder(this); 199 mFolderName.setOnFocusChangeListener(this); 200 201 // We find out how tall the text view wants to be (it is set to wrap_content), so that 202 // we can allocate the appropriate amount of space for it. 203 int measureSpec = MeasureSpec.UNSPECIFIED; 204 mFolderName.measure(measureSpec, measureSpec); 205 mFolderNameHeight = mFolderName.getMeasuredHeight(); 206 207 // We disable action mode for now since it messes up the view on phones 208 mFolderName.setCustomSelectionActionModeCallback(mActionModeCallback); 209 mFolderName.setOnEditorActionListener(this); 210 mFolderName.setSelectAllOnFocus(true); 211 mFolderName.setInputType(mFolderName.getInputType() | 212 InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS | InputType.TYPE_TEXT_FLAG_CAP_WORDS); 213 mAutoScrollHelper = new FolderAutoScrollHelper(mScrollView); 214 } 215 216 private ActionMode.Callback mActionModeCallback = new ActionMode.Callback() { 217 public boolean onActionItemClicked(ActionMode mode, MenuItem item) { 218 return false; 219 } 220 221 public boolean onCreateActionMode(ActionMode mode, Menu menu) { 222 return false; 223 } 224 225 public void onDestroyActionMode(ActionMode mode) { 226 } 227 228 public boolean onPrepareActionMode(ActionMode mode, Menu menu) { 229 return false; 230 } 231 }; 232 233 public void onClick(View v) { 234 Object tag = v.getTag(); 235 if (tag instanceof ShortcutInfo) { 236 mLauncher.onClick(v); 237 } 238 } 239 240 public boolean onLongClick(View v) { 241 // Return if global dragging is not enabled 242 if (!mLauncher.isDraggingEnabled()) return true; 243 244 Object tag = v.getTag(); 245 if (tag instanceof ShortcutInfo) { 246 ShortcutInfo item = (ShortcutInfo) tag; 247 if (!v.isInTouchMode()) { 248 return false; 249 } 250 251 mLauncher.getWorkspace().beginDragShared(v, this); 252 253 mCurrentDragInfo = item; 254 mEmptyCell[0] = item.cellX; 255 mEmptyCell[1] = item.cellY; 256 mCurrentDragView = v; 257 258 mContent.removeView(mCurrentDragView); 259 mInfo.remove(mCurrentDragInfo); 260 mDragInProgress = true; 261 mItemAddedBackToSelfViaIcon = false; 262 } 263 return true; 264 } 265 266 public boolean isEditingName() { 267 return mIsEditingName; 268 } 269 270 public void startEditingFolderName() { 271 mFolderName.setHint(""); 272 mIsEditingName = true; 273 } 274 275 public void dismissEditingName() { 276 mInputMethodManager.hideSoftInputFromWindow(getWindowToken(), 0); 277 doneEditingFolderName(true); 278 } 279 280 public void doneEditingFolderName(boolean commit) { 281 mFolderName.setHint(sHintText); 282 // Convert to a string here to ensure that no other state associated with the text field 283 // gets saved. 284 String newTitle = mFolderName.getText().toString(); 285 mInfo.setTitle(newTitle); 286 LauncherModel.updateItemInDatabase(mLauncher, mInfo); 287 288 if (commit) { 289 sendCustomAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED, 290 String.format(getContext().getString(R.string.folder_renamed), newTitle)); 291 } 292 // In order to clear the focus from the text field, we set the focus on ourself. This 293 // ensures that every time the field is clicked, focus is gained, giving reliable behavior. 294 requestFocus(); 295 296 Selection.setSelection((Spannable) mFolderName.getText(), 0, 0); 297 mIsEditingName = false; 298 } 299 300 public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { 301 if (actionId == EditorInfo.IME_ACTION_DONE) { 302 dismissEditingName(); 303 return true; 304 } 305 return false; 306 } 307 308 public View getEditTextRegion() { 309 return mFolderName; 310 } 311 312 public CellLayout getContent() { 313 return mContent; 314 } 315 316 /** 317 * We need to handle touch events to prevent them from falling through to the workspace below. 318 */ 319 @Override 320 public boolean onTouchEvent(MotionEvent ev) { 321 return true; 322 } 323 324 public void setDragController(DragController dragController) { 325 mDragController = dragController; 326 } 327 328 void setFolderIcon(FolderIcon icon) { 329 mFolderIcon = icon; 330 } 331 332 @Override 333 public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { 334 // When the folder gets focus, we don't want to announce the list of items. 335 return true; 336 } 337 338 /** 339 * @return the FolderInfo object associated with this folder 340 */ 341 FolderInfo getInfo() { 342 return mInfo; 343 } 344 345 private class GridComparator implements Comparator<ShortcutInfo> { 346 int mNumCols; 347 public GridComparator(int numCols) { 348 mNumCols = numCols; 349 } 350 351 @Override 352 public int compare(ShortcutInfo lhs, ShortcutInfo rhs) { 353 int lhIndex = lhs.cellY * mNumCols + lhs.cellX; 354 int rhIndex = rhs.cellY * mNumCols + rhs.cellX; 355 return (lhIndex - rhIndex); 356 } 357 } 358 359 private void placeInReadingOrder(ArrayList<ShortcutInfo> items) { 360 int maxX = 0; 361 int count = items.size(); 362 for (int i = 0; i < count; i++) { 363 ShortcutInfo item = items.get(i); 364 if (item.cellX > maxX) { 365 maxX = item.cellX; 366 } 367 } 368 369 GridComparator gridComparator = new GridComparator(maxX + 1); 370 Collections.sort(items, gridComparator); 371 final int countX = mContent.getCountX(); 372 for (int i = 0; i < count; i++) { 373 int x = i % countX; 374 int y = i / countX; 375 ShortcutInfo item = items.get(i); 376 item.cellX = x; 377 item.cellY = y; 378 } 379 } 380 381 void bind(FolderInfo info) { 382 mInfo = info; 383 ArrayList<ShortcutInfo> children = info.contents; 384 ArrayList<ShortcutInfo> overflow = new ArrayList<ShortcutInfo>(); 385 setupContentForNumItems(children.size()); 386 placeInReadingOrder(children); 387 int count = 0; 388 for (int i = 0; i < children.size(); i++) { 389 ShortcutInfo child = (ShortcutInfo) children.get(i); 390 if (createAndAddShortcut(child) == null) { 391 overflow.add(child); 392 } else { 393 count++; 394 } 395 } 396 397 // We rearrange the items in case there are any empty gaps 398 setupContentForNumItems(count); 399 400 // If our folder has too many items we prune them from the list. This is an issue 401 // when upgrading from the old Folders implementation which could contain an unlimited 402 // number of items. 403 for (ShortcutInfo item: overflow) { 404 mInfo.remove(item); 405 LauncherModel.deleteItemFromDatabase(mLauncher, item); 406 } 407 408 mItemsInvalidated = true; 409 updateTextViewFocus(); 410 mInfo.addListener(this); 411 412 if (!sDefaultFolderName.contentEquals(mInfo.title)) { 413 mFolderName.setText(mInfo.title); 414 } else { 415 mFolderName.setText(""); 416 } 417 updateItemLocationsInDatabase(); 418 419 // In case any children didn't come across during loading, clean up the folder accordingly 420 mFolderIcon.post(new Runnable() { 421 public void run() { 422 if (getItemCount() <= 1) { 423 replaceFolderWithFinalItem(); 424 } 425 } 426 }); 427 } 428 429 /** 430 * Creates a new UserFolder, inflated from R.layout.user_folder. 431 * 432 * @param context The application's context. 433 * 434 * @return A new UserFolder. 435 */ 436 static Folder fromXml(Context context) { 437 return (Folder) LayoutInflater.from(context).inflate(R.layout.user_folder, null); 438 } 439 440 /** 441 * This method is intended to make the UserFolder to be visually identical in size and position 442 * to its associated FolderIcon. This allows for a seamless transition into the expanded state. 443 */ 444 private void positionAndSizeAsIcon() { 445 if (!(getParent() instanceof DragLayer)) return; 446 setScaleX(0.8f); 447 setScaleY(0.8f); 448 setAlpha(0f); 449 mState = STATE_SMALL; 450 } 451 452 private void prepareReveal() { 453 setScaleX(1f); 454 setScaleY(1f); 455 setAlpha(1f); 456 mState = STATE_SMALL; 457 } 458 459 public void animateOpen() { 460 if (!(getParent() instanceof DragLayer)) return; 461 462 Animator openFolderAnim = null; 463 final Runnable onCompleteRunnable; 464 if (!Utilities.isLmpOrAbove()) { 465 positionAndSizeAsIcon(); 466 centerAboutIcon(); 467 468 PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 1); 469 PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", 1.0f); 470 PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat("scaleY", 1.0f); 471 final ObjectAnimator oa = 472 LauncherAnimUtils.ofPropertyValuesHolder(this, alpha, scaleX, scaleY); 473 oa.setDuration(mExpandDuration); 474 openFolderAnim = oa; 475 476 setLayerType(LAYER_TYPE_HARDWARE, null); 477 onCompleteRunnable = new Runnable() { 478 @Override 479 public void run() { 480 setLayerType(LAYER_TYPE_NONE, null); 481 } 482 }; 483 } else { 484 prepareReveal(); 485 centerAboutIcon(); 486 487 int width = getPaddingLeft() + getPaddingRight() + mContent.getDesiredWidth(); 488 int height = getFolderHeight(); 489 490 float transX = - 0.075f * (width / 2 - getPivotX()); 491 float transY = - 0.075f * (height / 2 - getPivotY()); 492 setTranslationX(transX); 493 setTranslationY(transY); 494 PropertyValuesHolder tx = PropertyValuesHolder.ofFloat("translationX", transX, 0); 495 PropertyValuesHolder ty = PropertyValuesHolder.ofFloat("translationY", transY, 0); 496 497 int rx = (int) Math.max(Math.max(width - getPivotX(), 0), getPivotX()); 498 int ry = (int) Math.max(Math.max(height - getPivotY(), 0), getPivotY()); 499 float radius = (float) Math.sqrt(rx * rx + ry * ry); 500 AnimatorSet anim = LauncherAnimUtils.createAnimatorSet(); 501 Animator reveal = LauncherAnimUtils.createCircularReveal(this, (int) getPivotX(), 502 (int) getPivotY(), 0, radius); 503 reveal.setDuration(mMaterialExpandDuration); 504 reveal.setInterpolator(new LogDecelerateInterpolator(100, 0)); 505 506 mContent.setAlpha(0f); 507 Animator iconsAlpha = LauncherAnimUtils.ofFloat(mContent, "alpha", 0f, 1f); 508 iconsAlpha.setDuration(mMaterialExpandDuration); 509 iconsAlpha.setStartDelay(mMaterialExpandStagger); 510 iconsAlpha.setInterpolator(new AccelerateInterpolator(1.5f)); 511 512 mFolderName.setAlpha(0f); 513 Animator textAlpha = LauncherAnimUtils.ofFloat(mFolderName, "alpha", 0f, 1f); 514 textAlpha.setDuration(mMaterialExpandDuration); 515 textAlpha.setStartDelay(mMaterialExpandStagger); 516 textAlpha.setInterpolator(new AccelerateInterpolator(1.5f)); 517 518 Animator drift = LauncherAnimUtils.ofPropertyValuesHolder(this, tx, ty); 519 drift.setDuration(mMaterialExpandDuration); 520 drift.setStartDelay(mMaterialExpandStagger); 521 drift.setInterpolator(new LogDecelerateInterpolator(60, 0)); 522 523 anim.play(drift); 524 anim.play(iconsAlpha); 525 anim.play(textAlpha); 526 anim.play(reveal); 527 528 openFolderAnim = anim; 529 530 mContent.setLayerType(LAYER_TYPE_HARDWARE, null); 531 onCompleteRunnable = new Runnable() { 532 @Override 533 public void run() { 534 mContent.setLayerType(LAYER_TYPE_NONE, null); 535 } 536 }; 537 } 538 openFolderAnim.addListener(new AnimatorListenerAdapter() { 539 @Override 540 public void onAnimationStart(Animator animation) { 541 sendCustomAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED, 542 String.format(getContext().getString(R.string.folder_opened), 543 mContent.getCountX(), mContent.getCountY())); 544 mState = STATE_ANIMATING; 545 } 546 @Override 547 public void onAnimationEnd(Animator animation) { 548 mState = STATE_OPEN; 549 550 if (onCompleteRunnable != null) { 551 onCompleteRunnable.run(); 552 } 553 554 setFocusOnFirstChild(); 555 } 556 }); 557 openFolderAnim.start(); 558 559 // Make sure the folder picks up the last drag move even if the finger doesn't move. 560 if (mDragController.isDragging()) { 561 mDragController.forceTouchMove(); 562 } 563 } 564 565 public void beginExternalDrag(ShortcutInfo item) { 566 setupContentForNumItems(getItemCount() + 1); 567 findAndSetEmptyCells(item); 568 569 mCurrentDragInfo = item; 570 mEmptyCell[0] = item.cellX; 571 mEmptyCell[1] = item.cellY; 572 mIsExternalDrag = true; 573 574 mDragInProgress = true; 575 } 576 577 private void sendCustomAccessibilityEvent(int type, String text) { 578 AccessibilityManager accessibilityManager = (AccessibilityManager) 579 getContext().getSystemService(Context.ACCESSIBILITY_SERVICE); 580 if (accessibilityManager.isEnabled()) { 581 AccessibilityEvent event = AccessibilityEvent.obtain(type); 582 onInitializeAccessibilityEvent(event); 583 event.getText().add(text); 584 accessibilityManager.sendAccessibilityEvent(event); 585 } 586 } 587 588 private void setFocusOnFirstChild() { 589 View firstChild = mContent.getChildAt(0, 0); 590 if (firstChild != null) { 591 firstChild.requestFocus(); 592 } 593 } 594 595 public void animateClosed() { 596 if (!(getParent() instanceof DragLayer)) return; 597 PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 0); 598 PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", 0.9f); 599 PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat("scaleY", 0.9f); 600 final ObjectAnimator oa = 601 LauncherAnimUtils.ofPropertyValuesHolder(this, alpha, scaleX, scaleY); 602 603 oa.addListener(new AnimatorListenerAdapter() { 604 @Override 605 public void onAnimationEnd(Animator animation) { 606 onCloseComplete(); 607 setLayerType(LAYER_TYPE_NONE, null); 608 mState = STATE_SMALL; 609 } 610 @Override 611 public void onAnimationStart(Animator animation) { 612 sendCustomAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED, 613 getContext().getString(R.string.folder_closed)); 614 mState = STATE_ANIMATING; 615 } 616 }); 617 oa.setDuration(mExpandDuration); 618 setLayerType(LAYER_TYPE_HARDWARE, null); 619 oa.start(); 620 } 621 622 public boolean acceptDrop(DragObject d) { 623 final ItemInfo item = (ItemInfo) d.dragInfo; 624 final int itemType = item.itemType; 625 return ((itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION || 626 itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) && 627 !isFull()); 628 } 629 630 protected boolean findAndSetEmptyCells(ShortcutInfo item) { 631 int[] emptyCell = new int[2]; 632 if (mContent.findCellForSpan(emptyCell, item.spanX, item.spanY)) { 633 item.cellX = emptyCell[0]; 634 item.cellY = emptyCell[1]; 635 return true; 636 } else { 637 return false; 638 } 639 } 640 641 protected View createAndAddShortcut(ShortcutInfo item) { 642 final BubbleTextView textView = 643 (BubbleTextView) mInflater.inflate(R.layout.folder_application, this, false); 644 textView.applyFromShortcutInfo(item, mIconCache, false); 645 646 textView.setOnClickListener(this); 647 textView.setOnLongClickListener(this); 648 textView.setOnFocusChangeListener(mFocusIndicatorHandler); 649 650 // We need to check here to verify that the given item's location isn't already occupied 651 // by another item. 652 if (mContent.getChildAt(item.cellX, item.cellY) != null || item.cellX < 0 || item.cellY < 0 653 || item.cellX >= mContent.getCountX() || item.cellY >= mContent.getCountY()) { 654 // This shouldn't happen, log it. 655 Log.e(TAG, "Folder order not properly persisted during bind"); 656 if (!findAndSetEmptyCells(item)) { 657 return null; 658 } 659 } 660 661 CellLayout.LayoutParams lp = 662 new CellLayout.LayoutParams(item.cellX, item.cellY, item.spanX, item.spanY); 663 boolean insert = false; 664 textView.setOnKeyListener(new FolderKeyEventListener()); 665 mContent.addViewToCellLayout(textView, insert ? 0 : -1, (int)item.id, lp, true); 666 return textView; 667 } 668 669 public void onDragEnter(DragObject d) { 670 mPreviousTargetCell[0] = -1; 671 mPreviousTargetCell[1] = -1; 672 mOnExitAlarm.cancelAlarm(); 673 } 674 675 OnAlarmListener mReorderAlarmListener = new OnAlarmListener() { 676 public void onAlarm(Alarm alarm) { 677 realTimeReorder(mEmptyCell, mTargetCell); 678 } 679 }; 680 681 boolean readingOrderGreaterThan(int[] v1, int[] v2) { 682 if (v1[1] > v2[1] || (v1[1] == v2[1] && v1[0] > v2[0])) { 683 return true; 684 } else { 685 return false; 686 } 687 } 688 689 private void realTimeReorder(int[] empty, int[] target) { 690 boolean wrap; 691 int startX; 692 int endX; 693 int startY; 694 int delay = 0; 695 float delayAmount = 30; 696 if (readingOrderGreaterThan(target, empty)) { 697 wrap = empty[0] >= mContent.getCountX() - 1; 698 startY = wrap ? empty[1] + 1 : empty[1]; 699 for (int y = startY; y <= target[1]; y++) { 700 startX = y == empty[1] ? empty[0] + 1 : 0; 701 endX = y < target[1] ? mContent.getCountX() - 1 : target[0]; 702 for (int x = startX; x <= endX; x++) { 703 View v = mContent.getChildAt(x,y); 704 if (mContent.animateChildToPosition(v, empty[0], empty[1], 705 REORDER_ANIMATION_DURATION, delay, true, true)) { 706 empty[0] = x; 707 empty[1] = y; 708 delay += delayAmount; 709 delayAmount *= 0.9; 710 } 711 } 712 } 713 } else { 714 wrap = empty[0] == 0; 715 startY = wrap ? empty[1] - 1 : empty[1]; 716 for (int y = startY; y >= target[1]; y--) { 717 startX = y == empty[1] ? empty[0] - 1 : mContent.getCountX() - 1; 718 endX = y > target[1] ? 0 : target[0]; 719 for (int x = startX; x >= endX; x--) { 720 View v = mContent.getChildAt(x,y); 721 if (mContent.animateChildToPosition(v, empty[0], empty[1], 722 REORDER_ANIMATION_DURATION, delay, true, true)) { 723 empty[0] = x; 724 empty[1] = y; 725 delay += delayAmount; 726 delayAmount *= 0.9; 727 } 728 } 729 } 730 } 731 } 732 733 public boolean isLayoutRtl() { 734 return (getLayoutDirection() == LAYOUT_DIRECTION_RTL); 735 } 736 737 public void onDragOver(DragObject d) { 738 final DragView dragView = d.dragView; 739 final int scrollOffset = mScrollView.getScrollY(); 740 final float[] r = getDragViewVisualCenter(d.x, d.y, d.xOffset, d.yOffset, dragView, null); 741 r[0] -= getPaddingLeft(); 742 r[1] -= getPaddingTop(); 743 744 final long downTime = SystemClock.uptimeMillis(); 745 final MotionEvent translatedEv = MotionEvent.obtain( 746 downTime, downTime, MotionEvent.ACTION_MOVE, d.x, d.y, 0); 747 748 if (!mAutoScrollHelper.isEnabled()) { 749 mAutoScrollHelper.setEnabled(true); 750 } 751 752 final boolean handled = mAutoScrollHelper.onTouch(this, translatedEv); 753 translatedEv.recycle(); 754 755 if (handled) { 756 mReorderAlarm.cancelAlarm(); 757 } else { 758 mTargetCell = mContent.findNearestArea( 759 (int) r[0], (int) r[1] + scrollOffset, 1, 1, mTargetCell); 760 if (isLayoutRtl()) { 761 mTargetCell[0] = mContent.getCountX() - mTargetCell[0] - 1; 762 } 763 if (mTargetCell[0] != mPreviousTargetCell[0] 764 || mTargetCell[1] != mPreviousTargetCell[1]) { 765 mReorderAlarm.cancelAlarm(); 766 mReorderAlarm.setOnAlarmListener(mReorderAlarmListener); 767 mReorderAlarm.setAlarm(REORDER_DELAY); 768 mPreviousTargetCell[0] = mTargetCell[0]; 769 mPreviousTargetCell[1] = mTargetCell[1]; 770 } 771 } 772 } 773 774 // This is used to compute the visual center of the dragView. The idea is that 775 // the visual center represents the user's interpretation of where the item is, and hence 776 // is the appropriate point to use when determining drop location. 777 private float[] getDragViewVisualCenter(int x, int y, int xOffset, int yOffset, 778 DragView dragView, float[] recycle) { 779 float res[]; 780 if (recycle == null) { 781 res = new float[2]; 782 } else { 783 res = recycle; 784 } 785 786 // These represent the visual top and left of drag view if a dragRect was provided. 787 // If a dragRect was not provided, then they correspond to the actual view left and 788 // top, as the dragRect is in that case taken to be the entire dragView. 789 // R.dimen.dragViewOffsetY. 790 int left = x - xOffset; 791 int top = y - yOffset; 792 793 // In order to find the visual center, we shift by half the dragRect 794 res[0] = left + dragView.getDragRegion().width() / 2; 795 res[1] = top + dragView.getDragRegion().height() / 2; 796 797 return res; 798 } 799 800 OnAlarmListener mOnExitAlarmListener = new OnAlarmListener() { 801 public void onAlarm(Alarm alarm) { 802 completeDragExit(); 803 } 804 }; 805 806 public void completeDragExit() { 807 mLauncher.closeFolder(); 808 mCurrentDragInfo = null; 809 mCurrentDragView = null; 810 mSuppressOnAdd = false; 811 mRearrangeOnClose = true; 812 mIsExternalDrag = false; 813 } 814 815 public void onDragExit(DragObject d) { 816 // Exiting folder; stop the auto scroller. 817 mAutoScrollHelper.setEnabled(false); 818 // We only close the folder if this is a true drag exit, ie. not because 819 // a drop has occurred above the folder. 820 if (!d.dragComplete) { 821 mOnExitAlarm.setOnAlarmListener(mOnExitAlarmListener); 822 mOnExitAlarm.setAlarm(ON_EXIT_CLOSE_DELAY); 823 } 824 mReorderAlarm.cancelAlarm(); 825 } 826 827 public void onDropCompleted(final View target, final DragObject d, 828 final boolean isFlingToDelete, final boolean success) { 829 if (mDeferDropAfterUninstall) { 830 Log.d(TAG, "Deferred handling drop because waiting for uninstall."); 831 mDeferredAction = new Runnable() { 832 public void run() { 833 onDropCompleted(target, d, isFlingToDelete, success); 834 mDeferredAction = null; 835 } 836 }; 837 return; 838 } 839 840 boolean beingCalledAfterUninstall = mDeferredAction != null; 841 boolean successfulDrop = 842 success && (!beingCalledAfterUninstall || mUninstallSuccessful); 843 844 if (successfulDrop) { 845 if (mDeleteFolderOnDropCompleted && !mItemAddedBackToSelfViaIcon && target != this) { 846 replaceFolderWithFinalItem(); 847 } 848 } else { 849 setupContentForNumItems(getItemCount()); 850 // The drag failed, we need to return the item to the folder 851 mFolderIcon.onDrop(d); 852 } 853 854 if (target != this) { 855 if (mOnExitAlarm.alarmPending()) { 856 mOnExitAlarm.cancelAlarm(); 857 if (!successfulDrop) { 858 mSuppressFolderDeletion = true; 859 } 860 completeDragExit(); 861 } 862 } 863 864 mDeleteFolderOnDropCompleted = false; 865 mDragInProgress = false; 866 mItemAddedBackToSelfViaIcon = false; 867 mCurrentDragInfo = null; 868 mCurrentDragView = null; 869 mSuppressOnAdd = false; 870 871 // Reordering may have occured, and we need to save the new item locations. We do this once 872 // at the end to prevent unnecessary database operations. 873 updateItemLocationsInDatabaseBatch(); 874 } 875 876 public void deferCompleteDropAfterUninstallActivity() { 877 mDeferDropAfterUninstall = true; 878 } 879 880 public void onUninstallActivityReturned(boolean success) { 881 mDeferDropAfterUninstall = false; 882 mUninstallSuccessful = success; 883 if (mDeferredAction != null) { 884 mDeferredAction.run(); 885 } 886 } 887 888 @Override 889 public float getIntrinsicIconScaleFactor() { 890 return 1f; 891 } 892 893 @Override 894 public boolean supportsFlingToDelete() { 895 return true; 896 } 897 898 @Override 899 public boolean supportsAppInfoDropTarget() { 900 return false; 901 } 902 903 @Override 904 public boolean supportsDeleteDropTarget() { 905 return true; 906 } 907 908 public void onFlingToDelete(DragObject d, int x, int y, PointF vec) { 909 // Do nothing 910 } 911 912 @Override 913 public void onFlingToDeleteCompleted() { 914 // Do nothing 915 } 916 917 private void updateItemLocationsInDatabase() { 918 ArrayList<View> list = getItemsInReadingOrder(); 919 for (int i = 0; i < list.size(); i++) { 920 View v = list.get(i); 921 ItemInfo info = (ItemInfo) v.getTag(); 922 LauncherModel.moveItemInDatabase(mLauncher, info, mInfo.id, 0, 923 info.cellX, info.cellY); 924 } 925 } 926 927 private void updateItemLocationsInDatabaseBatch() { 928 ArrayList<View> list = getItemsInReadingOrder(); 929 ArrayList<ItemInfo> items = new ArrayList<ItemInfo>(); 930 for (int i = 0; i < list.size(); i++) { 931 View v = list.get(i); 932 ItemInfo info = (ItemInfo) v.getTag(); 933 items.add(info); 934 } 935 936 LauncherModel.moveItemsInDatabase(mLauncher, items, mInfo.id, 0); 937 } 938 939 public void addItemLocationsInDatabase() { 940 ArrayList<View> list = getItemsInReadingOrder(); 941 for (int i = 0; i < list.size(); i++) { 942 View v = list.get(i); 943 ItemInfo info = (ItemInfo) v.getTag(); 944 LauncherModel.addItemToDatabase(mLauncher, info, mInfo.id, 0, 945 info.cellX, info.cellY, false); 946 } 947 } 948 949 public void notifyDrop() { 950 if (mDragInProgress) { 951 mItemAddedBackToSelfViaIcon = true; 952 } 953 } 954 955 public boolean isDropEnabled() { 956 return true; 957 } 958 959 private void setupContentDimensions(int count) { 960 ArrayList<View> list = getItemsInReadingOrder(); 961 962 int countX = mContent.getCountX(); 963 int countY = mContent.getCountY(); 964 boolean done = false; 965 966 while (!done) { 967 int oldCountX = countX; 968 int oldCountY = countY; 969 if (countX * countY < count) { 970 // Current grid is too small, expand it 971 if ((countX <= countY || countY == mMaxCountY) && countX < mMaxCountX) { 972 countX++; 973 } else if (countY < mMaxCountY) { 974 countY++; 975 } 976 if (countY == 0) countY++; 977 } else if ((countY - 1) * countX >= count && countY >= countX) { 978 countY = Math.max(0, countY - 1); 979 } else if ((countX - 1) * countY >= count) { 980 countX = Math.max(0, countX - 1); 981 } 982 done = countX == oldCountX && countY == oldCountY; 983 } 984 mContent.setGridSize(countX, countY); 985 arrangeChildren(list); 986 } 987 988 public boolean isFull() { 989 return getItemCount() >= mMaxNumItems; 990 } 991 992 private void centerAboutIcon() { 993 DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams(); 994 995 DragLayer parent = (DragLayer) mLauncher.findViewById(R.id.drag_layer); 996 int width = getPaddingLeft() + getPaddingRight() + mContent.getDesiredWidth(); 997 int height = getFolderHeight(); 998 999 float scale = parent.getDescendantRectRelativeToSelf(mFolderIcon, mTempRect); 1000 1001 LauncherAppState app = LauncherAppState.getInstance(); 1002 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile(); 1003 1004 int centerX = (int) (mTempRect.left + mTempRect.width() * scale / 2); 1005 int centerY = (int) (mTempRect.top + mTempRect.height() * scale / 2); 1006 int centeredLeft = centerX - width / 2; 1007 int centeredTop = centerY - height / 2; 1008 int currentPage = mLauncher.getWorkspace().getNextPage(); 1009 // In case the workspace is scrolling, we need to use the final scroll to compute 1010 // the folders bounds. 1011 mLauncher.getWorkspace().setFinalScrollForPageChange(currentPage); 1012 // We first fetch the currently visible CellLayoutChildren 1013 CellLayout currentLayout = (CellLayout) mLauncher.getWorkspace().getChildAt(currentPage); 1014 ShortcutAndWidgetContainer boundingLayout = currentLayout.getShortcutsAndWidgets(); 1015 Rect bounds = new Rect(); 1016 parent.getDescendantRectRelativeToSelf(boundingLayout, bounds); 1017 // We reset the workspaces scroll 1018 mLauncher.getWorkspace().resetFinalScrollForPageChange(currentPage); 1019 1020 // We need to bound the folder to the currently visible CellLayoutChildren 1021 int left = Math.min(Math.max(bounds.left, centeredLeft), 1022 bounds.left + bounds.width() - width); 1023 int top = Math.min(Math.max(bounds.top, centeredTop), 1024 bounds.top + bounds.height() - height); 1025 if (grid.isPhone() && (grid.availableWidthPx - width) < grid.iconSizePx) { 1026 // Center the folder if it is full (on phones only) 1027 left = (grid.availableWidthPx - width) / 2; 1028 } else if (width >= bounds.width()) { 1029 // If the folder doesn't fit within the bounds, center it about the desired bounds 1030 left = bounds.left + (bounds.width() - width) / 2; 1031 } 1032 if (height >= bounds.height()) { 1033 top = bounds.top + (bounds.height() - height) / 2; 1034 } 1035 1036 int folderPivotX = width / 2 + (centeredLeft - left); 1037 int folderPivotY = height / 2 + (centeredTop - top); 1038 setPivotX(folderPivotX); 1039 setPivotY(folderPivotY); 1040 mFolderIconPivotX = (int) (mFolderIcon.getMeasuredWidth() * 1041 (1.0f * folderPivotX / width)); 1042 mFolderIconPivotY = (int) (mFolderIcon.getMeasuredHeight() * 1043 (1.0f * folderPivotY / height)); 1044 1045 lp.width = width; 1046 lp.height = height; 1047 lp.x = left; 1048 lp.y = top; 1049 } 1050 1051 float getPivotXForIconAnimation() { 1052 return mFolderIconPivotX; 1053 } 1054 float getPivotYForIconAnimation() { 1055 return mFolderIconPivotY; 1056 } 1057 1058 private void setupContentForNumItems(int count) { 1059 setupContentDimensions(count); 1060 1061 DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams(); 1062 if (lp == null) { 1063 lp = new DragLayer.LayoutParams(0, 0); 1064 lp.customPosition = true; 1065 setLayoutParams(lp); 1066 } 1067 centerAboutIcon(); 1068 } 1069 1070 private int getContentAreaHeight() { 1071 LauncherAppState app = LauncherAppState.getInstance(); 1072 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile(); 1073 Rect workspacePadding = grid.getWorkspacePadding(grid.isLandscape ? 1074 CellLayout.LANDSCAPE : CellLayout.PORTRAIT); 1075 int maxContentAreaHeight = grid.availableHeightPx - 1076 workspacePadding.top - workspacePadding.bottom - 1077 mFolderNameHeight; 1078 int height = Math.min(maxContentAreaHeight, 1079 mContent.getDesiredHeight()); 1080 return Math.max(height, MIN_CONTENT_DIMEN); 1081 } 1082 1083 private int getContentAreaWidth() { 1084 return Math.max(mContent.getDesiredWidth(), MIN_CONTENT_DIMEN); 1085 } 1086 1087 private int getFolderHeight() { 1088 int height = getPaddingTop() + getPaddingBottom() 1089 + getContentAreaHeight() + mFolderNameHeight; 1090 return height; 1091 } 1092 1093 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 1094 int width = getPaddingLeft() + getPaddingRight() + mContent.getDesiredWidth(); 1095 int height = getFolderHeight(); 1096 int contentAreaWidthSpec = MeasureSpec.makeMeasureSpec(getContentAreaWidth(), 1097 MeasureSpec.EXACTLY); 1098 int contentAreaHeightSpec = MeasureSpec.makeMeasureSpec(getContentAreaHeight(), 1099 MeasureSpec.EXACTLY); 1100 1101 if (LauncherAppState.isDisableAllApps()) { 1102 // Don't cap the height of the content to allow scrolling. 1103 mContent.setFixedSize(getContentAreaWidth(), mContent.getDesiredHeight()); 1104 } else { 1105 mContent.setFixedSize(getContentAreaWidth(), getContentAreaHeight()); 1106 } 1107 1108 mScrollView.measure(contentAreaWidthSpec, contentAreaHeightSpec); 1109 mFolderName.measure(contentAreaWidthSpec, 1110 MeasureSpec.makeMeasureSpec(mFolderNameHeight, MeasureSpec.EXACTLY)); 1111 setMeasuredDimension(width, height); 1112 } 1113 1114 private void arrangeChildren(ArrayList<View> list) { 1115 int[] vacant = new int[2]; 1116 if (list == null) { 1117 list = getItemsInReadingOrder(); 1118 } 1119 mContent.removeAllViews(); 1120 1121 for (int i = 0; i < list.size(); i++) { 1122 View v = list.get(i); 1123 mContent.getVacantCell(vacant, 1, 1); 1124 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) v.getLayoutParams(); 1125 lp.cellX = vacant[0]; 1126 lp.cellY = vacant[1]; 1127 ItemInfo info = (ItemInfo) v.getTag(); 1128 if (info.cellX != vacant[0] || info.cellY != vacant[1]) { 1129 info.cellX = vacant[0]; 1130 info.cellY = vacant[1]; 1131 LauncherModel.addOrMoveItemInDatabase(mLauncher, info, mInfo.id, 0, 1132 info.cellX, info.cellY); 1133 } 1134 boolean insert = false; 1135 mContent.addViewToCellLayout(v, insert ? 0 : -1, (int)info.id, lp, true); 1136 } 1137 mItemsInvalidated = true; 1138 } 1139 1140 public int getItemCount() { 1141 return mContent.getShortcutsAndWidgets().getChildCount(); 1142 } 1143 1144 public View getItemAt(int index) { 1145 return mContent.getShortcutsAndWidgets().getChildAt(index); 1146 } 1147 1148 private void onCloseComplete() { 1149 DragLayer parent = (DragLayer) getParent(); 1150 if (parent != null) { 1151 parent.removeView(this); 1152 } 1153 mDragController.removeDropTarget((DropTarget) this); 1154 clearFocus(); 1155 mFolderIcon.requestFocus(); 1156 1157 if (mRearrangeOnClose) { 1158 setupContentForNumItems(getItemCount()); 1159 mRearrangeOnClose = false; 1160 } 1161 if (getItemCount() <= 1) { 1162 if (!mDragInProgress && !mSuppressFolderDeletion) { 1163 replaceFolderWithFinalItem(); 1164 } else if (mDragInProgress) { 1165 mDeleteFolderOnDropCompleted = true; 1166 } 1167 } 1168 mSuppressFolderDeletion = false; 1169 } 1170 1171 private void replaceFolderWithFinalItem() { 1172 // Add the last remaining child to the workspace in place of the folder 1173 Runnable onCompleteRunnable = new Runnable() { 1174 @Override 1175 public void run() { 1176 CellLayout cellLayout = mLauncher.getCellLayout(mInfo.container, mInfo.screenId); 1177 1178 View child = null; 1179 // Move the item from the folder to the workspace, in the position of the folder 1180 if (getItemCount() == 1) { 1181 ShortcutInfo finalItem = mInfo.contents.get(0); 1182 child = mLauncher.createShortcut(R.layout.application, cellLayout, 1183 finalItem); 1184 LauncherModel.addOrMoveItemInDatabase(mLauncher, finalItem, mInfo.container, 1185 mInfo.screenId, mInfo.cellX, mInfo.cellY); 1186 } 1187 if (getItemCount() <= 1) { 1188 // Remove the folder 1189 LauncherModel.deleteItemFromDatabase(mLauncher, mInfo); 1190 if (cellLayout != null) { 1191 // b/12446428 -- sometimes the cell layout has already gone away? 1192 cellLayout.removeView(mFolderIcon); 1193 } 1194 if (mFolderIcon instanceof DropTarget) { 1195 mDragController.removeDropTarget((DropTarget) mFolderIcon); 1196 } 1197 mLauncher.removeFolder(mInfo); 1198 } 1199 // We add the child after removing the folder to prevent both from existing at 1200 // the same time in the CellLayout. We need to add the new item with addInScreenFromBind() 1201 // to ensure that hotseat items are placed correctly. 1202 if (child != null) { 1203 mLauncher.getWorkspace().addInScreenFromBind(child, mInfo.container, mInfo.screenId, 1204 mInfo.cellX, mInfo.cellY, mInfo.spanX, mInfo.spanY); 1205 } 1206 } 1207 }; 1208 View finalChild = getItemAt(0); 1209 if (finalChild != null) { 1210 mFolderIcon.performDestroyAnimation(finalChild, onCompleteRunnable); 1211 } else { 1212 onCompleteRunnable.run(); 1213 } 1214 mDestroyed = true; 1215 } 1216 1217 boolean isDestroyed() { 1218 return mDestroyed; 1219 } 1220 1221 // This method keeps track of the last item in the folder for the purposes 1222 // of keyboard focus 1223 private void updateTextViewFocus() { 1224 View lastChild = getItemAt(getItemCount() - 1); 1225 getItemAt(getItemCount() - 1); 1226 if (lastChild != null) { 1227 mFolderName.setNextFocusDownId(lastChild.getId()); 1228 mFolderName.setNextFocusRightId(lastChild.getId()); 1229 mFolderName.setNextFocusLeftId(lastChild.getId()); 1230 mFolderName.setNextFocusUpId(lastChild.getId()); 1231 } 1232 } 1233 1234 public void onDrop(DragObject d) { 1235 Runnable cleanUpRunnable = null; 1236 1237 // If we are coming from All Apps space, we defer removing the extra empty screen 1238 // until the folder closes 1239 if (d.dragSource != mLauncher.getWorkspace() && !(d.dragSource instanceof Folder)) { 1240 cleanUpRunnable = new Runnable() { 1241 @Override 1242 public void run() { 1243 mLauncher.exitSpringLoadedDragModeDelayed(true, 1244 Launcher.EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, 1245 null); 1246 } 1247 }; 1248 } 1249 1250 View currentDragView; 1251 ShortcutInfo si = mCurrentDragInfo; 1252 if (mIsExternalDrag) { 1253 si.cellX = mEmptyCell[0]; 1254 si.cellY = mEmptyCell[1]; 1255 1256 // Actually move the item in the database if it was an external drag. Call this 1257 // before creating the view, so that ShortcutInfo is updated appropriately. 1258 LauncherModel.addOrMoveItemInDatabase( 1259 mLauncher, si, mInfo.id, 0, si.cellX, si.cellY); 1260 1261 // We only need to update the locations if it doesn't get handled in #onDropCompleted. 1262 if (d.dragSource != this) { 1263 updateItemLocationsInDatabaseBatch(); 1264 } 1265 mIsExternalDrag = false; 1266 1267 currentDragView = createAndAddShortcut(si); 1268 } else { 1269 currentDragView = mCurrentDragView; 1270 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) currentDragView.getLayoutParams(); 1271 si.cellX = lp.cellX = mEmptyCell[0]; 1272 si.cellX = lp.cellY = mEmptyCell[1]; 1273 mContent.addViewToCellLayout(currentDragView, -1, (int) si.id, lp, true); 1274 } 1275 1276 if (d.dragView.hasDrawn()) { 1277 1278 // Temporarily reset the scale such that the animation target gets calculated correctly. 1279 float scaleX = getScaleX(); 1280 float scaleY = getScaleY(); 1281 setScaleX(1.0f); 1282 setScaleY(1.0f); 1283 mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, currentDragView, 1284 cleanUpRunnable, null); 1285 setScaleX(scaleX); 1286 setScaleY(scaleY); 1287 } else { 1288 d.deferDragViewCleanupPostAnimation = false; 1289 currentDragView.setVisibility(VISIBLE); 1290 } 1291 mItemsInvalidated = true; 1292 setupContentDimensions(getItemCount()); 1293 1294 // Temporarily suppress the listener, as we did all the work already here. 1295 mSuppressOnAdd = true; 1296 mInfo.add(si); 1297 mSuppressOnAdd = false; 1298 // Clear the drag info, as it is no longer being dragged. 1299 mCurrentDragInfo = null; 1300 } 1301 1302 // This is used so the item doesn't immediately appear in the folder when added. In one case 1303 // we need to create the illusion that the item isn't added back to the folder yet, to 1304 // to correspond to the animation of the icon back into the folder. This is 1305 public void hideItem(ShortcutInfo info) { 1306 View v = getViewForInfo(info); 1307 v.setVisibility(INVISIBLE); 1308 } 1309 public void showItem(ShortcutInfo info) { 1310 View v = getViewForInfo(info); 1311 v.setVisibility(VISIBLE); 1312 } 1313 1314 public void onAdd(ShortcutInfo item) { 1315 mItemsInvalidated = true; 1316 // If the item was dropped onto this open folder, we have done the work associated 1317 // with adding the item to the folder, as indicated by mSuppressOnAdd being set 1318 if (mSuppressOnAdd) return; 1319 if (!findAndSetEmptyCells(item)) { 1320 // The current layout is full, can we expand it? 1321 setupContentForNumItems(getItemCount() + 1); 1322 findAndSetEmptyCells(item); 1323 } 1324 createAndAddShortcut(item); 1325 LauncherModel.addOrMoveItemInDatabase( 1326 mLauncher, item, mInfo.id, 0, item.cellX, item.cellY); 1327 } 1328 1329 public void onRemove(ShortcutInfo item) { 1330 mItemsInvalidated = true; 1331 // If this item is being dragged from this open folder, we have already handled 1332 // the work associated with removing the item, so we don't have to do anything here. 1333 if (item == mCurrentDragInfo) return; 1334 View v = getViewForInfo(item); 1335 mContent.removeView(v); 1336 if (mState == STATE_ANIMATING) { 1337 mRearrangeOnClose = true; 1338 } else { 1339 setupContentForNumItems(getItemCount()); 1340 } 1341 if (getItemCount() <= 1) { 1342 replaceFolderWithFinalItem(); 1343 } 1344 } 1345 1346 private View getViewForInfo(ShortcutInfo item) { 1347 for (int j = 0; j < mContent.getCountY(); j++) { 1348 for (int i = 0; i < mContent.getCountX(); i++) { 1349 View v = mContent.getChildAt(i, j); 1350 if (v.getTag() == item) { 1351 return v; 1352 } 1353 } 1354 } 1355 return null; 1356 } 1357 1358 public void onItemsChanged() { 1359 updateTextViewFocus(); 1360 } 1361 1362 public void onTitleChanged(CharSequence title) { 1363 } 1364 1365 public ArrayList<View> getItemsInReadingOrder() { 1366 if (mItemsInvalidated) { 1367 mItemsInReadingOrder.clear(); 1368 for (int j = 0; j < mContent.getCountY(); j++) { 1369 for (int i = 0; i < mContent.getCountX(); i++) { 1370 View v = mContent.getChildAt(i, j); 1371 if (v != null) { 1372 mItemsInReadingOrder.add(v); 1373 } 1374 } 1375 } 1376 mItemsInvalidated = false; 1377 } 1378 return mItemsInReadingOrder; 1379 } 1380 1381 public void getLocationInDragLayer(int[] loc) { 1382 mLauncher.getDragLayer().getLocationInDragLayer(this, loc); 1383 } 1384 1385 public void onFocusChange(View v, boolean hasFocus) { 1386 if (v == mFolderName && hasFocus) { 1387 startEditingFolderName(); 1388 } 1389 } 1390 1391 @Override 1392 public void getHitRectRelativeToDragLayer(Rect outRect) { 1393 getHitRect(outRect); 1394 } 1395 } 1396