blob: 960fa551c42676e4ae1a75187d34c00dad395781 [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;
102
103 private boolean mIsEditingName = false;
104 private InputMethodManager mInputMethodManager;
Adam Cohendf2cc412011-04-27 16:56:57 -0700105
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800106 /**
107 * Used to inflate the Workspace from XML.
108 *
109 * @param context The application's context.
110 * @param attrs The attribtues set containing the Workspace's customization values.
111 */
112 public Folder(Context context, AttributeSet attrs) {
113 super(context, attrs);
114 setAlwaysDrawnWithCacheEnabled(false);
Adam Cohendf2cc412011-04-27 16:56:57 -0700115 mInflater = LayoutInflater.from(context);
116 mIconCache = ((LauncherApplication)context.getApplicationContext()).getIconCache();
Adam Cohen2801caf2011-05-13 20:57:39 -0700117 mMaxCountX = LauncherModel.getCellCountX() - 1;
118 mMaxCountY = LauncherModel.getCellCountY() - 1;
Adam Cohen76fc0852011-06-17 13:26:23 -0700119
120 mInputMethodManager = (InputMethodManager)
121 mContext.getSystemService(Context.INPUT_METHOD_SERVICE);
122
123 Resources res = getResources();
124 mExpandDuration = res.getInteger(R.integer.config_folderAnimDuration);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800125 }
126
127 @Override
128 protected void onFinishInflate() {
129 super.onFinishInflate();
Adam Cohendf2cc412011-04-27 16:56:57 -0700130 mContent = (CellLayout) findViewById(R.id.folder_content);
Adam Cohen2801caf2011-05-13 20:57:39 -0700131 mContent.setGridSize(0, 0);
132 mContent.enableHardwareLayers();
Adam Cohen76fc0852011-06-17 13:26:23 -0700133 mFolderName = (TextView) findViewById(R.id.folder_name);
134
135 // We find out how tall the text view wants to be (it is set to wrap_content), so that
136 // we can allocate the appropriate amount of space for it.
137 int measureSpec = MeasureSpec.UNSPECIFIED;
138 mFolderName.measure(measureSpec, measureSpec);
139 mFolderNameHeight = mFolderName.getMeasuredHeight();
140
141 // We disable action mode for now since it messes up the view on phones
142 mFolderName.setCustomSelectionActionModeCallback(mActionModeCallback);
143 mFolderName.setCursorVisible(false);
144 mFolderName.setOnEditorActionListener(this);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800145 }
Adam Cohen2801caf2011-05-13 20:57:39 -0700146
Adam Cohen76fc0852011-06-17 13:26:23 -0700147 private ActionMode.Callback mActionModeCallback = new ActionMode.Callback() {
148 public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
149 return false;
150 }
151
152 public boolean onCreateActionMode(ActionMode mode, Menu menu) {
153 return false;
154 }
155
156 public void onDestroyActionMode(ActionMode mode) {
157 }
158
159 public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
160 return false;
161 }
162 };
163
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800164 public void onItemClick(AdapterView parent, View v, int position, long id) {
Joe Onorato0589f0f2010-02-08 13:44:00 -0800165 ShortcutInfo app = (ShortcutInfo) parent.getItemAtPosition(position);
Romain Guyfb5411e2010-02-24 10:04:17 -0800166 int[] pos = new int[2];
167 v.getLocationOnScreen(pos);
168 app.intent.setSourceBounds(new Rect(pos[0], pos[1],
169 pos[0] + v.getWidth(), pos[1] + v.getHeight()));
Joe Onoratof984e852010-03-25 09:47:45 -0700170 mLauncher.startActivitySafely(app.intent, app);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800171 }
172
173 public void onClick(View v) {
Adam Cohendf2cc412011-04-27 16:56:57 -0700174 Object tag = v.getTag();
175 if (tag instanceof ShortcutInfo) {
176 // refactor this code from Folder
177 ShortcutInfo item = (ShortcutInfo) tag;
178 int[] pos = new int[2];
179 v.getLocationOnScreen(pos);
180 item.intent.setSourceBounds(new Rect(pos[0], pos[1],
181 pos[0] + v.getWidth(), pos[1] + v.getHeight()));
182 mLauncher.startActivitySafely(item.intent, item);
Adam Cohendf2cc412011-04-27 16:56:57 -0700183 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800184 }
185
Adam Cohen76fc0852011-06-17 13:26:23 -0700186 private Rect mHitRect = new Rect();
187 public boolean onInterceptTouchEvent(MotionEvent ev) {
188 if (ev.getAction() == MotionEvent.ACTION_DOWN) {
189 mFolderName.getHitRect(mHitRect);
190 if (mHitRect.contains((int) ev.getX(), (int) ev.getY()) && !mIsEditingName) {
191 startEditingFolderName();
192 }
193 }
194 return false;
195 }
196
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800197 public boolean onLongClick(View v) {
Adam Cohendf2cc412011-04-27 16:56:57 -0700198 Object tag = v.getTag();
199 if (tag instanceof ShortcutInfo) {
Adam Cohendf2cc412011-04-27 16:56:57 -0700200 ShortcutInfo item = (ShortcutInfo) tag;
201 if (!v.isInTouchMode()) {
202 return false;
203 }
204
205 mLauncher.getWorkspace().onDragStartedWithItem(v);
206 mDragController.startDrag(v, this, item, DragController.DRAG_ACTION_COPY);
Adam Cohena9cf38f2011-05-02 15:36:58 -0700207 mDragItemPosition[0] = item.cellX;
208 mDragItemPosition[1] = item.cellY;
Adam Cohenbadf71e2011-05-26 19:08:29 -0700209 mIconDrawable = ((TextView) v).getCompoundDrawables()[1];
Adam Cohen76078c42011-06-09 15:06:52 -0700210
211 mCurrentDragInfo = item;
Adam Cohenbfbfd262011-06-13 16:55:12 -0700212 mEmptyCell[0] = item.cellX;
213 mEmptyCell[1] = item.cellY;
214 mCurrentDragView = v;
215 mContent.removeView(mCurrentDragView);
216 mInfo.remove(item);
Adam Cohendf2cc412011-04-27 16:56:57 -0700217 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800218 return true;
219 }
220
Adam Cohen76fc0852011-06-17 13:26:23 -0700221 public boolean isEditingName() {
222 return mIsEditingName;
223 }
224
225 public void startEditingFolderName() {
226 mFolderName.setCursorVisible(true);
227 mIsEditingName = true;
228 }
229
230 public void dismissEditingName() {
231 mInputMethodManager.hideSoftInputFromWindow(getWindowToken(), 0);
232 doneEditingFolderName(true);
233 }
234
235 public void doneEditingFolderName(boolean commit) {
236 mInfo.setTitle(mFolderName.getText());
237 LauncherModel.updateItemInDatabase(mLauncher, mInfo);
238 mFolderName.setCursorVisible(false);
239 mFolderName.clearFocus();
240 mIsEditingName = false;
241 }
242
243 public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
244 if (actionId == EditorInfo.IME_ACTION_DONE) {
245 dismissEditingName();
246 return true;
247 }
248 return false;
249 }
250
251 public View getEditTextRegion() {
252 return mFolderName;
253 }
254
Adam Cohenbadf71e2011-05-26 19:08:29 -0700255 public Drawable getDragDrawable() {
256 return mIconDrawable;
257 }
258
Adam Cohen0c872ba2011-05-05 10:34:16 -0700259 /**
260 * We need to handle touch events to prevent them from falling through to the workspace below.
261 */
262 @Override
263 public boolean onTouchEvent(MotionEvent ev) {
264 return true;
265 }
266
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800267 public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
268 if (!view.isInTouchMode()) {
269 return false;
270 }
271
Joe Onorato0589f0f2010-02-08 13:44:00 -0800272 ShortcutInfo app = (ShortcutInfo) parent.getItemAtPosition(position);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800273
Joe Onorato00acb122009-08-04 16:04:30 -0400274 mDragController.startDrag(view, this, app, DragController.DRAG_ACTION_COPY);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800275 mLauncher.closeFolder(this);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800276 return true;
277 }
278
Joe Onorato00acb122009-08-04 16:04:30 -0400279 public void setDragController(DragController dragController) {
280 mDragController = dragController;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800281 }
282
Patrick Dubroya669d792010-11-23 14:40:33 -0800283 public void onDragViewVisible() {
284 }
285
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800286 void setLauncher(Launcher launcher) {
287 mLauncher = launcher;
288 }
Adam Cohen2801caf2011-05-13 20:57:39 -0700289
290 void setFolderIcon(FolderIcon icon) {
291 mFolderIcon = icon;
292 }
293
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800294 /**
295 * @return the FolderInfo object associated with this folder
296 */
297 FolderInfo getInfo() {
298 return mInfo;
299 }
300
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800301 void onOpen() {
Adam Cohendf2cc412011-04-27 16:56:57 -0700302 // When the folder opens, we need to refresh the GridView's selection by
303 // forcing a layout
304 // TODO: find out if this is still necessary
305 mContent.requestLayout();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800306 }
307
308 void onClose() {
Adam Cohen76fc0852011-06-17 13:26:23 -0700309 CellLayoutChildren clc = (CellLayoutChildren) getParent();
310 final CellLayout cellLayout = (CellLayout) clc.getParent();
311 cellLayout.removeViewWithoutMarkingCells(Folder.this);
312 clearFocus();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800313 }
314
315 void bind(FolderInfo info) {
316 mInfo = info;
Adam Cohendf2cc412011-04-27 16:56:57 -0700317 ArrayList<ShortcutInfo> children = info.contents;
Adam Cohen7c693212011-05-18 15:26:57 -0700318 setupContentForNumItems(children.size());
Adam Cohendf2cc412011-04-27 16:56:57 -0700319 for (int i = 0; i < children.size(); i++) {
320 ShortcutInfo child = (ShortcutInfo) children.get(i);
Adam Cohen7c693212011-05-18 15:26:57 -0700321 createAndAddShortcut(child);
Adam Cohendf2cc412011-04-27 16:56:57 -0700322 }
Adam Cohen4dbe6d92011-05-18 17:14:15 -0700323 mItemsInvalidated = true;
Adam Cohena9cf38f2011-05-02 15:36:58 -0700324 mInfo.addListener(this);
Adam Cohen76fc0852011-06-17 13:26:23 -0700325 mFolderName.setText(mInfo.title);
Adam Cohendf2cc412011-04-27 16:56:57 -0700326 }
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 CellLayoutChildren)) return;
345
Adam Cohen2801caf2011-05-13 20:57:39 -0700346 CellLayout.LayoutParams iconLp = (CellLayout.LayoutParams) mFolderIcon.getLayoutParams();
Adam Cohendf2cc412011-04-27 16:56:57 -0700347 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) getLayoutParams();
348
Adam Cohen2801caf2011-05-13 20:57:39 -0700349 if (mMode == PARTIAL_GROW) {
350 setScaleX(0.8f);
351 setScaleY(0.8f);
352 setAlpha(0f);
353 } else {
354 lp.width = iconLp.width;
355 lp.height = iconLp.height;
356 lp.x = iconLp.x;
357 lp.y = iconLp.y;
358 mContent.setAlpha(0);
359 }
Adam Cohendf2cc412011-04-27 16:56:57 -0700360 mState = STATE_SMALL;
361 }
362
363 public void animateOpen() {
364 if (mState != STATE_SMALL) {
365 positionAndSizeAsIcon();
366 }
367 if (!(getParent() instanceof CellLayoutChildren)) return;
368
Adam Cohen2801caf2011-05-13 20:57:39 -0700369 ObjectAnimator oa;
Adam Cohendf2cc412011-04-27 16:56:57 -0700370 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) getLayoutParams();
371
Adam Cohen2801caf2011-05-13 20:57:39 -0700372 centerAboutIcon();
373 if (mMode == PARTIAL_GROW) {
374 PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 1);
375 PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", 1.0f);
376 PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat("scaleY", 1.0f);
377 oa = ObjectAnimator.ofPropertyValuesHolder(this, alpha, scaleX, scaleY);
378 } else {
379 PropertyValuesHolder width = PropertyValuesHolder.ofInt("width", mNewSize.width());
380 PropertyValuesHolder height = PropertyValuesHolder.ofInt("height", mNewSize.height());
381 PropertyValuesHolder x = PropertyValuesHolder.ofInt("x", mNewSize.left);
382 PropertyValuesHolder y = PropertyValuesHolder.ofInt("y", mNewSize.top);
383 oa = ObjectAnimator.ofPropertyValuesHolder(lp, width, height, x, y);
384 oa.addUpdateListener(new AnimatorUpdateListener() {
385 public void onAnimationUpdate(ValueAnimator animation) {
386 requestLayout();
387 }
388 });
Adam Cohendf2cc412011-04-27 16:56:57 -0700389
Adam Cohen2801caf2011-05-13 20:57:39 -0700390 PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 1.0f);
391 ObjectAnimator alphaOa = ObjectAnimator.ofPropertyValuesHolder(mContent, alpha);
392 alphaOa.setDuration(mExpandDuration);
393 alphaOa.setInterpolator(new AccelerateInterpolator(2.0f));
394 alphaOa.start();
395 }
Adam Cohendf2cc412011-04-27 16:56:57 -0700396
Adam Cohen2801caf2011-05-13 20:57:39 -0700397 oa.addListener(new AnimatorListenerAdapter() {
Adam Cohendf2cc412011-04-27 16:56:57 -0700398 @Override
399 public void onAnimationStart(Animator animation) {
400 mState = STATE_ANIMATING;
401 }
402 @Override
403 public void onAnimationEnd(Animator animation) {
Adam Cohen2801caf2011-05-13 20:57:39 -0700404 mState = STATE_OPEN;
Adam Cohendf2cc412011-04-27 16:56:57 -0700405 }
406 });
Adam Cohen2801caf2011-05-13 20:57:39 -0700407 oa.setDuration(mExpandDuration);
408 oa.start();
Adam Cohendf2cc412011-04-27 16:56:57 -0700409 }
410
411 public void animateClosed() {
412 if (!(getParent() instanceof CellLayoutChildren)) return;
413
Adam Cohen2801caf2011-05-13 20:57:39 -0700414 ObjectAnimator oa;
Adam Cohen2801caf2011-05-13 20:57:39 -0700415 if (mMode == PARTIAL_GROW) {
416 PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 0);
417 PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", 0.9f);
418 PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat("scaleY", 0.9f);
419 oa = ObjectAnimator.ofPropertyValuesHolder(this, alpha, scaleX, scaleY);
420 } else {
421 CellLayout.LayoutParams iconLp = (CellLayout.LayoutParams) mFolderIcon.getLayoutParams();
422 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) getLayoutParams();
Adam Cohendf2cc412011-04-27 16:56:57 -0700423
Adam Cohen2801caf2011-05-13 20:57:39 -0700424 PropertyValuesHolder width = PropertyValuesHolder.ofInt("width", iconLp.width);
425 PropertyValuesHolder height = PropertyValuesHolder.ofInt("height", iconLp.height);
426 PropertyValuesHolder x = PropertyValuesHolder.ofInt("x",iconLp.x);
427 PropertyValuesHolder y = PropertyValuesHolder.ofInt("y", iconLp.y);
428 oa = ObjectAnimator.ofPropertyValuesHolder(lp, width, height, x, y);
429 oa.addUpdateListener(new AnimatorUpdateListener() {
430 public void onAnimationUpdate(ValueAnimator animation) {
431 requestLayout();
432 }
433 });
Adam Cohendf2cc412011-04-27 16:56:57 -0700434
Adam Cohen2801caf2011-05-13 20:57:39 -0700435 PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 0f);
436 ObjectAnimator alphaOa = ObjectAnimator.ofPropertyValuesHolder(mContent, alpha);
437 alphaOa.setDuration(mExpandDuration);
438 alphaOa.setInterpolator(new DecelerateInterpolator(2.0f));
439 alphaOa.start();
440 }
Adam Cohendf2cc412011-04-27 16:56:57 -0700441
Adam Cohen2801caf2011-05-13 20:57:39 -0700442 oa.addListener(new AnimatorListenerAdapter() {
Adam Cohendf2cc412011-04-27 16:56:57 -0700443 @Override
444 public void onAnimationEnd(Animator animation) {
Adam Cohen76fc0852011-06-17 13:26:23 -0700445 onClose();
Adam Cohen2801caf2011-05-13 20:57:39 -0700446 onCloseComplete();
Adam Cohen2801caf2011-05-13 20:57:39 -0700447 mState = STATE_SMALL;
Adam Cohendf2cc412011-04-27 16:56:57 -0700448 }
449 @Override
450 public void onAnimationStart(Animator animation) {
451 mState = STATE_ANIMATING;
452 }
453 });
Adam Cohen2801caf2011-05-13 20:57:39 -0700454 oa.setDuration(mExpandDuration);
455 oa.start();
Adam Cohendf2cc412011-04-27 16:56:57 -0700456 }
457
458 void notifyDataSetChanged() {
459 // recreate all the children if the data set changes under us. We may want to do this more
460 // intelligently (ie just removing the views that should no longer exist)
461 mContent.removeAllViewsInLayout();
462 bind(mInfo);
463 }
464
Adam Cohencb3382b2011-05-24 14:07:08 -0700465 public boolean acceptDrop(DragObject d) {
466 final ItemInfo item = (ItemInfo) d.dragInfo;
Adam Cohendf2cc412011-04-27 16:56:57 -0700467 final int itemType = item.itemType;
Adam Cohen2801caf2011-05-13 20:57:39 -0700468 return ((itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION ||
469 itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) &&
470 !isFull());
Adam Cohendf2cc412011-04-27 16:56:57 -0700471 }
472
Adam Cohendf2cc412011-04-27 16:56:57 -0700473 protected boolean findAndSetEmptyCells(ShortcutInfo item) {
474 int[] emptyCell = new int[2];
475 if (mContent.findCellForSpan(emptyCell, item.spanX, item.spanY)) {
476 item.cellX = emptyCell[0];
477 item.cellY = emptyCell[1];
Adam Cohendf2cc412011-04-27 16:56:57 -0700478 return true;
479 } else {
480 return false;
481 }
482 }
483
484 protected void createAndAddShortcut(ShortcutInfo item) {
485 final TextView textView =
486 (TextView) mInflater.inflate(R.layout.application_boxed, this, false);
487 textView.setCompoundDrawablesWithIntrinsicBounds(null,
488 new FastBitmapDrawable(item.getIcon(mIconCache)), null, null);
489 textView.setText(item.title);
490 textView.setTag(item);
491
492 textView.setOnClickListener(this);
493 textView.setOnLongClickListener(this);
494
495 CellLayout.LayoutParams lp =
496 new CellLayout.LayoutParams(item.cellX, item.cellY, item.spanX, item.spanY);
497 boolean insert = false;
498 mContent.addViewToCellLayout(textView, insert ? 0 : -1, (int)item.id, lp, true);
499 }
500
Adam Cohencb3382b2011-05-24 14:07:08 -0700501 public void onDragEnter(DragObject d) {
Adam Cohenbfbfd262011-06-13 16:55:12 -0700502 mPreviousTargetCell[0] = -1;
503 mPreviousTargetCell[1] = -1;
Adam Cohen2801caf2011-05-13 20:57:39 -0700504 mContent.onDragEnter();
Adam Cohenbfbfd262011-06-13 16:55:12 -0700505 mOnExitAlarm.cancelAlarm();
506 }
507
508 OnAlarmListener mReorderAlarmListener = new OnAlarmListener() {
509 public void onAlarm(Alarm alarm) {
510 realTimeReorder(mEmptyCell, mTargetCell);
511 }
512 };
513
514 boolean readingOrderGreaterThan(int[] v1, int[] v2) {
515 if (v1[1] > v2[1] || (v1[1] == v2[1] && v1[0] > v2[0])) {
516 return true;
517 } else {
518 return false;
519 }
520 }
521
522 private void realTimeReorder(int[] empty, int[] target) {
523 boolean wrap;
524 int startX;
525 int endX;
526 int startY;
Adam Cohen76fc0852011-06-17 13:26:23 -0700527 int delay = 0;
528 float delayAmount = 30;
Adam Cohenbfbfd262011-06-13 16:55:12 -0700529 if (readingOrderGreaterThan(target, empty)) {
530 wrap = empty[0] >= mContent.getCountX() - 1;
531 startY = wrap ? empty[1] + 1 : empty[1];
532 for (int y = startY; y <= target[1]; y++) {
533 startX = y == empty[1] ? empty[0] + 1 : 0;
534 endX = y < target[1] ? mContent.getCountX() - 1 : target[0];
535 for (int x = startX; x <= endX; x++) {
536 View v = mContent.getChildAt(x,y);
537 if (mContent.animateChildToPosition(v, empty[0], empty[1],
Adam Cohen76fc0852011-06-17 13:26:23 -0700538 REORDER_ANIMATION_DURATION, delay)) {
Adam Cohenbfbfd262011-06-13 16:55:12 -0700539 empty[0] = x;
540 empty[1] = y;
Adam Cohen76fc0852011-06-17 13:26:23 -0700541 delay += delayAmount;
542 delayAmount *= 0.9;
Adam Cohenbfbfd262011-06-13 16:55:12 -0700543 }
544 }
545 }
546 } else {
547 wrap = empty[0] == 0;
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 : mContent.getCountX() - 1;
551 endX = y > target[1] ? 0 : 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],
Adam Cohen76fc0852011-06-17 13:26:23 -0700555 REORDER_ANIMATION_DURATION, delay)) {
Adam Cohenbfbfd262011-06-13 16:55:12 -0700556 empty[0] = x;
557 empty[1] = y;
Adam Cohen76fc0852011-06-17 13:26:23 -0700558 delay += delayAmount;
559 delayAmount *= 0.9;
Adam Cohenbfbfd262011-06-13 16:55:12 -0700560 }
561 }
562 }
563 }
Adam Cohendf2cc412011-04-27 16:56:57 -0700564 }
565
Adam Cohencb3382b2011-05-24 14:07:08 -0700566 public void onDragOver(DragObject d) {
Adam Cohenbfbfd262011-06-13 16:55:12 -0700567 float[] r = getDragViewVisualCenter(d.x, d.y, d.xOffset, d.yOffset, d.dragView, null);
568 mTargetCell = mContent.findNearestArea((int) r[0], (int) r[1], 1, 1, mTargetCell);
569
570 if (mTargetCell[0] != mPreviousTargetCell[0] || mTargetCell[1] != mPreviousTargetCell[1]) {
571 mReorderAlarm.cancelAlarm();
572 mReorderAlarm.setOnAlarmListener(mReorderAlarmListener);
573 mReorderAlarm.setAlarm(150);
574 mPreviousTargetCell[0] = mTargetCell[0];
575 mPreviousTargetCell[1] = mTargetCell[1];
576 }
Adam Cohendf2cc412011-04-27 16:56:57 -0700577 }
578
Adam Cohenbfbfd262011-06-13 16:55:12 -0700579 // This is used to compute the visual center of the dragView. The idea is that
580 // the visual center represents the user's interpretation of where the item is, and hence
581 // is the appropriate point to use when determining drop location.
582 private float[] getDragViewVisualCenter(int x, int y, int xOffset, int yOffset,
583 DragView dragView, float[] recycle) {
584 float res[];
585 if (recycle == null) {
586 res = new float[2];
587 } else {
588 res = recycle;
589 }
590
591 // These represent the visual top and left of drag view if a dragRect was provided.
592 // If a dragRect was not provided, then they correspond to the actual view left and
593 // top, as the dragRect is in that case taken to be the entire dragView.
594 // R.dimen.dragViewOffsetY.
595 int left = x - xOffset;
596 int top = y - yOffset;
597
598 // In order to find the visual center, we shift by half the dragRect
599 res[0] = left + dragView.getDragRegion().width() / 2;
600 res[1] = top + dragView.getDragRegion().height() / 2;
601
602 return res;
603 }
604
605 OnAlarmListener mOnExitAlarmListener = new OnAlarmListener() {
606 public void onAlarm(Alarm alarm) {
607 mLauncher.closeFolder(Folder.this);
608 mCurrentDragInfo = null;
609 mCurrentDragView = null;
610 mSuppressOnAdd = false;
611 mRearrangeOnClose = true;
612 }
613 };
614
Adam Cohencb3382b2011-05-24 14:07:08 -0700615 public void onDragExit(DragObject d) {
Adam Cohenbfbfd262011-06-13 16:55:12 -0700616 // We only close the folder if this is a true drag exit, ie. not because a drop
617 // has occurred above the folder.
618 if (!d.dragComplete) {
619 mOnExitAlarm.setOnAlarmListener(mOnExitAlarmListener);
620 mOnExitAlarm.setAlarm(ON_EXIT_CLOSE_DELAY);
621 }
622 mReorderAlarm.cancelAlarm();
Adam Cohen2801caf2011-05-13 20:57:39 -0700623 mContent.onDragExit();
624 }
625
Adam Cohenc0dcf592011-06-01 15:30:43 -0700626 public void onDropCompleted(View target, DragObject d, boolean success) {
Adam Cohenbfbfd262011-06-13 16:55:12 -0700627 mCurrentDragInfo = null;
628 mCurrentDragView = null;
629 mSuppressOnAdd = false;
Adam Cohen76078c42011-06-09 15:06:52 -0700630 if (!success) {
631 if (d.dragView != null) {
632 if (target instanceof CellLayout) {
633 // TODO: we should animate the item back to the folder in this case
634 }
635 }
Adam Cohenbfbfd262011-06-13 16:55:12 -0700636 // TODO: if the drag fails, we need to re-add the item
Adam Cohen76078c42011-06-09 15:06:52 -0700637 }
Adam Cohendf2cc412011-04-27 16:56:57 -0700638 }
639
640 public boolean isDropEnabled() {
641 return true;
642 }
643
Adam Cohencb3382b2011-05-24 14:07:08 -0700644 public DropTarget getDropTargetDelegate(DragObject d) {
Adam Cohendf2cc412011-04-27 16:56:57 -0700645 return null;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800646 }
Adam Cohena9cf38f2011-05-02 15:36:58 -0700647
Adam Cohen2801caf2011-05-13 20:57:39 -0700648 private void setupContentDimension(int count) {
649 ArrayList<View> list = getItemsInReadingOrder();
650
651 int countX = mContent.getCountX();
652 int countY = mContent.getCountY();
Adam Cohen7c693212011-05-18 15:26:57 -0700653 boolean done = false;
Adam Cohen2801caf2011-05-13 20:57:39 -0700654
Adam Cohen7c693212011-05-18 15:26:57 -0700655 while (!done) {
656 int oldCountX = countX;
657 int oldCountY = countY;
658 if (countX * countY < count) {
659 // Current grid is too small, expand it
660 if (countX <= countY && countX < mMaxCountX) {
661 countX++;
662 } else if (countY < mMaxCountY) {
663 countY++;
664 }
665 if (countY == 0) countY++;
666 } else if ((countY - 1) * countX >= count && countY >= countX) {
667 countY = Math.max(0, countY - 1);
668 } else if ((countX - 1) * countY >= count) {
669 countX = Math.max(0, countX - 1);
Adam Cohen2801caf2011-05-13 20:57:39 -0700670 }
Adam Cohen7c693212011-05-18 15:26:57 -0700671 done = countX == oldCountX && countY == oldCountY;
Adam Cohen2801caf2011-05-13 20:57:39 -0700672 }
Adam Cohen7c693212011-05-18 15:26:57 -0700673 mContent.setGridSize(countX, countY);
Adam Cohen2801caf2011-05-13 20:57:39 -0700674 arrangeChildren(list);
675 }
676
677 public boolean isFull() {
678 return getItemCount() >= mMaxCountX * mMaxCountY;
679 }
680
681 private void centerAboutIcon() {
682 CellLayout.LayoutParams iconLp = (CellLayout.LayoutParams) mFolderIcon.getLayoutParams();
683 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) getLayoutParams();
684
685 int width = getPaddingLeft() + getPaddingRight() + mContent.getDesiredWidth();
Adam Cohen76fc0852011-06-17 13:26:23 -0700686 // Technically there is no padding at the bottom, but we add space equal to the padding
687 // and have to account for that here.
688 int height = getPaddingTop() + mContent.getDesiredHeight() + mFolderNameHeight;
Adam Cohen2801caf2011-05-13 20:57:39 -0700689
690 int centerX = iconLp.x + iconLp.width / 2;
691 int centerY = iconLp.y + iconLp.height / 2;
692 int centeredLeft = centerX - width / 2;
693 int centeredTop = centerY - height / 2;
694
695 CellLayoutChildren clc = (CellLayoutChildren) getParent();
696 int parentWidth = 0;
697 int parentHeight = 0;
698 if (clc != null) {
699 parentWidth = clc.getMeasuredWidth();
700 parentHeight = clc.getMeasuredHeight();
701 }
702
703 int left = Math.min(Math.max(0, centeredLeft), parentWidth - width);
704 int top = Math.min(Math.max(0, centeredTop), parentHeight - height);
705
706 int folderPivotX = width / 2 + (centeredLeft - left);
707 int folderPivotY = height / 2 + (centeredTop - top);
708 setPivotX(folderPivotX);
709 setPivotY(folderPivotY);
710 int folderIconPivotX = (int) (mFolderIcon.getMeasuredWidth() *
711 (1.0f * folderPivotX / width));
712 int folderIconPivotY = (int) (mFolderIcon.getMeasuredHeight() *
713 (1.0f * folderPivotY / height));
714 mFolderIcon.setPivotX(folderIconPivotX);
715 mFolderIcon.setPivotY(folderIconPivotY);
716
717 if (mMode == PARTIAL_GROW) {
718 lp.width = width;
719 lp.height = height;
720 lp.x = left;
721 lp.y = top;
722 } else {
723 mNewSize.set(left, top, left + width, top + height);
724 }
725 }
726
727 private void setupContentForNumItems(int count) {
Adam Cohen2801caf2011-05-13 20:57:39 -0700728 setupContentDimension(count);
729
730 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) getLayoutParams();
731 if (lp == null) {
732 lp = new CellLayout.LayoutParams(0, 0, -1, -1);
733 lp.isLockedToGrid = false;
734 setLayoutParams(lp);
735 }
736 centerAboutIcon();
737 }
738
739 private void arrangeChildren(ArrayList<View> list) {
740 int[] vacant = new int[2];
741 if (list == null) {
742 list = getItemsInReadingOrder();
743 }
744 mContent.removeAllViews();
745
746 for (int i = 0; i < list.size(); i++) {
747 View v = list.get(i);
748 mContent.getVacantCell(vacant, 1, 1);
749 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) v.getLayoutParams();
750 lp.cellX = vacant[0];
751 lp.cellY = vacant[1];
752 ItemInfo info = (ItemInfo) v.getTag();
753 info.cellX = vacant[0];
754 info.cellY = vacant[1];
755 boolean insert = false;
756 mContent.addViewToCellLayout(v, insert ? 0 : -1, (int)info.id, lp, true);
Adam Cohen7c693212011-05-18 15:26:57 -0700757 LauncherModel.addOrMoveItemInDatabase(mLauncher, info, mInfo.id, 0,
758 info.cellX, info.cellY);
Adam Cohen2801caf2011-05-13 20:57:39 -0700759 }
Adam Cohen7c693212011-05-18 15:26:57 -0700760 mItemsInvalidated = true;
Adam Cohen2801caf2011-05-13 20:57:39 -0700761 }
762
Adam Cohena9cf38f2011-05-02 15:36:58 -0700763 public int getItemCount() {
764 return mContent.getChildrenLayout().getChildCount();
765 }
766
767 public View getItemAt(int index) {
768 return mContent.getChildrenLayout().getChildAt(index);
769 }
770
Adam Cohen2801caf2011-05-13 20:57:39 -0700771 private void onCloseComplete() {
772 if (mRearrangeOnClose) {
773 setupContentForNumItems(getItemCount());
774 mRearrangeOnClose = false;
775 }
776 }
777
Adam Cohenbfbfd262011-06-13 16:55:12 -0700778 public void onDrop(DragObject d) {
779 ShortcutInfo item;
780 if (d.dragInfo instanceof ApplicationInfo) {
781 // Came from all apps -- make a copy
782 item = ((ApplicationInfo) d.dragInfo).makeShortcut();
783 item.spanX = 1;
784 item.spanY = 1;
785 } else {
786 item = (ShortcutInfo) d.dragInfo;
787 }
788 // Dragged from self onto self
789 if (item == mCurrentDragInfo) {
790 ShortcutInfo si = (ShortcutInfo) mCurrentDragView.getTag();
791 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) mCurrentDragView.getLayoutParams();
792 si.cellX = lp.cellX = mEmptyCell[0];
793 si.cellX = lp.cellY = mEmptyCell[1];
794 mContent.addViewToCellLayout(mCurrentDragView, -1, (int)item.id, lp, true);
795 mSuppressOnAdd = true;
796 mItemsInvalidated = true;
797 setupContentDimension(getItemCount());
798 }
799 mInfo.add(item);
800 }
801
802 public void onAdd(ShortcutInfo item) {
803 mItemsInvalidated = true;
804 if (mSuppressOnAdd) return;
805 if (!findAndSetEmptyCells(item)) {
806 // The current layout is full, can we expand it?
807 setupContentForNumItems(getItemCount() + 1);
808 findAndSetEmptyCells(item);
809 }
810 createAndAddShortcut(item);
811 LauncherModel.addOrMoveItemInDatabase(
812 mLauncher, item, mInfo.id, 0, item.cellX, item.cellY);
813 }
814
Adam Cohena9cf38f2011-05-02 15:36:58 -0700815 public void onRemove(ShortcutInfo item) {
Adam Cohen7c693212011-05-18 15:26:57 -0700816 mItemsInvalidated = true;
Adam Cohenbfbfd262011-06-13 16:55:12 -0700817 if (item == mCurrentDragInfo) return;
Adam Cohena9cf38f2011-05-02 15:36:58 -0700818 View v = mContent.getChildAt(mDragItemPosition[0], mDragItemPosition[1]);
819 mContent.removeView(v);
Adam Cohen2801caf2011-05-13 20:57:39 -0700820 if (mState == STATE_ANIMATING) {
821 mRearrangeOnClose = true;
822 } else {
823 setupContentForNumItems(getItemCount());
824 }
Adam Cohena9cf38f2011-05-02 15:36:58 -0700825 }
Adam Cohen7c693212011-05-18 15:26:57 -0700826
Adam Cohen76078c42011-06-09 15:06:52 -0700827 public void onItemsChanged() {
828 }
Adam Cohen76fc0852011-06-17 13:26:23 -0700829 public void onTitleChanged(CharSequence title) {
830 }
Adam Cohen76078c42011-06-09 15:06:52 -0700831
Adam Cohen7c693212011-05-18 15:26:57 -0700832 public ArrayList<View> getItemsInReadingOrder() {
Adam Cohen76078c42011-06-09 15:06:52 -0700833 return getItemsInReadingOrder(true);
834 }
835
836 public ArrayList<View> getItemsInReadingOrder(boolean includeCurrentDragItem) {
Adam Cohen7c693212011-05-18 15:26:57 -0700837 if (mItemsInvalidated) {
838 mItemsInReadingOrder.clear();
839 for (int j = 0; j < mContent.getCountY(); j++) {
840 for (int i = 0; i < mContent.getCountX(); i++) {
841 View v = mContent.getChildAt(i, j);
842 if (v != null) {
Adam Cohen76078c42011-06-09 15:06:52 -0700843 ShortcutInfo info = (ShortcutInfo) v.getTag();
844 if (info != mCurrentDragInfo || includeCurrentDragItem) {
845 mItemsInReadingOrder.add(v);
846 }
Adam Cohen7c693212011-05-18 15:26:57 -0700847 }
848 }
849 }
850 mItemsInvalidated = false;
851 }
852 return mItemsInReadingOrder;
853 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800854}