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