16a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler/*
26a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler * Copyright (C) 2012 The Android Open Source Project
36a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler *
46a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler * Licensed under the Apache License, Version 2.0 (the "License");
56a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler * you may not use this file except in compliance with the License.
66a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler * You may obtain a copy of the License at
76a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler *
86a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler *      http://www.apache.org/licenses/LICENSE-2.0
96a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler *
106a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler * Unless required by applicable law or agreed to in writing, software
116a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler * distributed under the License is distributed on an "AS IS" BASIS,
126a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
136a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler * See the License for the specific language governing permissions and
146a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler * limitations under the License.
156a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler */
166a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler
176a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler
186a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandlerpackage com.android.systemui;
196a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler
208900e631940fdffe7b941b56dc0f17e55345441eRomain Guyimport android.animation.Animator;
218900e631940fdffe7b941b56dc0f17e55345441eRomain Guyimport android.animation.AnimatorListenerAdapter;
226a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandlerimport android.animation.ObjectAnimator;
236a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandlerimport android.content.Context;
247b41467704f941b11af6aace3e40993afc7f6c6fJohn Spurlockimport android.media.AudioAttributes;
25f9e1a0b369740e11ea1ed4f141ffb936fc1a6cdbJohn Spurlockimport android.media.AudioManager;
26b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wrenimport android.os.Vibrator;
27cd686b5b6d4166b510df8e32138479a9559bc117John Spurlockimport android.util.Log;
289b2cd15f0fed990f532f35590c2a2896b90dc7fcChris Wrenimport android.view.Gravity;
296a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandlerimport android.view.MotionEvent;
306a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandlerimport android.view.ScaleGestureDetector;
31ac47ff70c322614ff2ca9ad82fe41338daf55877Daniel Sandlerimport android.view.ScaleGestureDetector.OnScaleGestureListener;
3228c0b714ac7f6c98d63aab106447bfd1e727fae2Jorim Jaggiimport android.view.VelocityTracker;
336a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandlerimport android.view.View;
34b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wrenimport android.view.ViewConfiguration;
356a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler
36be565dfc1c17b7ddafa9753851b8f82849fd3f42Jorim Jaggiimport com.android.systemui.statusbar.ExpandableNotificationRow;
374222d9a7fb87d73e1443ec1a2de9782b05741af6Jorim Jaggiimport com.android.systemui.statusbar.ExpandableView;
3828c0b714ac7f6c98d63aab106447bfd1e727fae2Jorim Jaggiimport com.android.systemui.statusbar.FlingAnimationUtils;
39b6d85ebfe4f9f5d3b7d7ab7b6123af02a0deb516Selim Cinekimport com.android.systemui.statusbar.policy.ScrollAdapter;
40b6d85ebfe4f9f5d3b7d7ab7b6123af02a0deb516Selim Cinek
414222d9a7fb87d73e1443ec1a2de9782b05741af6Jorim Jaggipublic class ExpandHelper implements Gefingerpoken {
426a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler    public interface Callback {
434222d9a7fb87d73e1443ec1a2de9782b05741af6Jorim Jaggi        ExpandableView getChildAtRawPosition(float x, float y);
444222d9a7fb87d73e1443ec1a2de9782b05741af6Jorim Jaggi        ExpandableView getChildAtPosition(float x, float y);
4580a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren        boolean canChildBeExpanded(View v);
4651c7510e493680b4aca1ed7695b35c52d2cd63ffChris Wren        void setUserExpandedChild(View v, boolean userExpanded);
4751c7510e493680b4aca1ed7695b35c52d2cd63ffChris Wren        void setUserLockedChild(View v, boolean userLocked);
481408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek        void expansionStateChanged(boolean isExpanding);
496a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler    }
506a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler
516a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler    private static final String TAG = "ExpandHelper";
52e46647d28467ee9e88aafe2951a5736f494235daChris Wren    protected static final boolean DEBUG = false;
533c148f106f6625ce247a2c7211682c3a1df89bc9Chris Wren    protected static final boolean DEBUG_SCALE = false;
5428c0b714ac7f6c98d63aab106447bfd1e727fae2Jorim Jaggi    private static final float EXPAND_DURATION = 0.3f;
55ba925e8ecd9decff5701001a0190042d6797942dChris Wren
564377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler    // Set to false to disable focus-based gestures (spread-finger vertical pull).
5789139d74b27305a29ca082c75d94dcbed5f84625Chris Wren    private static final boolean USE_DRAG = true;
5889139d74b27305a29ca082c75d94dcbed5f84625Chris Wren    // Set to false to disable scale-based gestures (both horizontal and vertical).
5989139d74b27305a29ca082c75d94dcbed5f84625Chris Wren    private static final boolean USE_SPAN = true;
6089139d74b27305a29ca082c75d94dcbed5f84625Chris Wren    // Both gestures types may be active at the same time.
6189139d74b27305a29ca082c75d94dcbed5f84625Chris Wren    // At least one gesture type should be active.
6289139d74b27305a29ca082c75d94dcbed5f84625Chris Wren    // A variant of the screwdriver gesture will emerge from either gesture type.
636a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler
6480a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren    // amount of overstretch for maximum brightness expressed in U
6580a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren    // 2f: maximum brightness is stretching a 1U to 3U, or a 4U to 6U
6680a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren    private static final float STRETCH_INTERVAL = 2f;
6780a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren
6880a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren    // level of glow for a touch, without overstretch
6980a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren    // overstretch fills the range (GLOW_BASE, 1.0]
7080a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren    private static final float GLOW_BASE = 0.5f;
7180a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren
727b41467704f941b11af6aace3e40993afc7f6c6fJohn Spurlock    private static final AudioAttributes VIBRATION_ATTRIBUTES = new AudioAttributes.Builder()
737b41467704f941b11af6aace3e40993afc7f6c6fJohn Spurlock            .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
747b41467704f941b11af6aace3e40993afc7f6c6fJohn Spurlock            .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
757b41467704f941b11af6aace3e40993afc7f6c6fJohn Spurlock            .build();
767b41467704f941b11af6aace3e40993afc7f6c6fJohn Spurlock
776a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler    @SuppressWarnings("unused")
786a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler    private Context mContext;
796a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler
804377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler    private boolean mExpanding;
814377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler    private static final int NONE    = 0;
824377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler    private static final int BLINDS  = 1<<0;
834377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler    private static final int PULL    = 1<<1;
844377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler    private static final int STRETCH = 1<<2;
854377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler    private int mExpansionStyle = NONE;
86b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren    private boolean mWatchingForPull;
87b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren    private boolean mHasPopped;
885de6e94e36e2adbdd4ebfb5c1903c23c9ea3c388Chris Wren    private View mEventSource;
896a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler    private float mOldHeight;
906a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler    private float mNaturalHeight;
9189139d74b27305a29ca082c75d94dcbed5f84625Chris Wren    private float mInitialTouchFocusY;
92b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren    private float mInitialTouchY;
936a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler    private float mInitialTouchSpan;
94cea520747f344204a78db9d3f7f1abe3f695f49fChris Wren    private float mLastFocusY;
95cea520747f344204a78db9d3f7f1abe3f695f49fChris Wren    private float mLastSpanY;
96b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren    private int mTouchSlop;
971408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek    private float mLastMotionY;
98b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren    private int mPopDuration;
994377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler    private float mPullGestureMinXSpan;
1006a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler    private Callback mCallback;
1014377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler    private ScaleGestureDetector mSGD;
1026a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler    private ViewScaler mScaler;
103ba925e8ecd9decff5701001a0190042d6797942dChris Wren    private ObjectAnimator mScaleAnimation;
104b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren    private Vibrator mVibrator;
1051408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek    private boolean mEnabled = true;
1061408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek    private ExpandableView mResizedView;
1071408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek    private float mCurrentHeight;
1086a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler
1096a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler    private int mSmallSize;
1106a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler    private int mLargeSize;
11180a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren    private float mMaximumStretch;
1121408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek    private boolean mOnlyMovements;
1136a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler
1149b2cd15f0fed990f532f35590c2a2896b90dc7fcChris Wren    private int mGravity;
1159b2cd15f0fed990f532f35590c2a2896b90dc7fcChris Wren
116fab078b01fbad026f006744016272327f7ab116bSelim Cinek    private ScrollAdapter mScrollAdapter;
11728c0b714ac7f6c98d63aab106447bfd1e727fae2Jorim Jaggi    private FlingAnimationUtils mFlingAnimationUtils;
11828c0b714ac7f6c98d63aab106447bfd1e727fae2Jorim Jaggi    private VelocityTracker mVelocityTracker;
119b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren
120209bede6b9edb9171e5bee4077b48e35004a37b4John Spurlock    private OnScaleGestureListener mScaleGestureListener
121ac47ff70c322614ff2ca9ad82fe41338daf55877Daniel Sandler            = new ScaleGestureDetector.SimpleOnScaleGestureListener() {
122ac47ff70c322614ff2ca9ad82fe41338daf55877Daniel Sandler        @Override
123ac47ff70c322614ff2ca9ad82fe41338daf55877Daniel Sandler        public boolean onScaleBegin(ScaleGestureDetector detector) {
124cd686b5b6d4166b510df8e32138479a9559bc117John Spurlock            if (DEBUG_SCALE) Log.v(TAG, "onscalebegin()");
125ac47ff70c322614ff2ca9ad82fe41338daf55877Daniel Sandler
1261408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek            startExpanding(mResizedView, STRETCH);
127ac47ff70c322614ff2ca9ad82fe41338daf55877Daniel Sandler            return mExpanding;
128ac47ff70c322614ff2ca9ad82fe41338daf55877Daniel Sandler        }
129ac47ff70c322614ff2ca9ad82fe41338daf55877Daniel Sandler
130ac47ff70c322614ff2ca9ad82fe41338daf55877Daniel Sandler        @Override
131ac47ff70c322614ff2ca9ad82fe41338daf55877Daniel Sandler        public boolean onScale(ScaleGestureDetector detector) {
1321408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek            if (DEBUG_SCALE) Log.v(TAG, "onscale() on " + mResizedView);
133ac47ff70c322614ff2ca9ad82fe41338daf55877Daniel Sandler            return true;
134ac47ff70c322614ff2ca9ad82fe41338daf55877Daniel Sandler        }
135ac47ff70c322614ff2ca9ad82fe41338daf55877Daniel Sandler
136ac47ff70c322614ff2ca9ad82fe41338daf55877Daniel Sandler        @Override
137ac47ff70c322614ff2ca9ad82fe41338daf55877Daniel Sandler        public void onScaleEnd(ScaleGestureDetector detector) {
138ac47ff70c322614ff2ca9ad82fe41338daf55877Daniel Sandler        }
139ac47ff70c322614ff2ca9ad82fe41338daf55877Daniel Sandler    };
140ac47ff70c322614ff2ca9ad82fe41338daf55877Daniel Sandler
1416a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler    private class ViewScaler {
142be565dfc1c17b7ddafa9753851b8f82849fd3f42Jorim Jaggi        ExpandableView mView;
1439b2cd15f0fed990f532f35590c2a2896b90dc7fcChris Wren
1446a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        public ViewScaler() {}
145be565dfc1c17b7ddafa9753851b8f82849fd3f42Jorim Jaggi        public void setView(ExpandableView v) {
1466a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler            mView = v;
1476a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        }
1486a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        public void setHeight(float h) {
149cd686b5b6d4166b510df8e32138479a9559bc117John Spurlock            if (DEBUG_SCALE) Log.v(TAG, "SetHeight: setting to " + h);
150be565dfc1c17b7ddafa9753851b8f82849fd3f42Jorim Jaggi            mView.setActualHeight((int) h);
1511408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek            mCurrentHeight = h;
1526a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        }
1536a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        public float getHeight() {
154be565dfc1c17b7ddafa9753851b8f82849fd3f42Jorim Jaggi            return mView.getActualHeight();
1556a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        }
1566a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        public int getNaturalHeight(int maximum) {
157be565dfc1c17b7ddafa9753851b8f82849fd3f42Jorim Jaggi            return Math.min(maximum, mView.getMaxHeight());
1586a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        }
1596a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler    }
1606a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler
1619b2cd15f0fed990f532f35590c2a2896b90dc7fcChris Wren    /**
1629b2cd15f0fed990f532f35590c2a2896b90dc7fcChris Wren     * Handle expansion gestures to expand and contract children of the callback.
1639b2cd15f0fed990f532f35590c2a2896b90dc7fcChris Wren     *
1649b2cd15f0fed990f532f35590c2a2896b90dc7fcChris Wren     * @param context application context
1659b2cd15f0fed990f532f35590c2a2896b90dc7fcChris Wren     * @param callback the container that holds the items to be manipulated
1669b2cd15f0fed990f532f35590c2a2896b90dc7fcChris Wren     * @param small the smallest allowable size for the manuipulated items.
1679b2cd15f0fed990f532f35590c2a2896b90dc7fcChris Wren     * @param large the largest allowable size for the manuipulated items.
1689b2cd15f0fed990f532f35590c2a2896b90dc7fcChris Wren     */
1696a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler    public ExpandHelper(Context context, Callback callback, int small, int large) {
1706a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        mSmallSize = small;
17180a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren        mMaximumStretch = mSmallSize * STRETCH_INTERVAL;
1726a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        mLargeSize = large;
1736a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        mContext = context;
1746a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        mCallback = callback;
1756a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        mScaler = new ViewScaler();
1769b2cd15f0fed990f532f35590c2a2896b90dc7fcChris Wren        mGravity = Gravity.TOP;
177ba925e8ecd9decff5701001a0190042d6797942dChris Wren        mScaleAnimation = ObjectAnimator.ofFloat(mScaler, "height", 0f);
1784377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler        mPopDuration = mContext.getResources().getInteger(R.integer.blinds_pop_duration_ms);
1794377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler        mPullGestureMinXSpan = mContext.getResources().getDimension(R.dimen.pull_span_min);
180ba925e8ecd9decff5701001a0190042d6797942dChris Wren
181b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren        final ViewConfiguration configuration = ViewConfiguration.get(mContext);
182b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren        mTouchSlop = configuration.getScaledTouchSlop();
183b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren
184ac47ff70c322614ff2ca9ad82fe41338daf55877Daniel Sandler        mSGD = new ScaleGestureDetector(context, mScaleGestureListener);
18528c0b714ac7f6c98d63aab106447bfd1e727fae2Jorim Jaggi        mFlingAnimationUtils = new FlingAnimationUtils(context, EXPAND_DURATION);
1866a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler    }
1875de6e94e36e2adbdd4ebfb5c1903c23c9ea3c388Chris Wren
1884377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler    private void updateExpansion() {
189cd686b5b6d4166b510df8e32138479a9559bc117John Spurlock        if (DEBUG_SCALE) Log.v(TAG, "updateExpansion()");
1904377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler        // are we scaling or dragging?
191cea520747f344204a78db9d3f7f1abe3f695f49fChris Wren        float span = mSGD.getCurrentSpan() - mInitialTouchSpan;
1924377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler        span *= USE_SPAN ? 1f : 0f;
1934377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler        float drag = mSGD.getFocusY() - mInitialTouchFocusY;
1944377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler        drag *= USE_DRAG ? 1f : 0f;
1954377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler        drag *= mGravity == Gravity.BOTTOM ? -1f : 1f;
1964377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler        float pull = Math.abs(drag) + Math.abs(span) + 1f;
1974377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler        float hand = drag * Math.abs(drag) / pull + span * Math.abs(span) / pull;
1984377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler        float target = hand + mOldHeight;
1994377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler        float newHeight = clamp(target);
2004377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler        mScaler.setHeight(newHeight);
201cea520747f344204a78db9d3f7f1abe3f695f49fChris Wren        mLastFocusY = mSGD.getFocusY();
202cea520747f344204a78db9d3f7f1abe3f695f49fChris Wren        mLastSpanY = mSGD.getCurrentSpan();
2034377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler    }
2044377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler
205b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren    private float clamp(float target) {
206b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren        float out = target;
207b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren        out = out < mSmallSize ? mSmallSize : (out > mLargeSize ? mLargeSize : out);
208b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren        out = out > mNaturalHeight ? mNaturalHeight : out;
209b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren        return out;
210b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren    }
211b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren
2124222d9a7fb87d73e1443ec1a2de9782b05741af6Jorim Jaggi    private ExpandableView findView(float x, float y) {
2134222d9a7fb87d73e1443ec1a2de9782b05741af6Jorim Jaggi        ExpandableView v;
214b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren        if (mEventSource != null) {
215b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren            int[] location = new int[2];
216b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren            mEventSource.getLocationOnScreen(location);
2174377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler            x += location[0];
2184377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler            y += location[1];
219b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren            v = mCallback.getChildAtRawPosition(x, y);
220b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren        } else {
221b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren            v = mCallback.getChildAtPosition(x, y);
222b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren        }
223b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren        return v;
224b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren    }
225b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren
226b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren    private boolean isInside(View v, float x, float y) {
227cd686b5b6d4166b510df8e32138479a9559bc117John Spurlock        if (DEBUG) Log.d(TAG, "isinside (" + x + ", " + y + ")");
228b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren
229b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren        if (v == null) {
230cd686b5b6d4166b510df8e32138479a9559bc117John Spurlock            if (DEBUG) Log.d(TAG, "isinside null subject");
231b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren            return false;
232b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren        }
233b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren        if (mEventSource != null) {
234b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren            int[] location = new int[2];
235b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren            mEventSource.getLocationOnScreen(location);
2364377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler            x += location[0];
2374377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler            y += location[1];
238cd686b5b6d4166b510df8e32138479a9559bc117John Spurlock            if (DEBUG) Log.d(TAG, "  to global (" + x + ", " + y + ")");
239b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren        }
240b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren        int[] location = new int[2];
241b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren        v.getLocationOnScreen(location);
2424377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler        x -= location[0];
2434377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler        y -= location[1];
244cd686b5b6d4166b510df8e32138479a9559bc117John Spurlock        if (DEBUG) Log.d(TAG, "  to local (" + x + ", " + y + ")");
245cd686b5b6d4166b510df8e32138479a9559bc117John Spurlock        if (DEBUG) Log.d(TAG, "  inside (" + v.getWidth() + ", " + v.getHeight() + ")");
246b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren        boolean inside = (x > 0f && y > 0f && x < v.getWidth() & y < v.getHeight());
247b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren        return inside;
248b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren    }
249b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren
2505de6e94e36e2adbdd4ebfb5c1903c23c9ea3c388Chris Wren    public void setEventSource(View eventSource) {
2515de6e94e36e2adbdd4ebfb5c1903c23c9ea3c388Chris Wren        mEventSource = eventSource;
2525de6e94e36e2adbdd4ebfb5c1903c23c9ea3c388Chris Wren    }
2535de6e94e36e2adbdd4ebfb5c1903c23c9ea3c388Chris Wren
2549b2cd15f0fed990f532f35590c2a2896b90dc7fcChris Wren    public void setGravity(int gravity) {
2559b2cd15f0fed990f532f35590c2a2896b90dc7fcChris Wren        mGravity = gravity;
2569b2cd15f0fed990f532f35590c2a2896b90dc7fcChris Wren    }
2579b2cd15f0fed990f532f35590c2a2896b90dc7fcChris Wren
258fab078b01fbad026f006744016272327f7ab116bSelim Cinek    public void setScrollAdapter(ScrollAdapter adapter) {
259fab078b01fbad026f006744016272327f7ab116bSelim Cinek        mScrollAdapter = adapter;
260b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren    }
261b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren
2624377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler    @Override
2636a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler    public boolean onInterceptTouchEvent(MotionEvent ev) {
2641408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek        if (!isEnabled()) {
2651408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek            return false;
2661408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek        }
26728c0b714ac7f6c98d63aab106447bfd1e727fae2Jorim Jaggi        trackVelocity(ev);
2684377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler        final int action = ev.getAction();
269cd686b5b6d4166b510df8e32138479a9559bc117John Spurlock        if (DEBUG_SCALE) Log.d(TAG, "intercept: act=" + MotionEvent.actionToString(action) +
2704377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler                         " expanding=" + mExpanding +
2714377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler                         (0 != (mExpansionStyle & BLINDS) ? " (blinds)" : "") +
2724377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler                         (0 != (mExpansionStyle & PULL) ? " (pull)" : "") +
2734377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler                         (0 != (mExpansionStyle & STRETCH) ? " (stretch)" : ""));
2744377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler        // check for a spread-finger vertical pull gesture
2754377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler        mSGD.onTouchEvent(ev);
2764377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler        final int x = (int) mSGD.getFocusX();
2774377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler        final int y = (int) mSGD.getFocusY();
278cea520747f344204a78db9d3f7f1abe3f695f49fChris Wren
279cea520747f344204a78db9d3f7f1abe3f695f49fChris Wren        mInitialTouchFocusY = y;
280cea520747f344204a78db9d3f7f1abe3f695f49fChris Wren        mInitialTouchSpan = mSGD.getCurrentSpan();
281cea520747f344204a78db9d3f7f1abe3f695f49fChris Wren        mLastFocusY = mInitialTouchFocusY;
282cea520747f344204a78db9d3f7f1abe3f695f49fChris Wren        mLastSpanY = mInitialTouchSpan;
283cd686b5b6d4166b510df8e32138479a9559bc117John Spurlock        if (DEBUG_SCALE) Log.d(TAG, "set initial span: " + mInitialTouchSpan);
284cea520747f344204a78db9d3f7f1abe3f695f49fChris Wren
2854377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler        if (mExpanding) {
2861408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek            mLastMotionY = ev.getRawY();
28728c0b714ac7f6c98d63aab106447bfd1e727fae2Jorim Jaggi            maybeRecycleVelocityTracker(ev);
288b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren            return true;
289b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren        } else {
2904377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler            if ((action == MotionEvent.ACTION_MOVE) && 0 != (mExpansionStyle & BLINDS)) {
2914377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler                // we've begun Venetian blinds style expansion
2924377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler                return true;
2934377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler            }
294b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren            switch (action & MotionEvent.ACTION_MASK) {
295b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren            case MotionEvent.ACTION_MOVE: {
2961408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek                final float xspan = mSGD.getCurrentSpanX();
2971408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek                if (xspan > mPullGestureMinXSpan &&
2981408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek                        xspan > mSGD.getCurrentSpanY() && !mExpanding) {
2991408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek                    // detect a vertical pulling gesture with fingers somewhat separated
3001408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek                    if (DEBUG_SCALE) Log.v(TAG, "got pull gesture (xspan=" + xspan + "px)");
3011408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek                    startExpanding(mResizedView, PULL);
3021408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek                    mWatchingForPull = false;
3031408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek                }
304b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren                if (mWatchingForPull) {
3051408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek                    final float yDiff = ev.getRawY() - mInitialTouchY;
306b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren                    if (yDiff > mTouchSlop) {
307cd686b5b6d4166b510df8e32138479a9559bc117John Spurlock                        if (DEBUG) Log.v(TAG, "got venetian gesture (dy=" + yDiff + "px)");
3081408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek                        mWatchingForPull = false;
3091408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek                        if (mResizedView != null && !isFullyExpanded(mResizedView)) {
3101408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek                            if (startExpanding(mResizedView, BLINDS)) {
3111408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek                                mLastMotionY = ev.getRawY();
3121408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek                                mInitialTouchY = ev.getRawY();
3131408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek                                mHasPopped = false;
3141408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek                            }
315b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren                        }
316b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren                    }
317b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren                }
318b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren                break;
319b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren            }
320b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren
321b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren            case MotionEvent.ACTION_DOWN:
322de3b1a2b2d3056cb7caa5cdaac51fc9d80e1e3c5Chris Wren                mWatchingForPull = mScrollAdapter != null &&
3231408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek                        isInside(mScrollAdapter.getHostView(), x, y)
3241408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek                        && mScrollAdapter.isScrolledToTop();
3251408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek                mResizedView = findView(x, y);
3261408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek                mInitialTouchY = ev.getY();
327b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren                break;
328b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren
329b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren            case MotionEvent.ACTION_CANCEL:
330b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren            case MotionEvent.ACTION_UP:
331cd686b5b6d4166b510df8e32138479a9559bc117John Spurlock                if (DEBUG) Log.d(TAG, "up/cancel");
33228c0b714ac7f6c98d63aab106447bfd1e727fae2Jorim Jaggi                finishExpanding(false, getCurrentVelocity());
3334377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler                clearView();
334b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren                break;
335b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren            }
3361408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek            mLastMotionY = ev.getRawY();
33728c0b714ac7f6c98d63aab106447bfd1e727fae2Jorim Jaggi            maybeRecycleVelocityTracker(ev);
3384377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler            return mExpanding;
339b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren        }
3406a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler    }
3416a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler
34228c0b714ac7f6c98d63aab106447bfd1e727fae2Jorim Jaggi    private void trackVelocity(MotionEvent event) {
34328c0b714ac7f6c98d63aab106447bfd1e727fae2Jorim Jaggi        int action = event.getActionMasked();
34428c0b714ac7f6c98d63aab106447bfd1e727fae2Jorim Jaggi        switch(action) {
34528c0b714ac7f6c98d63aab106447bfd1e727fae2Jorim Jaggi            case MotionEvent.ACTION_DOWN:
34628c0b714ac7f6c98d63aab106447bfd1e727fae2Jorim Jaggi                if (mVelocityTracker == null) {
34728c0b714ac7f6c98d63aab106447bfd1e727fae2Jorim Jaggi                    mVelocityTracker = VelocityTracker.obtain();
34828c0b714ac7f6c98d63aab106447bfd1e727fae2Jorim Jaggi                } else {
34928c0b714ac7f6c98d63aab106447bfd1e727fae2Jorim Jaggi                    mVelocityTracker.clear();
35028c0b714ac7f6c98d63aab106447bfd1e727fae2Jorim Jaggi                }
35128c0b714ac7f6c98d63aab106447bfd1e727fae2Jorim Jaggi                mVelocityTracker.addMovement(event);
35228c0b714ac7f6c98d63aab106447bfd1e727fae2Jorim Jaggi                break;
35328c0b714ac7f6c98d63aab106447bfd1e727fae2Jorim Jaggi            case MotionEvent.ACTION_MOVE:
3541b7f51ebc66fbb2cbe8a468790bbbfd397d66964Selim Cinek                if (mVelocityTracker == null) {
3551b7f51ebc66fbb2cbe8a468790bbbfd397d66964Selim Cinek                    mVelocityTracker = VelocityTracker.obtain();
3561b7f51ebc66fbb2cbe8a468790bbbfd397d66964Selim Cinek                }
35728c0b714ac7f6c98d63aab106447bfd1e727fae2Jorim Jaggi                mVelocityTracker.addMovement(event);
35828c0b714ac7f6c98d63aab106447bfd1e727fae2Jorim Jaggi                break;
35928c0b714ac7f6c98d63aab106447bfd1e727fae2Jorim Jaggi            default:
36028c0b714ac7f6c98d63aab106447bfd1e727fae2Jorim Jaggi                break;
36128c0b714ac7f6c98d63aab106447bfd1e727fae2Jorim Jaggi        }
36228c0b714ac7f6c98d63aab106447bfd1e727fae2Jorim Jaggi    }
36328c0b714ac7f6c98d63aab106447bfd1e727fae2Jorim Jaggi
36428c0b714ac7f6c98d63aab106447bfd1e727fae2Jorim Jaggi    private void maybeRecycleVelocityTracker(MotionEvent event) {
365c503896a4d0cab029bca56cf7ac18ae182729a0aJorim Jaggi        if (mVelocityTracker != null && (event.getActionMasked() == MotionEvent.ACTION_CANCEL
366c503896a4d0cab029bca56cf7ac18ae182729a0aJorim Jaggi                || event.getActionMasked() == MotionEvent.ACTION_UP)) {
36728c0b714ac7f6c98d63aab106447bfd1e727fae2Jorim Jaggi            mVelocityTracker.recycle();
36828c0b714ac7f6c98d63aab106447bfd1e727fae2Jorim Jaggi            mVelocityTracker = null;
36928c0b714ac7f6c98d63aab106447bfd1e727fae2Jorim Jaggi        }
37028c0b714ac7f6c98d63aab106447bfd1e727fae2Jorim Jaggi    }
37128c0b714ac7f6c98d63aab106447bfd1e727fae2Jorim Jaggi
37228c0b714ac7f6c98d63aab106447bfd1e727fae2Jorim Jaggi    private float getCurrentVelocity() {
37328c0b714ac7f6c98d63aab106447bfd1e727fae2Jorim Jaggi        if (mVelocityTracker != null) {
37428c0b714ac7f6c98d63aab106447bfd1e727fae2Jorim Jaggi            mVelocityTracker.computeCurrentVelocity(1000);
37528c0b714ac7f6c98d63aab106447bfd1e727fae2Jorim Jaggi            return mVelocityTracker.getYVelocity();
37628c0b714ac7f6c98d63aab106447bfd1e727fae2Jorim Jaggi        } else {
37728c0b714ac7f6c98d63aab106447bfd1e727fae2Jorim Jaggi            return 0f;
37828c0b714ac7f6c98d63aab106447bfd1e727fae2Jorim Jaggi        }
37928c0b714ac7f6c98d63aab106447bfd1e727fae2Jorim Jaggi    }
38028c0b714ac7f6c98d63aab106447bfd1e727fae2Jorim Jaggi
3811408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek    public void setEnabled(boolean enable) {
3821408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek        mEnabled = enable;
3831408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek    }
3841408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek
3851408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek    private boolean isEnabled() {
3861408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek        return mEnabled;
3871408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek    }
3881408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek
3891408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek    private boolean isFullyExpanded(ExpandableView underFocus) {
3901408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek        return underFocus.getIntrinsicHeight() == underFocus.getMaxHeight();
3911408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek    }
3921408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek
3934377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler    @Override
3946a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler    public boolean onTouchEvent(MotionEvent ev) {
3951408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek        if (!isEnabled()) {
3961408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek            return false;
3971408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek        }
39828c0b714ac7f6c98d63aab106447bfd1e727fae2Jorim Jaggi        trackVelocity(ev);
399cea520747f344204a78db9d3f7f1abe3f695f49fChris Wren        final int action = ev.getActionMasked();
400cd686b5b6d4166b510df8e32138479a9559bc117John Spurlock        if (DEBUG_SCALE) Log.d(TAG, "touch: act=" + MotionEvent.actionToString(action) +
4014377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler                " expanding=" + mExpanding +
4024377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler                (0 != (mExpansionStyle & BLINDS) ? " (blinds)" : "") +
4034377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler                (0 != (mExpansionStyle & PULL) ? " (pull)" : "") +
4044377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler                (0 != (mExpansionStyle & STRETCH) ? " (stretch)" : ""));
4054377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler
4064377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler        mSGD.onTouchEvent(ev);
4071408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek        final int x = (int) mSGD.getFocusX();
4081408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek        final int y = (int) mSGD.getFocusY();
4094377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler
4101408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek        if (mOnlyMovements) {
4111408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek            mLastMotionY = ev.getRawY();
4121408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek            return false;
4131408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek        }
4146a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        switch (action) {
4151408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek            case MotionEvent.ACTION_DOWN:
4161408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek                mWatchingForPull = mScrollAdapter != null &&
4171408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek                        isInside(mScrollAdapter.getHostView(), x, y);
4181408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek                mResizedView = findView(x, y);
4191408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek                mInitialTouchY = ev.getY();
4201408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek                break;
421b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren            case MotionEvent.ACTION_MOVE: {
4221408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek                if (mWatchingForPull) {
4231408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek                    final float yDiff = ev.getRawY() - mInitialTouchY;
4241408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek                    if (yDiff > mTouchSlop) {
4251408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek                        if (DEBUG) Log.v(TAG, "got venetian gesture (dy=" + yDiff + "px)");
4261408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek                        mWatchingForPull = false;
4271408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek                        if (mResizedView != null && !isFullyExpanded(mResizedView)) {
4281408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek                            if (startExpanding(mResizedView, BLINDS)) {
4291408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek                                mInitialTouchY = ev.getRawY();
4301408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek                                mLastMotionY = ev.getRawY();
4311408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek                                mHasPopped = false;
4321408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek                            }
4331408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek                        }
4341408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek                    }
4351408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek                }
4361408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek                if (mExpanding && 0 != (mExpansionStyle & BLINDS)) {
4371408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek                    final float rawHeight = ev.getRawY() - mLastMotionY + mCurrentHeight;
43886d00fb40ae5cd01ce5a2e228e6de777eae6dee8Chris Wren                    final float newHeight = clamp(rawHeight);
43986d00fb40ae5cd01ce5a2e228e6de777eae6dee8Chris Wren                    boolean isFinished = false;
4401408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek                    boolean expanded = false;
44186d00fb40ae5cd01ce5a2e228e6de777eae6dee8Chris Wren                    if (rawHeight > mNaturalHeight) {
44286d00fb40ae5cd01ce5a2e228e6de777eae6dee8Chris Wren                        isFinished = true;
4431408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek                        expanded = true;
44486d00fb40ae5cd01ce5a2e228e6de777eae6dee8Chris Wren                    }
44586d00fb40ae5cd01ce5a2e228e6de777eae6dee8Chris Wren                    if (rawHeight < mSmallSize) {
44686d00fb40ae5cd01ce5a2e228e6de777eae6dee8Chris Wren                        isFinished = true;
4471408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek                        expanded = false;
44886d00fb40ae5cd01ce5a2e228e6de777eae6dee8Chris Wren                    }
44986d00fb40ae5cd01ce5a2e228e6de777eae6dee8Chris Wren
4501408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek                    if (!mHasPopped) {
4511408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek                        vibrate(mPopDuration);
4521408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek                        mHasPopped = true;
45386d00fb40ae5cd01ce5a2e228e6de777eae6dee8Chris Wren                    }
45486d00fb40ae5cd01ce5a2e228e6de777eae6dee8Chris Wren
4551408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek                    mScaler.setHeight(newHeight);
4561408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek                    mLastMotionY = ev.getRawY();
4571408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek                    if (isFinished) {
4581408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek                        mCallback.setUserExpandedChild(mResizedView, expanded);
4591408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek                        mCallback.expansionStateChanged(false);
4601408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek                        return false;
4611408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek                    } else {
4621408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek                        mCallback.expansionStateChanged(true);
463b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren                    }
464b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren                    return true;
465b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren                }
4664377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler
4674377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler                if (mExpanding) {
4681408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek
4691408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek                    // Gestural expansion is running
4704377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler                    updateExpansion();
4711408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek                    mLastMotionY = ev.getRawY();
4724377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler                    return true;
4734377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler                }
4744377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler
475b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren                break;
476b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren            }
477cea520747f344204a78db9d3f7f1abe3f695f49fChris Wren
478cea520747f344204a78db9d3f7f1abe3f695f49fChris Wren            case MotionEvent.ACTION_POINTER_UP:
479cea520747f344204a78db9d3f7f1abe3f695f49fChris Wren            case MotionEvent.ACTION_POINTER_DOWN:
480cd686b5b6d4166b510df8e32138479a9559bc117John Spurlock                if (DEBUG) Log.d(TAG, "pointer change");
481cea520747f344204a78db9d3f7f1abe3f695f49fChris Wren                mInitialTouchY += mSGD.getFocusY() - mLastFocusY;
482cea520747f344204a78db9d3f7f1abe3f695f49fChris Wren                mInitialTouchSpan += mSGD.getCurrentSpan() - mLastSpanY;
483cea520747f344204a78db9d3f7f1abe3f695f49fChris Wren                break;
484cea520747f344204a78db9d3f7f1abe3f695f49fChris Wren
4856a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler            case MotionEvent.ACTION_UP:
4866a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler            case MotionEvent.ACTION_CANCEL:
487cd686b5b6d4166b510df8e32138479a9559bc117John Spurlock                if (DEBUG) Log.d(TAG, "up/cancel");
48828c0b714ac7f6c98d63aab106447bfd1e727fae2Jorim Jaggi                finishExpanding(false, getCurrentVelocity());
48980a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren                clearView();
4906a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler                break;
4916a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        }
4921408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek        mLastMotionY = ev.getRawY();
49328c0b714ac7f6c98d63aab106447bfd1e727fae2Jorim Jaggi        maybeRecycleVelocityTracker(ev);
494787a0af8ebba004a6f1cd3bfe8c78d851003d227Jorim Jaggi        return mResizedView != null;
4956a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler    }
4964377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler
497be565dfc1c17b7ddafa9753851b8f82849fd3f42Jorim Jaggi    /**
498be565dfc1c17b7ddafa9753851b8f82849fd3f42Jorim Jaggi     * @return True if the view is expandable, false otherwise.
499be565dfc1c17b7ddafa9753851b8f82849fd3f42Jorim Jaggi     */
5004222d9a7fb87d73e1443ec1a2de9782b05741af6Jorim Jaggi    private boolean startExpanding(ExpandableView v, int expandType) {
501be565dfc1c17b7ddafa9753851b8f82849fd3f42Jorim Jaggi        if (!(v instanceof ExpandableNotificationRow)) {
502be565dfc1c17b7ddafa9753851b8f82849fd3f42Jorim Jaggi            return false;
503be565dfc1c17b7ddafa9753851b8f82849fd3f42Jorim Jaggi        }
504cea520747f344204a78db9d3f7f1abe3f695f49fChris Wren        mExpansionStyle = expandType;
5051408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek        if (mExpanding && v == mResizedView) {
506be565dfc1c17b7ddafa9753851b8f82849fd3f42Jorim Jaggi            return true;
507cea520747f344204a78db9d3f7f1abe3f695f49fChris Wren        }
5084377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler        mExpanding = true;
5091408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek        mCallback.expansionStateChanged(true);
510cd686b5b6d4166b510df8e32138479a9559bc117John Spurlock        if (DEBUG) Log.d(TAG, "scale type " + expandType + " beginning on view: " + v);
5114377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler        mCallback.setUserLockedChild(v, true);
5121408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek        mScaler.setView(v);
5134377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler        mOldHeight = mScaler.getHeight();
5141408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek        mCurrentHeight = mOldHeight;
5154377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler        if (mCallback.canChildBeExpanded(v)) {
516cd686b5b6d4166b510df8e32138479a9559bc117John Spurlock            if (DEBUG) Log.d(TAG, "working on an expandable child");
5174377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler            mNaturalHeight = mScaler.getNaturalHeight(mLargeSize);
518b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren        } else {
519cd686b5b6d4166b510df8e32138479a9559bc117John Spurlock            if (DEBUG) Log.d(TAG, "working on a non-expandable child");
5204377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler            mNaturalHeight = mOldHeight;
5216a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        }
522cd686b5b6d4166b510df8e32138479a9559bc117John Spurlock        if (DEBUG) Log.d(TAG, "got mOldHeight: " + mOldHeight +
5234377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler                    " mNaturalHeight: " + mNaturalHeight);
524be565dfc1c17b7ddafa9753851b8f82849fd3f42Jorim Jaggi        return true;
5256a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler    }
5266a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler
52728c0b714ac7f6c98d63aab106447bfd1e727fae2Jorim Jaggi    private void finishExpanding(boolean force, float velocity) {
5284377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler        if (!mExpanding) return;
5294377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler
5301408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek        if (DEBUG) Log.d(TAG, "scale in finishing on view: " + mResizedView);
531cea520747f344204a78db9d3f7f1abe3f695f49fChris Wren
5323ddab0dcc1039137f05a28ff86477601a223a0faChris Wren        float currentHeight = mScaler.getHeight();
5333ddab0dcc1039137f05a28ff86477601a223a0faChris Wren        float targetHeight = mSmallSize;
5346a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        float h = mScaler.getHeight();
5356a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        final boolean wasClosed = (mOldHeight == mSmallSize);
5366a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        if (wasClosed) {
5373ddab0dcc1039137f05a28ff86477601a223a0faChris Wren            targetHeight = (force || currentHeight > mSmallSize) ? mNaturalHeight : mSmallSize;
5386a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        } else {
5393ddab0dcc1039137f05a28ff86477601a223a0faChris Wren            targetHeight = (force || currentHeight < mNaturalHeight) ? mSmallSize : mNaturalHeight;
5406a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        }
541ba925e8ecd9decff5701001a0190042d6797942dChris Wren        if (mScaleAnimation.isRunning()) {
542ba925e8ecd9decff5701001a0190042d6797942dChris Wren            mScaleAnimation.cancel();
543ba925e8ecd9decff5701001a0190042d6797942dChris Wren        }
5441408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek        mCallback.setUserExpandedChild(mResizedView, targetHeight == mNaturalHeight);
5451408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek        mCallback.expansionStateChanged(false);
5463ddab0dcc1039137f05a28ff86477601a223a0faChris Wren        if (targetHeight != currentHeight) {
5473ddab0dcc1039137f05a28ff86477601a223a0faChris Wren            mScaleAnimation.setFloatValues(targetHeight);
5483ddab0dcc1039137f05a28ff86477601a223a0faChris Wren            mScaleAnimation.setupStartValues();
5491408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek            final View scaledView = mResizedView;
550be565dfc1c17b7ddafa9753851b8f82849fd3f42Jorim Jaggi            mScaleAnimation.addListener(new AnimatorListenerAdapter() {
551be565dfc1c17b7ddafa9753851b8f82849fd3f42Jorim Jaggi                @Override
552be565dfc1c17b7ddafa9753851b8f82849fd3f42Jorim Jaggi                public void onAnimationEnd(Animator animation) {
553be565dfc1c17b7ddafa9753851b8f82849fd3f42Jorim Jaggi                    mCallback.setUserLockedChild(scaledView, false);
554be565dfc1c17b7ddafa9753851b8f82849fd3f42Jorim Jaggi                    mScaleAnimation.removeListener(this);
555be565dfc1c17b7ddafa9753851b8f82849fd3f42Jorim Jaggi                }
556be565dfc1c17b7ddafa9753851b8f82849fd3f42Jorim Jaggi            });
55728c0b714ac7f6c98d63aab106447bfd1e727fae2Jorim Jaggi            mFlingAnimationUtils.apply(mScaleAnimation, currentHeight, targetHeight, velocity);
5583ddab0dcc1039137f05a28ff86477601a223a0faChris Wren            mScaleAnimation.start();
559343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek        } else {
5601408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek            mCallback.setUserLockedChild(mResizedView, false);
5613ddab0dcc1039137f05a28ff86477601a223a0faChris Wren        }
5624377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler
5634377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler        mExpanding = false;
5644377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler        mExpansionStyle = NONE;
5654377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler
566cd686b5b6d4166b510df8e32138479a9559bc117John Spurlock        if (DEBUG) Log.d(TAG, "wasClosed is: " + wasClosed);
567cd686b5b6d4166b510df8e32138479a9559bc117John Spurlock        if (DEBUG) Log.d(TAG, "currentHeight is: " + currentHeight);
568cd686b5b6d4166b510df8e32138479a9559bc117John Spurlock        if (DEBUG) Log.d(TAG, "mSmallSize is: " + mSmallSize);
569cd686b5b6d4166b510df8e32138479a9559bc117John Spurlock        if (DEBUG) Log.d(TAG, "targetHeight is: " + targetHeight);
5701408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek        if (DEBUG) Log.d(TAG, "scale was finished on view: " + mResizedView);
57180a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren    }
57280a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren
57380a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren    private void clearView() {
5741408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek        mResizedView = null;
5756a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler    }
5766a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler
577b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren    /**
578ac47ff70c322614ff2ca9ad82fe41338daf55877Daniel Sandler     * Use this to abort any pending expansions in progress.
579ac47ff70c322614ff2ca9ad82fe41338daf55877Daniel Sandler     */
580ac47ff70c322614ff2ca9ad82fe41338daf55877Daniel Sandler    public void cancel() {
58128c0b714ac7f6c98d63aab106447bfd1e727fae2Jorim Jaggi        finishExpanding(true, 0f /* velocity */);
582ac47ff70c322614ff2ca9ad82fe41338daf55877Daniel Sandler        clearView();
583ac47ff70c322614ff2ca9ad82fe41338daf55877Daniel Sandler
584ac47ff70c322614ff2ca9ad82fe41338daf55877Daniel Sandler        // reset the gesture detector
585ac47ff70c322614ff2ca9ad82fe41338daf55877Daniel Sandler        mSGD = new ScaleGestureDetector(mContext, mScaleGestureListener);
586ac47ff70c322614ff2ca9ad82fe41338daf55877Daniel Sandler    }
587ac47ff70c322614ff2ca9ad82fe41338daf55877Daniel Sandler
588ac47ff70c322614ff2ca9ad82fe41338daf55877Daniel Sandler    /**
5891408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek     * Change the expansion mode to only observe movements and don't perform any resizing.
5901408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek     * This is needed when the expanding is finished and the scroller kicks in,
5911408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek     * performing an overscroll motion. We only want to shrink it again when we are not
5921408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek     * overscrolled.
5931408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek     *
5941408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek     * @param onlyMovements Should only movements be observed?
5951408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek     */
5961408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek    public void onlyObserveMovements(boolean onlyMovements) {
5971408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek        mOnlyMovements = onlyMovements;
5981408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek    }
5991408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek
6001408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek    /**
601b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren     * Triggers haptic feedback.
602b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren     */
603b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren    private synchronized void vibrate(long duration) {
604b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren        if (mVibrator == null) {
605b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren            mVibrator = (android.os.Vibrator)
606b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren                    mContext.getSystemService(Context.VIBRATOR_SERVICE);
607b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren        }
6087b41467704f941b11af6aace3e40993afc7f6c6fJohn Spurlock        mVibrator.vibrate(duration, VIBRATION_ATTRIBUTES);
6096a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler    }
6106a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler}
611b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren
612