blob: 1324a08edf80ae0e9fc35b0d47d50d31aec34a7e [file] [log] [blame]
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001/*
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
Joe Onoratoa5902522009-07-30 13:37:37 -070017package com.android.launcher2;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080018
Adam Cohendf2cc412011-04-27 16:56:57 -070019import android.animation.Animator;
20import android.animation.AnimatorListenerAdapter;
Adam Cohendf2cc412011-04-27 16:56:57 -070021import android.animation.ObjectAnimator;
22import android.animation.PropertyValuesHolder;
23import android.animation.ValueAnimator;
24import android.animation.ValueAnimator.AnimatorUpdateListener;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080025import android.content.Context;
Adam Cohen76fc0852011-06-17 13:26:23 -070026import android.content.res.Resources;
Romain Guyfb5411e2010-02-24 10:04:17 -080027import android.graphics.Rect;
Adam Cohenbadf71e2011-05-26 19:08:29 -070028import android.graphics.drawable.Drawable;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080029import android.util.AttributeSet;
Adam Cohen76fc0852011-06-17 13:26:23 -070030import android.view.ActionMode;
31import android.view.KeyEvent;
Adam Cohendf2cc412011-04-27 16:56:57 -070032import android.view.LayoutInflater;
Adam Cohen76fc0852011-06-17 13:26:23 -070033import android.view.Menu;
34import android.view.MenuItem;
Adam Cohen0c872ba2011-05-05 10:34:16 -070035import android.view.MotionEvent;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080036import android.view.View;
37import android.view.View.OnClickListener;
Adam Cohen2801caf2011-05-13 20:57:39 -070038import android.view.animation.AccelerateInterpolator;
39import android.view.animation.DecelerateInterpolator;
Adam Cohen76fc0852011-06-17 13:26:23 -070040import android.view.inputmethod.EditorInfo;
41import android.view.inputmethod.InputMethodManager;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080042import android.widget.AdapterView;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080043import android.widget.LinearLayout;
Adam Cohendf2cc412011-04-27 16:56:57 -070044import android.widget.TextView;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080045import android.widget.AdapterView.OnItemClickListener;
46import android.widget.AdapterView.OnItemLongClickListener;
47
Romain Guyedcce092010-03-04 13:03:17 -080048import com.android.launcher.R;
Adam Cohena9cf38f2011-05-02 15:36:58 -070049import com.android.launcher2.FolderInfo.FolderListener;
Romain Guyedcce092010-03-04 13:03:17 -080050
Adam Cohenc0dcf592011-06-01 15:30:43 -070051import java.util.ArrayList;
52
The Android Open Source Project31dd5032009-03-03 19:32:27 -080053/**
54 * Represents a set of icons chosen by the user or generated by the system.
55 */
56public class Folder extends LinearLayout implements DragSource, OnItemLongClickListener,
Adam Cohen76fc0852011-06-17 13:26:23 -070057 OnItemClickListener, OnClickListener, View.OnLongClickListener, DropTarget, FolderListener,
58 TextView.OnEditorActionListener {
The Android Open Source Project31dd5032009-03-03 19:32:27 -080059
Joe Onorato00acb122009-08-04 16:04:30 -040060 protected DragController mDragController;
Adam Cohendf2cc412011-04-27 16:56:57 -070061
The Android Open Source Project31dd5032009-03-03 19:32:27 -080062 protected Launcher mLauncher;
63
The Android Open Source Project31dd5032009-03-03 19:32:27 -080064 protected FolderInfo mInfo;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080065
Adam Cohendf2cc412011-04-27 16:56:57 -070066 private static final String TAG = "Launcher.Folder";
67
68 static final int STATE_NONE = -1;
69 static final int STATE_SMALL = 0;
70 static final int STATE_ANIMATING = 1;
71 static final int STATE_OPEN = 2;
72
73 private int mExpandDuration;
74 protected CellLayout mContent;
75 private final LayoutInflater mInflater;
76 private final IconCache mIconCache;
77 private int mState = STATE_NONE;
Adam Cohena9cf38f2011-05-02 15:36:58 -070078 private int[] mDragItemPosition = new int[2];
Adam Cohen2801caf2011-05-13 20:57:39 -070079 private static final int FULL_GROW = 0;
80 private static final int PARTIAL_GROW = 1;
Adam Cohenbfbfd262011-06-13 16:55:12 -070081 private static final int REORDER_ANIMATION_DURATION = 230;
82 private static final int ON_EXIT_CLOSE_DELAY = 800;
Adam Cohen2801caf2011-05-13 20:57:39 -070083 private int mMode = PARTIAL_GROW;
84 private boolean mRearrangeOnClose = false;
85 private FolderIcon mFolderIcon;
86 private int mMaxCountX;
87 private int mMaxCountY;
88 private Rect mNewSize = new Rect();
Adam Cohen7c693212011-05-18 15:26:57 -070089 private ArrayList<View> mItemsInReadingOrder = new ArrayList<View>();
Adam Cohenbadf71e2011-05-26 19:08:29 -070090 private Drawable mIconDrawable;
Adam Cohen7c693212011-05-18 15:26:57 -070091 boolean mItemsInvalidated = false;
Adam Cohenbfbfd262011-06-13 16:55:12 -070092 private ShortcutInfo mCurrentDragInfo;
93 private View mCurrentDragView;
94 boolean mSuppressOnAdd = false;
95 private int[] mTargetCell = new int[2];
96 private int[] mPreviousTargetCell = new int[2];
97 private int[] mEmptyCell = new int[2];
98 private Alarm mReorderAlarm = new Alarm();
99 private Alarm mOnExitAlarm = new Alarm();
Adam Cohen76fc0852011-06-17 13:26:23 -0700100 private TextView mFolderName;
101 private int mFolderNameHeight;
Adam Cohen4ef610f2011-06-21 22:37:36 -0700102 private static String sDefaultFolderName;
103 private Rect mHitRect = new Rect();
Adam Cohen76fc0852011-06-17 13:26:23 -0700104
105 private boolean mIsEditingName = false;
106 private InputMethodManager mInputMethodManager;
Adam Cohendf2cc412011-04-27 16:56:57 -0700107
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800108 /**
109 * Used to inflate the Workspace from XML.
110 *
111 * @param context The application's context.
112 * @param attrs The attribtues set containing the Workspace's customization values.
113 */
114 public Folder(Context context, AttributeSet attrs) {
115 super(context, attrs);
116 setAlwaysDrawnWithCacheEnabled(false);
Adam Cohendf2cc412011-04-27 16:56:57 -0700117 mInflater = LayoutInflater.from(context);
118 mIconCache = ((LauncherApplication)context.getApplicationContext()).getIconCache();
Adam Cohen2801caf2011-05-13 20:57:39 -0700119 mMaxCountX = LauncherModel.getCellCountX() - 1;
120 mMaxCountY = LauncherModel.getCellCountY() - 1;
Adam Cohen76fc0852011-06-17 13:26:23 -0700121
122 mInputMethodManager = (InputMethodManager)
123 mContext.getSystemService(Context.INPUT_METHOD_SERVICE);
124
125 Resources res = getResources();
126 mExpandDuration = res.getInteger(R.integer.config_folderAnimDuration);
Adam Cohen4ef610f2011-06-21 22:37:36 -0700127
128 if (sDefaultFolderName == null) {
129 sDefaultFolderName = res.getString(R.string.folder_name);
130 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800131 }
132
133 @Override
134 protected void onFinishInflate() {
135 super.onFinishInflate();
Adam Cohendf2cc412011-04-27 16:56:57 -0700136 mContent = (CellLayout) findViewById(R.id.folder_content);
Adam Cohen2801caf2011-05-13 20:57:39 -0700137 mContent.setGridSize(0, 0);
138 mContent.enableHardwareLayers();
Adam Cohen76fc0852011-06-17 13:26:23 -0700139 mFolderName = (TextView) findViewById(R.id.folder_name);
140
141 // We find out how tall the text view wants to be (it is set to wrap_content), so that
142 // we can allocate the appropriate amount of space for it.
143 int measureSpec = MeasureSpec.UNSPECIFIED;
144 mFolderName.measure(measureSpec, measureSpec);
145 mFolderNameHeight = mFolderName.getMeasuredHeight();
146
147 // We disable action mode for now since it messes up the view on phones
148 mFolderName.setCustomSelectionActionModeCallback(mActionModeCallback);
149 mFolderName.setCursorVisible(false);
150 mFolderName.setOnEditorActionListener(this);
Adam Cohen4ef610f2011-06-21 22:37:36 -0700151 mFolderName.setSelectAllOnFocus(true);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800152 }
Adam Cohen2801caf2011-05-13 20:57:39 -0700153
Adam Cohen76fc0852011-06-17 13:26:23 -0700154 private ActionMode.Callback mActionModeCallback = new ActionMode.Callback() {
155 public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
156 return false;
157 }
158
159 public boolean onCreateActionMode(ActionMode mode, Menu menu) {
160 return false;
161 }
162
163 public void onDestroyActionMode(ActionMode mode) {
164 }
165
166 public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
167 return false;
168 }
169 };
170
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800171 public void onItemClick(AdapterView parent, View v, int position, long id) {
Joe Onorato0589f0f2010-02-08 13:44:00 -0800172 ShortcutInfo app = (ShortcutInfo) parent.getItemAtPosition(position);
Romain Guyfb5411e2010-02-24 10:04:17 -0800173 int[] pos = new int[2];
174 v.getLocationOnScreen(pos);
175 app.intent.setSourceBounds(new Rect(pos[0], pos[1],
176 pos[0] + v.getWidth(), pos[1] + v.getHeight()));
Joe Onoratof984e852010-03-25 09:47:45 -0700177 mLauncher.startActivitySafely(app.intent, app);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800178 }
179
180 public void onClick(View v) {
Adam Cohendf2cc412011-04-27 16:56:57 -0700181 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);
Adam Cohendf2cc412011-04-27 16:56:57 -0700190 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800191 }
192
Adam Cohen76fc0852011-06-17 13:26:23 -0700193 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
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800203 public boolean onLongClick(View v) {
Adam Cohendf2cc412011-04-27 16:56:57 -0700204 Object tag = v.getTag();
205 if (tag instanceof ShortcutInfo) {
Adam Cohendf2cc412011-04-27 16:56:57 -0700206 ShortcutInfo item = (ShortcutInfo) tag;
207 if (!v.isInTouchMode()) {
208 return false;
209 }
210
211 mLauncher.getWorkspace().onDragStartedWithItem(v);
212 mDragController.startDrag(v, this, item, DragController.DRAG_ACTION_COPY);
Adam Cohena9cf38f2011-05-02 15:36:58 -0700213 mDragItemPosition[0] = item.cellX;
214 mDragItemPosition[1] = item.cellY;
Adam Cohenbadf71e2011-05-26 19:08:29 -0700215 mIconDrawable = ((TextView) v).getCompoundDrawables()[1];
Adam Cohen76078c42011-06-09 15:06:52 -0700216
217 mCurrentDragInfo = item;
Adam Cohenbfbfd262011-06-13 16:55:12 -0700218 mEmptyCell[0] = item.cellX;
219 mEmptyCell[1] = item.cellY;
220 mCurrentDragView = v;
221 mContent.removeView(mCurrentDragView);
222 mInfo.remove(item);
Adam Cohendf2cc412011-04-27 16:56:57 -0700223 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800224 return true;
225 }
226
Adam Cohen76fc0852011-06-17 13:26:23 -0700227 public boolean isEditingName() {
228 return mIsEditingName;
229 }
230
231 public void startEditingFolderName() {
232 mFolderName.setCursorVisible(true);
233 mIsEditingName = true;
234 }
235
236 public void dismissEditingName() {
237 mInputMethodManager.hideSoftInputFromWindow(getWindowToken(), 0);
238 doneEditingFolderName(true);
239 }
240
241 public void doneEditingFolderName(boolean commit) {
242 mInfo.setTitle(mFolderName.getText());
243 LauncherModel.updateItemInDatabase(mLauncher, mInfo);
244 mFolderName.setCursorVisible(false);
245 mFolderName.clearFocus();
246 mIsEditingName = false;
247 }
248
249 public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
250 if (actionId == EditorInfo.IME_ACTION_DONE) {
251 dismissEditingName();
252 return true;
253 }
254 return false;
255 }
256
257 public View getEditTextRegion() {
258 return mFolderName;
259 }
260
Adam Cohenbadf71e2011-05-26 19:08:29 -0700261 public Drawable getDragDrawable() {
262 return mIconDrawable;
263 }
264
Adam Cohen0c872ba2011-05-05 10:34:16 -0700265 /**
266 * We need to handle touch events to prevent them from falling through to the workspace below.
267 */
268 @Override
269 public boolean onTouchEvent(MotionEvent ev) {
270 return true;
271 }
272
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800273 public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
274 if (!view.isInTouchMode()) {
275 return false;
276 }
277
Joe Onorato0589f0f2010-02-08 13:44:00 -0800278 ShortcutInfo app = (ShortcutInfo) parent.getItemAtPosition(position);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800279
Joe Onorato00acb122009-08-04 16:04:30 -0400280 mDragController.startDrag(view, this, app, DragController.DRAG_ACTION_COPY);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800281 mLauncher.closeFolder(this);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800282 return true;
283 }
284
Joe Onorato00acb122009-08-04 16:04:30 -0400285 public void setDragController(DragController dragController) {
286 mDragController = dragController;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800287 }
288
Patrick Dubroya669d792010-11-23 14:40:33 -0800289 public void onDragViewVisible() {
290 }
291
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800292 void setLauncher(Launcher launcher) {
293 mLauncher = launcher;
294 }
Adam Cohen2801caf2011-05-13 20:57:39 -0700295
296 void setFolderIcon(FolderIcon icon) {
297 mFolderIcon = icon;
298 }
299
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800300 /**
301 * @return the FolderInfo object associated with this folder
302 */
303 FolderInfo getInfo() {
304 return mInfo;
305 }
306
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800307 void onOpen() {
Adam Cohendf2cc412011-04-27 16:56:57 -0700308 // When the folder opens, we need to refresh the GridView's selection by
309 // forcing a layout
310 // TODO: find out if this is still necessary
311 mContent.requestLayout();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800312 }
313
314 void onClose() {
Adam Cohen76fc0852011-06-17 13:26:23 -0700315 CellLayoutChildren clc = (CellLayoutChildren) getParent();
316 final CellLayout cellLayout = (CellLayout) clc.getParent();
317 cellLayout.removeViewWithoutMarkingCells(Folder.this);
318 clearFocus();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800319 }
320
321 void bind(FolderInfo info) {
322 mInfo = info;
Adam Cohendf2cc412011-04-27 16:56:57 -0700323 ArrayList<ShortcutInfo> children = info.contents;
Adam Cohen7c693212011-05-18 15:26:57 -0700324 setupContentForNumItems(children.size());
Adam Cohendf2cc412011-04-27 16:56:57 -0700325 for (int i = 0; i < children.size(); i++) {
326 ShortcutInfo child = (ShortcutInfo) children.get(i);
Adam Cohen7c693212011-05-18 15:26:57 -0700327 createAndAddShortcut(child);
Adam Cohendf2cc412011-04-27 16:56:57 -0700328 }
Adam Cohen4dbe6d92011-05-18 17:14:15 -0700329 mItemsInvalidated = true;
Adam Cohena9cf38f2011-05-02 15:36:58 -0700330 mInfo.addListener(this);
Adam Cohen4ef610f2011-06-21 22:37:36 -0700331
Adam Cohenafb01ee2011-06-23 15:38:03 -0700332 if (!sDefaultFolderName.contentEquals(mInfo.title)) {
Adam Cohen4ef610f2011-06-21 22:37:36 -0700333 mFolderName.setText(mInfo.title);
334 } else {
335 mFolderName.setText("");
336 }
Adam Cohendf2cc412011-04-27 16:56:57 -0700337 }
338
339 /**
340 * Creates a new UserFolder, inflated from R.layout.user_folder.
341 *
342 * @param context The application's context.
343 *
344 * @return A new UserFolder.
345 */
346 static Folder fromXml(Context context) {
347 return (Folder) LayoutInflater.from(context).inflate(R.layout.user_folder, null);
348 }
349
350 /**
351 * This method is intended to make the UserFolder to be visually identical in size and position
352 * to its associated FolderIcon. This allows for a seamless transition into the expanded state.
353 */
354 private void positionAndSizeAsIcon() {
355 if (!(getParent() instanceof CellLayoutChildren)) return;
356
Adam Cohen2801caf2011-05-13 20:57:39 -0700357 CellLayout.LayoutParams iconLp = (CellLayout.LayoutParams) mFolderIcon.getLayoutParams();
Adam Cohendf2cc412011-04-27 16:56:57 -0700358 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) getLayoutParams();
359
Adam Cohen2801caf2011-05-13 20:57:39 -0700360 if (mMode == PARTIAL_GROW) {
361 setScaleX(0.8f);
362 setScaleY(0.8f);
363 setAlpha(0f);
364 } else {
365 lp.width = iconLp.width;
366 lp.height = iconLp.height;
367 lp.x = iconLp.x;
368 lp.y = iconLp.y;
369 mContent.setAlpha(0);
370 }
Adam Cohendf2cc412011-04-27 16:56:57 -0700371 mState = STATE_SMALL;
372 }
373
374 public void animateOpen() {
375 if (mState != STATE_SMALL) {
376 positionAndSizeAsIcon();
377 }
378 if (!(getParent() instanceof CellLayoutChildren)) return;
379
Adam Cohen2801caf2011-05-13 20:57:39 -0700380 ObjectAnimator oa;
Adam Cohendf2cc412011-04-27 16:56:57 -0700381 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) getLayoutParams();
382
Adam Cohen2801caf2011-05-13 20:57:39 -0700383 centerAboutIcon();
384 if (mMode == PARTIAL_GROW) {
385 PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 1);
386 PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", 1.0f);
387 PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat("scaleY", 1.0f);
388 oa = ObjectAnimator.ofPropertyValuesHolder(this, alpha, scaleX, scaleY);
389 } else {
390 PropertyValuesHolder width = PropertyValuesHolder.ofInt("width", mNewSize.width());
391 PropertyValuesHolder height = PropertyValuesHolder.ofInt("height", mNewSize.height());
392 PropertyValuesHolder x = PropertyValuesHolder.ofInt("x", mNewSize.left);
393 PropertyValuesHolder y = PropertyValuesHolder.ofInt("y", mNewSize.top);
394 oa = ObjectAnimator.ofPropertyValuesHolder(lp, width, height, x, y);
395 oa.addUpdateListener(new AnimatorUpdateListener() {
396 public void onAnimationUpdate(ValueAnimator animation) {
397 requestLayout();
398 }
399 });
Adam Cohendf2cc412011-04-27 16:56:57 -0700400
Adam Cohen2801caf2011-05-13 20:57:39 -0700401 PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 1.0f);
402 ObjectAnimator alphaOa = ObjectAnimator.ofPropertyValuesHolder(mContent, alpha);
403 alphaOa.setDuration(mExpandDuration);
404 alphaOa.setInterpolator(new AccelerateInterpolator(2.0f));
405 alphaOa.start();
406 }
Adam Cohendf2cc412011-04-27 16:56:57 -0700407
Adam Cohen2801caf2011-05-13 20:57:39 -0700408 oa.addListener(new AnimatorListenerAdapter() {
Adam Cohendf2cc412011-04-27 16:56:57 -0700409 @Override
410 public void onAnimationStart(Animator animation) {
411 mState = STATE_ANIMATING;
412 }
413 @Override
414 public void onAnimationEnd(Animator animation) {
Adam Cohen2801caf2011-05-13 20:57:39 -0700415 mState = STATE_OPEN;
Adam Cohendf2cc412011-04-27 16:56:57 -0700416 }
417 });
Adam Cohen2801caf2011-05-13 20:57:39 -0700418 oa.setDuration(mExpandDuration);
419 oa.start();
Adam Cohendf2cc412011-04-27 16:56:57 -0700420 }
421
422 public void animateClosed() {
423 if (!(getParent() instanceof CellLayoutChildren)) return;
424
Adam Cohen2801caf2011-05-13 20:57:39 -0700425 ObjectAnimator oa;
Adam Cohen2801caf2011-05-13 20:57:39 -0700426 if (mMode == PARTIAL_GROW) {
427 PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 0);
428 PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", 0.9f);
429 PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat("scaleY", 0.9f);
430 oa = ObjectAnimator.ofPropertyValuesHolder(this, alpha, scaleX, scaleY);
431 } else {
432 CellLayout.LayoutParams iconLp = (CellLayout.LayoutParams) mFolderIcon.getLayoutParams();
433 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) getLayoutParams();
Adam Cohendf2cc412011-04-27 16:56:57 -0700434
Adam Cohen2801caf2011-05-13 20:57:39 -0700435 PropertyValuesHolder width = PropertyValuesHolder.ofInt("width", iconLp.width);
436 PropertyValuesHolder height = PropertyValuesHolder.ofInt("height", iconLp.height);
437 PropertyValuesHolder x = PropertyValuesHolder.ofInt("x",iconLp.x);
438 PropertyValuesHolder y = PropertyValuesHolder.ofInt("y", iconLp.y);
439 oa = ObjectAnimator.ofPropertyValuesHolder(lp, width, height, x, y);
440 oa.addUpdateListener(new AnimatorUpdateListener() {
441 public void onAnimationUpdate(ValueAnimator animation) {
442 requestLayout();
443 }
444 });
Adam Cohendf2cc412011-04-27 16:56:57 -0700445
Adam Cohen2801caf2011-05-13 20:57:39 -0700446 PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 0f);
447 ObjectAnimator alphaOa = ObjectAnimator.ofPropertyValuesHolder(mContent, alpha);
448 alphaOa.setDuration(mExpandDuration);
449 alphaOa.setInterpolator(new DecelerateInterpolator(2.0f));
450 alphaOa.start();
451 }
Adam Cohendf2cc412011-04-27 16:56:57 -0700452
Adam Cohen2801caf2011-05-13 20:57:39 -0700453 oa.addListener(new AnimatorListenerAdapter() {
Adam Cohendf2cc412011-04-27 16:56:57 -0700454 @Override
455 public void onAnimationEnd(Animator animation) {
Adam Cohen76fc0852011-06-17 13:26:23 -0700456 onClose();
Adam Cohen2801caf2011-05-13 20:57:39 -0700457 onCloseComplete();
Adam Cohen2801caf2011-05-13 20:57:39 -0700458 mState = STATE_SMALL;
Adam Cohendf2cc412011-04-27 16:56:57 -0700459 }
460 @Override
461 public void onAnimationStart(Animator animation) {
462 mState = STATE_ANIMATING;
463 }
464 });
Adam Cohen2801caf2011-05-13 20:57:39 -0700465 oa.setDuration(mExpandDuration);
466 oa.start();
Adam Cohendf2cc412011-04-27 16:56:57 -0700467 }
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
Adam Cohencb3382b2011-05-24 14:07:08 -0700476 public boolean acceptDrop(DragObject d) {
477 final ItemInfo item = (ItemInfo) d.dragInfo;
Adam Cohendf2cc412011-04-27 16:56:57 -0700478 final int itemType = item.itemType;
Adam Cohen2801caf2011-05-13 20:57:39 -0700479 return ((itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION ||
480 itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) &&
481 !isFull());
Adam Cohendf2cc412011-04-27 16:56:57 -0700482 }
483
Adam Cohendf2cc412011-04-27 16:56:57 -0700484 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];
Adam Cohendf2cc412011-04-27 16:56:57 -0700489 return true;
490 } else {
491 return false;
492 }
493 }
494
495 protected void createAndAddShortcut(ShortcutInfo item) {
496 final TextView textView =
497 (TextView) mInflater.inflate(R.layout.application_boxed, 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 CellLayout.LayoutParams lp =
507 new CellLayout.LayoutParams(item.cellX, item.cellY, item.spanX, item.spanY);
508 boolean insert = false;
509 mContent.addViewToCellLayout(textView, insert ? 0 : -1, (int)item.id, lp, true);
510 }
511
Adam Cohencb3382b2011-05-24 14:07:08 -0700512 public void onDragEnter(DragObject d) {
Adam Cohenbfbfd262011-06-13 16:55:12 -0700513 mPreviousTargetCell[0] = -1;
514 mPreviousTargetCell[1] = -1;
Adam Cohen2801caf2011-05-13 20:57:39 -0700515 mContent.onDragEnter();
Adam Cohenbfbfd262011-06-13 16:55:12 -0700516 mOnExitAlarm.cancelAlarm();
517 }
518
519 OnAlarmListener mReorderAlarmListener = new OnAlarmListener() {
520 public void onAlarm(Alarm alarm) {
521 realTimeReorder(mEmptyCell, mTargetCell);
522 }
523 };
524
525 boolean readingOrderGreaterThan(int[] v1, int[] v2) {
526 if (v1[1] > v2[1] || (v1[1] == v2[1] && v1[0] > v2[0])) {
527 return true;
528 } else {
529 return false;
530 }
531 }
532
533 private void realTimeReorder(int[] empty, int[] target) {
534 boolean wrap;
535 int startX;
536 int endX;
537 int startY;
Adam Cohen76fc0852011-06-17 13:26:23 -0700538 int delay = 0;
539 float delayAmount = 30;
Adam Cohenbfbfd262011-06-13 16:55:12 -0700540 if (readingOrderGreaterThan(target, empty)) {
541 wrap = empty[0] >= mContent.getCountX() - 1;
542 startY = wrap ? empty[1] + 1 : empty[1];
543 for (int y = startY; y <= target[1]; y++) {
544 startX = y == empty[1] ? empty[0] + 1 : 0;
545 endX = y < target[1] ? mContent.getCountX() - 1 : target[0];
546 for (int x = startX; x <= endX; x++) {
547 View v = mContent.getChildAt(x,y);
548 if (mContent.animateChildToPosition(v, empty[0], empty[1],
Adam Cohen76fc0852011-06-17 13:26:23 -0700549 REORDER_ANIMATION_DURATION, delay)) {
Adam Cohenbfbfd262011-06-13 16:55:12 -0700550 empty[0] = x;
551 empty[1] = y;
Adam Cohen76fc0852011-06-17 13:26:23 -0700552 delay += delayAmount;
553 delayAmount *= 0.9;
Adam Cohenbfbfd262011-06-13 16:55:12 -0700554 }
555 }
556 }
557 } else {
558 wrap = empty[0] == 0;
559 startY = wrap ? empty[1] - 1 : empty[1];
560 for (int y = startY; y >= target[1]; y--) {
561 startX = y == empty[1] ? empty[0] - 1 : mContent.getCountX() - 1;
562 endX = y > target[1] ? 0 : target[0];
563 for (int x = startX; x >= endX; x--) {
564 View v = mContent.getChildAt(x,y);
565 if (mContent.animateChildToPosition(v, empty[0], empty[1],
Adam Cohen76fc0852011-06-17 13:26:23 -0700566 REORDER_ANIMATION_DURATION, delay)) {
Adam Cohenbfbfd262011-06-13 16:55:12 -0700567 empty[0] = x;
568 empty[1] = y;
Adam Cohen76fc0852011-06-17 13:26:23 -0700569 delay += delayAmount;
570 delayAmount *= 0.9;
Adam Cohenbfbfd262011-06-13 16:55:12 -0700571 }
572 }
573 }
574 }
Adam Cohendf2cc412011-04-27 16:56:57 -0700575 }
576
Adam Cohencb3382b2011-05-24 14:07:08 -0700577 public void onDragOver(DragObject d) {
Adam Cohenbfbfd262011-06-13 16:55:12 -0700578 float[] r = getDragViewVisualCenter(d.x, d.y, d.xOffset, d.yOffset, d.dragView, null);
579 mTargetCell = mContent.findNearestArea((int) r[0], (int) r[1], 1, 1, mTargetCell);
580
581 if (mTargetCell[0] != mPreviousTargetCell[0] || mTargetCell[1] != mPreviousTargetCell[1]) {
582 mReorderAlarm.cancelAlarm();
583 mReorderAlarm.setOnAlarmListener(mReorderAlarmListener);
584 mReorderAlarm.setAlarm(150);
585 mPreviousTargetCell[0] = mTargetCell[0];
586 mPreviousTargetCell[1] = mTargetCell[1];
587 }
Adam Cohendf2cc412011-04-27 16:56:57 -0700588 }
589
Adam Cohenbfbfd262011-06-13 16:55:12 -0700590 // This is used to compute the visual center of the dragView. The idea is that
591 // the visual center represents the user's interpretation of where the item is, and hence
592 // is the appropriate point to use when determining drop location.
593 private float[] getDragViewVisualCenter(int x, int y, int xOffset, int yOffset,
594 DragView dragView, float[] recycle) {
595 float res[];
596 if (recycle == null) {
597 res = new float[2];
598 } else {
599 res = recycle;
600 }
601
602 // These represent the visual top and left of drag view if a dragRect was provided.
603 // If a dragRect was not provided, then they correspond to the actual view left and
604 // top, as the dragRect is in that case taken to be the entire dragView.
605 // R.dimen.dragViewOffsetY.
606 int left = x - xOffset;
607 int top = y - yOffset;
608
609 // In order to find the visual center, we shift by half the dragRect
610 res[0] = left + dragView.getDragRegion().width() / 2;
611 res[1] = top + dragView.getDragRegion().height() / 2;
612
613 return res;
614 }
615
616 OnAlarmListener mOnExitAlarmListener = new OnAlarmListener() {
617 public void onAlarm(Alarm alarm) {
Adam Cohen1d9af7d2011-06-22 15:26:58 -0700618 mLauncher.closeFolder();
Adam Cohenbfbfd262011-06-13 16:55:12 -0700619 mCurrentDragInfo = null;
620 mCurrentDragView = null;
621 mSuppressOnAdd = false;
622 mRearrangeOnClose = true;
623 }
624 };
625
Adam Cohencb3382b2011-05-24 14:07:08 -0700626 public void onDragExit(DragObject d) {
Adam Cohenbfbfd262011-06-13 16:55:12 -0700627 // We only close the folder if this is a true drag exit, ie. not because a drop
628 // has occurred above the folder.
629 if (!d.dragComplete) {
630 mOnExitAlarm.setOnAlarmListener(mOnExitAlarmListener);
631 mOnExitAlarm.setAlarm(ON_EXIT_CLOSE_DELAY);
632 }
633 mReorderAlarm.cancelAlarm();
Adam Cohen2801caf2011-05-13 20:57:39 -0700634 mContent.onDragExit();
635 }
636
Adam Cohenc0dcf592011-06-01 15:30:43 -0700637 public void onDropCompleted(View target, DragObject d, boolean success) {
Adam Cohenbfbfd262011-06-13 16:55:12 -0700638 mCurrentDragInfo = null;
639 mCurrentDragView = null;
640 mSuppressOnAdd = false;
Adam Cohen76078c42011-06-09 15:06:52 -0700641 if (!success) {
642 if (d.dragView != null) {
643 if (target instanceof CellLayout) {
644 // TODO: we should animate the item back to the folder in this case
645 }
646 }
Adam Cohenbfbfd262011-06-13 16:55:12 -0700647 // TODO: if the drag fails, we need to re-add the item
Adam Cohen76078c42011-06-09 15:06:52 -0700648 }
Adam Cohendf2cc412011-04-27 16:56:57 -0700649 }
650
651 public boolean isDropEnabled() {
652 return true;
653 }
654
Adam Cohencb3382b2011-05-24 14:07:08 -0700655 public DropTarget getDropTargetDelegate(DragObject d) {
Adam Cohendf2cc412011-04-27 16:56:57 -0700656 return null;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800657 }
Adam Cohena9cf38f2011-05-02 15:36:58 -0700658
Adam Cohen2801caf2011-05-13 20:57:39 -0700659 private void setupContentDimension(int count) {
660 ArrayList<View> list = getItemsInReadingOrder();
661
662 int countX = mContent.getCountX();
663 int countY = mContent.getCountY();
Adam Cohen7c693212011-05-18 15:26:57 -0700664 boolean done = false;
Adam Cohen2801caf2011-05-13 20:57:39 -0700665
Adam Cohen7c693212011-05-18 15:26:57 -0700666 while (!done) {
667 int oldCountX = countX;
668 int oldCountY = countY;
669 if (countX * countY < count) {
670 // Current grid is too small, expand it
671 if (countX <= countY && countX < mMaxCountX) {
672 countX++;
673 } else if (countY < mMaxCountY) {
674 countY++;
675 }
676 if (countY == 0) countY++;
677 } else if ((countY - 1) * countX >= count && countY >= countX) {
678 countY = Math.max(0, countY - 1);
679 } else if ((countX - 1) * countY >= count) {
680 countX = Math.max(0, countX - 1);
Adam Cohen2801caf2011-05-13 20:57:39 -0700681 }
Adam Cohen7c693212011-05-18 15:26:57 -0700682 done = countX == oldCountX && countY == oldCountY;
Adam Cohen2801caf2011-05-13 20:57:39 -0700683 }
Adam Cohen7c693212011-05-18 15:26:57 -0700684 mContent.setGridSize(countX, countY);
Adam Cohen2801caf2011-05-13 20:57:39 -0700685 arrangeChildren(list);
686 }
687
688 public boolean isFull() {
689 return getItemCount() >= mMaxCountX * mMaxCountY;
690 }
691
692 private void centerAboutIcon() {
693 CellLayout.LayoutParams iconLp = (CellLayout.LayoutParams) mFolderIcon.getLayoutParams();
694 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) getLayoutParams();
695
696 int width = getPaddingLeft() + getPaddingRight() + mContent.getDesiredWidth();
Adam Cohen76fc0852011-06-17 13:26:23 -0700697 // Technically there is no padding at the bottom, but we add space equal to the padding
698 // and have to account for that here.
699 int height = getPaddingTop() + mContent.getDesiredHeight() + mFolderNameHeight;
Adam Cohen2801caf2011-05-13 20:57:39 -0700700
701 int centerX = iconLp.x + iconLp.width / 2;
702 int centerY = iconLp.y + iconLp.height / 2;
703 int centeredLeft = centerX - width / 2;
704 int centeredTop = centerY - height / 2;
705
706 CellLayoutChildren clc = (CellLayoutChildren) getParent();
707 int parentWidth = 0;
708 int parentHeight = 0;
709 if (clc != null) {
710 parentWidth = clc.getMeasuredWidth();
711 parentHeight = clc.getMeasuredHeight();
712 }
713
714 int left = Math.min(Math.max(0, centeredLeft), parentWidth - width);
715 int top = Math.min(Math.max(0, centeredTop), parentHeight - height);
716
717 int folderPivotX = width / 2 + (centeredLeft - left);
718 int folderPivotY = height / 2 + (centeredTop - top);
719 setPivotX(folderPivotX);
720 setPivotY(folderPivotY);
721 int folderIconPivotX = (int) (mFolderIcon.getMeasuredWidth() *
722 (1.0f * folderPivotX / width));
723 int folderIconPivotY = (int) (mFolderIcon.getMeasuredHeight() *
724 (1.0f * folderPivotY / height));
725 mFolderIcon.setPivotX(folderIconPivotX);
726 mFolderIcon.setPivotY(folderIconPivotY);
727
728 if (mMode == PARTIAL_GROW) {
729 lp.width = width;
730 lp.height = height;
731 lp.x = left;
732 lp.y = top;
733 } else {
734 mNewSize.set(left, top, left + width, top + height);
735 }
736 }
737
738 private void setupContentForNumItems(int count) {
Adam Cohen2801caf2011-05-13 20:57:39 -0700739 setupContentDimension(count);
740
741 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) getLayoutParams();
742 if (lp == null) {
743 lp = new CellLayout.LayoutParams(0, 0, -1, -1);
744 lp.isLockedToGrid = false;
745 setLayoutParams(lp);
746 }
747 centerAboutIcon();
748 }
749
750 private void arrangeChildren(ArrayList<View> list) {
751 int[] vacant = new int[2];
752 if (list == null) {
753 list = getItemsInReadingOrder();
754 }
755 mContent.removeAllViews();
756
757 for (int i = 0; i < list.size(); i++) {
758 View v = list.get(i);
759 mContent.getVacantCell(vacant, 1, 1);
760 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) v.getLayoutParams();
761 lp.cellX = vacant[0];
762 lp.cellY = vacant[1];
763 ItemInfo info = (ItemInfo) v.getTag();
764 info.cellX = vacant[0];
765 info.cellY = vacant[1];
766 boolean insert = false;
767 mContent.addViewToCellLayout(v, insert ? 0 : -1, (int)info.id, lp, true);
Adam Cohen7c693212011-05-18 15:26:57 -0700768 LauncherModel.addOrMoveItemInDatabase(mLauncher, info, mInfo.id, 0,
769 info.cellX, info.cellY);
Adam Cohen2801caf2011-05-13 20:57:39 -0700770 }
Adam Cohen7c693212011-05-18 15:26:57 -0700771 mItemsInvalidated = true;
Adam Cohen2801caf2011-05-13 20:57:39 -0700772 }
773
Adam Cohena9cf38f2011-05-02 15:36:58 -0700774 public int getItemCount() {
775 return mContent.getChildrenLayout().getChildCount();
776 }
777
778 public View getItemAt(int index) {
779 return mContent.getChildrenLayout().getChildAt(index);
780 }
781
Adam Cohen2801caf2011-05-13 20:57:39 -0700782 private void onCloseComplete() {
783 if (mRearrangeOnClose) {
784 setupContentForNumItems(getItemCount());
785 mRearrangeOnClose = false;
786 }
Adam Cohenafb01ee2011-06-23 15:38:03 -0700787 if (getItemCount() <= 1) {
788 replaceFolderWithFinalItem();
789 }
790 }
791
792 private void replaceFolderWithFinalItem() {
793 ItemInfo finalItem = null;
794
795 if (getItemCount() == 1) {
796 finalItem = mInfo.contents.get(0);
797 }
798
799 // Remove the folder completely
800 final CellLayout cellLayout = (CellLayout)
801 mLauncher.getWorkspace().getChildAt(mInfo.screen);
802 cellLayout.removeView(mFolderIcon);
803 if (mFolderIcon instanceof DropTarget) {
804 mDragController.removeDropTarget((DropTarget) mFolderIcon);
805 }
806 mLauncher.removeFolder(mInfo);
807
808 if (finalItem != null) {
809 LauncherModel.addOrMoveItemInDatabase(mLauncher, finalItem,
810 LauncherSettings.Favorites.CONTAINER_DESKTOP, mInfo.screen,
811 mInfo.cellX, mInfo.cellY);
812 }
813 LauncherModel.deleteFolderContentsFromDatabase(mLauncher, mInfo, true);
814
815 // Add the last remaining child to the workspace in place of the folder
816 if (finalItem != null) {
817 View child = mLauncher.createShortcut(R.layout.application, cellLayout,
818 (ShortcutInfo) finalItem);
819
820 mLauncher.getWorkspace().addInScreen(child, mInfo.screen, mInfo.cellX, mInfo.cellY,
821 mInfo.spanX, mInfo.spanY);
822 }
Adam Cohen2801caf2011-05-13 20:57:39 -0700823 }
824
Adam Cohenbfbfd262011-06-13 16:55:12 -0700825 public void onDrop(DragObject d) {
826 ShortcutInfo item;
827 if (d.dragInfo instanceof ApplicationInfo) {
828 // Came from all apps -- make a copy
829 item = ((ApplicationInfo) d.dragInfo).makeShortcut();
830 item.spanX = 1;
831 item.spanY = 1;
832 } else {
833 item = (ShortcutInfo) d.dragInfo;
834 }
835 // Dragged from self onto self
836 if (item == mCurrentDragInfo) {
837 ShortcutInfo si = (ShortcutInfo) mCurrentDragView.getTag();
838 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) mCurrentDragView.getLayoutParams();
839 si.cellX = lp.cellX = mEmptyCell[0];
840 si.cellX = lp.cellY = mEmptyCell[1];
841 mContent.addViewToCellLayout(mCurrentDragView, -1, (int)item.id, lp, true);
842 mSuppressOnAdd = true;
843 mItemsInvalidated = true;
844 setupContentDimension(getItemCount());
845 }
846 mInfo.add(item);
847 }
848
849 public void onAdd(ShortcutInfo item) {
850 mItemsInvalidated = true;
851 if (mSuppressOnAdd) return;
852 if (!findAndSetEmptyCells(item)) {
853 // The current layout is full, can we expand it?
854 setupContentForNumItems(getItemCount() + 1);
855 findAndSetEmptyCells(item);
856 }
857 createAndAddShortcut(item);
858 LauncherModel.addOrMoveItemInDatabase(
859 mLauncher, item, mInfo.id, 0, item.cellX, item.cellY);
860 }
861
Adam Cohena9cf38f2011-05-02 15:36:58 -0700862 public void onRemove(ShortcutInfo item) {
Adam Cohen7c693212011-05-18 15:26:57 -0700863 mItemsInvalidated = true;
Adam Cohenbfbfd262011-06-13 16:55:12 -0700864 if (item == mCurrentDragInfo) return;
Adam Cohena9cf38f2011-05-02 15:36:58 -0700865 View v = mContent.getChildAt(mDragItemPosition[0], mDragItemPosition[1]);
866 mContent.removeView(v);
Adam Cohen2801caf2011-05-13 20:57:39 -0700867 if (mState == STATE_ANIMATING) {
868 mRearrangeOnClose = true;
869 } else {
870 setupContentForNumItems(getItemCount());
871 }
Adam Cohenafb01ee2011-06-23 15:38:03 -0700872 if (getItemCount() <= 1) {
873 replaceFolderWithFinalItem();
874 }
Adam Cohena9cf38f2011-05-02 15:36:58 -0700875 }
Adam Cohen7c693212011-05-18 15:26:57 -0700876
Adam Cohen76078c42011-06-09 15:06:52 -0700877 public void onItemsChanged() {
878 }
Adam Cohen76fc0852011-06-17 13:26:23 -0700879 public void onTitleChanged(CharSequence title) {
880 }
Adam Cohen76078c42011-06-09 15:06:52 -0700881
Adam Cohen7c693212011-05-18 15:26:57 -0700882 public ArrayList<View> getItemsInReadingOrder() {
Adam Cohen76078c42011-06-09 15:06:52 -0700883 return getItemsInReadingOrder(true);
884 }
885
886 public ArrayList<View> getItemsInReadingOrder(boolean includeCurrentDragItem) {
Adam Cohen7c693212011-05-18 15:26:57 -0700887 if (mItemsInvalidated) {
888 mItemsInReadingOrder.clear();
889 for (int j = 0; j < mContent.getCountY(); j++) {
890 for (int i = 0; i < mContent.getCountX(); i++) {
891 View v = mContent.getChildAt(i, j);
892 if (v != null) {
Adam Cohen76078c42011-06-09 15:06:52 -0700893 ShortcutInfo info = (ShortcutInfo) v.getTag();
894 if (info != mCurrentDragInfo || includeCurrentDragItem) {
895 mItemsInReadingOrder.add(v);
896 }
Adam Cohen7c693212011-05-18 15:26:57 -0700897 }
898 }
899 }
900 mItemsInvalidated = false;
901 }
902 return mItemsInReadingOrder;
903 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800904}