Folder.java revision 1df26a368a135395b7bd61e9b109044b33b1b0be
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 17package com.android.launcher2; 18 19import android.animation.Animator; 20import android.animation.AnimatorListenerAdapter; 21import android.animation.ObjectAnimator; 22import android.animation.PropertyValuesHolder; 23import android.animation.ValueAnimator; 24import android.animation.ValueAnimator.AnimatorUpdateListener; 25import android.content.Context; 26import android.content.res.Resources; 27import android.graphics.Rect; 28import android.graphics.drawable.Drawable; 29import android.text.InputType; 30import android.text.Selection; 31import android.text.Spannable; 32import android.util.AttributeSet; 33import android.view.ActionMode; 34import android.view.KeyEvent; 35import android.view.LayoutInflater; 36import android.view.Menu; 37import android.view.MenuItem; 38import android.view.MotionEvent; 39import android.view.View; 40import android.view.animation.AccelerateInterpolator; 41import android.view.animation.DecelerateInterpolator; 42import android.view.inputmethod.EditorInfo; 43import android.view.inputmethod.InputMethodManager; 44import android.widget.LinearLayout; 45import android.widget.TextView; 46 47import com.android.launcher.R; 48import com.android.launcher2.FolderInfo.FolderListener; 49 50import java.util.ArrayList; 51 52/** 53 * Represents a set of icons chosen by the user or generated by the system. 54 */ 55public class Folder extends LinearLayout implements DragSource, View.OnClickListener, 56 View.OnLongClickListener, DropTarget, FolderListener, TextView.OnEditorActionListener { 57 58 private static final String TAG = "Launcher.Folder"; 59 60 protected DragController mDragController; 61 protected Launcher mLauncher; 62 protected FolderInfo mInfo; 63 64 static final int STATE_NONE = -1; 65 static final int STATE_SMALL = 0; 66 static final int STATE_ANIMATING = 1; 67 static final int STATE_OPEN = 2; 68 69 private int mExpandDuration; 70 protected CellLayout mContent; 71 private final LayoutInflater mInflater; 72 private final IconCache mIconCache; 73 private int mState = STATE_NONE; 74 private static final int FULL_GROW = 0; 75 private static final int PARTIAL_GROW = 1; 76 private static final int REORDER_ANIMATION_DURATION = 230; 77 private static final int ON_EXIT_CLOSE_DELAY = 800; 78 private int mMode = PARTIAL_GROW; 79 private boolean mRearrangeOnClose = false; 80 private FolderIcon mFolderIcon; 81 private int mMaxCountX; 82 private int mMaxCountY; 83 private Rect mNewSize = new Rect(); 84 private Rect mIconRect = new Rect(); 85 private ArrayList<View> mItemsInReadingOrder = new ArrayList<View>(); 86 private Drawable mIconDrawable; 87 boolean mItemsInvalidated = false; 88 private ShortcutInfo mCurrentDragInfo; 89 private View mCurrentDragView; 90 boolean mSuppressOnAdd = false; 91 private int[] mTargetCell = new int[2]; 92 private int[] mPreviousTargetCell = new int[2]; 93 private int[] mEmptyCell = new int[2]; 94 private Alarm mReorderAlarm = new Alarm(); 95 private Alarm mOnExitAlarm = new Alarm(); 96 private TextView mFolderName; 97 private int mFolderNameHeight; 98 private Rect mHitRect = new Rect(); 99 private Rect mTempRect = new Rect(); 100 private boolean mFirstOpen = true; 101 private boolean mDragInProgress = false; 102 private boolean mDeleteFolderOnDropCompleted = false; 103 private boolean mSuppressFolderDeletion = false; 104 private boolean mItemAddedBackToSelfViaIcon = false; 105 106 private boolean mIsEditingName = false; 107 private InputMethodManager mInputMethodManager; 108 109 private static String sDefaultFolderName; 110 private static String sHintText; 111 112 /** 113 * Used to inflate the Workspace from XML. 114 * 115 * @param context The application's context. 116 * @param attrs The attribtues set containing the Workspace's customization values. 117 */ 118 public Folder(Context context, AttributeSet attrs) { 119 super(context, attrs); 120 setAlwaysDrawnWithCacheEnabled(false); 121 mInflater = LayoutInflater.from(context); 122 mIconCache = ((LauncherApplication)context.getApplicationContext()).getIconCache(); 123 mMaxCountX = LauncherModel.getCellCountX(); 124 mMaxCountY = LauncherModel.getCellCountY(); 125 126 mInputMethodManager = (InputMethodManager) 127 mContext.getSystemService(Context.INPUT_METHOD_SERVICE); 128 129 Resources res = getResources(); 130 mExpandDuration = res.getInteger(R.integer.config_folderAnimDuration); 131 132 if (sDefaultFolderName == null) { 133 sDefaultFolderName = res.getString(R.string.folder_name); 134 } 135 if (sHintText == null) { 136 sHintText = res.getString(R.string.folder_hint_text); 137 } 138 139 mLauncher = (Launcher) context; 140 } 141 142 @Override 143 protected void onFinishInflate() { 144 super.onFinishInflate(); 145 mContent = (CellLayout) findViewById(R.id.folder_content); 146 mContent.setGridSize(0, 0); 147 mFolderName = (TextView) findViewById(R.id.folder_name); 148 149 // We find out how tall the text view wants to be (it is set to wrap_content), so that 150 // we can allocate the appropriate amount of space for it. 151 int measureSpec = MeasureSpec.UNSPECIFIED; 152 mFolderName.measure(measureSpec, measureSpec); 153 mFolderNameHeight = mFolderName.getMeasuredHeight(); 154 155 // We disable action mode for now since it messes up the view on phones 156 mFolderName.setCustomSelectionActionModeCallback(mActionModeCallback); 157 mFolderName.setCursorVisible(false); 158 mFolderName.setOnEditorActionListener(this); 159 mFolderName.setSelectAllOnFocus(true); 160 mFolderName.setInputType(mFolderName.getInputType() | 161 InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS | InputType.TYPE_TEXT_FLAG_CAP_WORDS); 162 } 163 164 private ActionMode.Callback mActionModeCallback = new ActionMode.Callback() { 165 public boolean onActionItemClicked(ActionMode mode, MenuItem item) { 166 return false; 167 } 168 169 public boolean onCreateActionMode(ActionMode mode, Menu menu) { 170 return false; 171 } 172 173 public void onDestroyActionMode(ActionMode mode) { 174 } 175 176 public boolean onPrepareActionMode(ActionMode mode, Menu menu) { 177 return false; 178 } 179 }; 180 181 public void onClick(View v) { 182 Object tag = v.getTag(); 183 if (tag instanceof ShortcutInfo) { 184 // refactor this code from Folder 185 ShortcutInfo item = (ShortcutInfo) tag; 186 int[] pos = new int[2]; 187 v.getLocationOnScreen(pos); 188 item.intent.setSourceBounds(new Rect(pos[0], pos[1], 189 pos[0] + v.getWidth(), pos[1] + v.getHeight())); 190 mLauncher.startActivitySafely(item.intent, item); 191 } 192 } 193 194 public boolean onInterceptTouchEvent(MotionEvent ev) { 195 if (ev.getAction() == MotionEvent.ACTION_DOWN) { 196 mFolderName.getHitRect(mHitRect); 197 if (mHitRect.contains((int) ev.getX(), (int) ev.getY()) && !mIsEditingName) { 198 startEditingFolderName(); 199 } 200 } 201 return false; 202 } 203 204 public boolean onLongClick(View v) { 205 Object tag = v.getTag(); 206 if (tag instanceof ShortcutInfo) { 207 ShortcutInfo item = (ShortcutInfo) tag; 208 if (!v.isInTouchMode()) { 209 return false; 210 } 211 212 mLauncher.getWorkspace().onDragStartedWithItem(v); 213 mLauncher.getWorkspace().beginDragShared(v, this); 214 mIconDrawable = ((TextView) v).getCompoundDrawables()[1]; 215 216 mCurrentDragInfo = item; 217 mEmptyCell[0] = item.cellX; 218 mEmptyCell[1] = item.cellY; 219 mCurrentDragView = v; 220 221 mContent.removeView(mCurrentDragView); 222 mInfo.remove(mCurrentDragInfo); 223 mDragInProgress = true; 224 mItemAddedBackToSelfViaIcon = false; 225 } 226 return true; 227 } 228 229 public boolean isEditingName() { 230 return mIsEditingName; 231 } 232 233 public void startEditingFolderName() { 234 mFolderName.setHint(""); 235 mFolderName.setCursorVisible(true); 236 mIsEditingName = true; 237 } 238 239 public void dismissEditingName() { 240 mInputMethodManager.hideSoftInputFromWindow(getWindowToken(), 0); 241 doneEditingFolderName(true); 242 } 243 244 public void doneEditingFolderName(boolean commit) { 245 mFolderName.setHint(sHintText); 246 // Convert to a string here to ensure that no other state associated with the text field 247 // gets saved. 248 mInfo.setTitle(mFolderName.getText().toString()); 249 LauncherModel.updateItemInDatabase(mLauncher, mInfo); 250 mFolderName.setCursorVisible(false); 251 mFolderName.clearFocus(); 252 Selection.setSelection((Spannable) mFolderName.getText(), 0, 0); 253 mIsEditingName = false; 254 } 255 256 public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { 257 if (actionId == EditorInfo.IME_ACTION_DONE) { 258 dismissEditingName(); 259 return true; 260 } 261 return false; 262 } 263 264 public View getEditTextRegion() { 265 return mFolderName; 266 } 267 268 public Drawable getDragDrawable() { 269 return mIconDrawable; 270 } 271 272 /** 273 * We need to handle touch events to prevent them from falling through to the workspace below. 274 */ 275 @Override 276 public boolean onTouchEvent(MotionEvent ev) { 277 return true; 278 } 279 280 public void setDragController(DragController dragController) { 281 mDragController = dragController; 282 } 283 284 void setFolderIcon(FolderIcon icon) { 285 mFolderIcon = icon; 286 } 287 288 /** 289 * @return the FolderInfo object associated with this folder 290 */ 291 FolderInfo getInfo() { 292 return mInfo; 293 } 294 295 void bind(FolderInfo info) { 296 mInfo = info; 297 ArrayList<ShortcutInfo> children = info.contents; 298 ArrayList<ShortcutInfo> overflow = new ArrayList<ShortcutInfo>(); 299 setupContentForNumItems(children.size()); 300 for (int i = 0; i < children.size(); i++) { 301 ShortcutInfo child = (ShortcutInfo) children.get(i); 302 if (!createAndAddShortcut(child)) { 303 overflow.add(child); 304 } 305 } 306 307 // If our folder has too many items we prune them from the list. This is an issue 308 // when upgrading from the old Folders implementation which could contain an unlimited 309 // number of items. 310 for (ShortcutInfo item: overflow) { 311 mInfo.remove(item); 312 LauncherModel.deleteItemFromDatabase(mLauncher, item); 313 } 314 315 mItemsInvalidated = true; 316 mInfo.addListener(this); 317 318 if (!sDefaultFolderName.contentEquals(mInfo.title)) { 319 mFolderName.setText(mInfo.title); 320 } else { 321 mFolderName.setText(""); 322 } 323 } 324 325 /** 326 * Creates a new UserFolder, inflated from R.layout.user_folder. 327 * 328 * @param context The application's context. 329 * 330 * @return A new UserFolder. 331 */ 332 static Folder fromXml(Context context) { 333 return (Folder) LayoutInflater.from(context).inflate(R.layout.user_folder, null); 334 } 335 336 /** 337 * This method is intended to make the UserFolder to be visually identical in size and position 338 * to its associated FolderIcon. This allows for a seamless transition into the expanded state. 339 */ 340 private void positionAndSizeAsIcon() { 341 if (!(getParent() instanceof DragLayer)) return; 342 343 DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams(); 344 345 if (mMode == PARTIAL_GROW) { 346 setScaleX(0.8f); 347 setScaleY(0.8f); 348 setAlpha(0f); 349 } else { 350 mLauncher.getDragLayer().getDescendantRectRelativeToSelf(mFolderIcon, mIconRect); 351 lp.width = mIconRect.width(); 352 lp.height = mIconRect.height(); 353 lp.x = mIconRect.left; 354 lp.y = mIconRect.top; 355 mContent.setAlpha(0); 356 } 357 mState = STATE_SMALL; 358 } 359 360 public void animateOpen() { 361 if (mFirstOpen) { 362 setLayerType(LAYER_TYPE_HARDWARE, null); 363 buildLayer(); 364 mFirstOpen = false; 365 } 366 367 positionAndSizeAsIcon(); 368 369 if (!(getParent() instanceof DragLayer)) return; 370 371 ObjectAnimator oa; 372 DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams(); 373 374 centerAboutIcon(); 375 if (mMode == PARTIAL_GROW) { 376 PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 1); 377 PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", 1.0f); 378 PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat("scaleY", 1.0f); 379 oa = ObjectAnimator.ofPropertyValuesHolder(this, alpha, scaleX, scaleY); 380 } else { 381 PropertyValuesHolder width = PropertyValuesHolder.ofInt("width", mNewSize.width()); 382 PropertyValuesHolder height = PropertyValuesHolder.ofInt("height", mNewSize.height()); 383 PropertyValuesHolder x = PropertyValuesHolder.ofInt("x", mNewSize.left); 384 PropertyValuesHolder y = PropertyValuesHolder.ofInt("y", mNewSize.top); 385 oa = ObjectAnimator.ofPropertyValuesHolder(lp, width, height, x, y); 386 oa.addUpdateListener(new AnimatorUpdateListener() { 387 public void onAnimationUpdate(ValueAnimator animation) { 388 requestLayout(); 389 } 390 }); 391 392 PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 1.0f); 393 ObjectAnimator alphaOa = ObjectAnimator.ofPropertyValuesHolder(mContent, alpha); 394 alphaOa.setDuration(mExpandDuration); 395 alphaOa.setInterpolator(new AccelerateInterpolator(2.0f)); 396 alphaOa.start(); 397 } 398 399 oa.addListener(new AnimatorListenerAdapter() { 400 @Override 401 public void onAnimationStart(Animator animation) { 402 mState = STATE_ANIMATING; 403 } 404 @Override 405 public void onAnimationEnd(Animator animation) { 406 mState = STATE_OPEN; 407 setLayerType(LAYER_TYPE_NONE, null); 408 enableHardwareLayersForChildren(); 409 } 410 }); 411 oa.setDuration(mExpandDuration); 412 oa.start(); 413 } 414 415 void enableHardwareLayersForChildren() { 416 ArrayList<View> children = getItemsInReadingOrder(); 417 for (View child: children) { 418 child.setLayerType(LAYER_TYPE_HARDWARE, null); 419 } 420 } 421 422 public void animateClosed() { 423 if (!(getParent() instanceof DragLayer)) return; 424 setLayerType(LAYER_TYPE_HARDWARE, null); 425 buildLayer(); 426 427 ObjectAnimator oa; 428 if (mMode == PARTIAL_GROW) { 429 PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 0); 430 PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", 0.9f); 431 PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat("scaleY", 0.9f); 432 oa = ObjectAnimator.ofPropertyValuesHolder(this, alpha, scaleX, scaleY); 433 } else { 434 DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams(); 435 436 PropertyValuesHolder width = PropertyValuesHolder.ofInt("width", mIconRect.width()); 437 PropertyValuesHolder height = PropertyValuesHolder.ofInt("height", mIconRect.height()); 438 PropertyValuesHolder x = PropertyValuesHolder.ofInt("x", mIconRect.left); 439 PropertyValuesHolder y = PropertyValuesHolder.ofInt("y", mIconRect.top); 440 oa = ObjectAnimator.ofPropertyValuesHolder(lp, width, height, x, y); 441 oa.addUpdateListener(new AnimatorUpdateListener() { 442 public void onAnimationUpdate(ValueAnimator animation) { 443 requestLayout(); 444 } 445 }); 446 447 PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 0f); 448 ObjectAnimator alphaOa = ObjectAnimator.ofPropertyValuesHolder(mContent, alpha); 449 alphaOa.setDuration(mExpandDuration); 450 alphaOa.setInterpolator(new DecelerateInterpolator(2.0f)); 451 alphaOa.start(); 452 } 453 454 oa.addListener(new AnimatorListenerAdapter() { 455 @Override 456 public void onAnimationEnd(Animator animation) { 457 onCloseComplete(); 458 mState = STATE_SMALL; 459 } 460 @Override 461 public void onAnimationStart(Animator animation) { 462 mState = STATE_ANIMATING; 463 } 464 }); 465 oa.setDuration(mExpandDuration); 466 oa.start(); 467 } 468 469 void notifyDataSetChanged() { 470 // recreate all the children if the data set changes under us. We may want to do this more 471 // intelligently (ie just removing the views that should no longer exist) 472 mContent.removeAllViewsInLayout(); 473 bind(mInfo); 474 } 475 476 public boolean acceptDrop(DragObject d) { 477 final ItemInfo item = (ItemInfo) d.dragInfo; 478 final int itemType = item.itemType; 479 return ((itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION || 480 itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) && 481 !isFull()); 482 } 483 484 protected boolean findAndSetEmptyCells(ShortcutInfo item) { 485 int[] emptyCell = new int[2]; 486 if (mContent.findCellForSpan(emptyCell, item.spanX, item.spanY)) { 487 item.cellX = emptyCell[0]; 488 item.cellY = emptyCell[1]; 489 return true; 490 } else { 491 return false; 492 } 493 } 494 495 protected boolean createAndAddShortcut(ShortcutInfo item) { 496 final TextView textView = 497 (TextView) mInflater.inflate(R.layout.application, this, false); 498 textView.setCompoundDrawablesWithIntrinsicBounds(null, 499 new FastBitmapDrawable(item.getIcon(mIconCache)), null, null); 500 textView.setText(item.title); 501 textView.setTag(item); 502 503 textView.setOnClickListener(this); 504 textView.setOnLongClickListener(this); 505 506 // We need to check here to verify that the given item's location isn't already occupied 507 // by another item. If it is, we need to find the next available slot and assign 508 // it that position. This is an issue when upgrading from the old Folders implementation 509 // which could contain an unlimited number of items. 510 if (mContent.getChildAt(item.cellX, item.cellY) != null || 511 item.cellX < 0 || item.cellY < 0) { 512 if (!findAndSetEmptyCells(item)) { 513 return false; 514 } 515 } 516 517 CellLayout.LayoutParams lp = 518 new CellLayout.LayoutParams(item.cellX, item.cellY, item.spanX, item.spanY); 519 boolean insert = false; 520 mContent.addViewToCellLayout(textView, insert ? 0 : -1, (int)item.id, lp, true); 521 return true; 522 } 523 524 public void onDragEnter(DragObject d) { 525 mPreviousTargetCell[0] = -1; 526 mPreviousTargetCell[1] = -1; 527 mOnExitAlarm.cancelAlarm(); 528 } 529 530 OnAlarmListener mReorderAlarmListener = new OnAlarmListener() { 531 public void onAlarm(Alarm alarm) { 532 realTimeReorder(mEmptyCell, mTargetCell); 533 } 534 }; 535 536 boolean readingOrderGreaterThan(int[] v1, int[] v2) { 537 if (v1[1] > v2[1] || (v1[1] == v2[1] && v1[0] > v2[0])) { 538 return true; 539 } else { 540 return false; 541 } 542 } 543 544 private void realTimeReorder(int[] empty, int[] target) { 545 boolean wrap; 546 int startX; 547 int endX; 548 int startY; 549 int delay = 0; 550 float delayAmount = 30; 551 if (readingOrderGreaterThan(target, empty)) { 552 wrap = empty[0] >= mContent.getCountX() - 1; 553 startY = wrap ? empty[1] + 1 : empty[1]; 554 for (int y = startY; y <= target[1]; y++) { 555 startX = y == empty[1] ? empty[0] + 1 : 0; 556 endX = y < target[1] ? mContent.getCountX() - 1 : target[0]; 557 for (int x = startX; x <= endX; x++) { 558 View v = mContent.getChildAt(x,y); 559 if (mContent.animateChildToPosition(v, empty[0], empty[1], 560 REORDER_ANIMATION_DURATION, delay)) { 561 empty[0] = x; 562 empty[1] = y; 563 delay += delayAmount; 564 delayAmount *= 0.9; 565 } 566 } 567 } 568 } else { 569 wrap = empty[0] == 0; 570 startY = wrap ? empty[1] - 1 : empty[1]; 571 for (int y = startY; y >= target[1]; y--) { 572 startX = y == empty[1] ? empty[0] - 1 : mContent.getCountX() - 1; 573 endX = y > target[1] ? 0 : target[0]; 574 for (int x = startX; x >= endX; x--) { 575 View v = mContent.getChildAt(x,y); 576 if (mContent.animateChildToPosition(v, empty[0], empty[1], 577 REORDER_ANIMATION_DURATION, delay)) { 578 empty[0] = x; 579 empty[1] = y; 580 delay += delayAmount; 581 delayAmount *= 0.9; 582 } 583 } 584 } 585 } 586 } 587 588 public void onDragOver(DragObject d) { 589 float[] r = getDragViewVisualCenter(d.x, d.y, d.xOffset, d.yOffset, d.dragView, null); 590 mTargetCell = mContent.findNearestArea((int) r[0], (int) r[1], 1, 1, mTargetCell); 591 592 if (mTargetCell[0] != mPreviousTargetCell[0] || mTargetCell[1] != mPreviousTargetCell[1]) { 593 mReorderAlarm.cancelAlarm(); 594 mReorderAlarm.setOnAlarmListener(mReorderAlarmListener); 595 mReorderAlarm.setAlarm(150); 596 mPreviousTargetCell[0] = mTargetCell[0]; 597 mPreviousTargetCell[1] = mTargetCell[1]; 598 } 599 } 600 601 // This is used to compute the visual center of the dragView. The idea is that 602 // the visual center represents the user's interpretation of where the item is, and hence 603 // is the appropriate point to use when determining drop location. 604 private float[] getDragViewVisualCenter(int x, int y, int xOffset, int yOffset, 605 DragView dragView, float[] recycle) { 606 float res[]; 607 if (recycle == null) { 608 res = new float[2]; 609 } else { 610 res = recycle; 611 } 612 613 // These represent the visual top and left of drag view if a dragRect was provided. 614 // If a dragRect was not provided, then they correspond to the actual view left and 615 // top, as the dragRect is in that case taken to be the entire dragView. 616 // R.dimen.dragViewOffsetY. 617 int left = x - xOffset; 618 int top = y - yOffset; 619 620 // In order to find the visual center, we shift by half the dragRect 621 res[0] = left + dragView.getDragRegion().width() / 2; 622 res[1] = top + dragView.getDragRegion().height() / 2; 623 624 return res; 625 } 626 627 OnAlarmListener mOnExitAlarmListener = new OnAlarmListener() { 628 public void onAlarm(Alarm alarm) { 629 completeDragExit(); 630 } 631 }; 632 633 public void completeDragExit() { 634 mLauncher.closeFolder(); 635 mCurrentDragInfo = null; 636 mCurrentDragView = null; 637 mSuppressOnAdd = false; 638 mRearrangeOnClose = true; 639 } 640 641 public void onDragExit(DragObject d) { 642 // We only close the folder if this is a true drag exit, ie. not because a drop 643 // has occurred above the folder. 644 if (!d.dragComplete) { 645 mOnExitAlarm.setOnAlarmListener(mOnExitAlarmListener); 646 mOnExitAlarm.setAlarm(ON_EXIT_CLOSE_DELAY); 647 } 648 mReorderAlarm.cancelAlarm(); 649 } 650 651 public void onDropCompleted(View target, DragObject d, boolean success) { 652 if (success) { 653 if (mDeleteFolderOnDropCompleted && !mItemAddedBackToSelfViaIcon) { 654 replaceFolderWithFinalItem(); 655 } 656 } else { 657 // The drag failed, we need to return the item to the folder 658 mFolderIcon.onDrop(d); 659 660 // We're going to trigger a "closeFolder" which may occur before this item has 661 // been added back to the folder -- this could cause the folder to be deleted 662 if (mOnExitAlarm.alarmPending()) { 663 mSuppressFolderDeletion = true; 664 } 665 } 666 667 if (target != this) { 668 if (mOnExitAlarm.alarmPending()) { 669 mOnExitAlarm.cancelAlarm(); 670 completeDragExit(); 671 } 672 } 673 mDeleteFolderOnDropCompleted = false; 674 mDragInProgress = false; 675 mItemAddedBackToSelfViaIcon = false; 676 mCurrentDragInfo = null; 677 mCurrentDragView = null; 678 mSuppressOnAdd = false; 679 } 680 681 public void notifyDrop() { 682 if (mDragInProgress) { 683 mItemAddedBackToSelfViaIcon = true; 684 } 685 } 686 687 public boolean isDropEnabled() { 688 return true; 689 } 690 691 public DropTarget getDropTargetDelegate(DragObject d) { 692 return null; 693 } 694 695 private void setupContentDimension(int count) { 696 ArrayList<View> list = getItemsInReadingOrder(); 697 698 int countX = mContent.getCountX(); 699 int countY = mContent.getCountY(); 700 boolean done = false; 701 702 while (!done) { 703 int oldCountX = countX; 704 int oldCountY = countY; 705 if (countX * countY < count) { 706 // Current grid is too small, expand it 707 if (countX <= countY && countX < mMaxCountX) { 708 countX++; 709 } else if (countY < mMaxCountY) { 710 countY++; 711 } 712 if (countY == 0) countY++; 713 } else if ((countY - 1) * countX >= count && countY >= countX) { 714 countY = Math.max(0, countY - 1); 715 } else if ((countX - 1) * countY >= count) { 716 countX = Math.max(0, countX - 1); 717 } 718 done = countX == oldCountX && countY == oldCountY; 719 } 720 mContent.setGridSize(countX, countY); 721 arrangeChildren(list); 722 } 723 724 public boolean isFull() { 725 return getItemCount() >= mMaxCountX * mMaxCountY; 726 } 727 728 private void centerAboutIcon() { 729 DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams(); 730 731 int width = getPaddingLeft() + getPaddingRight() + mContent.getDesiredWidth(); 732 int height = getPaddingTop() + getPaddingBottom() + mContent.getDesiredHeight() 733 + mFolderNameHeight; 734 DragLayer parent = (DragLayer) mLauncher.findViewById(R.id.drag_layer); 735 736 parent.getDescendantRectRelativeToSelf(mFolderIcon, mTempRect); 737 738 int centerX = mTempRect.centerX(); 739 int centerY = mTempRect.centerY(); 740 int centeredLeft = centerX - width / 2; 741 int centeredTop = centerY - height / 2; 742 743 // We first fetch the currently visible CellLayoutChildren 744 CellLayout currentPage = mLauncher.getWorkspace().getCurrentDropLayout(); 745 CellLayoutChildren boundingLayout = currentPage.getChildrenLayout(); 746 Rect bounds = new Rect(); 747 parent.getDescendantRectRelativeToSelf(boundingLayout, bounds); 748 749 // We need to bound the folder to the currently visible CellLayoutChildren 750 int left = Math.min(Math.max(bounds.left, centeredLeft), 751 bounds.left + bounds.width() - width); 752 int top = Math.min(Math.max(bounds.top, centeredTop), 753 bounds.top + bounds.height() - height); 754 // If the folder doesn't fit within the bounds, center it about the desired bounds 755 if (width >= bounds.width()) { 756 left = bounds.left + (bounds.width() - width) / 2; 757 } 758 if (height >= bounds.height()) { 759 top = bounds.top + (bounds.height() - height) / 2; 760 } 761 762 int folderPivotX = width / 2 + (centeredLeft - left); 763 int folderPivotY = height / 2 + (centeredTop - top); 764 setPivotX(folderPivotX); 765 setPivotY(folderPivotY); 766 int folderIconPivotX = (int) (mFolderIcon.getMeasuredWidth() * 767 (1.0f * folderPivotX / width)); 768 int folderIconPivotY = (int) (mFolderIcon.getMeasuredHeight() * 769 (1.0f * folderPivotY / height)); 770 mFolderIcon.setPivotX(folderIconPivotX); 771 mFolderIcon.setPivotY(folderIconPivotY); 772 773 if (mMode == PARTIAL_GROW) { 774 lp.width = width; 775 lp.height = height; 776 lp.x = left; 777 lp.y = top; 778 } else { 779 mNewSize.set(left, top, left + width, top + height); 780 } 781 } 782 783 private void setupContentForNumItems(int count) { 784 setupContentDimension(count); 785 786 DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams(); 787 if (lp == null) { 788 lp = new DragLayer.LayoutParams(0, 0); 789 lp.customPosition = true; 790 setLayoutParams(lp); 791 } 792 centerAboutIcon(); 793 } 794 795 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 796 int width = getPaddingLeft() + getPaddingRight() + mContent.getDesiredWidth(); 797 int height = getPaddingTop() + getPaddingBottom() + mContent.getDesiredHeight() 798 + mFolderNameHeight; 799 800 int contentWidthSpec = MeasureSpec.makeMeasureSpec(mContent.getDesiredWidth(), 801 MeasureSpec.EXACTLY); 802 int contentHeightSpec = MeasureSpec.makeMeasureSpec(mContent.getDesiredHeight(), 803 MeasureSpec.EXACTLY); 804 mContent.measure(contentWidthSpec, contentHeightSpec); 805 806 mFolderName.measure(contentWidthSpec, 807 MeasureSpec.makeMeasureSpec(mFolderNameHeight, MeasureSpec.EXACTLY)); 808 setMeasuredDimension(width, height); 809 } 810 811 private void arrangeChildren(ArrayList<View> list) { 812 int[] vacant = new int[2]; 813 if (list == null) { 814 list = getItemsInReadingOrder(); 815 } 816 mContent.removeAllViews(); 817 818 for (int i = 0; i < list.size(); i++) { 819 View v = list.get(i); 820 mContent.getVacantCell(vacant, 1, 1); 821 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) v.getLayoutParams(); 822 lp.cellX = vacant[0]; 823 lp.cellY = vacant[1]; 824 ItemInfo info = (ItemInfo) v.getTag(); 825 info.cellX = vacant[0]; 826 info.cellY = vacant[1]; 827 boolean insert = false; 828 mContent.addViewToCellLayout(v, insert ? 0 : -1, (int)info.id, lp, true); 829 LauncherModel.addOrMoveItemInDatabase(mLauncher, info, mInfo.id, 0, 830 info.cellX, info.cellY); 831 } 832 mItemsInvalidated = true; 833 } 834 835 public int getItemCount() { 836 return mContent.getChildrenLayout().getChildCount(); 837 } 838 839 public View getItemAt(int index) { 840 return mContent.getChildrenLayout().getChildAt(index); 841 } 842 843 private void onCloseComplete() { 844 DragLayer parent = (DragLayer) getParent(); 845 parent.removeView(this); 846 mDragController.removeDropTarget((DropTarget) this); 847 clearFocus(); 848 849 if (mRearrangeOnClose) { 850 setupContentForNumItems(getItemCount()); 851 mRearrangeOnClose = false; 852 } 853 if (getItemCount() <= 1) { 854 if (!mDragInProgress && !mSuppressFolderDeletion) { 855 replaceFolderWithFinalItem(); 856 } else if (mDragInProgress) { 857 mDeleteFolderOnDropCompleted = true; 858 } 859 } 860 mSuppressFolderDeletion = false; 861 } 862 863 private void replaceFolderWithFinalItem() { 864 ItemInfo finalItem = null; 865 866 if (getItemCount() == 1) { 867 finalItem = mInfo.contents.get(0); 868 } 869 870 // Remove the folder completely 871 CellLayout cellLayout = mLauncher.getCellLayout(mInfo.container, mInfo.screen); 872 cellLayout.removeView(mFolderIcon); 873 if (mFolderIcon instanceof DropTarget) { 874 mDragController.removeDropTarget((DropTarget) mFolderIcon); 875 } 876 mLauncher.removeFolder(mInfo); 877 878 if (finalItem != null) { 879 LauncherModel.addOrMoveItemInDatabase(mLauncher, finalItem, mInfo.container, 880 mInfo.screen, mInfo.cellX, mInfo.cellY); 881 } 882 LauncherModel.deleteItemFromDatabase(mLauncher, mInfo); 883 884 // Add the last remaining child to the workspace in place of the folder 885 if (finalItem != null) { 886 View child = mLauncher.createShortcut(R.layout.application, cellLayout, 887 (ShortcutInfo) finalItem); 888 889 mLauncher.getWorkspace().addInScreen(child, mInfo.container, mInfo.screen, mInfo.cellX, 890 mInfo.cellY, mInfo.spanX, mInfo.spanY); 891 } 892 } 893 894 public void onDrop(DragObject d) { 895 ShortcutInfo item; 896 if (d.dragInfo instanceof ApplicationInfo) { 897 // Came from all apps -- make a copy 898 item = ((ApplicationInfo) d.dragInfo).makeShortcut(); 899 item.spanX = 1; 900 item.spanY = 1; 901 } else { 902 item = (ShortcutInfo) d.dragInfo; 903 } 904 // Dragged from self onto self, currently this is the only path possible, however 905 // we keep this as a distinct code path. 906 if (item == mCurrentDragInfo) { 907 ShortcutInfo si = (ShortcutInfo) mCurrentDragView.getTag(); 908 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) mCurrentDragView.getLayoutParams(); 909 si.cellX = lp.cellX = mEmptyCell[0]; 910 si.cellX = lp.cellY = mEmptyCell[1]; 911 mContent.addViewToCellLayout(mCurrentDragView, -1, (int)item.id, lp, true); 912 if (d.dragView.hasDrawn()) { 913 mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, mCurrentDragView); 914 } else { 915 mCurrentDragView.setVisibility(VISIBLE); 916 } 917 mItemsInvalidated = true; 918 setupContentDimension(getItemCount()); 919 mSuppressOnAdd = true; 920 } 921 mInfo.add(item); 922 } 923 924 public void onAdd(ShortcutInfo item) { 925 mItemsInvalidated = true; 926 // If the item was dropped onto this open folder, we have done the work associated 927 // with adding the item to the folder, as indicated by mSuppressOnAdd being set 928 if (mSuppressOnAdd) return; 929 if (!findAndSetEmptyCells(item)) { 930 // The current layout is full, can we expand it? 931 setupContentForNumItems(getItemCount() + 1); 932 findAndSetEmptyCells(item); 933 } 934 createAndAddShortcut(item); 935 LauncherModel.addOrMoveItemInDatabase( 936 mLauncher, item, mInfo.id, 0, item.cellX, item.cellY); 937 } 938 939 public void onRemove(ShortcutInfo item) { 940 mItemsInvalidated = true; 941 // If this item is being dragged from this open folder, we have already handled 942 // the work associated with removing the item, so we don't have to do anything here. 943 if (item == mCurrentDragInfo) return; 944 View v = getViewForInfo(item); 945 mContent.removeView(v); 946 if (mState == STATE_ANIMATING) { 947 mRearrangeOnClose = true; 948 } else { 949 setupContentForNumItems(getItemCount()); 950 } 951 if (getItemCount() <= 1) { 952 replaceFolderWithFinalItem(); 953 } 954 } 955 956 private View getViewForInfo(ShortcutInfo item) { 957 for (int j = 0; j < mContent.getCountY(); j++) { 958 for (int i = 0; i < mContent.getCountX(); i++) { 959 View v = mContent.getChildAt(i, j); 960 if (v.getTag() == item) { 961 return v; 962 } 963 } 964 } 965 return null; 966 } 967 968 public void onItemsChanged() { 969 } 970 public void onTitleChanged(CharSequence title) { 971 } 972 973 public ArrayList<View> getItemsInReadingOrder() { 974 return getItemsInReadingOrder(true); 975 } 976 977 public ArrayList<View> getItemsInReadingOrder(boolean includeCurrentDragItem) { 978 if (mItemsInvalidated) { 979 mItemsInReadingOrder.clear(); 980 for (int j = 0; j < mContent.getCountY(); j++) { 981 for (int i = 0; i < mContent.getCountX(); i++) { 982 View v = mContent.getChildAt(i, j); 983 if (v != null) { 984 ShortcutInfo info = (ShortcutInfo) v.getTag(); 985 if (info != mCurrentDragInfo || includeCurrentDragItem) { 986 mItemsInReadingOrder.add(v); 987 } 988 } 989 } 990 } 991 mItemsInvalidated = false; 992 } 993 return mItemsInReadingOrder; 994 } 995 996 public void getLocationInDragLayer(int[] loc) { 997 mLauncher.getDragLayer().getLocationInDragLayer(this, loc); 998 } 999} 1000