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;
267a1a406b24c235ce59c13126d20cfb101c4e2777Daniel Sandlerimport android.util.Slog;
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;
32b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wrenimport android.view.ViewConfiguration;
336a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandlerimport android.view.ViewGroup;
346a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandlerimport android.view.View.OnClickListener;
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);
413ddab0dcc1039137f05a28ff86477601a223a0faChris Wren        boolean setUserExpandedChild(View v, boolean userExpanded);
423ddab0dcc1039137f05a28ff86477601a223a0faChris Wren        boolean 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
112ac47ff70c322614ff2ca9ad82fe41338daf55877Daniel Sandler    private OnScaleGestureListener mScaleGestureListener
113ac47ff70c322614ff2ca9ad82fe41338daf55877Daniel Sandler            = new ScaleGestureDetector.SimpleOnScaleGestureListener() {
114ac47ff70c322614ff2ca9ad82fe41338daf55877Daniel Sandler        @Override
115ac47ff70c322614ff2ca9ad82fe41338daf55877Daniel Sandler        public boolean onScaleBegin(ScaleGestureDetector detector) {
116ac47ff70c322614ff2ca9ad82fe41338daf55877Daniel Sandler            if (DEBUG_SCALE) Slog.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) {
129ac47ff70c322614ff2ca9ad82fe41338daf55877Daniel Sandler            if (DEBUG_SCALE) Slog.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) {
1463c148f106f6625ce247a2c7211682c3a1df89bc9Chris Wren            if (DEBUG_SCALE) Slog.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();
1613c148f106f6625ce247a2c7211682c3a1df89bc9Chris Wren            if (DEBUG_SCALE) Slog.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     * @param scoller if non-null also manipulate the scroll position to obey the gravity.
1859b2cd15f0fed990f532f35590c2a2896b90dc7fcChris Wren     */
1866a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler    public ExpandHelper(Context context, Callback callback, int small, int large) {
1876a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        mSmallSize = small;
18880a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren        mMaximumStretch = mSmallSize * STRETCH_INTERVAL;
1896a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        mLargeSize = large;
1906a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        mContext = context;
1916a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        mCallback = callback;
1926a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        mScaler = new ViewScaler();
1939b2cd15f0fed990f532f35590c2a2896b90dc7fcChris Wren        mGravity = Gravity.TOP;
194ba925e8ecd9decff5701001a0190042d6797942dChris Wren        mScaleAnimation = ObjectAnimator.ofFloat(mScaler, "height", 0f);
195ba925e8ecd9decff5701001a0190042d6797942dChris Wren        mScaleAnimation.setDuration(EXPAND_DURATION);
1964377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler        mPopLimit = mContext.getResources().getDimension(R.dimen.blinds_pop_threshold);
1974377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler        mPopDuration = mContext.getResources().getInteger(R.integer.blinds_pop_duration_ms);
1984377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler        mPullGestureMinXSpan = mContext.getResources().getDimension(R.dimen.pull_span_min);
199ba925e8ecd9decff5701001a0190042d6797942dChris Wren
2008900e631940fdffe7b941b56dc0f17e55345441eRomain Guy        AnimatorListenerAdapter glowVisibilityController = new AnimatorListenerAdapter() {
2018900e631940fdffe7b941b56dc0f17e55345441eRomain Guy            @Override
2028900e631940fdffe7b941b56dc0f17e55345441eRomain Guy            public void onAnimationStart(Animator animation) {
2038900e631940fdffe7b941b56dc0f17e55345441eRomain Guy                View target = (View) ((ObjectAnimator) animation).getTarget();
2048900e631940fdffe7b941b56dc0f17e55345441eRomain Guy                if (target.getAlpha() <= 0.0f) {
2058900e631940fdffe7b941b56dc0f17e55345441eRomain Guy                    target.setVisibility(View.VISIBLE);
2068900e631940fdffe7b941b56dc0f17e55345441eRomain Guy                }
2078900e631940fdffe7b941b56dc0f17e55345441eRomain Guy            }
2088900e631940fdffe7b941b56dc0f17e55345441eRomain Guy
2098900e631940fdffe7b941b56dc0f17e55345441eRomain Guy            @Override
2108900e631940fdffe7b941b56dc0f17e55345441eRomain Guy            public void onAnimationEnd(Animator animation) {
2118900e631940fdffe7b941b56dc0f17e55345441eRomain Guy                View target = (View) ((ObjectAnimator) animation).getTarget();
2128900e631940fdffe7b941b56dc0f17e55345441eRomain Guy                if (target.getAlpha() <= 0.0f) {
2138900e631940fdffe7b941b56dc0f17e55345441eRomain Guy                    target.setVisibility(View.INVISIBLE);
2148900e631940fdffe7b941b56dc0f17e55345441eRomain Guy                }
2158900e631940fdffe7b941b56dc0f17e55345441eRomain Guy            }
2168900e631940fdffe7b941b56dc0f17e55345441eRomain Guy        };
2178900e631940fdffe7b941b56dc0f17e55345441eRomain Guy
218ba925e8ecd9decff5701001a0190042d6797942dChris Wren        mGlowTopAnimation = ObjectAnimator.ofFloat(null, "alpha", 0f);
2198900e631940fdffe7b941b56dc0f17e55345441eRomain Guy        mGlowTopAnimation.addListener(glowVisibilityController);
220ba925e8ecd9decff5701001a0190042d6797942dChris Wren        mGlowBottomAnimation = ObjectAnimator.ofFloat(null, "alpha", 0f);
2218900e631940fdffe7b941b56dc0f17e55345441eRomain Guy        mGlowBottomAnimation.addListener(glowVisibilityController);
222ba925e8ecd9decff5701001a0190042d6797942dChris Wren        mGlowAnimationSet = new AnimatorSet();
223ba925e8ecd9decff5701001a0190042d6797942dChris Wren        mGlowAnimationSet.play(mGlowTopAnimation).with(mGlowBottomAnimation);
224ba925e8ecd9decff5701001a0190042d6797942dChris Wren        mGlowAnimationSet.setDuration(GLOW_DURATION);
225ba925e8ecd9decff5701001a0190042d6797942dChris Wren
226b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren        final ViewConfiguration configuration = ViewConfiguration.get(mContext);
227b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren        mTouchSlop = configuration.getScaledTouchSlop();
228b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren
229ac47ff70c322614ff2ca9ad82fe41338daf55877Daniel Sandler        mSGD = new ScaleGestureDetector(context, mScaleGestureListener);
2306a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler    }
2315de6e94e36e2adbdd4ebfb5c1903c23c9ea3c388Chris Wren
2324377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler    private void updateExpansion() {
233cea520747f344204a78db9d3f7f1abe3f695f49fChris Wren        if (DEBUG_SCALE) Slog.v(TAG, "updateExpansion()");
2344377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler        // are we scaling or dragging?
235cea520747f344204a78db9d3f7f1abe3f695f49fChris Wren        float span = mSGD.getCurrentSpan() - mInitialTouchSpan;
2364377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler        span *= USE_SPAN ? 1f : 0f;
2374377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler        float drag = mSGD.getFocusY() - mInitialTouchFocusY;
2384377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler        drag *= USE_DRAG ? 1f : 0f;
2394377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler        drag *= mGravity == Gravity.BOTTOM ? -1f : 1f;
2404377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler        float pull = Math.abs(drag) + Math.abs(span) + 1f;
2414377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler        float hand = drag * Math.abs(drag) / pull + span * Math.abs(span) / pull;
2424377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler        float target = hand + mOldHeight;
2434377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler        float newHeight = clamp(target);
2444377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler        mScaler.setHeight(newHeight);
2454377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler
2464377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler        setGlow(calculateGlow(target, newHeight));
247cea520747f344204a78db9d3f7f1abe3f695f49fChris Wren        mLastFocusY = mSGD.getFocusY();
248cea520747f344204a78db9d3f7f1abe3f695f49fChris Wren        mLastSpanY = mSGD.getCurrentSpan();
2494377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler    }
2504377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler
251b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren    private float clamp(float target) {
252b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren        float out = target;
253b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren        out = out < mSmallSize ? mSmallSize : (out > mLargeSize ? mLargeSize : out);
254b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren        out = out > mNaturalHeight ? mNaturalHeight : out;
255b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren        return out;
256b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren    }
257b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren
258b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren    private View findView(float x, float y) {
259b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren        View v = null;
260b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren        if (mEventSource != null) {
261b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren            int[] location = new int[2];
262b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren            mEventSource.getLocationOnScreen(location);
2634377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler            x += location[0];
2644377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler            y += location[1];
265b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren            v = mCallback.getChildAtRawPosition(x, y);
266b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren        } else {
267b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren            v = mCallback.getChildAtPosition(x, y);
268b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren        }
269b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren        return v;
270b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren    }
271b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren
272b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren    private boolean isInside(View v, float x, float y) {
273b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren        if (DEBUG) Slog.d(TAG, "isinside (" + x + ", " + y + ")");
274b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren
275b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren        if (v == null) {
276b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren            if (DEBUG) Slog.d(TAG, "isinside null subject");
277b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren            return false;
278b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren        }
279b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren        if (mEventSource != null) {
280b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren            int[] location = new int[2];
281b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren            mEventSource.getLocationOnScreen(location);
2824377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler            x += location[0];
2834377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler            y += location[1];
284b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren            if (DEBUG) Slog.d(TAG, "  to global (" + x + ", " + y + ")");
285b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren        }
286b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren        int[] location = new int[2];
287b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren        v.getLocationOnScreen(location);
2884377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler        x -= location[0];
2894377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler        y -= location[1];
290b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren        if (DEBUG) Slog.d(TAG, "  to local (" + x + ", " + y + ")");
291b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren        if (DEBUG) Slog.d(TAG, "  inside (" + v.getWidth() + ", " + v.getHeight() + ")");
292b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren        boolean inside = (x > 0f && y > 0f && x < v.getWidth() & y < v.getHeight());
293b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren        return inside;
294b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren    }
295b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren
2965de6e94e36e2adbdd4ebfb5c1903c23c9ea3c388Chris Wren    public void setEventSource(View eventSource) {
2975de6e94e36e2adbdd4ebfb5c1903c23c9ea3c388Chris Wren        mEventSource = eventSource;
2985de6e94e36e2adbdd4ebfb5c1903c23c9ea3c388Chris Wren    }
2995de6e94e36e2adbdd4ebfb5c1903c23c9ea3c388Chris Wren
3009b2cd15f0fed990f532f35590c2a2896b90dc7fcChris Wren    public void setGravity(int gravity) {
3019b2cd15f0fed990f532f35590c2a2896b90dc7fcChris Wren        mGravity = gravity;
3029b2cd15f0fed990f532f35590c2a2896b90dc7fcChris Wren    }
3039b2cd15f0fed990f532f35590c2a2896b90dc7fcChris Wren
304b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren    public void setScrollView(View scrollView) {
305b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren        mScrollView = scrollView;
306b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren    }
307b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren
308b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren    private float calculateGlow(float target, float actual) {
309b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren        // glow if overscale
3103c148f106f6625ce247a2c7211682c3a1df89bc9Chris Wren        if (DEBUG_GLOW) Slog.d(TAG, "target: " + target + " actual: " + actual);
3114377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler        float stretch = Math.abs((target - actual) / mMaximumStretch);
312b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren        float strength = 1f / (1f + (float) Math.pow(Math.E, -1 * ((8f * stretch) - 5f)));
3133c148f106f6625ce247a2c7211682c3a1df89bc9Chris Wren        if (DEBUG_GLOW) Slog.d(TAG, "stretch: " + stretch + " strength: " + strength);
314b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren        return (GLOW_BASE + strength * (1f - GLOW_BASE));
315b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren    }
316b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren
31780a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren    public void setGlow(float glow) {
31889139d74b27305a29ca082c75d94dcbed5f84625Chris Wren        if (!mGlowAnimationSet.isRunning() || glow == 0f) {
31989139d74b27305a29ca082c75d94dcbed5f84625Chris Wren            if (mGlowAnimationSet.isRunning()) {
3203c148f106f6625ce247a2c7211682c3a1df89bc9Chris Wren                mGlowAnimationSet.end();
32189139d74b27305a29ca082c75d94dcbed5f84625Chris Wren            }
322ba925e8ecd9decff5701001a0190042d6797942dChris Wren            if (mCurrViewTopGlow != null && mCurrViewBottomGlow != null) {
323b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren                if (glow == 0f || mCurrViewTopGlow.getAlpha() == 0f) {
324ba925e8ecd9decff5701001a0190042d6797942dChris Wren                    // animate glow in and out
325ba925e8ecd9decff5701001a0190042d6797942dChris Wren                    mGlowTopAnimation.setTarget(mCurrViewTopGlow);
326ba925e8ecd9decff5701001a0190042d6797942dChris Wren                    mGlowBottomAnimation.setTarget(mCurrViewBottomGlow);
327ba925e8ecd9decff5701001a0190042d6797942dChris Wren                    mGlowTopAnimation.setFloatValues(glow);
328ba925e8ecd9decff5701001a0190042d6797942dChris Wren                    mGlowBottomAnimation.setFloatValues(glow);
329ba925e8ecd9decff5701001a0190042d6797942dChris Wren                    mGlowAnimationSet.setupStartValues();
330ba925e8ecd9decff5701001a0190042d6797942dChris Wren                    mGlowAnimationSet.start();
331ba925e8ecd9decff5701001a0190042d6797942dChris Wren                } else {
332ba925e8ecd9decff5701001a0190042d6797942dChris Wren                    // set it explicitly in reponse to touches.
333ba925e8ecd9decff5701001a0190042d6797942dChris Wren                    mCurrViewTopGlow.setAlpha(glow);
334ba925e8ecd9decff5701001a0190042d6797942dChris Wren                    mCurrViewBottomGlow.setAlpha(glow);
3358900e631940fdffe7b941b56dc0f17e55345441eRomain Guy                    handleGlowVisibility();
336ba925e8ecd9decff5701001a0190042d6797942dChris Wren                }
337ba925e8ecd9decff5701001a0190042d6797942dChris Wren            }
33880a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren        }
33980a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren    }
3408900e631940fdffe7b941b56dc0f17e55345441eRomain Guy
3418900e631940fdffe7b941b56dc0f17e55345441eRomain Guy    private void handleGlowVisibility() {
3428900e631940fdffe7b941b56dc0f17e55345441eRomain Guy        mCurrViewTopGlow.setVisibility(mCurrViewTopGlow.getAlpha() <= 0.0f ?
3438900e631940fdffe7b941b56dc0f17e55345441eRomain Guy                View.INVISIBLE : View.VISIBLE);
3448900e631940fdffe7b941b56dc0f17e55345441eRomain Guy        mCurrViewBottomGlow.setVisibility(mCurrViewBottomGlow.getAlpha() <= 0.0f ?
3458900e631940fdffe7b941b56dc0f17e55345441eRomain Guy                View.INVISIBLE : View.VISIBLE);
3468900e631940fdffe7b941b56dc0f17e55345441eRomain Guy    }
3478900e631940fdffe7b941b56dc0f17e55345441eRomain Guy
3484377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler    @Override
3496a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler    public boolean onInterceptTouchEvent(MotionEvent ev) {
3504377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler        final int action = ev.getAction();
3514377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler        if (DEBUG_SCALE) Slog.d(TAG, "intercept: act=" + MotionEvent.actionToString(action) +
3524377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler                         " expanding=" + mExpanding +
3534377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler                         (0 != (mExpansionStyle & BLINDS) ? " (blinds)" : "") +
3544377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler                         (0 != (mExpansionStyle & PULL) ? " (pull)" : "") +
3554377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler                         (0 != (mExpansionStyle & STRETCH) ? " (stretch)" : ""));
3564377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler        // check for a spread-finger vertical pull gesture
3574377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler        mSGD.onTouchEvent(ev);
3584377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler        final int x = (int) mSGD.getFocusX();
3594377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler        final int y = (int) mSGD.getFocusY();
360cea520747f344204a78db9d3f7f1abe3f695f49fChris Wren
361cea520747f344204a78db9d3f7f1abe3f695f49fChris Wren        mInitialTouchFocusY = y;
362cea520747f344204a78db9d3f7f1abe3f695f49fChris Wren        mInitialTouchSpan = mSGD.getCurrentSpan();
363cea520747f344204a78db9d3f7f1abe3f695f49fChris Wren        mLastFocusY = mInitialTouchFocusY;
364cea520747f344204a78db9d3f7f1abe3f695f49fChris Wren        mLastSpanY = mInitialTouchSpan;
365cea520747f344204a78db9d3f7f1abe3f695f49fChris Wren        if (DEBUG_SCALE) Slog.d(TAG, "set initial span: " + mInitialTouchSpan);
366cea520747f344204a78db9d3f7f1abe3f695f49fChris Wren
3674377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler        if (mExpanding) {
368b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren            return true;
369b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren        } else {
3704377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler            if ((action == MotionEvent.ACTION_MOVE) && 0 != (mExpansionStyle & BLINDS)) {
3714377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler                // we've begun Venetian blinds style expansion
3724377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler                return true;
3734377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler            }
3744377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler            final float xspan = mSGD.getCurrentSpanX();
3754377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler            if ((action == MotionEvent.ACTION_MOVE &&
3764377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler                    xspan > mPullGestureMinXSpan &&
3774377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler                    xspan > mSGD.getCurrentSpanY())) {
3784377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler                // detect a vertical pulling gesture with fingers somewhat separated
3794377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler                if (DEBUG_SCALE) Slog.v(TAG, "got pull gesture (xspan=" + xspan + "px)");
3804377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler
3814377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler                final View underFocus = findView(x, y);
3824377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler                if (underFocus != null) {
3834377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler                    startExpanding(underFocus, PULL);
3844377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler                }
385b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren                return true;
386b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren            }
387b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren            if (mScrollView != null && mScrollView.getScrollY() > 0) {
388b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren                return false;
389b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren            }
3904377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler            // Now look for other gestures
391b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren            switch (action & MotionEvent.ACTION_MASK) {
392b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren            case MotionEvent.ACTION_MOVE: {
393b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren                if (mWatchingForPull) {
394b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren                    final int yDiff = y - mLastMotionY;
395b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren                    if (yDiff > mTouchSlop) {
3964377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler                        if (DEBUG) Slog.v(TAG, "got venetian gesture (dy=" + yDiff + "px)");
397b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren                        mLastMotionY = y;
3984377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler                        final View underFocus = findView(x, y);
3994377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler                        if (underFocus != null) {
4004377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler                            startExpanding(underFocus, BLINDS);
4013c148f106f6625ce247a2c7211682c3a1df89bc9Chris Wren                            mInitialTouchY = mLastMotionY;
402b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren                            mHasPopped = false;
403b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren                        }
404b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren                    }
405b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren                }
406b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren                break;
407b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren            }
408b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren
409b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren            case MotionEvent.ACTION_DOWN:
4104377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler                mWatchingForPull = isInside(mScrollView, x, y);
4114377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler                mLastMotionY = y;
412b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren                break;
413b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren
414b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren            case MotionEvent.ACTION_CANCEL:
415b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren            case MotionEvent.ACTION_UP:
4164377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler                if (DEBUG) Slog.d(TAG, "up/cancel");
4174377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler                finishExpanding(false);
4184377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler                clearView();
419b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren                break;
420b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren            }
4214377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler            return mExpanding;
422b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren        }
4236a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler    }
4246a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler
4254377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler    @Override
4266a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler    public boolean onTouchEvent(MotionEvent ev) {
427cea520747f344204a78db9d3f7f1abe3f695f49fChris Wren        final int action = ev.getActionMasked();
4284377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler        if (DEBUG_SCALE) Slog.d(TAG, "touch: act=" + MotionEvent.actionToString(action) +
4294377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler                " expanding=" + mExpanding +
4304377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler                (0 != (mExpansionStyle & BLINDS) ? " (blinds)" : "") +
4314377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler                (0 != (mExpansionStyle & PULL) ? " (pull)" : "") +
4324377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler                (0 != (mExpansionStyle & STRETCH) ? " (stretch)" : ""));
4334377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler
4344377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler        mSGD.onTouchEvent(ev);
4354377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler
4366a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        switch (action) {
437b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren            case MotionEvent.ACTION_MOVE: {
4384377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler                if (0 != (mExpansionStyle & BLINDS)) {
43986d00fb40ae5cd01ce5a2e228e6de777eae6dee8Chris Wren                    final float rawHeight = ev.getY() - mInitialTouchY + mOldHeight;
44086d00fb40ae5cd01ce5a2e228e6de777eae6dee8Chris Wren                    final float newHeight = clamp(rawHeight);
44186d00fb40ae5cd01ce5a2e228e6de777eae6dee8Chris Wren                    final boolean wasClosed = (mOldHeight == mSmallSize);
44286d00fb40ae5cd01ce5a2e228e6de777eae6dee8Chris Wren                    boolean isFinished = false;
44386d00fb40ae5cd01ce5a2e228e6de777eae6dee8Chris Wren                    if (rawHeight > mNaturalHeight) {
44486d00fb40ae5cd01ce5a2e228e6de777eae6dee8Chris Wren                        isFinished = true;
44586d00fb40ae5cd01ce5a2e228e6de777eae6dee8Chris Wren                    }
44686d00fb40ae5cd01ce5a2e228e6de777eae6dee8Chris Wren                    if (rawHeight < mSmallSize) {
44786d00fb40ae5cd01ce5a2e228e6de777eae6dee8Chris Wren                        isFinished = true;
44886d00fb40ae5cd01ce5a2e228e6de777eae6dee8Chris Wren                    }
44986d00fb40ae5cd01ce5a2e228e6de777eae6dee8Chris Wren
45086d00fb40ae5cd01ce5a2e228e6de777eae6dee8Chris Wren                    final float pull = Math.abs(ev.getY() - mInitialTouchY);
45186d00fb40ae5cd01ce5a2e228e6de777eae6dee8Chris Wren                    if (mHasPopped || pull > mPopLimit) {
452b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren                        if (!mHasPopped) {
453b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren                            vibrate(mPopDuration);
454b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren                            mHasPopped = true;
455b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren                        }
45686d00fb40ae5cd01ce5a2e228e6de777eae6dee8Chris Wren                    }
45786d00fb40ae5cd01ce5a2e228e6de777eae6dee8Chris Wren
45886d00fb40ae5cd01ce5a2e228e6de777eae6dee8Chris Wren                    if (mHasPopped) {
459b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren                        mScaler.setHeight(newHeight);
46086d00fb40ae5cd01ce5a2e228e6de777eae6dee8Chris Wren                        setGlow(GLOW_BASE);
461b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren                    } else {
46286d00fb40ae5cd01ce5a2e228e6de777eae6dee8Chris Wren                        setGlow(calculateGlow(4f * pull, 0f));
46386d00fb40ae5cd01ce5a2e228e6de777eae6dee8Chris Wren                    }
46486d00fb40ae5cd01ce5a2e228e6de777eae6dee8Chris Wren
4654377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler                    final int x = (int) mSGD.getFocusX();
4664377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler                    final int y = (int) mSGD.getFocusY();
4674377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler                    View underFocus = findView(x, y);
4684377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler                    if (isFinished && underFocus != null && underFocus != mCurrView) {
4694377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler                        finishExpanding(false); // @@@ needed?
4704377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler                        startExpanding(underFocus, BLINDS);
4714377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler                        mInitialTouchY = y;
47286d00fb40ae5cd01ce5a2e228e6de777eae6dee8Chris Wren                        mHasPopped = false;
473b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren                    }
474b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren                    return true;
475b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren                }
4764377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler
4774377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler                if (mExpanding) {
4784377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler                    updateExpansion();
4794377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler                    return true;
4804377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler                }
4814377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler
482b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren                break;
483b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren            }
484cea520747f344204a78db9d3f7f1abe3f695f49fChris Wren
485cea520747f344204a78db9d3f7f1abe3f695f49fChris Wren            case MotionEvent.ACTION_POINTER_UP:
486cea520747f344204a78db9d3f7f1abe3f695f49fChris Wren            case MotionEvent.ACTION_POINTER_DOWN:
487cea520747f344204a78db9d3f7f1abe3f695f49fChris Wren                if (DEBUG) Slog.d(TAG, "pointer change");
488cea520747f344204a78db9d3f7f1abe3f695f49fChris Wren                mInitialTouchY += mSGD.getFocusY() - mLastFocusY;
489cea520747f344204a78db9d3f7f1abe3f695f49fChris Wren                mInitialTouchSpan += mSGD.getCurrentSpan() - mLastSpanY;
490cea520747f344204a78db9d3f7f1abe3f695f49fChris Wren                break;
491cea520747f344204a78db9d3f7f1abe3f695f49fChris Wren
4926a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler            case MotionEvent.ACTION_UP:
4936a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler            case MotionEvent.ACTION_CANCEL:
4944377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler                if (DEBUG) Slog.d(TAG, "up/cancel");
4954377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler                finishExpanding(false);
49680a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren                clearView();
4976a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler                break;
4986a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        }
4996a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        return true;
5006a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler    }
5014377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler
5024377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler    private void startExpanding(View v, int expandType) {
503cea520747f344204a78db9d3f7f1abe3f695f49fChris Wren        mExpansionStyle = expandType;
504cea520747f344204a78db9d3f7f1abe3f695f49fChris Wren        if (mExpanding &&  v == mCurrView) {
505cea520747f344204a78db9d3f7f1abe3f695f49fChris Wren            return;
506cea520747f344204a78db9d3f7f1abe3f695f49fChris Wren        }
5074377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler        mExpanding = true;
5084377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler        if (DEBUG) Slog.d(TAG, "scale type " + expandType + " beginning on view: " + v);
5094377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler        mCallback.setUserLockedChild(v, true);
5104377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler        setView(v);
5114377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler        setGlow(GLOW_BASE);
5124377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler        mScaler.setView(v);
5134377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler        mOldHeight = mScaler.getHeight();
5144377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler        if (mCallback.canChildBeExpanded(v)) {
5154377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler            if (DEBUG) Slog.d(TAG, "working on an expandable child");
5164377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler            mNaturalHeight = mScaler.getNaturalHeight(mLargeSize);
517b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren        } else {
5184377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler            if (DEBUG) Slog.d(TAG, "working on a non-expandable child");
5194377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler            mNaturalHeight = mOldHeight;
5206a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        }
5214377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler        if (DEBUG) Slog.d(TAG, "got mOldHeight: " + mOldHeight +
5224377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler                    " mNaturalHeight: " + mNaturalHeight);
5234377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler        v.getParent().requestDisallowInterceptTouchEvent(true);
5246a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler    }
5256a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler
5264377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler    private void finishExpanding(boolean force) {
5274377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler        if (!mExpanding) return;
5284377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler
529cea520747f344204a78db9d3f7f1abe3f695f49fChris Wren        if (DEBUG) Slog.d(TAG, "scale in finishing on view: " + mCurrView);
530cea520747f344204a78db9d3f7f1abe3f695f49fChris Wren
5313ddab0dcc1039137f05a28ff86477601a223a0faChris Wren        float currentHeight = mScaler.getHeight();
5323ddab0dcc1039137f05a28ff86477601a223a0faChris Wren        float targetHeight = mSmallSize;
5336a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        float h = mScaler.getHeight();
5346a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        final boolean wasClosed = (mOldHeight == mSmallSize);
5356a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        if (wasClosed) {
5363ddab0dcc1039137f05a28ff86477601a223a0faChris Wren            targetHeight = (force || currentHeight > mSmallSize) ? mNaturalHeight : mSmallSize;
5376a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        } else {
5383ddab0dcc1039137f05a28ff86477601a223a0faChris Wren            targetHeight = (force || currentHeight < mNaturalHeight) ? mSmallSize : mNaturalHeight;
5396a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        }
540ba925e8ecd9decff5701001a0190042d6797942dChris Wren        if (mScaleAnimation.isRunning()) {
541ba925e8ecd9decff5701001a0190042d6797942dChris Wren            mScaleAnimation.cancel();
542ba925e8ecd9decff5701001a0190042d6797942dChris Wren        }
54380a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren        setGlow(0f);
5448fd12657e353a4a6f6d875a0d86850426fec00e8Chris Wren        mCallback.setUserExpandedChild(mCurrView, h == mNaturalHeight);
5453ddab0dcc1039137f05a28ff86477601a223a0faChris Wren        if (targetHeight != currentHeight) {
5463ddab0dcc1039137f05a28ff86477601a223a0faChris Wren            mScaleAnimation.setFloatValues(targetHeight);
5473ddab0dcc1039137f05a28ff86477601a223a0faChris Wren            mScaleAnimation.setupStartValues();
5483ddab0dcc1039137f05a28ff86477601a223a0faChris Wren            mScaleAnimation.start();
5493ddab0dcc1039137f05a28ff86477601a223a0faChris Wren        }
5503ddab0dcc1039137f05a28ff86477601a223a0faChris Wren        mCallback.setUserLockedChild(mCurrView, false);
5514377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler
5524377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler        mExpanding = false;
5534377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler        mExpansionStyle = NONE;
5544377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler
555cea520747f344204a78db9d3f7f1abe3f695f49fChris Wren        if (DEBUG) Slog.d(TAG, "wasClosed is: " + wasClosed);
556cea520747f344204a78db9d3f7f1abe3f695f49fChris Wren        if (DEBUG) Slog.d(TAG, "currentHeight is: " + currentHeight);
557cea520747f344204a78db9d3f7f1abe3f695f49fChris Wren        if (DEBUG) Slog.d(TAG, "mSmallSize is: " + mSmallSize);
558cea520747f344204a78db9d3f7f1abe3f695f49fChris Wren        if (DEBUG) Slog.d(TAG, "targetHeight is: " + targetHeight);
5597a1a406b24c235ce59c13126d20cfb101c4e2777Daniel Sandler        if (DEBUG) Slog.d(TAG, "scale was finished on view: " + mCurrView);
56080a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren    }
56180a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren
56280a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren    private void clearView() {
5636a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        mCurrView = null;
56480a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren        mCurrViewTopGlow = null;
56580a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren        mCurrViewBottomGlow = null;
56680a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren    }
56780a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren
56880a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren    private void setView(View v) {
56989139d74b27305a29ca082c75d94dcbed5f84625Chris Wren        mCurrView = v;
57080a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren        if (v instanceof ViewGroup) {
57180a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren            ViewGroup g = (ViewGroup) v;
57280a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren            mCurrViewTopGlow = g.findViewById(R.id.top_glow);
57380a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren            mCurrViewBottomGlow = g.findViewById(R.id.bottom_glow);
57489139d74b27305a29ca082c75d94dcbed5f84625Chris Wren            if (DEBUG) {
575b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren                String debugLog = "Looking for glows: " +
57680a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren                        (mCurrViewTopGlow != null ? "found top " : "didn't find top") +
57780a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren                        (mCurrViewBottomGlow != null ? "found bottom " : "didn't find bottom");
5787a1a406b24c235ce59c13126d20cfb101c4e2777Daniel Sandler                Slog.v(TAG,  debugLog);
57980a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren            }
58080a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren        }
5816a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler    }
5826a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler
5836a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler    @Override
5846a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler    public void onClick(View v) {
5854377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler        startExpanding(v, STRETCH);
5864377d1494cab30ab299f6065cf6857df7367db3bDaniel Sandler        finishExpanding(true);
5873c148f106f6625ce247a2c7211682c3a1df89bc9Chris Wren        clearView();
588b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren    }
5896a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler
590b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren    /**
591ac47ff70c322614ff2ca9ad82fe41338daf55877Daniel Sandler     * Use this to abort any pending expansions in progress.
592ac47ff70c322614ff2ca9ad82fe41338daf55877Daniel Sandler     */
593ac47ff70c322614ff2ca9ad82fe41338daf55877Daniel Sandler    public void cancel() {
594ac47ff70c322614ff2ca9ad82fe41338daf55877Daniel Sandler        finishExpanding(true);
595ac47ff70c322614ff2ca9ad82fe41338daf55877Daniel Sandler        clearView();
596ac47ff70c322614ff2ca9ad82fe41338daf55877Daniel Sandler
597ac47ff70c322614ff2ca9ad82fe41338daf55877Daniel Sandler        // reset the gesture detector
598ac47ff70c322614ff2ca9ad82fe41338daf55877Daniel Sandler        mSGD = new ScaleGestureDetector(mContext, mScaleGestureListener);
599ac47ff70c322614ff2ca9ad82fe41338daf55877Daniel Sandler    }
600ac47ff70c322614ff2ca9ad82fe41338daf55877Daniel Sandler
601ac47ff70c322614ff2ca9ad82fe41338daf55877Daniel Sandler    /**
602b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren     * Triggers haptic feedback.
603b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren     */
604b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren    private synchronized void vibrate(long duration) {
605b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren        if (mVibrator == null) {
606b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren            mVibrator = (android.os.Vibrator)
607b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren                    mContext.getSystemService(Context.VIBRATOR_SERVICE);
608b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren        }
609b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren        mVibrator.vibrate(duration);
6106a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler    }
6116a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler}
612b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren
613