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 
17 package com.android.music;
18 
19 import android.content.Context;
20 import android.content.SharedPreferences;
21 import android.content.res.Resources;
22 import android.graphics.Bitmap;
23 import android.graphics.PixelFormat;
24 import android.graphics.Rect;
25 import android.graphics.drawable.Drawable;
26 import android.graphics.drawable.LevelListDrawable;
27 import android.util.AttributeSet;
28 import android.util.Log;
29 import android.view.GestureDetector;
30 import android.view.Gravity;
31 import android.view.MotionEvent;
32 import android.view.View;
33 import android.view.ViewConfiguration;
34 import android.view.ViewGroup;
35 import android.view.WindowManager;
36 import android.view.GestureDetector.SimpleOnGestureListener;
37 import android.widget.AdapterView;
38 import android.widget.ImageView;
39 import android.widget.ListView;
40 
41 public class TouchInterceptor extends ListView {
42 
43     private ImageView mDragView;
44     private WindowManager mWindowManager;
45     private WindowManager.LayoutParams mWindowParams;
46     /**
47      * At which position is the item currently being dragged. Note that this
48      * takes in to account header items.
49      */
50     private int mDragPos;
51     /**
52      * At which position was the item being dragged originally
53      */
54     private int mSrcDragPos;
55     private int mDragPointX;    // at what x offset inside the item did the user grab it
56     private int mDragPointY;    // at what y offset inside the item did the user grab it
57     private int mXOffset;  // the difference between screen coordinates and coordinates in this view
58     private int mYOffset;  // the difference between screen coordinates and coordinates in this view
59     private DragListener mDragListener;
60     private DropListener mDropListener;
61     private RemoveListener mRemoveListener;
62     private int mUpperBound;
63     private int mLowerBound;
64     private int mHeight;
65     private GestureDetector mGestureDetector;
66     private static final int FLING = 0;
67     private static final int SLIDE = 1;
68     private static final int TRASH = 2;
69     private int mRemoveMode = -1;
70     private Rect mTempRect = new Rect();
71     private Bitmap mDragBitmap;
72     private final int mTouchSlop;
73     private int mItemHeightNormal;
74     private int mItemHeightExpanded;
75     private int mItemHeightHalf;
76     private Drawable mTrashcan;
77 
TouchInterceptor(Context context, AttributeSet attrs)78     public TouchInterceptor(Context context, AttributeSet attrs) {
79         super(context, attrs);
80         SharedPreferences pref = context.getSharedPreferences("Music", 3);
81         mRemoveMode = pref.getInt("deletemode", -1);
82         mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
83         Resources res = getResources();
84         mItemHeightNormal = res.getDimensionPixelSize(R.dimen.normal_height);
85         mItemHeightHalf = mItemHeightNormal / 2;
86         mItemHeightExpanded = res.getDimensionPixelSize(R.dimen.expanded_height);
87     }
88 
89     @Override
onInterceptTouchEvent(MotionEvent ev)90     public boolean onInterceptTouchEvent(MotionEvent ev) {
91         if (mRemoveListener != null && mGestureDetector == null) {
92             if (mRemoveMode == FLING) {
93                 mGestureDetector = new GestureDetector(getContext(), new SimpleOnGestureListener() {
94                     @Override
95                     public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
96                             float velocityY) {
97                         if (mDragView != null) {
98                             if (velocityX > 1000) {
99                                 Rect r = mTempRect;
100                                 mDragView.getDrawingRect(r);
101                                 if ( e2.getX() > r.right * 2 / 3) {
102                                     // fast fling right with release near the right edge of the screen
103                                     stopDragging();
104                                     mRemoveListener.remove(mSrcDragPos);
105                                     unExpandViews(true);
106                                 }
107                             }
108                             // flinging while dragging should have no effect
109                             return true;
110                         }
111                         return false;
112                     }
113                 });
114             }
115         }
116         if (mDragListener != null || mDropListener != null) {
117             switch (ev.getAction()) {
118                 case MotionEvent.ACTION_DOWN:
119                     int x = (int) ev.getX();
120                     int y = (int) ev.getY();
121                     int itemnum = pointToPosition(x, y);
122                     if (itemnum == AdapterView.INVALID_POSITION) {
123                         break;
124                     }
125                     ViewGroup item = (ViewGroup) getChildAt(itemnum - getFirstVisiblePosition());
126                     mDragPointX = x - item.getLeft();
127                     mDragPointY = y - item.getTop();
128                     mXOffset = ((int)ev.getRawX()) - x;
129                     mYOffset = ((int)ev.getRawY()) - y;
130                     // The left side of the item is the grabber for dragging the item
131                     if (x < 64) {
132                         item.setDrawingCacheEnabled(true);
133                         // Create a copy of the drawing cache so that it does not get recycled
134                         // by the framework when the list tries to clean up memory
135                         Bitmap bitmap = Bitmap.createBitmap(item.getDrawingCache());
136                         startDragging(bitmap, x, y);
137                         mDragPos = itemnum;
138                         mSrcDragPos = mDragPos;
139                         mHeight = getHeight();
140                         int touchSlop = mTouchSlop;
141                         mUpperBound = Math.min(y - touchSlop, mHeight / 3);
142                         mLowerBound = Math.max(y + touchSlop, mHeight * 2 /3);
143                         return false;
144                     }
145                     stopDragging();
146                     break;
147             }
148         }
149         return super.onInterceptTouchEvent(ev);
150     }
151 
152     /*
153      * pointToPosition() doesn't consider invisible views, but we
154      * need to, so implement a slightly different version.
155      */
myPointToPosition(int x, int y)156     private int myPointToPosition(int x, int y) {
157 
158         if (y < 0) {
159             // when dragging off the top of the screen, calculate position
160             // by going back from a visible item
161             int pos = myPointToPosition(x, y + mItemHeightNormal);
162             if (pos > 0) {
163                 return pos - 1;
164             }
165         }
166 
167         Rect frame = mTempRect;
168         final int count = getChildCount();
169         for (int i = count - 1; i >= 0; i--) {
170             final View child = getChildAt(i);
171             child.getHitRect(frame);
172             if (frame.contains(x, y)) {
173                 return getFirstVisiblePosition() + i;
174             }
175         }
176         return INVALID_POSITION;
177     }
178 
getItemForPosition(int y)179     private int getItemForPosition(int y) {
180         int adjustedy = y - mDragPointY - mItemHeightHalf;
181         int pos = myPointToPosition(0, adjustedy);
182         if (pos >= 0) {
183             if (pos <= mSrcDragPos) {
184                 pos += 1;
185             }
186         } else if (adjustedy < 0) {
187             // this shouldn't happen anymore now that myPointToPosition deals
188             // with this situation
189             pos = 0;
190         }
191         return pos;
192     }
193 
adjustScrollBounds(int y)194     private void adjustScrollBounds(int y) {
195         if (y >= mHeight / 3) {
196             mUpperBound = mHeight / 3;
197         }
198         if (y <= mHeight * 2 / 3) {
199             mLowerBound = mHeight * 2 / 3;
200         }
201     }
202 
203     /*
204      * Restore size and visibility for all listitems
205      */
unExpandViews(boolean deletion)206     private void unExpandViews(boolean deletion) {
207         for (int i = 0;; i++) {
208             View v = getChildAt(i);
209             if (v == null) {
210                 if (deletion) {
211                     // HACK force update of mItemCount
212                     int position = getFirstVisiblePosition();
213                     int y = getChildAt(0).getTop();
214                     setAdapter(getAdapter());
215                     setSelectionFromTop(position, y);
216                     // end hack
217                 }
218                 try {
219                     layoutChildren(); // force children to be recreated where needed
220                     v = getChildAt(i);
221                 } catch (IllegalStateException ex) {
222                     // layoutChildren throws this sometimes, presumably because we're
223                     // in the process of being torn down but are still getting touch
224                     // events
225                 }
226                 if (v == null) {
227                     return;
228                 }
229             }
230             ViewGroup.LayoutParams params = v.getLayoutParams();
231             params.height = mItemHeightNormal;
232             v.setLayoutParams(params);
233             v.setVisibility(View.VISIBLE);
234         }
235     }
236 
237     /* Adjust visibility and size to make it appear as though
238      * an item is being dragged around and other items are making
239      * room for it:
240      * If dropping the item would result in it still being in the
241      * same place, then make the dragged listitem's size normal,
242      * but make the item invisible.
243      * Otherwise, if the dragged listitem is still on screen, make
244      * it as small as possible and expand the item below the insert
245      * point.
246      * If the dragged item is not on screen, only expand the item
247      * below the current insertpoint.
248      */
doExpansion()249     private void doExpansion() {
250         int childnum = mDragPos - getFirstVisiblePosition();
251         if (mDragPos > mSrcDragPos) {
252             childnum++;
253         }
254         int numheaders = getHeaderViewsCount();
255 
256         View first = getChildAt(mSrcDragPos - getFirstVisiblePosition());
257         for (int i = 0;; i++) {
258             View vv = getChildAt(i);
259             if (vv == null) {
260                 break;
261             }
262 
263             int height = mItemHeightNormal;
264             int visibility = View.VISIBLE;
265             if (mDragPos < numheaders && i == numheaders) {
266                 // dragging on top of the header item, so adjust the item below
267                 // instead
268                 if (vv.equals(first)) {
269                     visibility = View.INVISIBLE;
270                 } else {
271                     height = mItemHeightExpanded;
272                 }
273             } else if (vv.equals(first)) {
274                 // processing the item that is being dragged
275                 if (mDragPos == mSrcDragPos || getPositionForView(vv) == getCount() - 1) {
276                     // hovering over the original location
277                     visibility = View.INVISIBLE;
278                 } else {
279                     // not hovering over it
280                     // Ideally the item would be completely gone, but neither
281                     // setting its size to 0 nor settings visibility to GONE
282                     // has the desired effect.
283                     height = 1;
284                 }
285             } else if (i == childnum) {
286                 if (mDragPos >= numheaders && mDragPos < getCount() - 1) {
287                     height = mItemHeightExpanded;
288                 }
289             }
290             ViewGroup.LayoutParams params = vv.getLayoutParams();
291             params.height = height;
292             vv.setLayoutParams(params);
293             vv.setVisibility(visibility);
294         }
295     }
296 
297     @Override
onTouchEvent(MotionEvent ev)298     public boolean onTouchEvent(MotionEvent ev) {
299         if (mGestureDetector != null) {
300             mGestureDetector.onTouchEvent(ev);
301         }
302         if ((mDragListener != null || mDropListener != null) && mDragView != null) {
303             int action = ev.getAction();
304             switch (action) {
305                 case MotionEvent.ACTION_UP:
306                 case MotionEvent.ACTION_CANCEL:
307                     Rect r = mTempRect;
308                     mDragView.getDrawingRect(r);
309                     stopDragging();
310                     if (mRemoveMode == SLIDE && ev.getX() > r.right * 3 / 4) {
311                         if (mRemoveListener != null) {
312                             mRemoveListener.remove(mSrcDragPos);
313                         }
314                         unExpandViews(true);
315                     } else {
316                         if (mDropListener != null && mDragPos >= 0 && mDragPos < getCount()) {
317                             mDropListener.drop(mSrcDragPos, mDragPos);
318                         }
319                         unExpandViews(false);
320                     }
321                     break;
322 
323                 case MotionEvent.ACTION_DOWN:
324                 case MotionEvent.ACTION_MOVE:
325                     int x = (int) ev.getX();
326                     int y = (int) ev.getY();
327                     dragView(x, y);
328                     int itemnum = getItemForPosition(y);
329                     if (itemnum >= 0) {
330                         if (action == MotionEvent.ACTION_DOWN || itemnum != mDragPos) {
331                             if (mDragListener != null) {
332                                 mDragListener.drag(mDragPos, itemnum);
333                             }
334                             mDragPos = itemnum;
335                             doExpansion();
336                         }
337                         int speed = 0;
338                         adjustScrollBounds(y);
339                         if (y > mLowerBound) {
340                             // scroll the list up a bit
341                             if (getLastVisiblePosition() < getCount() - 1) {
342                                 speed = y > (mHeight + mLowerBound) / 2 ? 16 : 4;
343                             } else {
344                                 speed = 1;
345                             }
346                         } else if (y < mUpperBound) {
347                             // scroll the list down a bit
348                             speed = y < mUpperBound / 2 ? -16 : -4;
349                             if (getFirstVisiblePosition() == 0
350                                     && getChildAt(0).getTop() >= getPaddingTop()) {
351                                 // if we're already at the top, don't try to scroll, because
352                                 // it causes the framework to do some extra drawing that messes
353                                 // up our animation
354                                 speed = 0;
355                             }
356                         }
357                         if (speed != 0) {
358                             smoothScrollBy(speed, 30);
359                         }
360                     }
361                     break;
362             }
363             return true;
364         }
365         return super.onTouchEvent(ev);
366     }
367 
startDragging(Bitmap bm, int x, int y)368     private void startDragging(Bitmap bm, int x, int y) {
369         stopDragging();
370 
371         mWindowParams = new WindowManager.LayoutParams();
372         mWindowParams.gravity = Gravity.TOP | Gravity.LEFT;
373         mWindowParams.x = x - mDragPointX + mXOffset;
374         mWindowParams.y = y - mDragPointY + mYOffset;
375 
376         mWindowParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
377         mWindowParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
378         mWindowParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
379                 | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
380                 | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
381                 | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
382                 | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
383         mWindowParams.format = PixelFormat.TRANSLUCENT;
384         mWindowParams.windowAnimations = 0;
385 
386         Context context = getContext();
387         ImageView v = new ImageView(context);
388         //int backGroundColor = context.getResources().getColor(R.color.dragndrop_background);
389         //v.setBackgroundColor(backGroundColor);
390         v.setBackgroundResource(R.drawable.playlist_tile_drag);
391         v.setPadding(0, 0, 0, 0);
392         v.setImageBitmap(bm);
393         mDragBitmap = bm;
394 
395         mWindowManager = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
396         mWindowManager.addView(v, mWindowParams);
397         mDragView = v;
398     }
399 
dragView(int x, int y)400     private void dragView(int x, int y) {
401         if (mRemoveMode == SLIDE) {
402             float alpha = 1.0f;
403             int width = mDragView.getWidth();
404             if (x > width / 2) {
405                 alpha = ((float)(width - x)) / (width / 2);
406             }
407             mWindowParams.alpha = alpha;
408         }
409 
410         if (mRemoveMode == FLING || mRemoveMode == TRASH) {
411             mWindowParams.x = x - mDragPointX + mXOffset;
412         } else {
413             mWindowParams.x = 0;
414         }
415         mWindowParams.y = y - mDragPointY + mYOffset;
416         mWindowManager.updateViewLayout(mDragView, mWindowParams);
417 
418         if (mTrashcan != null) {
419             int width = mDragView.getWidth();
420             if (y > getHeight() * 3 / 4) {
421                 mTrashcan.setLevel(2);
422             } else if (width > 0 && x > width / 4) {
423                 mTrashcan.setLevel(1);
424             } else {
425                 mTrashcan.setLevel(0);
426             }
427         }
428     }
429 
stopDragging()430     private void stopDragging() {
431         if (mDragView != null) {
432             mDragView.setVisibility(GONE);
433             WindowManager wm = (WindowManager)getContext().getSystemService(Context.WINDOW_SERVICE);
434             wm.removeView(mDragView);
435             mDragView.setImageDrawable(null);
436             mDragView = null;
437         }
438         if (mDragBitmap != null) {
439             mDragBitmap.recycle();
440             mDragBitmap = null;
441         }
442         if (mTrashcan != null) {
443             mTrashcan.setLevel(0);
444         }
445     }
446 
setTrashcan(Drawable trash)447     public void setTrashcan(Drawable trash) {
448         mTrashcan = trash;
449         mRemoveMode = TRASH;
450     }
451 
setDragListener(DragListener l)452     public void setDragListener(DragListener l) {
453         mDragListener = l;
454     }
455 
setDropListener(DropListener l)456     public void setDropListener(DropListener l) {
457         mDropListener = l;
458     }
459 
setRemoveListener(RemoveListener l)460     public void setRemoveListener(RemoveListener l) {
461         mRemoveListener = l;
462     }
463 
464     public interface DragListener {
465         void drag(int from, int to);
466     }
467     public interface DropListener {
468         void drop(int from, int to);
469     }
470     public interface RemoveListener {
471         void remove(int which);
472     }
473 }
474