ExpandHelper.java revision e46647d28467ee9e88aafe2951a5736f494235da
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;
306a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandlerimport android.view.View;
31b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wrenimport android.view.ViewConfiguration;
326a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandlerimport android.view.ViewGroup;
336a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandlerimport android.view.View.OnClickListener;
346a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler
353c148f106f6625ce247a2c7211682c3a1df89bc9Chris Wrenimport java.util.Stack;
363c148f106f6625ce247a2c7211682c3a1df89bc9Chris Wren
376a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandlerpublic class ExpandHelper implements Gefingerpoken, OnClickListener {
386a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler    public interface Callback {
395de6e94e36e2adbdd4ebfb5c1903c23c9ea3c388Chris Wren        View getChildAtRawPosition(float x, float y);
406a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        View getChildAtPosition(float x, float y);
413c148f106f6625ce247a2c7211682c3a1df89bc9Chris Wren        View getPreviousChild(View currentChild);
4280a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren        boolean canChildBeExpanded(View v);
438fd12657e353a4a6f6d875a0d86850426fec00e8Chris Wren        boolean setUserExpandedChild(View v, boolean userxpanded);
446a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler    }
456a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler
466a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler    private static final String TAG = "ExpandHelper";
47e46647d28467ee9e88aafe2951a5736f494235daChris Wren    protected static final boolean DEBUG = false;
483c148f106f6625ce247a2c7211682c3a1df89bc9Chris Wren    protected static final boolean DEBUG_SCALE = false;
49e46647d28467ee9e88aafe2951a5736f494235daChris Wren    protected static final boolean DEBUG_GLOW = false;
506a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler    private static final long EXPAND_DURATION = 250;
51ba925e8ecd9decff5701001a0190042d6797942dChris Wren    private static final long GLOW_DURATION = 150;
52ba925e8ecd9decff5701001a0190042d6797942dChris Wren
5389139d74b27305a29ca082c75d94dcbed5f84625Chris Wren    // Set to false to disable focus-based gestures (two-finger pull).
5489139d74b27305a29ca082c75d94dcbed5f84625Chris Wren    private static final boolean USE_DRAG = true;
5589139d74b27305a29ca082c75d94dcbed5f84625Chris Wren    // Set to false to disable scale-based gestures (both horizontal and vertical).
5689139d74b27305a29ca082c75d94dcbed5f84625Chris Wren    private static final boolean USE_SPAN = true;
5789139d74b27305a29ca082c75d94dcbed5f84625Chris Wren    // Both gestures types may be active at the same time.
5889139d74b27305a29ca082c75d94dcbed5f84625Chris Wren    // At least one gesture type should be active.
5989139d74b27305a29ca082c75d94dcbed5f84625Chris Wren    // A variant of the screwdriver gesture will emerge from either gesture type.
606a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler
6180a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren    // amount of overstretch for maximum brightness expressed in U
6280a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren    // 2f: maximum brightness is stretching a 1U to 3U, or a 4U to 6U
6380a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren    private static final float STRETCH_INTERVAL = 2f;
6480a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren
6580a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren    // level of glow for a touch, without overstretch
6680a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren    // overstretch fills the range (GLOW_BASE, 1.0]
6780a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren    private static final float GLOW_BASE = 0.5f;
6880a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren
696a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler    @SuppressWarnings("unused")
706a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler    private Context mContext;
716a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler
726a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler    private boolean mStretching;
73b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren    private boolean mPullingWithOneFinger;
74b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren    private boolean mWatchingForPull;
75b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren    private boolean mHasPopped;
765de6e94e36e2adbdd4ebfb5c1903c23c9ea3c388Chris Wren    private View mEventSource;
776a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler    private View mCurrView;
7880a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren    private View mCurrViewTopGlow;
7980a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren    private View mCurrViewBottomGlow;
806a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler    private float mOldHeight;
816a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler    private float mNaturalHeight;
8289139d74b27305a29ca082c75d94dcbed5f84625Chris Wren    private float mInitialTouchFocusY;
83b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren    private float mInitialTouchY;
846a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler    private float mInitialTouchSpan;
85b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren    private int mTouchSlop;
86b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren    private int mLastMotionY;
87b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren    private float mPopLimit;
88b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren    private int mPopDuration;
896a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler    private Callback mCallback;
906a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler    private ScaleGestureDetector mDetector;
916a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler    private ViewScaler mScaler;
92ba925e8ecd9decff5701001a0190042d6797942dChris Wren    private ObjectAnimator mScaleAnimation;
93ba925e8ecd9decff5701001a0190042d6797942dChris Wren    private AnimatorSet mGlowAnimationSet;
94ba925e8ecd9decff5701001a0190042d6797942dChris Wren    private ObjectAnimator mGlowTopAnimation;
95ba925e8ecd9decff5701001a0190042d6797942dChris Wren    private ObjectAnimator mGlowBottomAnimation;
96b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren    private Vibrator mVibrator;
976a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler
986a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler    private int mSmallSize;
996a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler    private int mLargeSize;
10080a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren    private float mMaximumStretch;
1016a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler
1029b2cd15f0fed990f532f35590c2a2896b90dc7fcChris Wren    private int mGravity;
1039b2cd15f0fed990f532f35590c2a2896b90dc7fcChris Wren
104b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren    private View mScrollView;
105b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren
1066a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler    private class ViewScaler {
1076a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        View mView;
1089b2cd15f0fed990f532f35590c2a2896b90dc7fcChris Wren
1096a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        public ViewScaler() {}
1106a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        public void setView(View v) {
1116a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler            mView = v;
1126a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        }
1136a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        public void setHeight(float h) {
1143c148f106f6625ce247a2c7211682c3a1df89bc9Chris Wren            if (DEBUG_SCALE) Slog.v(TAG, "SetHeight: setting to " + h);
1156a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler            ViewGroup.LayoutParams lp = mView.getLayoutParams();
1166a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler            lp.height = (int)h;
1176a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler            mView.setLayoutParams(lp);
11889139d74b27305a29ca082c75d94dcbed5f84625Chris Wren            mView.requestLayout();
1196a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        }
1206a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        public float getHeight() {
1216a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler            int height = mView.getLayoutParams().height;
1226a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler            if (height < 0) {
1236a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler                height = mView.getMeasuredHeight();
1246a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler            }
1256a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler            return (float) height;
1266a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        }
1276a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        public int getNaturalHeight(int maximum) {
1286a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler            ViewGroup.LayoutParams lp = mView.getLayoutParams();
1293c148f106f6625ce247a2c7211682c3a1df89bc9Chris Wren            if (DEBUG_SCALE) Slog.v(TAG, "Inspecting a child of type: " +
1303c148f106f6625ce247a2c7211682c3a1df89bc9Chris Wren                    mView.getClass().getName());
1316a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler            int oldHeight = lp.height;
1326a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler            lp.height = ViewGroup.LayoutParams.WRAP_CONTENT;
1336a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler            mView.setLayoutParams(lp);
1346a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler            mView.measure(
1356a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler                    View.MeasureSpec.makeMeasureSpec(mView.getMeasuredWidth(),
13689139d74b27305a29ca082c75d94dcbed5f84625Chris Wren                                                     View.MeasureSpec.EXACTLY),
1376a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler                    View.MeasureSpec.makeMeasureSpec(maximum,
13889139d74b27305a29ca082c75d94dcbed5f84625Chris Wren                                                     View.MeasureSpec.AT_MOST));
1396a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler            lp.height = oldHeight;
1406a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler            mView.setLayoutParams(lp);
1416a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler            return mView.getMeasuredHeight();
1426a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        }
1436a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler    }
1446a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler
1453c148f106f6625ce247a2c7211682c3a1df89bc9Chris Wren    class PopState {
1463c148f106f6625ce247a2c7211682c3a1df89bc9Chris Wren        View mCurrView;
1473c148f106f6625ce247a2c7211682c3a1df89bc9Chris Wren        View mCurrViewTopGlow;
1483c148f106f6625ce247a2c7211682c3a1df89bc9Chris Wren        View mCurrViewBottomGlow;
1493c148f106f6625ce247a2c7211682c3a1df89bc9Chris Wren        float mOldHeight;
1503c148f106f6625ce247a2c7211682c3a1df89bc9Chris Wren        float mNaturalHeight;
1513c148f106f6625ce247a2c7211682c3a1df89bc9Chris Wren        float mInitialTouchY;
1523c148f106f6625ce247a2c7211682c3a1df89bc9Chris Wren    }
1533c148f106f6625ce247a2c7211682c3a1df89bc9Chris Wren
1543c148f106f6625ce247a2c7211682c3a1df89bc9Chris Wren    private Stack<PopState> popStack;
1553c148f106f6625ce247a2c7211682c3a1df89bc9Chris Wren
1569b2cd15f0fed990f532f35590c2a2896b90dc7fcChris Wren    /**
1579b2cd15f0fed990f532f35590c2a2896b90dc7fcChris Wren     * Handle expansion gestures to expand and contract children of the callback.
1589b2cd15f0fed990f532f35590c2a2896b90dc7fcChris Wren     *
1599b2cd15f0fed990f532f35590c2a2896b90dc7fcChris Wren     * @param context application context
1609b2cd15f0fed990f532f35590c2a2896b90dc7fcChris Wren     * @param callback the container that holds the items to be manipulated
1619b2cd15f0fed990f532f35590c2a2896b90dc7fcChris Wren     * @param small the smallest allowable size for the manuipulated items.
1629b2cd15f0fed990f532f35590c2a2896b90dc7fcChris Wren     * @param large the largest allowable size for the manuipulated items.
1639b2cd15f0fed990f532f35590c2a2896b90dc7fcChris Wren     * @param scoller if non-null also manipulate the scroll position to obey the gravity.
1649b2cd15f0fed990f532f35590c2a2896b90dc7fcChris Wren     */
1656a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler    public ExpandHelper(Context context, Callback callback, int small, int large) {
1666a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        mSmallSize = small;
16780a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren        mMaximumStretch = mSmallSize * STRETCH_INTERVAL;
1686a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        mLargeSize = large;
1696a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        mContext = context;
1706a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        mCallback = callback;
1713c148f106f6625ce247a2c7211682c3a1df89bc9Chris Wren        popStack = new Stack<PopState>();
1726a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        mScaler = new ViewScaler();
1739b2cd15f0fed990f532f35590c2a2896b90dc7fcChris Wren        mGravity = Gravity.TOP;
174ba925e8ecd9decff5701001a0190042d6797942dChris Wren        mScaleAnimation = ObjectAnimator.ofFloat(mScaler, "height", 0f);
175ba925e8ecd9decff5701001a0190042d6797942dChris Wren        mScaleAnimation.setDuration(EXPAND_DURATION);
176b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren        mPopLimit = mContext.getResources().getDimension(R.dimen.one_finger_pop_limit);
177b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren        mPopDuration = mContext.getResources().getInteger(R.integer.one_finger_pop_duration_ms);
178ba925e8ecd9decff5701001a0190042d6797942dChris Wren
1798900e631940fdffe7b941b56dc0f17e55345441eRomain Guy        AnimatorListenerAdapter glowVisibilityController = new AnimatorListenerAdapter() {
1808900e631940fdffe7b941b56dc0f17e55345441eRomain Guy            @Override
1818900e631940fdffe7b941b56dc0f17e55345441eRomain Guy            public void onAnimationStart(Animator animation) {
1828900e631940fdffe7b941b56dc0f17e55345441eRomain Guy                View target = (View) ((ObjectAnimator) animation).getTarget();
1838900e631940fdffe7b941b56dc0f17e55345441eRomain Guy                if (target.getAlpha() <= 0.0f) {
1848900e631940fdffe7b941b56dc0f17e55345441eRomain Guy                    target.setVisibility(View.VISIBLE);
1858900e631940fdffe7b941b56dc0f17e55345441eRomain Guy                }
1868900e631940fdffe7b941b56dc0f17e55345441eRomain Guy            }
1878900e631940fdffe7b941b56dc0f17e55345441eRomain Guy
1888900e631940fdffe7b941b56dc0f17e55345441eRomain Guy            @Override
1898900e631940fdffe7b941b56dc0f17e55345441eRomain Guy            public void onAnimationEnd(Animator animation) {
1908900e631940fdffe7b941b56dc0f17e55345441eRomain Guy                View target = (View) ((ObjectAnimator) animation).getTarget();
1918900e631940fdffe7b941b56dc0f17e55345441eRomain Guy                if (target.getAlpha() <= 0.0f) {
1928900e631940fdffe7b941b56dc0f17e55345441eRomain Guy                    target.setVisibility(View.INVISIBLE);
1938900e631940fdffe7b941b56dc0f17e55345441eRomain Guy                }
1948900e631940fdffe7b941b56dc0f17e55345441eRomain Guy            }
1958900e631940fdffe7b941b56dc0f17e55345441eRomain Guy        };
1968900e631940fdffe7b941b56dc0f17e55345441eRomain Guy
197ba925e8ecd9decff5701001a0190042d6797942dChris Wren        mGlowTopAnimation = ObjectAnimator.ofFloat(null, "alpha", 0f);
1988900e631940fdffe7b941b56dc0f17e55345441eRomain Guy        mGlowTopAnimation.addListener(glowVisibilityController);
199ba925e8ecd9decff5701001a0190042d6797942dChris Wren        mGlowBottomAnimation = ObjectAnimator.ofFloat(null, "alpha", 0f);
2008900e631940fdffe7b941b56dc0f17e55345441eRomain Guy        mGlowBottomAnimation.addListener(glowVisibilityController);
201ba925e8ecd9decff5701001a0190042d6797942dChris Wren        mGlowAnimationSet = new AnimatorSet();
202ba925e8ecd9decff5701001a0190042d6797942dChris Wren        mGlowAnimationSet.play(mGlowTopAnimation).with(mGlowBottomAnimation);
203ba925e8ecd9decff5701001a0190042d6797942dChris Wren        mGlowAnimationSet.setDuration(GLOW_DURATION);
204ba925e8ecd9decff5701001a0190042d6797942dChris Wren
205b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren        final ViewConfiguration configuration = ViewConfiguration.get(mContext);
206b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren        mTouchSlop = configuration.getScaledTouchSlop();
207b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren
2086a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        mDetector =
2096a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler                new ScaleGestureDetector(context,
2106a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler                                         new ScaleGestureDetector.SimpleOnScaleGestureListener() {
2116a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler            @Override
2126a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler            public boolean onScaleBegin(ScaleGestureDetector detector) {
2133c148f106f6625ce247a2c7211682c3a1df89bc9Chris Wren                if (DEBUG_SCALE) Slog.v(TAG, "onscalebegin()");
2145de6e94e36e2adbdd4ebfb5c1903c23c9ea3c388Chris Wren                float x = detector.getFocusX();
2155de6e94e36e2adbdd4ebfb5c1903c23c9ea3c388Chris Wren                float y = detector.getFocusY();
2165de6e94e36e2adbdd4ebfb5c1903c23c9ea3c388Chris Wren
2176a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler                // your fingers have to be somewhat close to the bounds of the view in question
21889139d74b27305a29ca082c75d94dcbed5f84625Chris Wren                mInitialTouchFocusY = detector.getFocusY();
21989139d74b27305a29ca082c75d94dcbed5f84625Chris Wren                mInitialTouchSpan = Math.abs(detector.getCurrentSpan());
2203c148f106f6625ce247a2c7211682c3a1df89bc9Chris Wren                if (DEBUG_SCALE) Slog.d(TAG, "got mInitialTouchSpan: (" + mInitialTouchSpan + ")");
2216a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler
222b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren                mStretching = initScale(findView(x, y));
2236a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler                return mStretching;
2246a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler            }
2256a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler
2266a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler            @Override
2276a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler            public boolean onScale(ScaleGestureDetector detector) {
2283c148f106f6625ce247a2c7211682c3a1df89bc9Chris Wren                if (DEBUG_SCALE) Slog.v(TAG, "onscale() on " + mCurrView);
22989139d74b27305a29ca082c75d94dcbed5f84625Chris Wren
23089139d74b27305a29ca082c75d94dcbed5f84625Chris Wren                // are we scaling or dragging?
23189139d74b27305a29ca082c75d94dcbed5f84625Chris Wren                float span = Math.abs(detector.getCurrentSpan()) - mInitialTouchSpan;
23289139d74b27305a29ca082c75d94dcbed5f84625Chris Wren                span *= USE_SPAN ? 1f : 0f;
23389139d74b27305a29ca082c75d94dcbed5f84625Chris Wren                float drag = detector.getFocusY() - mInitialTouchFocusY;
23489139d74b27305a29ca082c75d94dcbed5f84625Chris Wren                drag *= USE_DRAG ? 1f : 0f;
2359b2cd15f0fed990f532f35590c2a2896b90dc7fcChris Wren                drag *= mGravity == Gravity.BOTTOM ? -1f : 1f;
23689139d74b27305a29ca082c75d94dcbed5f84625Chris Wren                float pull = Math.abs(drag) + Math.abs(span) + 1f;
23789139d74b27305a29ca082c75d94dcbed5f84625Chris Wren                float hand = drag * Math.abs(drag) / pull + span * Math.abs(span) / pull;
238b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren                float target = hand + mOldHeight;
239b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren                float newHeight = clamp(target);
240b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren                mScaler.setHeight(newHeight);
241b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren
242b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren                setGlow(calculateGlow(target, newHeight));
2436a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler                return true;
2446a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler            }
2456a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler
2466a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler            @Override
2476a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler            public void onScaleEnd(ScaleGestureDetector detector) {
2483c148f106f6625ce247a2c7211682c3a1df89bc9Chris Wren                if (DEBUG_SCALE) Slog.v(TAG, "onscaleend()");
2496a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler                // I guess we're alone now
2503c148f106f6625ce247a2c7211682c3a1df89bc9Chris Wren                if (DEBUG_SCALE) Slog.d(TAG, "scale end");
2516a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler                finishScale(false);
2523c148f106f6625ce247a2c7211682c3a1df89bc9Chris Wren                clearView();
253b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren                mStretching = false;
2546a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler            }
2556a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        });
2566a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler    }
2575de6e94e36e2adbdd4ebfb5c1903c23c9ea3c388Chris Wren
258b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren    private float clamp(float target) {
259b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren        float out = target;
260b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren        out = out < mSmallSize ? mSmallSize : (out > mLargeSize ? mLargeSize : out);
261b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren        out = out > mNaturalHeight ? mNaturalHeight : out;
262b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren        return out;
263b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren    }
264b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren
265b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren    private View findView(float x, float y) {
266b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren        View v = null;
267b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren        if (mEventSource != null) {
268b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren            int[] location = new int[2];
269b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren            mEventSource.getLocationOnScreen(location);
270b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren            x += (float) location[0];
271b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren            y += (float) location[1];
272b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren            v = mCallback.getChildAtRawPosition(x, y);
273b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren        } else {
274b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren            v = mCallback.getChildAtPosition(x, y);
275b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren        }
276b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren        return v;
277b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren    }
278b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren
279b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren    private boolean isInside(View v, float x, float y) {
280b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren        if (DEBUG) Slog.d(TAG, "isinside (" + x + ", " + y + ")");
281b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren
282b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren        if (v == null) {
283b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren            if (DEBUG) Slog.d(TAG, "isinside null subject");
284b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren            return false;
285b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren        }
286b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren        if (mEventSource != null) {
287b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren            int[] location = new int[2];
288b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren            mEventSource.getLocationOnScreen(location);
289b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren            x += (float) location[0];
290b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren            y += (float) location[1];
291b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren            if (DEBUG) Slog.d(TAG, "  to global (" + x + ", " + y + ")");
292b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren        }
293b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren        int[] location = new int[2];
294b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren        v.getLocationOnScreen(location);
295b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren        x -= (float) location[0];
296b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren        y -= (float) location[1];
297b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren        if (DEBUG) Slog.d(TAG, "  to local (" + x + ", " + y + ")");
298b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren        if (DEBUG) Slog.d(TAG, "  inside (" + v.getWidth() + ", " + v.getHeight() + ")");
299b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren        boolean inside = (x > 0f && y > 0f && x < v.getWidth() & y < v.getHeight());
300b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren        return inside;
301b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren    }
302b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren
3035de6e94e36e2adbdd4ebfb5c1903c23c9ea3c388Chris Wren    public void setEventSource(View eventSource) {
3045de6e94e36e2adbdd4ebfb5c1903c23c9ea3c388Chris Wren        mEventSource = eventSource;
3055de6e94e36e2adbdd4ebfb5c1903c23c9ea3c388Chris Wren    }
3065de6e94e36e2adbdd4ebfb5c1903c23c9ea3c388Chris Wren
3079b2cd15f0fed990f532f35590c2a2896b90dc7fcChris Wren    public void setGravity(int gravity) {
3089b2cd15f0fed990f532f35590c2a2896b90dc7fcChris Wren        mGravity = gravity;
3099b2cd15f0fed990f532f35590c2a2896b90dc7fcChris Wren    }
3109b2cd15f0fed990f532f35590c2a2896b90dc7fcChris Wren
311b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren    public void setScrollView(View scrollView) {
312b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren        mScrollView = scrollView;
313b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren    }
314b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren
315b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren    private float calculateGlow(float target, float actual) {
316b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren        // glow if overscale
3173c148f106f6625ce247a2c7211682c3a1df89bc9Chris Wren        if (DEBUG_GLOW) Slog.d(TAG, "target: " + target + " actual: " + actual);
318b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren        float stretch = (float) Math.abs((target - actual) / mMaximumStretch);
319b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren        float strength = 1f / (1f + (float) Math.pow(Math.E, -1 * ((8f * stretch) - 5f)));
3203c148f106f6625ce247a2c7211682c3a1df89bc9Chris Wren        if (DEBUG_GLOW) Slog.d(TAG, "stretch: " + stretch + " strength: " + strength);
321b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren        return (GLOW_BASE + strength * (1f - GLOW_BASE));
322b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren    }
323b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren
32480a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren    public void setGlow(float glow) {
32589139d74b27305a29ca082c75d94dcbed5f84625Chris Wren        if (!mGlowAnimationSet.isRunning() || glow == 0f) {
32689139d74b27305a29ca082c75d94dcbed5f84625Chris Wren            if (mGlowAnimationSet.isRunning()) {
3273c148f106f6625ce247a2c7211682c3a1df89bc9Chris Wren                mGlowAnimationSet.end();
32889139d74b27305a29ca082c75d94dcbed5f84625Chris Wren            }
329ba925e8ecd9decff5701001a0190042d6797942dChris Wren            if (mCurrViewTopGlow != null && mCurrViewBottomGlow != null) {
330b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren                if (glow == 0f || mCurrViewTopGlow.getAlpha() == 0f) {
331ba925e8ecd9decff5701001a0190042d6797942dChris Wren                    // animate glow in and out
332ba925e8ecd9decff5701001a0190042d6797942dChris Wren                    mGlowTopAnimation.setTarget(mCurrViewTopGlow);
333ba925e8ecd9decff5701001a0190042d6797942dChris Wren                    mGlowBottomAnimation.setTarget(mCurrViewBottomGlow);
334ba925e8ecd9decff5701001a0190042d6797942dChris Wren                    mGlowTopAnimation.setFloatValues(glow);
335ba925e8ecd9decff5701001a0190042d6797942dChris Wren                    mGlowBottomAnimation.setFloatValues(glow);
336ba925e8ecd9decff5701001a0190042d6797942dChris Wren                    mGlowAnimationSet.setupStartValues();
337ba925e8ecd9decff5701001a0190042d6797942dChris Wren                    mGlowAnimationSet.start();
338ba925e8ecd9decff5701001a0190042d6797942dChris Wren                } else {
339ba925e8ecd9decff5701001a0190042d6797942dChris Wren                    // set it explicitly in reponse to touches.
340ba925e8ecd9decff5701001a0190042d6797942dChris Wren                    mCurrViewTopGlow.setAlpha(glow);
341ba925e8ecd9decff5701001a0190042d6797942dChris Wren                    mCurrViewBottomGlow.setAlpha(glow);
3428900e631940fdffe7b941b56dc0f17e55345441eRomain Guy                    handleGlowVisibility();
343ba925e8ecd9decff5701001a0190042d6797942dChris Wren                }
344ba925e8ecd9decff5701001a0190042d6797942dChris Wren            }
34580a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren        }
34680a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren    }
3478900e631940fdffe7b941b56dc0f17e55345441eRomain Guy
3488900e631940fdffe7b941b56dc0f17e55345441eRomain Guy    private void handleGlowVisibility() {
3498900e631940fdffe7b941b56dc0f17e55345441eRomain Guy        mCurrViewTopGlow.setVisibility(mCurrViewTopGlow.getAlpha() <= 0.0f ?
3508900e631940fdffe7b941b56dc0f17e55345441eRomain Guy                View.INVISIBLE : View.VISIBLE);
3518900e631940fdffe7b941b56dc0f17e55345441eRomain Guy        mCurrViewBottomGlow.setVisibility(mCurrViewBottomGlow.getAlpha() <= 0.0f ?
3528900e631940fdffe7b941b56dc0f17e55345441eRomain Guy                View.INVISIBLE : View.VISIBLE);
3538900e631940fdffe7b941b56dc0f17e55345441eRomain Guy    }
3548900e631940fdffe7b941b56dc0f17e55345441eRomain Guy
3556a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler    public boolean onInterceptTouchEvent(MotionEvent ev) {
3567a1a406b24c235ce59c13126d20cfb101c4e2777Daniel Sandler        if (DEBUG) Slog.d(TAG, "interceptTouch: act=" + (ev.getAction()) +
357b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren                         " stretching=" + mStretching +
358b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren                         " onefinger=" + mPullingWithOneFinger);
359b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren        // check for a two-finger gesture
3606a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        mDetector.onTouchEvent(ev);
361b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren        if (mStretching) {
362b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren            return true;
363b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren        } else {
364b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren            final int action = ev.getAction();
365b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren            if ((action == MotionEvent.ACTION_MOVE) && mPullingWithOneFinger) {
366b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren                return true;
367b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren            }
368b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren            if (mScrollView != null && mScrollView.getScrollY() > 0) {
369b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren                return false;
370b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren            }
371b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren            switch (action & MotionEvent.ACTION_MASK) {
372b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren            case MotionEvent.ACTION_MOVE: {
373b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren                if (mWatchingForPull) {
374b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren                    final int x = (int) ev.getX();
375b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren                    final int y = (int) ev.getY();
376b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren                    final int yDiff = y - mLastMotionY;
377b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren                    if (yDiff > mTouchSlop) {
378b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren                        mLastMotionY = y;
379b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren                        mPullingWithOneFinger = initScale(findView(x, y));
380b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren                        if (mPullingWithOneFinger) {
3813c148f106f6625ce247a2c7211682c3a1df89bc9Chris Wren                            mInitialTouchY = mLastMotionY;
382b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren                            mHasPopped = false;
383b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren                        }
384b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren                    }
385b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren                }
386b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren                break;
387b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren            }
388b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren
389b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren            case MotionEvent.ACTION_DOWN:
390b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren                mWatchingForPull = isInside(mScrollView, ev.getX(), ev.getY());
391b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren                mLastMotionY = (int) ev.getY();
392b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren                break;
393b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren
394b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren            case MotionEvent.ACTION_CANCEL:
395b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren            case MotionEvent.ACTION_UP:
396b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren                if (mPullingWithOneFinger) {
397b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren                    finishScale(false);
3983c148f106f6625ce247a2c7211682c3a1df89bc9Chris Wren                    clearView();
399b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren                }
400b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren                mPullingWithOneFinger = false;
401b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren                mWatchingForPull = false;
402b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren                break;
403b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren            }
404b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren            return mPullingWithOneFinger;
405b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren        }
4066a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler    }
4076a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler
4086a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler    public boolean onTouchEvent(MotionEvent ev) {
4096a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        final int action = ev.getAction();
4103c148f106f6625ce247a2c7211682c3a1df89bc9Chris Wren        if (DEBUG_SCALE) Slog.d(TAG, "touch: act=" + (action) +
411b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren                         " stretching=" + mStretching +
412b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren                         " onefinger=" + mPullingWithOneFinger);
4136a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        if (mStretching) {
4146a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler            mDetector.onTouchEvent(ev);
4156a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        }
4166a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        switch (action) {
417b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren            case MotionEvent.ACTION_MOVE: {
418b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren                if (mPullingWithOneFinger) {
4193c148f106f6625ce247a2c7211682c3a1df89bc9Chris Wren                    float target = ev.getY() - mInitialTouchY + mOldHeight;
4203c148f106f6625ce247a2c7211682c3a1df89bc9Chris Wren                    float newHeight = clamp(target);
4213c148f106f6625ce247a2c7211682c3a1df89bc9Chris Wren                    if (mHasPopped || target > mPopLimit) {
422b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren                        if (!mHasPopped) {
423b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren                            vibrate(mPopDuration);
424b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren                            mHasPopped = true;
425b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren                        }
426b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren                        mScaler.setHeight(newHeight);
427b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren                        // glow if overscale
4283c148f106f6625ce247a2c7211682c3a1df89bc9Chris Wren                        if (target > mNaturalHeight) {
4293c148f106f6625ce247a2c7211682c3a1df89bc9Chris Wren                            View previous = mCallback.getPreviousChild(mCurrView);
4303c148f106f6625ce247a2c7211682c3a1df89bc9Chris Wren                            if (previous != null) {
4313c148f106f6625ce247a2c7211682c3a1df89bc9Chris Wren                                setGlow(0f);
4323c148f106f6625ce247a2c7211682c3a1df89bc9Chris Wren                                pushView(previous);
4333c148f106f6625ce247a2c7211682c3a1df89bc9Chris Wren                                initScale(previous);
4343c148f106f6625ce247a2c7211682c3a1df89bc9Chris Wren                                mInitialTouchY = ev.getY();
4353c148f106f6625ce247a2c7211682c3a1df89bc9Chris Wren                                target = mOldHeight;
4363c148f106f6625ce247a2c7211682c3a1df89bc9Chris Wren                                newHeight = clamp(target);
4373c148f106f6625ce247a2c7211682c3a1df89bc9Chris Wren                                mHasPopped = false;
4383c148f106f6625ce247a2c7211682c3a1df89bc9Chris Wren                            } else {
4393c148f106f6625ce247a2c7211682c3a1df89bc9Chris Wren                                setGlow(calculateGlow(target, newHeight));
4403c148f106f6625ce247a2c7211682c3a1df89bc9Chris Wren                            }
4413c148f106f6625ce247a2c7211682c3a1df89bc9Chris Wren                        } else if (target < mSmallSize && !popStack.empty()) {
4423c148f106f6625ce247a2c7211682c3a1df89bc9Chris Wren                            setGlow(0f);
4433c148f106f6625ce247a2c7211682c3a1df89bc9Chris Wren                            initScale(popView());
4443c148f106f6625ce247a2c7211682c3a1df89bc9Chris Wren                            mInitialTouchY = ev.getY();
4453c148f106f6625ce247a2c7211682c3a1df89bc9Chris Wren                            setGlow(GLOW_BASE);
4463c148f106f6625ce247a2c7211682c3a1df89bc9Chris Wren                        } else {
4473c148f106f6625ce247a2c7211682c3a1df89bc9Chris Wren                            setGlow(calculateGlow(target, newHeight));
4483c148f106f6625ce247a2c7211682c3a1df89bc9Chris Wren                        }
449b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren                    } else {
4503c148f106f6625ce247a2c7211682c3a1df89bc9Chris Wren                         if (target < mSmallSize && !popStack.empty()) {
4513c148f106f6625ce247a2c7211682c3a1df89bc9Chris Wren                            setGlow(0f);
4523c148f106f6625ce247a2c7211682c3a1df89bc9Chris Wren                            initScale(popView());
4533c148f106f6625ce247a2c7211682c3a1df89bc9Chris Wren                            mInitialTouchY = ev.getY();
4543c148f106f6625ce247a2c7211682c3a1df89bc9Chris Wren                            setGlow(GLOW_BASE);
4553c148f106f6625ce247a2c7211682c3a1df89bc9Chris Wren                         } else {
4563c148f106f6625ce247a2c7211682c3a1df89bc9Chris Wren                             setGlow(calculateGlow(4f * target, mSmallSize));
4573c148f106f6625ce247a2c7211682c3a1df89bc9Chris Wren                         }
458b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren                    }
459b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren                    return true;
460b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren                }
461b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren                break;
462b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren            }
4636a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler            case MotionEvent.ACTION_UP:
4646a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler            case MotionEvent.ACTION_CANCEL:
4657a1a406b24c235ce59c13126d20cfb101c4e2777Daniel Sandler                if (DEBUG) Slog.d(TAG, "cancel");
4666a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler                mStretching = false;
467b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren                if (mPullingWithOneFinger) {
468b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren                    finishScale(false);
469b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren                    mPullingWithOneFinger = false;
470b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren                }
47180a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren                clearView();
4726a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler                break;
4736a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        }
4746a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        return true;
4756a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler    }
4766a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler    private boolean initScale(View v) {
4776a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        if (v != null) {
4787a1a406b24c235ce59c13126d20cfb101c4e2777Daniel Sandler            if (DEBUG) Slog.d(TAG, "scale begins on view: " + v);
47980a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren            setView(v);
48080a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren            setGlow(GLOW_BASE);
4816a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler            mScaler.setView(v);
4826a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler            mOldHeight = mScaler.getHeight();
48380a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren            if (mCallback.canChildBeExpanded(v)) {
4847a1a406b24c235ce59c13126d20cfb101c4e2777Daniel Sandler                if (DEBUG) Slog.d(TAG, "working on an expandable child");
48580a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren                mNaturalHeight = mScaler.getNaturalHeight(mLargeSize);
48680a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren            } else {
4877a1a406b24c235ce59c13126d20cfb101c4e2777Daniel Sandler                if (DEBUG) Slog.d(TAG, "working on a non-expandable child");
48880a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren                mNaturalHeight = mOldHeight;
48980a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren            }
4907a1a406b24c235ce59c13126d20cfb101c4e2777Daniel Sandler            if (DEBUG) Slog.d(TAG, "got mOldHeight: " + mOldHeight +
4916a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler                        " mNaturalHeight: " + mNaturalHeight);
4926a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler            v.getParent().requestDisallowInterceptTouchEvent(true);
493b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren            return true;
494b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren        } else {
495b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren            return false;
4966a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        }
4976a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler    }
4986a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler
4996a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler    private void finishScale(boolean force) {
5006a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        float h = mScaler.getHeight();
5016a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        final boolean wasClosed = (mOldHeight == mSmallSize);
5026a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        if (wasClosed) {
5036a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler            h = (force || h > mSmallSize) ? mNaturalHeight : mSmallSize;
5046a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        } else {
5056a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler            h = (force || h < mNaturalHeight) ? mSmallSize : mNaturalHeight;
5066a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        }
507ba925e8ecd9decff5701001a0190042d6797942dChris Wren        if (mScaleAnimation.isRunning()) {
508ba925e8ecd9decff5701001a0190042d6797942dChris Wren            mScaleAnimation.cancel();
509ba925e8ecd9decff5701001a0190042d6797942dChris Wren        }
510ba925e8ecd9decff5701001a0190042d6797942dChris Wren        mScaleAnimation.setFloatValues(h);
511ba925e8ecd9decff5701001a0190042d6797942dChris Wren        mScaleAnimation.setupStartValues();
512ba925e8ecd9decff5701001a0190042d6797942dChris Wren        mScaleAnimation.start();
51380a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren        setGlow(0f);
5148fd12657e353a4a6f6d875a0d86850426fec00e8Chris Wren        mCallback.setUserExpandedChild(mCurrView, h == mNaturalHeight);
5157a1a406b24c235ce59c13126d20cfb101c4e2777Daniel Sandler        if (DEBUG) Slog.d(TAG, "scale was finished on view: " + mCurrView);
51680a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren    }
51780a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren
51880a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren    private void clearView() {
5193c148f106f6625ce247a2c7211682c3a1df89bc9Chris Wren        while (!popStack.empty()) {
5203c148f106f6625ce247a2c7211682c3a1df89bc9Chris Wren            popStack.pop();
5213c148f106f6625ce247a2c7211682c3a1df89bc9Chris Wren        }
5226a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        mCurrView = null;
52380a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren        mCurrViewTopGlow = null;
52480a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren        mCurrViewBottomGlow = null;
52580a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren    }
52680a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren
52780a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren    private void setView(View v) {
52889139d74b27305a29ca082c75d94dcbed5f84625Chris Wren        mCurrView = v;
52980a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren        if (v instanceof ViewGroup) {
53080a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren            ViewGroup g = (ViewGroup) v;
53180a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren            mCurrViewTopGlow = g.findViewById(R.id.top_glow);
53280a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren            mCurrViewBottomGlow = g.findViewById(R.id.bottom_glow);
53389139d74b27305a29ca082c75d94dcbed5f84625Chris Wren            if (DEBUG) {
534b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren                String debugLog = "Looking for glows: " +
53580a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren                        (mCurrViewTopGlow != null ? "found top " : "didn't find top") +
53680a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren                        (mCurrViewBottomGlow != null ? "found bottom " : "didn't find bottom");
5377a1a406b24c235ce59c13126d20cfb101c4e2777Daniel Sandler                Slog.v(TAG,  debugLog);
53880a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren            }
53980a76276dc9440ffad30dc4c820eb7d65f4df368Chris Wren        }
5406a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler    }
5416a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler
5423c148f106f6625ce247a2c7211682c3a1df89bc9Chris Wren    private void pushView(View v) {
5433c148f106f6625ce247a2c7211682c3a1df89bc9Chris Wren        PopState state = new PopState();
5443c148f106f6625ce247a2c7211682c3a1df89bc9Chris Wren        state.mCurrView = mCurrView;
5453c148f106f6625ce247a2c7211682c3a1df89bc9Chris Wren        state.mCurrViewTopGlow = mCurrViewTopGlow;
5463c148f106f6625ce247a2c7211682c3a1df89bc9Chris Wren        state.mCurrViewBottomGlow = mCurrViewBottomGlow;
5473c148f106f6625ce247a2c7211682c3a1df89bc9Chris Wren        state.mOldHeight = mOldHeight;
5483c148f106f6625ce247a2c7211682c3a1df89bc9Chris Wren        state.mNaturalHeight = mNaturalHeight;
5493c148f106f6625ce247a2c7211682c3a1df89bc9Chris Wren        state.mInitialTouchY = mInitialTouchY;
5503c148f106f6625ce247a2c7211682c3a1df89bc9Chris Wren        popStack.push(state);
5513c148f106f6625ce247a2c7211682c3a1df89bc9Chris Wren    }
5523c148f106f6625ce247a2c7211682c3a1df89bc9Chris Wren
5533c148f106f6625ce247a2c7211682c3a1df89bc9Chris Wren    private View popView() {
5543c148f106f6625ce247a2c7211682c3a1df89bc9Chris Wren        if (popStack.empty()) {
5553c148f106f6625ce247a2c7211682c3a1df89bc9Chris Wren            return null;
5563c148f106f6625ce247a2c7211682c3a1df89bc9Chris Wren        }
5573c148f106f6625ce247a2c7211682c3a1df89bc9Chris Wren
5583c148f106f6625ce247a2c7211682c3a1df89bc9Chris Wren        PopState state = popStack.pop();
5593c148f106f6625ce247a2c7211682c3a1df89bc9Chris Wren        mCurrView = state.mCurrView;
5603c148f106f6625ce247a2c7211682c3a1df89bc9Chris Wren        mCurrViewTopGlow = state.mCurrViewTopGlow;
5613c148f106f6625ce247a2c7211682c3a1df89bc9Chris Wren        mCurrViewBottomGlow = state.mCurrViewBottomGlow;
5623c148f106f6625ce247a2c7211682c3a1df89bc9Chris Wren        mOldHeight = state.mOldHeight;
5633c148f106f6625ce247a2c7211682c3a1df89bc9Chris Wren        mNaturalHeight = state.mNaturalHeight;
5643c148f106f6625ce247a2c7211682c3a1df89bc9Chris Wren        mInitialTouchY = state.mInitialTouchY;
5653c148f106f6625ce247a2c7211682c3a1df89bc9Chris Wren
5663c148f106f6625ce247a2c7211682c3a1df89bc9Chris Wren        return mCurrView;
5673c148f106f6625ce247a2c7211682c3a1df89bc9Chris Wren    }
5683c148f106f6625ce247a2c7211682c3a1df89bc9Chris Wren
5696a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler    @Override
5706a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler    public void onClick(View v) {
5716a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        initScale(v);
5726a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler        finishScale(true);
5733c148f106f6625ce247a2c7211682c3a1df89bc9Chris Wren        clearView();
574b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren    }
5756a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler
576b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren    /**
577b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren     * Triggers haptic feedback.
578b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren     */
579b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren    private synchronized void vibrate(long duration) {
580b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren        if (mVibrator == null) {
581b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren            mVibrator = (android.os.Vibrator)
582b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren                    mContext.getSystemService(Context.VIBRATOR_SERVICE);
583b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren        }
584b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren        mVibrator.vibrate(duration);
5856a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler    }
5866a858c347f4d4e5db4c8f00d5e285967631b71caDaniel Sandler}
587b4e2c48b4d75e7d68209412152011441fb6deda3Chris Wren
588