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