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;
22ba925e8ecd9decff5701001a0190042d6797942dChris Wrenimport android.animation.AnimatorSet;
236a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandlerimport android.animation.ObjectAnimator;
246a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandlerimport android.content.Context;
25b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wrenimport android.os.Vibrator;
26cd686b5b6d4166b510df8e32138479a9559bc117John Spurlockimport android.util.Log;
279b2cd15f0fed990f532f35590c2a2896b90dc7fcChris Wrenimport android.view.Gravity;
286a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandlerimport android.view.MotionEvent;
296a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandlerimport android.view.ScaleGestureDetector;
30ac47ff70c322614ff2ca9ad82fe41338daf55877Daniel Sandlerimport android.view.ScaleGestureDetector.OnScaleGestureListener;
316a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandlerimport android.view.View;
32de84f0e77ea2bf713d15c290264059a413c2486aJohn Spurlockimport android.view.View.OnClickListener;
33b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wrenimport android.view.ViewConfiguration;
346a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandlerimport android.view.ViewGroup;
356a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler
366a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandlerpublic class ExpandHelper implements Gefingerpoken, OnClickListener {
376a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler    public interface Callback {
385de6e94e36e2adbdd4ebfb5c1903c23c9ea3c388Chris Wren        View getChildAtRawPosition(float x, float y);
396a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        View getChildAtPosition(float x, float y);
4080a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren        boolean canChildBeExpanded(View v);
4151c7510e493680b4aca1ed7695b35c52d2cd63ffChris Wren        void setUserExpandedChild(View v, boolean userExpanded);
4251c7510e493680b4aca1ed7695b35c52d2cd63ffChris Wren        void setUserLockedChild(View v, boolean userLocked);
436a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler    }
446a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler
456a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler    private static final String TAG = "ExpandHelper";
46e46647d28467ee9e88aafe2951a5736f494235daChris Wren    protected static final boolean DEBUG = false;
473c148f106f6625ce247a2c7211682c3a1df89bc9Chris Wren    protected static final boolean DEBUG_SCALE = false;
48e46647d28467ee9e88aafe2951a5736f494235daChris Wren    protected static final boolean DEBUG_GLOW = false;
496a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler    private static final long EXPAND_DURATION = 250;
50ba925e8ecd9decff5701001a0190042d6797942dChris Wren    private static final long GLOW_DURATION = 150;
51ba925e8ecd9decff5701001a0190042d6797942dChris Wren
524377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler    // Set to false to disable focus-based gestures (spread-finger vertical pull).
5389139d74b27305a29ca082c75d94dcbed5f84625Chris Wren    private static final boolean USE_DRAG = true;
5489139d74b27305a29ca082c75d94dcbed5f84625Chris Wren    // Set to false to disable scale-based gestures (both horizontal and vertical).
5589139d74b27305a29ca082c75d94dcbed5f84625Chris Wren    private static final boolean USE_SPAN = true;
5689139d74b27305a29ca082c75d94dcbed5f84625Chris Wren    // Both gestures types may be active at the same time.
5789139d74b27305a29ca082c75d94dcbed5f84625Chris Wren    // At least one gesture type should be active.
5889139d74b27305a29ca082c75d94dcbed5f84625Chris Wren    // A variant of the screwdriver gesture will emerge from either gesture type.
596a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler
6080a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren    // amount of overstretch for maximum brightness expressed in U
6180a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren    // 2f: maximum brightness is stretching a 1U to 3U, or a 4U to 6U
6280a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren    private static final float STRETCH_INTERVAL = 2f;
6380a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren
6480a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren    // level of glow for a touch, without overstretch
6580a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren    // overstretch fills the range (GLOW_BASE, 1.0]
6680a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren    private static final float GLOW_BASE = 0.5f;
6780a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren
686a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler    @SuppressWarnings("unused")
696a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler    private Context mContext;
706a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler
714377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler    private boolean mExpanding;
724377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler    private static final int NONE    = 0;
734377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler    private static final int BLINDS  = 1<<0;
744377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler    private static final int PULL    = 1<<1;
754377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler    private static final int STRETCH = 1<<2;
764377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler    private int mExpansionStyle = NONE;
77b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren    private boolean mWatchingForPull;
78b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren    private boolean mHasPopped;
795de6e94e36e2adbdd4ebfb5c1903c23c9ea3c388Chris Wren    private View mEventSource;
806a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler    private View mCurrView;
8180a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren    private View mCurrViewTopGlow;
8280a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren    private View mCurrViewBottomGlow;
836a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler    private float mOldHeight;
846a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler    private float mNaturalHeight;
8589139d74b27305a29ca082c75d94dcbed5f84625Chris Wren    private float mInitialTouchFocusY;
86b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren    private float mInitialTouchY;
876a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler    private float mInitialTouchSpan;
88cea520747f344204a78db9d3f7f1abe3f695f49fChris Wren    private float mLastFocusY;
89cea520747f344204a78db9d3f7f1abe3f695f49fChris Wren    private float mLastSpanY;
90b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren    private int mTouchSlop;
91b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren    private int mLastMotionY;
92b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren    private float mPopLimit;
93b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren    private int mPopDuration;
944377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler    private float mPullGestureMinXSpan;
956a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler    private Callback mCallback;
964377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler    private ScaleGestureDetector mSGD;
976a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler    private ViewScaler mScaler;
98ba925e8ecd9decff5701001a0190042d6797942dChris Wren    private ObjectAnimator mScaleAnimation;
99ba925e8ecd9decff5701001a0190042d6797942dChris Wren    private AnimatorSet mGlowAnimationSet;
100ba925e8ecd9decff5701001a0190042d6797942dChris Wren    private ObjectAnimator mGlowTopAnimation;
101ba925e8ecd9decff5701001a0190042d6797942dChris Wren    private ObjectAnimator mGlowBottomAnimation;
102b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren    private Vibrator mVibrator;
1036a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler
1046a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler    private int mSmallSize;
1056a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler    private int mLargeSize;
10680a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren    private float mMaximumStretch;
1076a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler
1089b2cd15f0fed990f532f35590c2a2896b90dc7fcChris Wren    private int mGravity;
1099b2cd15f0fed990f532f35590c2a2896b90dc7fcChris Wren
110b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren    private View mScrollView;
111b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren
112209bede6b9edb9171e5bee4077b48e35004a37b4John Spurlock    private OnScaleGestureListener mScaleGestureListener
113ac47ff70c322614ff2ca9ad82fe41338daf55877Daniel Sandler            = new ScaleGestureDetector.SimpleOnScaleGestureListener() {
114ac47ff70c322614ff2ca9ad82fe41338daf55877Daniel Sandler        @Override
115ac47ff70c322614ff2ca9ad82fe41338daf55877Daniel Sandler        public boolean onScaleBegin(ScaleGestureDetector detector) {
116cd686b5b6d4166b510df8e32138479a9559bc117John Spurlock            if (DEBUG_SCALE) Log.v(TAG, "onscalebegin()");
117ac47ff70c322614ff2ca9ad82fe41338daf55877Daniel Sandler            float focusX = detector.getFocusX();
118ac47ff70c322614ff2ca9ad82fe41338daf55877Daniel Sandler            float focusY = detector.getFocusY();
119ac47ff70c322614ff2ca9ad82fe41338daf55877Daniel Sandler
120ac47ff70c322614ff2ca9ad82fe41338daf55877Daniel Sandler            final View underFocus = findView(focusX, focusY);
121ac47ff70c322614ff2ca9ad82fe41338daf55877Daniel Sandler            if (underFocus != null) {
122ac47ff70c322614ff2ca9ad82fe41338daf55877Daniel Sandler                startExpanding(underFocus, STRETCH);
123ac47ff70c322614ff2ca9ad82fe41338daf55877Daniel Sandler            }
124ac47ff70c322614ff2ca9ad82fe41338daf55877Daniel Sandler            return mExpanding;
125ac47ff70c322614ff2ca9ad82fe41338daf55877Daniel Sandler        }
126ac47ff70c322614ff2ca9ad82fe41338daf55877Daniel Sandler
127ac47ff70c322614ff2ca9ad82fe41338daf55877Daniel Sandler        @Override
128ac47ff70c322614ff2ca9ad82fe41338daf55877Daniel Sandler        public boolean onScale(ScaleGestureDetector detector) {
129cd686b5b6d4166b510df8e32138479a9559bc117John Spurlock            if (DEBUG_SCALE) Log.v(TAG, "onscale() on " + mCurrView);
130ac47ff70c322614ff2ca9ad82fe41338daf55877Daniel Sandler            return true;
131ac47ff70c322614ff2ca9ad82fe41338daf55877Daniel Sandler        }
132ac47ff70c322614ff2ca9ad82fe41338daf55877Daniel Sandler
133ac47ff70c322614ff2ca9ad82fe41338daf55877Daniel Sandler        @Override
134ac47ff70c322614ff2ca9ad82fe41338daf55877Daniel Sandler        public void onScaleEnd(ScaleGestureDetector detector) {
135ac47ff70c322614ff2ca9ad82fe41338daf55877Daniel Sandler        }
136ac47ff70c322614ff2ca9ad82fe41338daf55877Daniel Sandler    };
137ac47ff70c322614ff2ca9ad82fe41338daf55877Daniel Sandler
1386a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler    private class ViewScaler {
1396a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        View mView;
1409b2cd15f0fed990f532f35590c2a2896b90dc7fcChris Wren
1416a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        public ViewScaler() {}
1426a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        public void setView(View v) {
1436a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler            mView = v;
1446a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        }
1456a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        public void setHeight(float h) {
146cd686b5b6d4166b510df8e32138479a9559bc117John Spurlock            if (DEBUG_SCALE) Log.v(TAG, "SetHeight: setting to " + h);
1476a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler            ViewGroup.LayoutParams lp = mView.getLayoutParams();
1486a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler            lp.height = (int)h;
1496a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler            mView.setLayoutParams(lp);
15089139d74b27305a29ca082c75d94dcbed5f84625Chris Wren            mView.requestLayout();
1516a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        }
1526a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        public float getHeight() {
1536a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler            int height = mView.getLayoutParams().height;
1546a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler            if (height < 0) {
1556a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler                height = mView.getMeasuredHeight();
1566a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler            }
1574377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler            return height;
1586a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        }
1596a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        public int getNaturalHeight(int maximum) {
1606a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler            ViewGroup.LayoutParams lp = mView.getLayoutParams();
161cd686b5b6d4166b510df8e32138479a9559bc117John Spurlock            if (DEBUG_SCALE) Log.v(TAG, "Inspecting a child of type: " +
1623c148f106f6625ce247a2c7211682c3a1df89bc9Chris Wren                    mView.getClass().getName());
1636a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler            int oldHeight = lp.height;
1646a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler            lp.height = ViewGroup.LayoutParams.WRAP_CONTENT;
1656a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler            mView.setLayoutParams(lp);
1666a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler            mView.measure(
1676a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler                    View.MeasureSpec.makeMeasureSpec(mView.getMeasuredWidth(),
16889139d74b27305a29ca082c75d94dcbed5f84625Chris Wren                                                     View.MeasureSpec.EXACTLY),
1696a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler                    View.MeasureSpec.makeMeasureSpec(maximum,
17089139d74b27305a29ca082c75d94dcbed5f84625Chris Wren                                                     View.MeasureSpec.AT_MOST));
1716a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler            lp.height = oldHeight;
1726a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler            mView.setLayoutParams(lp);
1736a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler            return mView.getMeasuredHeight();
1746a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        }
1756a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler    }
1766a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler
1779b2cd15f0fed990f532f35590c2a2896b90dc7fcChris Wren    /**
1789b2cd15f0fed990f532f35590c2a2896b90dc7fcChris Wren     * Handle expansion gestures to expand and contract children of the callback.
1799b2cd15f0fed990f532f35590c2a2896b90dc7fcChris Wren     *
1809b2cd15f0fed990f532f35590c2a2896b90dc7fcChris Wren     * @param context application context
1819b2cd15f0fed990f532f35590c2a2896b90dc7fcChris Wren     * @param callback the container that holds the items to be manipulated
1829b2cd15f0fed990f532f35590c2a2896b90dc7fcChris Wren     * @param small the smallest allowable size for the manuipulated items.
1839b2cd15f0fed990f532f35590c2a2896b90dc7fcChris Wren     * @param large the largest allowable size for the manuipulated items.
1849b2cd15f0fed990f532f35590c2a2896b90dc7fcChris Wren     */
1856a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler    public ExpandHelper(Context context, Callback callback, int small, int large) {
1866a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        mSmallSize = small;
18780a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren        mMaximumStretch = mSmallSize * STRETCH_INTERVAL;
1886a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        mLargeSize = large;
1896a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        mContext = context;
1906a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        mCallback = callback;
1916a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        mScaler = new ViewScaler();
1929b2cd15f0fed990f532f35590c2a2896b90dc7fcChris Wren        mGravity = Gravity.TOP;
193ba925e8ecd9decff5701001a0190042d6797942dChris Wren        mScaleAnimation = ObjectAnimator.ofFloat(mScaler, "height", 0f);
194ba925e8ecd9decff5701001a0190042d6797942dChris Wren        mScaleAnimation.setDuration(EXPAND_DURATION);
1954377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler        mPopLimit = mContext.getResources().getDimension(R.dimen.blinds_pop_threshold);
1964377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler        mPopDuration = mContext.getResources().getInteger(R.integer.blinds_pop_duration_ms);
1974377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler        mPullGestureMinXSpan = mContext.getResources().getDimension(R.dimen.pull_span_min);
198ba925e8ecd9decff5701001a0190042d6797942dChris Wren
1998900e631940fdffe7b941b56dc0f17e55345441eRomain Guy        AnimatorListenerAdapter glowVisibilityController = new AnimatorListenerAdapter() {
2008900e631940fdffe7b941b56dc0f17e55345441eRomain Guy            @Override
2018900e631940fdffe7b941b56dc0f17e55345441eRomain Guy            public void onAnimationStart(Animator animation) {
2028900e631940fdffe7b941b56dc0f17e55345441eRomain Guy                View target = (View) ((ObjectAnimator) animation).getTarget();
2038900e631940fdffe7b941b56dc0f17e55345441eRomain Guy                if (target.getAlpha() <= 0.0f) {
2048900e631940fdffe7b941b56dc0f17e55345441eRomain Guy                    target.setVisibility(View.VISIBLE);
2058900e631940fdffe7b941b56dc0f17e55345441eRomain Guy                }
2068900e631940fdffe7b941b56dc0f17e55345441eRomain Guy            }
2078900e631940fdffe7b941b56dc0f17e55345441eRomain Guy
2088900e631940fdffe7b941b56dc0f17e55345441eRomain Guy            @Override
2098900e631940fdffe7b941b56dc0f17e55345441eRomain Guy            public void onAnimationEnd(Animator animation) {
2108900e631940fdffe7b941b56dc0f17e55345441eRomain Guy                View target = (View) ((ObjectAnimator) animation).getTarget();
2118900e631940fdffe7b941b56dc0f17e55345441eRomain Guy                if (target.getAlpha() <= 0.0f) {
2128900e631940fdffe7b941b56dc0f17e55345441eRomain Guy                    target.setVisibility(View.INVISIBLE);
2138900e631940fdffe7b941b56dc0f17e55345441eRomain Guy                }
2148900e631940fdffe7b941b56dc0f17e55345441eRomain Guy            }
2158900e631940fdffe7b941b56dc0f17e55345441eRomain Guy        };
2168900e631940fdffe7b941b56dc0f17e55345441eRomain Guy
217ba925e8ecd9decff5701001a0190042d6797942dChris Wren        mGlowTopAnimation = ObjectAnimator.ofFloat(null, "alpha", 0f);
2188900e631940fdffe7b941b56dc0f17e55345441eRomain Guy        mGlowTopAnimation.addListener(glowVisibilityController);
219ba925e8ecd9decff5701001a0190042d6797942dChris Wren        mGlowBottomAnimation = ObjectAnimator.ofFloat(null, "alpha", 0f);
2208900e631940fdffe7b941b56dc0f17e55345441eRomain Guy        mGlowBottomAnimation.addListener(glowVisibilityController);
221ba925e8ecd9decff5701001a0190042d6797942dChris Wren        mGlowAnimationSet = new AnimatorSet();
222ba925e8ecd9decff5701001a0190042d6797942dChris Wren        mGlowAnimationSet.play(mGlowTopAnimation).with(mGlowBottomAnimation);
223ba925e8ecd9decff5701001a0190042d6797942dChris Wren        mGlowAnimationSet.setDuration(GLOW_DURATION);
224ba925e8ecd9decff5701001a0190042d6797942dChris Wren
225b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren        final ViewConfiguration configuration = ViewConfiguration.get(mContext);
226b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren        mTouchSlop = configuration.getScaledTouchSlop();
227b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren
228ac47ff70c322614ff2ca9ad82fe41338daf55877Daniel Sandler        mSGD = new ScaleGestureDetector(context, mScaleGestureListener);
2296a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler    }
2305de6e94e36e2adbdd4ebfb5c1903c23c9ea3c388Chris Wren
2314377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler    private void updateExpansion() {
232cd686b5b6d4166b510df8e32138479a9559bc117John Spurlock        if (DEBUG_SCALE) Log.v(TAG, "updateExpansion()");
2334377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler        // are we scaling or dragging?
234cea520747f344204a78db9d3f7f1abe3f695f49fChris Wren        float span = mSGD.getCurrentSpan() - mInitialTouchSpan;
2354377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler        span *= USE_SPAN ? 1f : 0f;
2364377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler        float drag = mSGD.getFocusY() - mInitialTouchFocusY;
2374377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler        drag *= USE_DRAG ? 1f : 0f;
2384377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler        drag *= mGravity == Gravity.BOTTOM ? -1f : 1f;
2394377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler        float pull = Math.abs(drag) + Math.abs(span) + 1f;
2404377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler        float hand = drag * Math.abs(drag) / pull + span * Math.abs(span) / pull;
2414377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler        float target = hand + mOldHeight;
2424377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler        float newHeight = clamp(target);
2434377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler        mScaler.setHeight(newHeight);
2444377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler
2454377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler        setGlow(calculateGlow(target, newHeight));
246cea520747f344204a78db9d3f7f1abe3f695f49fChris Wren        mLastFocusY = mSGD.getFocusY();
247cea520747f344204a78db9d3f7f1abe3f695f49fChris Wren        mLastSpanY = mSGD.getCurrentSpan();
2484377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler    }
2494377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler
250b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren    private float clamp(float target) {
251b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren        float out = target;
252b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren        out = out < mSmallSize ? mSmallSize : (out > mLargeSize ? mLargeSize : out);
253b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren        out = out > mNaturalHeight ? mNaturalHeight : out;
254b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren        return out;
255b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren    }
256b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren
257b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren    private View findView(float x, float y) {
258b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren        View v = null;
259b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren        if (mEventSource != null) {
260b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren            int[] location = new int[2];
261b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren            mEventSource.getLocationOnScreen(location);
2624377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler            x += location[0];
2634377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler            y += location[1];
264b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren            v = mCallback.getChildAtRawPosition(x, y);
265b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren        } else {
266b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren            v = mCallback.getChildAtPosition(x, y);
267b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren        }
268b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren        return v;
269b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren    }
270b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren
271b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren    private boolean isInside(View v, float x, float y) {
272cd686b5b6d4166b510df8e32138479a9559bc117John Spurlock        if (DEBUG) Log.d(TAG, "isinside (" + x + ", " + y + ")");
273b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren
274b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren        if (v == null) {
275cd686b5b6d4166b510df8e32138479a9559bc117John Spurlock            if (DEBUG) Log.d(TAG, "isinside null subject");
276b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren            return false;
277b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren        }
278b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren        if (mEventSource != null) {
279b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren            int[] location = new int[2];
280b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren            mEventSource.getLocationOnScreen(location);
2814377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler            x += location[0];
2824377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler            y += location[1];
283cd686b5b6d4166b510df8e32138479a9559bc117John Spurlock            if (DEBUG) Log.d(TAG, "  to global (" + x + ", " + y + ")");
284b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren        }
285b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren        int[] location = new int[2];
286b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren        v.getLocationOnScreen(location);
2874377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler        x -= location[0];
2884377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler        y -= location[1];
289cd686b5b6d4166b510df8e32138479a9559bc117John Spurlock        if (DEBUG) Log.d(TAG, "  to local (" + x + ", " + y + ")");
290cd686b5b6d4166b510df8e32138479a9559bc117John Spurlock        if (DEBUG) Log.d(TAG, "  inside (" + v.getWidth() + ", " + v.getHeight() + ")");
291b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren        boolean inside = (x > 0f && y > 0f && x < v.getWidth() & y < v.getHeight());
292b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren        return inside;
293b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren    }
294b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren
2955de6e94e36e2adbdd4ebfb5c1903c23c9ea3c388Chris Wren    public void setEventSource(View eventSource) {
2965de6e94e36e2adbdd4ebfb5c1903c23c9ea3c388Chris Wren        mEventSource = eventSource;
2975de6e94e36e2adbdd4ebfb5c1903c23c9ea3c388Chris Wren    }
2985de6e94e36e2adbdd4ebfb5c1903c23c9ea3c388Chris Wren
2999b2cd15f0fed990f532f35590c2a2896b90dc7fcChris Wren    public void setGravity(int gravity) {
3009b2cd15f0fed990f532f35590c2a2896b90dc7fcChris Wren        mGravity = gravity;
3019b2cd15f0fed990f532f35590c2a2896b90dc7fcChris Wren    }
3029b2cd15f0fed990f532f35590c2a2896b90dc7fcChris Wren
303b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren    public void setScrollView(View scrollView) {
304b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren        mScrollView = scrollView;
305b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren    }
306b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren
307b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren    private float calculateGlow(float target, float actual) {
308b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren        // glow if overscale
309cd686b5b6d4166b510df8e32138479a9559bc117John Spurlock        if (DEBUG_GLOW) Log.d(TAG, "target: " + target + " actual: " + actual);
3104377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler        float stretch = Math.abs((target - actual) / mMaximumStretch);
311b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren        float strength = 1f / (1f + (float) Math.pow(Math.E, -1 * ((8f * stretch) - 5f)));
312cd686b5b6d4166b510df8e32138479a9559bc117John Spurlock        if (DEBUG_GLOW) Log.d(TAG, "stretch: " + stretch + " strength: " + strength);
313b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren        return (GLOW_BASE + strength * (1f - GLOW_BASE));
314b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren    }
315b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren
31680a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren    public void setGlow(float glow) {
31789139d74b27305a29ca082c75d94dcbed5f84625Chris Wren        if (!mGlowAnimationSet.isRunning() || glow == 0f) {
31889139d74b27305a29ca082c75d94dcbed5f84625Chris Wren            if (mGlowAnimationSet.isRunning()) {
3193c148f106f6625ce247a2c7211682c3a1df89bc9Chris Wren                mGlowAnimationSet.end();
32089139d74b27305a29ca082c75d94dcbed5f84625Chris Wren            }
321ba925e8ecd9decff5701001a0190042d6797942dChris Wren            if (mCurrViewTopGlow != null && mCurrViewBottomGlow != null) {
322b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren                if (glow == 0f || mCurrViewTopGlow.getAlpha() == 0f) {
323ba925e8ecd9decff5701001a0190042d6797942dChris Wren                    // animate glow in and out
324ba925e8ecd9decff5701001a0190042d6797942dChris Wren                    mGlowTopAnimation.setTarget(mCurrViewTopGlow);
325ba925e8ecd9decff5701001a0190042d6797942dChris Wren                    mGlowBottomAnimation.setTarget(mCurrViewBottomGlow);
326ba925e8ecd9decff5701001a0190042d6797942dChris Wren                    mGlowTopAnimation.setFloatValues(glow);
327ba925e8ecd9decff5701001a0190042d6797942dChris Wren                    mGlowBottomAnimation.setFloatValues(glow);
328ba925e8ecd9decff5701001a0190042d6797942dChris Wren                    mGlowAnimationSet.setupStartValues();
329ba925e8ecd9decff5701001a0190042d6797942dChris Wren                    mGlowAnimationSet.start();
330ba925e8ecd9decff5701001a0190042d6797942dChris Wren                } else {
331ba925e8ecd9decff5701001a0190042d6797942dChris Wren                    // set it explicitly in reponse to touches.
332ba925e8ecd9decff5701001a0190042d6797942dChris Wren                    mCurrViewTopGlow.setAlpha(glow);
333ba925e8ecd9decff5701001a0190042d6797942dChris Wren                    mCurrViewBottomGlow.setAlpha(glow);
3348900e631940fdffe7b941b56dc0f17e55345441eRomain Guy                    handleGlowVisibility();
335ba925e8ecd9decff5701001a0190042d6797942dChris Wren                }
336ba925e8ecd9decff5701001a0190042d6797942dChris Wren            }
33780a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren        }
33880a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren    }
3398900e631940fdffe7b941b56dc0f17e55345441eRomain Guy
3408900e631940fdffe7b941b56dc0f17e55345441eRomain Guy    private void handleGlowVisibility() {
3418900e631940fdffe7b941b56dc0f17e55345441eRomain Guy        mCurrViewTopGlow.setVisibility(mCurrViewTopGlow.getAlpha() <= 0.0f ?
3428900e631940fdffe7b941b56dc0f17e55345441eRomain Guy                View.INVISIBLE : View.VISIBLE);
3438900e631940fdffe7b941b56dc0f17e55345441eRomain Guy        mCurrViewBottomGlow.setVisibility(mCurrViewBottomGlow.getAlpha() <= 0.0f ?
3448900e631940fdffe7b941b56dc0f17e55345441eRomain Guy                View.INVISIBLE : View.VISIBLE);
3458900e631940fdffe7b941b56dc0f17e55345441eRomain Guy    }
3468900e631940fdffe7b941b56dc0f17e55345441eRomain Guy
3474377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler    @Override
3486a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler    public boolean onInterceptTouchEvent(MotionEvent ev) {
3494377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler        final int action = ev.getAction();
350cd686b5b6d4166b510df8e32138479a9559bc117John Spurlock        if (DEBUG_SCALE) Log.d(TAG, "intercept: act=" + MotionEvent.actionToString(action) +
3514377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler                         " expanding=" + mExpanding +
3524377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler                         (0 != (mExpansionStyle & BLINDS) ? " (blinds)" : "") +
3534377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler                         (0 != (mExpansionStyle & PULL) ? " (pull)" : "") +
3544377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler                         (0 != (mExpansionStyle & STRETCH) ? " (stretch)" : ""));
3554377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler        // check for a spread-finger vertical pull gesture
3564377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler        mSGD.onTouchEvent(ev);
3574377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler        final int x = (int) mSGD.getFocusX();
3584377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler        final int y = (int) mSGD.getFocusY();
359cea520747f344204a78db9d3f7f1abe3f695f49fChris Wren
360cea520747f344204a78db9d3f7f1abe3f695f49fChris Wren        mInitialTouchFocusY = y;
361cea520747f344204a78db9d3f7f1abe3f695f49fChris Wren        mInitialTouchSpan = mSGD.getCurrentSpan();
362cea520747f344204a78db9d3f7f1abe3f695f49fChris Wren        mLastFocusY = mInitialTouchFocusY;
363cea520747f344204a78db9d3f7f1abe3f695f49fChris Wren        mLastSpanY = mInitialTouchSpan;
364cd686b5b6d4166b510df8e32138479a9559bc117John Spurlock        if (DEBUG_SCALE) Log.d(TAG, "set initial span: " + mInitialTouchSpan);
365cea520747f344204a78db9d3f7f1abe3f695f49fChris Wren
3664377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler        if (mExpanding) {
367b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren            return true;
368b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren        } else {
3694377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler            if ((action == MotionEvent.ACTION_MOVE) && 0 != (mExpansionStyle & BLINDS)) {
3704377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler                // we've begun Venetian blinds style expansion
3714377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler                return true;
3724377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler            }
3734377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler            final float xspan = mSGD.getCurrentSpanX();
3744377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler            if ((action == MotionEvent.ACTION_MOVE &&
3754377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler                    xspan > mPullGestureMinXSpan &&
3764377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler                    xspan > mSGD.getCurrentSpanY())) {
3774377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler                // detect a vertical pulling gesture with fingers somewhat separated
378cd686b5b6d4166b510df8e32138479a9559bc117John Spurlock                if (DEBUG_SCALE) Log.v(TAG, "got pull gesture (xspan=" + xspan + "px)");
3794377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler
3804377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler                final View underFocus = findView(x, y);
3814377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler                if (underFocus != null) {
3824377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler                    startExpanding(underFocus, PULL);
3834377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler                }
384b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren                return true;
385b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren            }
386b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren            if (mScrollView != null && mScrollView.getScrollY() > 0) {
387b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren                return false;
388b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren            }
3894377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler            // Now look for other gestures
390b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren            switch (action & MotionEvent.ACTION_MASK) {
391b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren            case MotionEvent.ACTION_MOVE: {
392b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren                if (mWatchingForPull) {
393b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren                    final int yDiff = y - mLastMotionY;
394b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren                    if (yDiff > mTouchSlop) {
395cd686b5b6d4166b510df8e32138479a9559bc117John Spurlock                        if (DEBUG) Log.v(TAG, "got venetian gesture (dy=" + yDiff + "px)");
396b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren                        mLastMotionY = y;
3974377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler                        final View underFocus = findView(x, y);
3984377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler                        if (underFocus != null) {
3994377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler                            startExpanding(underFocus, BLINDS);
4003c148f106f6625ce247a2c7211682c3a1df89bc9Chris Wren                            mInitialTouchY = mLastMotionY;
401b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren                            mHasPopped = false;
402b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren                        }
403b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren                    }
404b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren                }
405b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren                break;
406b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren            }
407b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren
408b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren            case MotionEvent.ACTION_DOWN:
4094377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler                mWatchingForPull = isInside(mScrollView, x, y);
4104377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler                mLastMotionY = y;
411b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren                break;
412b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren
413b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren            case MotionEvent.ACTION_CANCEL:
414b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren            case MotionEvent.ACTION_UP:
415cd686b5b6d4166b510df8e32138479a9559bc117John Spurlock                if (DEBUG) Log.d(TAG, "up/cancel");
4164377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler                finishExpanding(false);
4174377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler                clearView();
418b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren                break;
419b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren            }
4204377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler            return mExpanding;
421b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren        }
4226a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler    }
4236a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler
4244377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler    @Override
4256a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler    public boolean onTouchEvent(MotionEvent ev) {
426cea520747f344204a78db9d3f7f1abe3f695f49fChris Wren        final int action = ev.getActionMasked();
427cd686b5b6d4166b510df8e32138479a9559bc117John Spurlock        if (DEBUG_SCALE) Log.d(TAG, "touch: act=" + MotionEvent.actionToString(action) +
4284377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler                " expanding=" + mExpanding +
4294377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler                (0 != (mExpansionStyle & BLINDS) ? " (blinds)" : "") +
4304377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler                (0 != (mExpansionStyle & PULL) ? " (pull)" : "") +
4314377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler                (0 != (mExpansionStyle & STRETCH) ? " (stretch)" : ""));
4324377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler
4334377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler        mSGD.onTouchEvent(ev);
4344377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler
4356a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        switch (action) {
436b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren            case MotionEvent.ACTION_MOVE: {
4374377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler                if (0 != (mExpansionStyle & BLINDS)) {
43886d00fb40ae5cd01ce5a2e228e6de777eae6dee8Chris Wren                    final float rawHeight = ev.getY() - mInitialTouchY + mOldHeight;
43986d00fb40ae5cd01ce5a2e228e6de777eae6dee8Chris Wren                    final float newHeight = clamp(rawHeight);
44086d00fb40ae5cd01ce5a2e228e6de777eae6dee8Chris Wren                    final boolean wasClosed = (mOldHeight == mSmallSize);
44186d00fb40ae5cd01ce5a2e228e6de777eae6dee8Chris Wren                    boolean isFinished = false;
44286d00fb40ae5cd01ce5a2e228e6de777eae6dee8Chris Wren                    if (rawHeight > mNaturalHeight) {
44386d00fb40ae5cd01ce5a2e228e6de777eae6dee8Chris Wren                        isFinished = true;
44486d00fb40ae5cd01ce5a2e228e6de777eae6dee8Chris Wren                    }
44586d00fb40ae5cd01ce5a2e228e6de777eae6dee8Chris Wren                    if (rawHeight < mSmallSize) {
44686d00fb40ae5cd01ce5a2e228e6de777eae6dee8Chris Wren                        isFinished = true;
44786d00fb40ae5cd01ce5a2e228e6de777eae6dee8Chris Wren                    }
44886d00fb40ae5cd01ce5a2e228e6de777eae6dee8Chris Wren
44986d00fb40ae5cd01ce5a2e228e6de777eae6dee8Chris Wren                    final float pull = Math.abs(ev.getY() - mInitialTouchY);
45086d00fb40ae5cd01ce5a2e228e6de777eae6dee8Chris Wren                    if (mHasPopped || pull > mPopLimit) {
451b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren                        if (!mHasPopped) {
452b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren                            vibrate(mPopDuration);
453b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren                            mHasPopped = true;
454b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren                        }
45586d00fb40ae5cd01ce5a2e228e6de777eae6dee8Chris Wren                    }
45686d00fb40ae5cd01ce5a2e228e6de777eae6dee8Chris Wren
45786d00fb40ae5cd01ce5a2e228e6de777eae6dee8Chris Wren                    if (mHasPopped) {
458b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren                        mScaler.setHeight(newHeight);
45986d00fb40ae5cd01ce5a2e228e6de777eae6dee8Chris Wren                        setGlow(GLOW_BASE);
460b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren                    } else {
46186d00fb40ae5cd01ce5a2e228e6de777eae6dee8Chris Wren                        setGlow(calculateGlow(4f * pull, 0f));
46286d00fb40ae5cd01ce5a2e228e6de777eae6dee8Chris Wren                    }
46386d00fb40ae5cd01ce5a2e228e6de777eae6dee8Chris Wren
4644377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler                    final int x = (int) mSGD.getFocusX();
4654377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler                    final int y = (int) mSGD.getFocusY();
4664377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler                    View underFocus = findView(x, y);
4674377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler                    if (isFinished && underFocus != null && underFocus != mCurrView) {
4684377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler                        finishExpanding(false); // @@@ needed?
4694377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler                        startExpanding(underFocus, BLINDS);
4704377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler                        mInitialTouchY = y;
47186d00fb40ae5cd01ce5a2e228e6de777eae6dee8Chris Wren                        mHasPopped = false;
472b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren                    }
473b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren                    return true;
474b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren                }
4754377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler
4764377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler                if (mExpanding) {
4774377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler                    updateExpansion();
4784377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler                    return true;
4794377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler                }
4804377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler
481b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren                break;
482b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren            }
483cea520747f344204a78db9d3f7f1abe3f695f49fChris Wren
484cea520747f344204a78db9d3f7f1abe3f695f49fChris Wren            case MotionEvent.ACTION_POINTER_UP:
485cea520747f344204a78db9d3f7f1abe3f695f49fChris Wren            case MotionEvent.ACTION_POINTER_DOWN:
486cd686b5b6d4166b510df8e32138479a9559bc117John Spurlock                if (DEBUG) Log.d(TAG, "pointer change");
487cea520747f344204a78db9d3f7f1abe3f695f49fChris Wren                mInitialTouchY += mSGD.getFocusY() - mLastFocusY;
488cea520747f344204a78db9d3f7f1abe3f695f49fChris Wren                mInitialTouchSpan += mSGD.getCurrentSpan() - mLastSpanY;
489cea520747f344204a78db9d3f7f1abe3f695f49fChris Wren                break;
490cea520747f344204a78db9d3f7f1abe3f695f49fChris Wren
4916a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler            case MotionEvent.ACTION_UP:
4926a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler            case MotionEvent.ACTION_CANCEL:
493cd686b5b6d4166b510df8e32138479a9559bc117John Spurlock                if (DEBUG) Log.d(TAG, "up/cancel");
4944377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler                finishExpanding(false);
49580a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren                clearView();
4966a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler                break;
4976a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        }
4986a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        return true;
4996a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler    }
5004377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler
5014377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler    private void startExpanding(View v, int expandType) {
502cea520747f344204a78db9d3f7f1abe3f695f49fChris Wren        mExpansionStyle = expandType;
503cea520747f344204a78db9d3f7f1abe3f695f49fChris Wren        if (mExpanding &&  v == mCurrView) {
504cea520747f344204a78db9d3f7f1abe3f695f49fChris Wren            return;
505cea520747f344204a78db9d3f7f1abe3f695f49fChris Wren        }
5064377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler        mExpanding = true;
507cd686b5b6d4166b510df8e32138479a9559bc117John Spurlock        if (DEBUG) Log.d(TAG, "scale type " + expandType + " beginning on view: " + v);
5084377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler        mCallback.setUserLockedChild(v, true);
5094377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler        setView(v);
5104377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler        setGlow(GLOW_BASE);
5114377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler        mScaler.setView(v);
5124377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler        mOldHeight = mScaler.getHeight();
5134377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler        if (mCallback.canChildBeExpanded(v)) {
514cd686b5b6d4166b510df8e32138479a9559bc117John Spurlock            if (DEBUG) Log.d(TAG, "working on an expandable child");
5154377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler            mNaturalHeight = mScaler.getNaturalHeight(mLargeSize);
516b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren        } else {
517cd686b5b6d4166b510df8e32138479a9559bc117John Spurlock            if (DEBUG) Log.d(TAG, "working on a non-expandable child");
5184377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler            mNaturalHeight = mOldHeight;
5196a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        }
520cd686b5b6d4166b510df8e32138479a9559bc117John Spurlock        if (DEBUG) Log.d(TAG, "got mOldHeight: " + mOldHeight +
5214377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler                    " mNaturalHeight: " + mNaturalHeight);
5224377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler        v.getParent().requestDisallowInterceptTouchEvent(true);
5236a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler    }
5246a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler
5254377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler    private void finishExpanding(boolean force) {
5264377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler        if (!mExpanding) return;
5274377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler
528cd686b5b6d4166b510df8e32138479a9559bc117John Spurlock        if (DEBUG) Log.d(TAG, "scale in finishing on view: " + mCurrView);
529cea520747f344204a78db9d3f7f1abe3f695f49fChris Wren
5303ddab0dcc1039137f05a28ff86477601a223a0faChris Wren        float currentHeight = mScaler.getHeight();
5313ddab0dcc1039137f05a28ff86477601a223a0faChris Wren        float targetHeight = mSmallSize;
5326a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        float h = mScaler.getHeight();
5336a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        final boolean wasClosed = (mOldHeight == mSmallSize);
5346a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        if (wasClosed) {
5353ddab0dcc1039137f05a28ff86477601a223a0faChris Wren            targetHeight = (force || currentHeight > mSmallSize) ? mNaturalHeight : mSmallSize;
5366a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        } else {
5373ddab0dcc1039137f05a28ff86477601a223a0faChris Wren            targetHeight = (force || currentHeight < mNaturalHeight) ? mSmallSize : mNaturalHeight;
5386a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        }
539ba925e8ecd9decff5701001a0190042d6797942dChris Wren        if (mScaleAnimation.isRunning()) {
540ba925e8ecd9decff5701001a0190042d6797942dChris Wren            mScaleAnimation.cancel();
541ba925e8ecd9decff5701001a0190042d6797942dChris Wren        }
54280a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren        setGlow(0f);
5438fd12657e353a4a6f6d875a0d86850426fec00e8Chris Wren        mCallback.setUserExpandedChild(mCurrView, h == mNaturalHeight);
5443ddab0dcc1039137f05a28ff86477601a223a0faChris Wren        if (targetHeight != currentHeight) {
5453ddab0dcc1039137f05a28ff86477601a223a0faChris Wren            mScaleAnimation.setFloatValues(targetHeight);
5463ddab0dcc1039137f05a28ff86477601a223a0faChris Wren            mScaleAnimation.setupStartValues();
5473ddab0dcc1039137f05a28ff86477601a223a0faChris Wren            mScaleAnimation.start();
5483ddab0dcc1039137f05a28ff86477601a223a0faChris Wren        }
5493ddab0dcc1039137f05a28ff86477601a223a0faChris Wren        mCallback.setUserLockedChild(mCurrView, false);
5504377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler
5514377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler        mExpanding = false;
5524377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler        mExpansionStyle = NONE;
5534377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler
554cd686b5b6d4166b510df8e32138479a9559bc117John Spurlock        if (DEBUG) Log.d(TAG, "wasClosed is: " + wasClosed);
555cd686b5b6d4166b510df8e32138479a9559bc117John Spurlock        if (DEBUG) Log.d(TAG, "currentHeight is: " + currentHeight);
556cd686b5b6d4166b510df8e32138479a9559bc117John Spurlock        if (DEBUG) Log.d(TAG, "mSmallSize is: " + mSmallSize);
557cd686b5b6d4166b510df8e32138479a9559bc117John Spurlock        if (DEBUG) Log.d(TAG, "targetHeight is: " + targetHeight);
558cd686b5b6d4166b510df8e32138479a9559bc117John Spurlock        if (DEBUG) Log.d(TAG, "scale was finished on view: " + mCurrView);
55980a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren    }
56080a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren
56180a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren    private void clearView() {
5626a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        mCurrView = null;
56380a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren        mCurrViewTopGlow = null;
56480a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren        mCurrViewBottomGlow = null;
56580a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren    }
56680a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren
56780a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren    private void setView(View v) {
56889139d74b27305a29ca082c75d94dcbed5f84625Chris Wren        mCurrView = v;
56980a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren        if (v instanceof ViewGroup) {
57080a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren            ViewGroup g = (ViewGroup) v;
57180a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren            mCurrViewTopGlow = g.findViewById(R.id.top_glow);
57280a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren            mCurrViewBottomGlow = g.findViewById(R.id.bottom_glow);
57389139d74b27305a29ca082c75d94dcbed5f84625Chris Wren            if (DEBUG) {
574b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren                String debugLog = "Looking for glows: " +
57580a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren                        (mCurrViewTopGlow != null ? "found top " : "didn't find top") +
57680a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren                        (mCurrViewBottomGlow != null ? "found bottom " : "didn't find bottom");
577cd686b5b6d4166b510df8e32138479a9559bc117John Spurlock                Log.v(TAG,  debugLog);
57880a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren            }
57980a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren        }
5806a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler    }
5816a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler
5826a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler    @Override
5836a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler    public void onClick(View v) {
5844377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler        startExpanding(v, STRETCH);
5854377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler        finishExpanding(true);
5863c148f106f6625ce247a2c7211682c3a1df89bc9Chris Wren        clearView();
587b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren    }
5886a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler
589b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren    /**
590ac47ff70c322614ff2ca9ad82fe41338daf55877Daniel Sandler     * Use this to abort any pending expansions in progress.
591ac47ff70c322614ff2ca9ad82fe41338daf55877Daniel Sandler     */
592ac47ff70c322614ff2ca9ad82fe41338daf55877Daniel Sandler    public void cancel() {
593ac47ff70c322614ff2ca9ad82fe41338daf55877Daniel Sandler        finishExpanding(true);
594ac47ff70c322614ff2ca9ad82fe41338daf55877Daniel Sandler        clearView();
595ac47ff70c322614ff2ca9ad82fe41338daf55877Daniel Sandler
596ac47ff70c322614ff2ca9ad82fe41338daf55877Daniel Sandler        // reset the gesture detector
597ac47ff70c322614ff2ca9ad82fe41338daf55877Daniel Sandler        mSGD = new ScaleGestureDetector(mContext, mScaleGestureListener);
598ac47ff70c322614ff2ca9ad82fe41338daf55877Daniel Sandler    }
599ac47ff70c322614ff2ca9ad82fe41338daf55877Daniel Sandler
600ac47ff70c322614ff2ca9ad82fe41338daf55877Daniel Sandler    /**
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        }
608b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren        mVibrator.vibrate(duration);
6096a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler    }
6106a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler}
611b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren
612